Compare commits
1040 Commits
v2.0.1+for
...
v2.1.6+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d96b9558b4 | ||
|
|
1ed3f00f82 | ||
|
|
327aef0271 | ||
|
|
66f8cc5d18 | ||
|
|
36699e0ab1 | ||
|
|
1a382606d7 | ||
|
|
2522c9c428 | ||
|
|
d1ee12f248 | ||
|
|
d5f9c19fd3 | ||
|
|
dc700d9e37 | ||
|
|
6eb87149e2 | ||
|
|
79a0f8a891 | ||
|
|
26d41b006f | ||
|
|
60d9dc22d2 | ||
|
|
c0bf78fa7c | ||
|
|
f0c2bd0fe6 | ||
|
|
235cfd1e80 | ||
|
|
c96931ba72 | ||
|
|
2c654a28d6 | ||
|
|
72d3fc84f5 | ||
|
|
3b77604ae6 | ||
|
|
4e5f49b37d | ||
|
|
a89c075082 | ||
|
|
99e881bb95 | ||
|
|
4c585fc5d0 | ||
|
|
ae934b5167 | ||
|
|
6b234209c6 | ||
|
|
786ce78b08 | ||
|
|
cde332684e | ||
|
|
d51e06b61f | ||
|
|
0c376d57e7 | ||
|
|
ee82772fee | ||
|
|
d5b6750abe | ||
|
|
70328217c6 | ||
|
|
733fb3f53a | ||
|
|
f82eb96a35 | ||
|
|
148952b96c | ||
|
|
52928e1577 | ||
|
|
cd13777c06 | ||
|
|
b21e2acdb6 | ||
|
|
2b65aeb8b4 | ||
|
|
a8dcb11094 | ||
|
|
aa42873274 | ||
|
|
8a5b36db96 | ||
|
|
c85af5502d | ||
|
|
06698d3c52 | ||
|
|
2818672cda | ||
|
|
24794f28aa | ||
|
|
50d1523210 | ||
|
|
613cd2a1ea | ||
|
|
295cb74287 | ||
|
|
95dd3ff068 | ||
|
|
d6aeb753fc | ||
|
|
a085744038 | ||
|
|
26391a6f14 | ||
|
|
4cf734ce9a | ||
|
|
c8122aa65b | ||
|
|
ef1584de55 | ||
|
|
3075030b1c | ||
|
|
42c56401db | ||
|
|
a18a4383e5 | ||
|
|
375b5b3133 | ||
|
|
4cab916957 | ||
|
|
7902691093 | ||
|
|
d5d9e20a06 | ||
|
|
1eb55428db | ||
|
|
9b82d704f6 | ||
|
|
dbf3d2776e | ||
|
|
c077b9d47c | ||
|
|
34157805a1 | ||
|
|
576da2a3eb | ||
|
|
e99e5b2836 | ||
|
|
fe25974958 | ||
|
|
1a95b4e361 | ||
|
|
35e3d5afc4 | ||
|
|
cc1e13d1bd | ||
|
|
0b622f8f2a | ||
|
|
9cf7da6419 | ||
|
|
812ab7198c | ||
|
|
e8c9253a76 | ||
|
|
c8f633ae3b | ||
|
|
af339a833f | ||
|
|
708142e1b8 | ||
|
|
55f32671c5 | ||
|
|
289db09770 | ||
|
|
89d7dfd694 | ||
|
|
60a998be89 | ||
|
|
128e75bc24 | ||
|
|
69c60d484c | ||
|
|
c70750a508 | ||
|
|
5553ca25a1 | ||
|
|
ff87829e72 | ||
|
|
02983c76b9 | ||
|
|
3e28eb2ccf | ||
|
|
3cf23474e3 | ||
|
|
9f0ff2dcd4 | ||
|
|
8ed9fb6276 | ||
|
|
df7a0ee490 | ||
|
|
2e360dc275 | ||
|
|
8dc88d2fd7 | ||
|
|
901c70efc3 | ||
|
|
3d44e5d2cc | ||
|
|
33ea3da84d | ||
|
|
e97203a6e3 | ||
|
|
b3f2987b14 | ||
|
|
c7426453a5 | ||
|
|
b60c1a5db3 | ||
|
|
664d5cc4c3 | ||
|
|
47d1b182ac | ||
|
|
c33c3d9112 | ||
|
|
ff99e1023a | ||
|
|
8009dc0a75 | ||
|
|
20039ec97e | ||
|
|
172d25eeb0 | ||
|
|
fa04e00032 | ||
|
|
3ff442894d | ||
|
|
edb0823bf9 | ||
|
|
9de83355e1 | ||
|
|
183fb0e8c0 | ||
|
|
d624a04e18 | ||
|
|
303461d803 | ||
|
|
11c7816bb1 | ||
|
|
b5eae13a16 | ||
|
|
59026286a1 | ||
|
|
ef0fbb26a4 | ||
|
|
387b31193f | ||
|
|
95fa547f15 | ||
|
|
3198c9adb1 | ||
|
|
e1af6f4643 | ||
|
|
cc52e491b0 | ||
|
|
b0db0d9f2e | ||
|
|
d699788a12 | ||
|
|
bdbf441e1a | ||
|
|
0deba2885b | ||
|
|
48b39fd3bc | ||
|
|
875b235e6d | ||
|
|
1711682322 | ||
|
|
70f5ec7500 | ||
|
|
d34c73c210 | ||
|
|
50a0586e8a | ||
|
|
1d2ca1e500 | ||
|
|
34b7123a8d | ||
|
|
ec718ff58e | ||
|
|
20a442e27f | ||
|
|
47cb74abc9 | ||
|
|
a11e53c89a | ||
|
|
d721e8ca46 | ||
|
|
23e5dcd030 | ||
|
|
25dd2cfab8 | ||
|
|
8537c7a7ae | ||
|
|
ebd7f9c36c | ||
|
|
49cda3aeb2 | ||
|
|
67cbc8aff2 | ||
|
|
5db44cbf9d | ||
|
|
95858e3280 | ||
|
|
36846acbe5 | ||
|
|
b95d944003 | ||
|
|
f52886af74 | ||
|
|
6cbe406cef | ||
|
|
3097bc7168 | ||
|
|
5f7faa69e8 | ||
|
|
ed24e89a96 | ||
|
|
8ea05c6ebd | ||
|
|
a30fcbbe34 | ||
|
|
39f76f9988 | ||
|
|
5135653cd3 | ||
|
|
2e4f04cd88 | ||
|
|
a44e0e036a | ||
|
|
5d54c1bae4 | ||
|
|
09577074a9 | ||
|
|
53f0e2a933 | ||
|
|
b8dccbbef1 | ||
|
|
2ef17ba051 | ||
|
|
f63daf3a4e | ||
|
|
52b079be2a | ||
|
|
efeca17106 | ||
|
|
6827166c1d | ||
|
|
04483e61e8 | ||
|
|
22fe174922 | ||
|
|
f143da3913 | ||
|
|
7e9f41c74b | ||
|
|
a1474d0d29 | ||
|
|
0dce936ad3 | ||
|
|
6ebbbb4c6c | ||
|
|
dc6ddbd0ee | ||
|
|
86c81d6b53 | ||
|
|
451a92aa36 | ||
|
|
5c42e67e73 | ||
|
|
d20d36d964 | ||
|
|
1a8d46c71e | ||
|
|
91da10eca3 | ||
|
|
3bb0dcee53 | ||
|
|
d3fd4b200f | ||
|
|
fed96864e1 | ||
|
|
3b351bea27 | ||
|
|
254e01dca1 | ||
|
|
19158e1d48 | ||
|
|
bffb78fccf | ||
|
|
a3800592a2 | ||
|
|
22a498dfc9 | ||
|
|
9ea96e32bd | ||
|
|
68f51a123e | ||
|
|
43b1b63581 | ||
|
|
43b4a2c515 | ||
|
|
5b9cfdb689 | ||
|
|
b43cd7103a | ||
|
|
30e4f6e0f5 | ||
|
|
1d0ebf889b | ||
|
|
7c4f1da485 | ||
|
|
8163921014 | ||
|
|
993393dd96 | ||
|
|
82aed43934 | ||
|
|
fa65134c26 | ||
|
|
af4266c739 | ||
|
|
f72ea2e763 | ||
|
|
c5540270a3 | ||
|
|
5840c94395 | ||
|
|
92d7ae67ef | ||
|
|
fc09514a4d | ||
|
|
300fa97781 | ||
|
|
e8f0891f3a | ||
|
|
f25906a694 | ||
|
|
e52699bb1c | ||
|
|
712826451f | ||
|
|
3b3065d8bd | ||
|
|
e11fe7d8cf | ||
|
|
9a2f7475c9 | ||
|
|
6dffb10906 | ||
|
|
71039d6901 | ||
|
|
db18c7a0d0 | ||
|
|
7e80ed6af2 | ||
|
|
bdd2b90581 | ||
|
|
50d017d8ba | ||
|
|
98e003437c | ||
|
|
d5d06af614 | ||
|
|
1c930ca3bb | ||
|
|
0f0c1093d4 | ||
|
|
2ad5dc5a74 | ||
|
|
e02e0865bd | ||
|
|
59ee1af75d | ||
|
|
c04584dfa6 | ||
|
|
190a3b5b08 | ||
|
|
f5a67e65f0 | ||
|
|
498078b6e0 | ||
|
|
de8c289ca7 | ||
|
|
07b205a746 | ||
|
|
a7941310bc | ||
|
|
864b6dcdac | ||
|
|
d3744bb397 | ||
|
|
7d1853bc88 | ||
|
|
9f0db755d1 | ||
|
|
06819806be | ||
|
|
526f5e319b | ||
|
|
30a4d0efd9 | ||
|
|
d419dba44a | ||
|
|
87da6b9b81 | ||
|
|
67cc1553da | ||
|
|
9a985aad29 | ||
|
|
fd98159fce | ||
|
|
42fac30e63 | ||
|
|
18e7f14c16 | ||
|
|
7b263800a6 | ||
|
|
17387a32b2 | ||
|
|
3fe642c2f2 | ||
|
|
9225447409 | ||
|
|
e05dee64b7 | ||
|
|
7f30973b39 | ||
|
|
25da9bb2d0 | ||
|
|
e17b49e704 | ||
|
|
db661b56cb | ||
|
|
638e1bf8e9 | ||
|
|
83786171a7 | ||
|
|
f638a538c1 | ||
|
|
11e2316cbc | ||
|
|
1b372c7dca | ||
|
|
cf4e37fade | ||
|
|
f3fc60ac00 | ||
|
|
1026d99025 | ||
|
|
3fb947fbfe | ||
|
|
f097506d49 | ||
|
|
3df7d74fbf | ||
|
|
3fc17eb9c9 | ||
|
|
67ee74c1a0 | ||
|
|
bcebba8896 | ||
|
|
451b0c1ac5 | ||
|
|
5d08b4923b | ||
|
|
9ba948c721 | ||
|
|
23e7513133 | ||
|
|
aaed8e53c8 | ||
|
|
f2754cc5c9 | ||
|
|
6a65edd089 | ||
|
|
4d49b10585 | ||
|
|
86bfab81bd | ||
|
|
50e313cff0 | ||
|
|
5d07cde6dd | ||
|
|
f84a923102 | ||
|
|
c9eac34ae6 | ||
|
|
181a0577c8 | ||
|
|
0426084194 | ||
|
|
197d0caf44 | ||
|
|
a4a082f76a | ||
|
|
2528d48010 | ||
|
|
5456d71979 | ||
|
|
e36aae3cf3 | ||
|
|
d6040c0895 | ||
|
|
6d12e2dd72 | ||
|
|
ff47e6edba | ||
|
|
327ceb04d4 | ||
|
|
40a34b07de | ||
|
|
f117249bb5 | ||
|
|
1c90164ece | ||
|
|
0d5fb250bc | ||
|
|
e9bd5a373a | ||
|
|
c494d283ba | ||
|
|
cf1d537367 | ||
|
|
517d13b400 | ||
|
|
fae870c93a | ||
|
|
f8e00dcc80 | ||
|
|
5fdbb597bb | ||
|
|
d74b286a9d | ||
|
|
ecb3c521ff | ||
|
|
1d093ce928 | ||
|
|
46b711af2e | ||
|
|
772e6ddb5d | ||
|
|
f84e8443d2 | ||
|
|
250c18ebf1 | ||
|
|
e3d0f38b79 | ||
|
|
c512f97783 | ||
|
|
0594680775 | ||
|
|
f999881f59 | ||
|
|
4fe9192ac6 | ||
|
|
d936702fa9 | ||
|
|
74e284b0de | ||
|
|
4c42b72ed8 | ||
|
|
0e0046df65 | ||
|
|
c80d1d10c2 | ||
|
|
da97971011 | ||
|
|
700447dbe7 | ||
|
|
37e7b5ee93 | ||
|
|
1265afa93f | ||
|
|
1e09481b02 | ||
|
|
9996a5a05e | ||
|
|
f20aac7c81 | ||
|
|
98f7b0bacd | ||
|
|
3f6d3fb3a2 | ||
|
|
663b49c76b | ||
|
|
16e38f2541 | ||
|
|
842cc55e47 | ||
|
|
72db099e6f | ||
|
|
be130bc3a7 | ||
|
|
42253336e1 | ||
|
|
572631e1d7 | ||
|
|
723777a800 | ||
|
|
b825d534c1 | ||
|
|
b9749620a8 | ||
|
|
70ea9989aa | ||
|
|
b3ec9c981c | ||
|
|
bf72085abb | ||
|
|
64dd416b59 | ||
|
|
ab2a920455 | ||
|
|
7580446d60 | ||
|
|
ade18ac6fc | ||
|
|
005c851d72 | ||
|
|
0f1d46c765 | ||
|
|
21fbb07b1d | ||
|
|
58e0ce3970 | ||
|
|
139a7d7c98 | ||
|
|
a1c81e89e8 | ||
|
|
a5c197b496 | ||
|
|
df49ef9d58 | ||
|
|
f747d4c979 | ||
|
|
98677cd307 | ||
|
|
cd3de97d55 | ||
|
|
4853a25710 | ||
|
|
eba9a1da7b | ||
|
|
068c62b060 | ||
|
|
5d7f06eba0 | ||
|
|
fed9dec33a | ||
|
|
7973914a5f | ||
|
|
35efb3f047 | ||
|
|
0a4ed50904 | ||
|
|
002c66174a | ||
|
|
22aac3d943 | ||
|
|
872f47305a | ||
|
|
75d5332411 | ||
|
|
035da8a517 | ||
|
|
4c2c877d41 | ||
|
|
0cc8cddfc3 | ||
|
|
4428ef7ac2 | ||
|
|
44912b7982 | ||
|
|
c930db6068 | ||
|
|
d96d4dd581 | ||
|
|
67e3a5bb47 | ||
|
|
b0a5aa93e1 | ||
|
|
0bc1459898 | ||
|
|
fae25e93a5 | ||
|
|
c0c121050c | ||
|
|
afe572ca7f | ||
|
|
8cb4db5fcf | ||
|
|
de235ec7cc | ||
|
|
140c2a7b9d | ||
|
|
c833513344 | ||
|
|
1e95536208 | ||
|
|
b58fda9795 | ||
|
|
3135aef398 | ||
|
|
c83dc51322 | ||
|
|
9cfaed89e6 | ||
|
|
5374ac766c | ||
|
|
98596e77f2 | ||
|
|
54aa89c7f8 | ||
|
|
f59157b160 | ||
|
|
6b38db9607 | ||
|
|
53afc120f3 | ||
|
|
c10cdfd795 | ||
|
|
c2184e7bd8 | ||
|
|
baf756e163 | ||
|
|
efc67fd7e8 | ||
|
|
43e737425a | ||
|
|
b5b3cb42a1 | ||
|
|
f72f7cb831 | ||
|
|
f86d60be23 | ||
|
|
7c8624bd53 | ||
|
|
a75ce70615 | ||
|
|
331548b38d | ||
|
|
8b8f192dfa | ||
|
|
061b2ee3de | ||
|
|
5ea2864bd5 | ||
|
|
ee2b4b6a1f | ||
|
|
697f801c1a | ||
|
|
ebb49c44fe | ||
|
|
bc4619e6b1 | ||
|
|
4a3b948760 | ||
|
|
f81283c892 | ||
|
|
7eae879037 | ||
|
|
1b0ce5d893 | ||
|
|
5d26ea85e9 | ||
|
|
6efe263dd8 | ||
|
|
0379347f2d | ||
|
|
1299b2ad42 | ||
|
|
f3b3bcaa0a | ||
|
|
b1bec870c5 | ||
|
|
36e05a6d14 | ||
|
|
2e11f78e9d | ||
|
|
9fcfbe5593 | ||
|
|
c17745368d | ||
|
|
e78b518654 | ||
|
|
55a8634be2 | ||
|
|
ac891eea53 | ||
|
|
74fa2a3081 | ||
|
|
6c1c5b7759 | ||
|
|
1f4152b588 | ||
|
|
70386ea1b2 | ||
|
|
cbce90c461 | ||
|
|
74ae3bf706 | ||
|
|
1feccdc26d | ||
|
|
c38c2a425b | ||
|
|
f43352b790 | ||
|
|
c5b52b2781 | ||
|
|
b91840fb95 | ||
|
|
1988849b26 | ||
|
|
fc10fbffb0 | ||
|
|
e40841c128 | ||
|
|
a21a74a8e7 | ||
|
|
20e5d2a545 | ||
|
|
7da363fb87 | ||
|
|
c46f78272d | ||
|
|
95685d4de8 | ||
|
|
cbee0fe72e | ||
|
|
6d085ae6f0 | ||
|
|
4de7211523 | ||
|
|
05f7a44bd5 | ||
|
|
1079f600bc | ||
|
|
72580dadd0 | ||
|
|
98a02e874b | ||
|
|
d219d7aa4b | ||
|
|
68a9fe8376 | ||
|
|
f2f8620312 | ||
|
|
4ee229ea79 | ||
|
|
c261214e49 | ||
|
|
b06df8c3d0 | ||
|
|
a00afd5d7f | ||
|
|
9a41a2d6fb | ||
|
|
2cd98a6620 | ||
|
|
283b56be5b | ||
|
|
6d56771aba | ||
|
|
1724d8a532 | ||
|
|
52030b3b2d | ||
|
|
b4cdf35d36 | ||
|
|
cad0ad7a59 | ||
|
|
ca60003c39 | ||
|
|
0f030e0bac | ||
|
|
6d4f212a18 | ||
|
|
183b39bc24 | ||
|
|
27ad0c6fcf | ||
|
|
b5f661f1af | ||
|
|
0015f3f0bf | ||
|
|
c5d0fdd645 | ||
|
|
2d09ad44fb | ||
|
|
667fffd124 | ||
|
|
699233d8c7 | ||
|
|
56aabdc4a6 | ||
|
|
443e2c7a6f | ||
|
|
985b0f6e63 | ||
|
|
cc86edf276 | ||
|
|
4071b9342d | ||
|
|
f71d1bc5d3 | ||
|
|
2b926ffa46 | ||
|
|
6bcdbaba34 | ||
|
|
a2beead3a5 | ||
|
|
e7a25e353d | ||
|
|
af04a01130 | ||
|
|
fe1cfa1d7b | ||
|
|
b248797bb0 | ||
|
|
f24eba08d3 | ||
|
|
0e89559a47 | ||
|
|
858657799f | ||
|
|
d02a72e079 | ||
|
|
3be57d1b0b | ||
|
|
bed550e97c | ||
|
|
7e2619ea75 | ||
|
|
4b22f1d3a7 | ||
|
|
9dcc7e293f | ||
|
|
6a68cf5e41 | ||
|
|
29297be4a3 | ||
|
|
90b87529e0 | ||
|
|
39af05524d | ||
|
|
e3fb2cd03c | ||
|
|
90f84d628a | ||
|
|
b89e0b5c5a | ||
|
|
f724644d84 | ||
|
|
aac89c354c | ||
|
|
a032f9af10 | ||
|
|
642aaec6da | ||
|
|
ff667d6aed | ||
|
|
5e98496ea6 | ||
|
|
972fe1d15b | ||
|
|
26eaa36faa | ||
|
|
c517f41595 | ||
|
|
56a6d7243f | ||
|
|
18e43dfc22 | ||
|
|
816f6370ef | ||
|
|
30866a5292 | ||
|
|
3e1403d18a | ||
|
|
ebc2b2e59d | ||
|
|
c9a796dbfe | ||
|
|
1ba185ea9c | ||
|
|
1a50c3ff5f | ||
|
|
35a85c3247 | ||
|
|
6a729fa97f | ||
|
|
923639a329 | ||
|
|
a78be8bc1d | ||
|
|
abfb497577 | ||
|
|
a10b184508 | ||
|
|
f0ea6660e6 | ||
|
|
a829f25d56 | ||
|
|
deff3dd8e0 | ||
|
|
dab596f527 | ||
|
|
0c18ab2319 | ||
|
|
6c5fb5ea09 | ||
|
|
afe0c9e0db | ||
|
|
1f2213042f | ||
|
|
5edd2466f9 | ||
|
|
f3b3a1a577 | ||
|
|
068619b815 | ||
|
|
f121e94979 | ||
|
|
b5b52529d4 | ||
|
|
876bf73454 | ||
|
|
522dbf6e4a | ||
|
|
ae685095ba | ||
|
|
30d5fe2f12 | ||
|
|
2bf27c561c | ||
|
|
bbdc72323d | ||
|
|
6e335930f3 | ||
|
|
9b309939da | ||
|
|
faf2e5115d | ||
|
|
dc5d9412c8 | ||
|
|
fc0680d66f | ||
|
|
56c9a5433f | ||
|
|
60e473ee55 | ||
|
|
ae34ecd5c3 | ||
|
|
fd1caa8729 | ||
|
|
1182e5c60c | ||
|
|
d99d515dfa | ||
|
|
70a15e7d9c | ||
|
|
1691382369 | ||
|
|
b7da9c6d51 | ||
|
|
3426538dca | ||
|
|
63de2b200b | ||
|
|
ff1ee766dc | ||
|
|
f033411adf | ||
|
|
a738eaf8c0 | ||
|
|
5074aadd6e | ||
|
|
0854961470 | ||
|
|
227b077935 | ||
|
|
1e4358290a | ||
|
|
925169eb31 | ||
|
|
e1abeb9252 | ||
|
|
cbe0add211 | ||
|
|
299b524d62 | ||
|
|
31c094e696 | ||
|
|
a8038a2863 | ||
|
|
29933bb916 | ||
|
|
5ec0c078d8 | ||
|
|
e6287f1ff2 | ||
|
|
be9caf8905 | ||
|
|
f375142084 | ||
|
|
fd3668d520 | ||
|
|
d5e03e9d9e | ||
|
|
d62f094919 | ||
|
|
6d84f28600 | ||
|
|
209e603f2c | ||
|
|
1b4dc01c74 | ||
|
|
6aab8f6578 | ||
|
|
645af12c3f | ||
|
|
fadc42d72b | ||
|
|
fc831e7d42 | ||
|
|
2998ee9145 | ||
|
|
971c4e5879 | ||
|
|
48c53ee88b | ||
|
|
acf1fa15da | ||
|
|
1c3b28f9d7 | ||
|
|
b396ee7987 | ||
|
|
90856a414a | ||
|
|
ea19925be6 | ||
|
|
03b3775843 | ||
|
|
38b39751ae | ||
|
|
54a4b0fe41 | ||
|
|
3bf591c944 | ||
|
|
584a6bbfa3 | ||
|
|
0f803cd4fa | ||
|
|
167a14b8db | ||
|
|
81cbc2d10c | ||
|
|
9bd8aff99b | ||
|
|
a770828165 | ||
|
|
ab457035ff | ||
|
|
f886e4c1d2 | ||
|
|
380e4ff77e | ||
|
|
58f0c07357 | ||
|
|
77dee59b9c | ||
|
|
464dc93d99 | ||
|
|
dcdfd3e5d3 | ||
|
|
646f83ff0a | ||
|
|
fdf0414698 | ||
|
|
cc699a3f5e | ||
|
|
12eaa8d5f1 | ||
|
|
70680e39c6 | ||
|
|
8bd8f90d58 | ||
|
|
54b53a266e | ||
|
|
66921e3b5a | ||
|
|
9d7af3964b | ||
|
|
ec73687e9b | ||
|
|
c8af800b88 | ||
|
|
e74ac5da56 | ||
|
|
efa003a9a5 | ||
|
|
de5165434d | ||
|
|
be648cc5ab | ||
|
|
90f1f464dc | ||
|
|
734aa52816 | ||
|
|
068d42175e | ||
|
|
2314871246 | ||
|
|
e4f13c900b | ||
|
|
4ddfa483d4 | ||
|
|
20f41ce7c9 | ||
|
|
c8df9e085e | ||
|
|
0238aa4375 | ||
|
|
79d7873790 | ||
|
|
996842489d | ||
|
|
23c2c2b5e7 | ||
|
|
9dd694ce2e | ||
|
|
f51f2a1197 | ||
|
|
3020cab243 | ||
|
|
be73c9e81c | ||
|
|
1c2183bf1a | ||
|
|
1789d90dc3 | ||
|
|
57306ff7fe | ||
|
|
2aba90f353 | ||
|
|
5065c7e7e2 | ||
|
|
a10e661b21 | ||
|
|
4975bde76f | ||
|
|
ebbd56e3bc | ||
|
|
454ec6b4c0 | ||
|
|
10a8b195b1 | ||
|
|
6f273df060 | ||
|
|
7cfade62d3 | ||
|
|
0a338ad607 | ||
|
|
0cb3e1863e | ||
|
|
209081f1f0 | ||
|
|
f0eb6573f4 | ||
|
|
e7f5dd3357 | ||
|
|
8101bb9ea1 | ||
|
|
54d48253d5 | ||
|
|
3373b2bb04 | ||
|
|
a7e23aa228 | ||
|
|
8cd55fc365 | ||
|
|
f14df2bb0f | ||
|
|
228fdc8ffe | ||
|
|
2d24e50ff2 | ||
|
|
53369eb2d4 | ||
|
|
e9df125cde | ||
|
|
807010893a | ||
|
|
ea01b14ffb | ||
|
|
3fd9dc1dcd | ||
|
|
02a4a77885 | ||
|
|
651090a504 | ||
|
|
203254c9f4 | ||
|
|
16ef577a7a | ||
|
|
734b3bced6 | ||
|
|
e26c641dc7 | ||
|
|
9295cf4e9c | ||
|
|
dd9237e9ca | ||
|
|
ea81c1fad6 | ||
|
|
d334703c65 | ||
|
|
5f6f3c94c9 | ||
|
|
09ba42a974 | ||
|
|
b1e43d6f97 | ||
|
|
ea92a61d13 | ||
|
|
7fda69a6aa | ||
|
|
cf8b9ac649 | ||
|
|
3c122b005d | ||
|
|
f9dc6105f4 | ||
|
|
d7fe3c80e6 | ||
|
|
e996deea0b | ||
|
|
580ae15af6 | ||
|
|
d76e823489 | ||
|
|
e66078e52e | ||
|
|
f84356f5d0 | ||
|
|
1fa5a8436b | ||
|
|
2a471ffa96 | ||
|
|
1ee5e1ab3a | ||
|
|
4d90cad034 | ||
|
|
45615b1fc5 | ||
|
|
9fd0e7fea4 | ||
|
|
696016bd8f | ||
|
|
cc46e09853 | ||
|
|
83e84836b5 | ||
|
|
14bb544344 | ||
|
|
ceec18ff5e | ||
|
|
d36ad43700 | ||
|
|
fb39f74ba5 | ||
|
|
9bfc73d6ee | ||
|
|
efb72eace9 | ||
|
|
84b15f4a49 | ||
|
|
2c793fd83b | ||
|
|
900b204bb0 | ||
|
|
17d679901a | ||
|
|
c3d9147705 | ||
|
|
d8036779f8 | ||
|
|
2fafdcc4f8 | ||
|
|
ef3f96ba74 | ||
|
|
91bf1b277f | ||
|
|
c6acd35ceb | ||
|
|
a85a0b7d78 | ||
|
|
3a35674ea2 | ||
|
|
3235cf1c4f | ||
|
|
3f6bda28b3 | ||
|
|
c0b4f4dd79 | ||
|
|
ad96031aeb | ||
|
|
aa9e66e6a2 | ||
|
|
86081654fb | ||
|
|
cac030ffed | ||
|
|
2a913e26e7 | ||
|
|
f60375cd5f | ||
|
|
5920270899 | ||
|
|
f36aee44c6 | ||
|
|
cd24526a9d | ||
|
|
a345ac1390 | ||
|
|
71f4f089b6 | ||
|
|
0f72809342 | ||
|
|
40a033d692 | ||
|
|
d44df2c23c | ||
|
|
47fde1e08b | ||
|
|
0688521ae8 | ||
|
|
bdf0f21647 | ||
|
|
bb03342ff2 | ||
|
|
937304f27b | ||
|
|
6b4ce0ea69 | ||
|
|
7f0c4860f8 | ||
|
|
9b4c70a5ed | ||
|
|
49137273ae | ||
|
|
647e3e5e85 | ||
|
|
4920bf63e3 | ||
|
|
0afcdb2cdf | ||
|
|
d96c3c3c8a | ||
|
|
3d987b8e1d | ||
|
|
f5e5408d70 | ||
|
|
d62899c990 | ||
|
|
57043912e0 | ||
|
|
00aef5ea6b | ||
|
|
369b69668c | ||
|
|
65245f4560 | ||
|
|
4d4fdc97d4 | ||
|
|
c96577891c | ||
|
|
f48b2fc9cb | ||
|
|
2fca2580ed | ||
|
|
7adc1da361 | ||
|
|
6dc24dde43 | ||
|
|
4929e0e6ec | ||
|
|
16a8b8ed71 | ||
|
|
ad1412817e | ||
|
|
d9e6bb3bea | ||
|
|
8970404638 | ||
|
|
2a2241d7f9 | ||
|
|
db1a47e8eb | ||
|
|
3e57061cef | ||
|
|
cd200f8450 | ||
|
|
782013079f | ||
|
|
e5db8acd66 | ||
|
|
1a6a8019c8 | ||
|
|
e935eef29f | ||
|
|
381defda51 | ||
|
|
02ae80c204 | ||
|
|
82214b30e8 | ||
|
|
33a1f48602 | ||
|
|
aee845e5cc | ||
|
|
cd780f6006 | ||
|
|
d4741fefa0 | ||
|
|
7e1e8a2616 | ||
|
|
d73c05cdfc | ||
|
|
78323023cb | ||
|
|
2cf084c98f | ||
|
|
e5bdeba1d7 | ||
|
|
8d7db7774f | ||
|
|
78d22c670c | ||
|
|
0a679109f5 | ||
|
|
e843142b7e | ||
|
|
72e728f655 | ||
|
|
ef56792f56 | ||
|
|
504a6959e8 | ||
|
|
6054a3d65c | ||
|
|
f50eac02d8 | ||
|
|
9634db9061 | ||
|
|
97e3e283dd | ||
|
|
f1e233569b | ||
|
|
04dd637fa9 | ||
|
|
c48a4105a9 | ||
|
|
aac53d949b | ||
|
|
9bb4e5b467 | ||
|
|
fb0391d5cd | ||
|
|
e4d898c903 | ||
|
|
da222f75bb | ||
|
|
25fbd91eb3 | ||
|
|
d458cca7bf | ||
|
|
3933a61b5a | ||
|
|
29092bbf36 | ||
|
|
a33d2578c9 | ||
|
|
9afe4b5ac6 | ||
|
|
6782006b05 | ||
|
|
90bdbefd48 | ||
|
|
b7bcf1082e | ||
|
|
ba9bbc5b6e | ||
|
|
0fecfbd50c | ||
|
|
0031dc6119 | ||
|
|
79e606698e | ||
|
|
3c9fc43780 | ||
|
|
adb9b7394a | ||
|
|
6191fdfaef | ||
|
|
446754e8a6 | ||
|
|
30c67b0b39 | ||
|
|
1043ea7b11 | ||
|
|
b449bcd006 | ||
|
|
a9d513b564 | ||
|
|
5cef527810 | ||
|
|
8bb907747d | ||
|
|
9c889f8df3 | ||
|
|
cbc164d844 | ||
|
|
b8e3060887 | ||
|
|
1aa1ede421 | ||
|
|
480dba7629 | ||
|
|
9b9c66a149 | ||
|
|
0f5eb923ee | ||
|
|
7f521b3129 | ||
|
|
9ce217d1f2 | ||
|
|
7b1bd3ccad | ||
|
|
13b1cbde6b | ||
|
|
8d898a1a78 | ||
|
|
51c2890ede | ||
|
|
03642faa9c | ||
|
|
084c6e1e59 | ||
|
|
90ed28e7a0 | ||
|
|
d2b45c1c84 | ||
|
|
a119ba5f80 | ||
|
|
8c1191a08f | ||
|
|
4275d596e6 | ||
|
|
2709d5226d | ||
|
|
8f613e3255 | ||
|
|
6831e846cf | ||
|
|
034eb9427d | ||
|
|
f73c325db3 | ||
|
|
5e2b11c504 | ||
|
|
ec13133431 | ||
|
|
a8a56a3ed8 | ||
|
|
4e9c7c4de2 | ||
|
|
ffb7894098 | ||
|
|
0d9520ac45 | ||
|
|
be852e57df | ||
|
|
157b38b8ae | ||
|
|
83196a1a0d | ||
|
|
306225b054 | ||
|
|
6efc71d8d2 | ||
|
|
cc4cd4d3f8 | ||
|
|
00e3292205 | ||
|
|
316952423c | ||
|
|
61c2abd014 | ||
|
|
ee5f299b90 | ||
|
|
f153846381 | ||
|
|
0656db0858 | ||
|
|
7f250cb8df | ||
|
|
a1e73eca89 | ||
|
|
1dc6936da6 | ||
|
|
0431d80a8d | ||
|
|
eaa78093f7 | ||
|
|
25b7151fde | ||
|
|
0438b579b6 | ||
|
|
afa50a4e8c | ||
|
|
85bdb0067b | ||
|
|
760cbc7f9a | ||
|
|
d0e34fcd90 | ||
|
|
da434b9a9b | ||
|
|
48863dd510 | ||
|
|
2ca34278f9 | ||
|
|
a79779f813 | ||
|
|
cc83f2baf3 | ||
|
|
728496b831 | ||
|
|
bbc99162c6 | ||
|
|
eed3af9e3e | ||
|
|
50187ff376 | ||
|
|
5f30919fb4 | ||
|
|
14c3cfac85 | ||
|
|
e978f02765 | ||
|
|
8d877c480f | ||
|
|
c53efee9a1 | ||
|
|
148c461e86 | ||
|
|
fcadb9883d | ||
|
|
bb6491e10a | ||
|
|
6248ccf376 | ||
|
|
c9e08f36fa | ||
|
|
10b95d753b | ||
|
|
c3989083cf | ||
|
|
01db585094 | ||
|
|
cc67cb330c | ||
|
|
52ed3c5a04 | ||
|
|
5976f6230a | ||
|
|
3553f03a95 | ||
|
|
d6e2d889c3 | ||
|
|
a777b3b450 | ||
|
|
9957efbea0 | ||
|
|
22e7b9730f | ||
|
|
91470b8509 | ||
|
|
c9d5327328 | ||
|
|
1aa61b72e5 | ||
|
|
3ca5edc3fc | ||
|
|
a092ebaeb3 | ||
|
|
5b9e84c255 | ||
|
|
9c058b926f | ||
|
|
4f2d2ae6e8 | ||
|
|
75aa26a018 | ||
|
|
0f795254e5 | ||
|
|
33592f0a83 | ||
|
|
d6fd01eaca | ||
|
|
1cdc58378a | ||
|
|
584b11fce3 | ||
|
|
fe2039062b | ||
|
|
0269756b52 | ||
|
|
df1a6cf764 | ||
|
|
6d2385b6b3 | ||
|
|
44eaa36cef | ||
|
|
50b40c4a07 | ||
|
|
ee6e0ff26c | ||
|
|
4d9574bf38 | ||
|
|
813be9a2be | ||
|
|
cc76ebfafb | ||
|
|
7989ee0243 | ||
|
|
3aa1997cfd | ||
|
|
c3da15552e | ||
|
|
a014fe9443 | ||
|
|
92551d4ca3 | ||
|
|
8010858e85 | ||
|
|
4efb4875b0 | ||
|
|
c5d041e46d | ||
|
|
53c2223aae | ||
|
|
25034ac0ae | ||
|
|
ac9de72b75 | ||
|
|
1f48ad93f2 | ||
|
|
38f7f7aa00 | ||
|
|
fe8175c63a | ||
|
|
2d9e01bbc1 | ||
|
|
022a227b08 | ||
|
|
a2228259f1 | ||
|
|
a61af7c56f | ||
|
|
5d6a646976 | ||
|
|
628d0d7492 | ||
|
|
b76c8745ec | ||
|
|
51e67bc441 | ||
|
|
8887f75b70 | ||
|
|
9436a838c0 | ||
|
|
ef120fa36f | ||
|
|
8c6385e2c5 | ||
|
|
0bd85d9905 | ||
|
|
ce0dab7b28 | ||
|
|
bdcebf1576 | ||
|
|
ddcc5670ce | ||
|
|
86afa184e2 | ||
|
|
77f341f139 | ||
|
|
918b5d99c2 | ||
|
|
7a098d6eff | ||
|
|
239b6f8202 | ||
|
|
8404c79148 | ||
|
|
5b2d04e09d | ||
|
|
6bd13f99d2 | ||
|
|
2e8e12c1c8 | ||
|
|
17929a6b2d | ||
|
|
9455eaf820 | ||
|
|
cc054487ba | ||
|
|
e2df320d00 | ||
|
|
d74313f996 | ||
|
|
b3cab67049 | ||
|
|
996f0b22b9 | ||
|
|
67952ea98e | ||
|
|
7a02ca435f | ||
|
|
71f81283f5 | ||
|
|
058c7c3c33 | ||
|
|
870e33879b | ||
|
|
3ca82bdfc5 | ||
|
|
4721bad286 | ||
|
|
f040cf2f07 | ||
|
|
8d50717c90 | ||
|
|
2512ad3c95 | ||
|
|
8d55f62da9 | ||
|
|
bc7e007634 | ||
|
|
1f3c87e0c7 | ||
|
|
ee0048a406 | ||
|
|
14dcc769f2 | ||
|
|
f2e6255eb3 | ||
|
|
7d392e20fb | ||
|
|
73e08faee9 | ||
|
|
02dc7711e4 | ||
|
|
67b4d80e5b | ||
|
|
5168d2bb39 | ||
|
|
57190a75bf | ||
|
|
f10e865895 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -3,7 +3,6 @@
|
|||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
patreon: # mastodon
|
patreon: # mastodon
|
||||||
open_collective: # Replace with a single Open Collective username e.g., user1
|
open_collective: # Replace with a single Open Collective username e.g., user1
|
||||||
ko_fi: xsk22
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
liberapay: # Replace with a single Liberapay username e.g., user1
|
liberapay: # Replace with a single Liberapay username e.g., user1
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -54,9 +54,15 @@ You can create drafts, edit them, send them manually later or set a scheduled da
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### IzzyOnDroid
|
### Google Play Store
|
||||||
|
|
||||||
[apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
|
[https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
|
||||||
|
|
||||||
|
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
|
||||||
|
|
||||||
|
### F-Droid via IzzyOnDroid
|
||||||
|
|
||||||
|
[https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
|
||||||
|
|
||||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
||||||
|
|
||||||
@@ -64,11 +70,11 @@ Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first
|
|||||||
|
|
||||||
[`https://apt.izzysoft.de/fdroid/repo`](https://apt.izzysoft.de/fdroid/repo)
|
[`https://apt.izzysoft.de/fdroid/repo`](https://apt.izzysoft.de/fdroid/repo)
|
||||||
|
|
||||||
### Google Play Store
|
### F-Droid via saunarepo
|
||||||
|
|
||||||
[play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
|
[https://repo.the-sauna.icu](https://repo.the-sauna.icu/)
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
|
<a href="https://repo.the-sauna.icu"><img height="28" alt="Get it on SaunaRepo" src="img/saunarepo-badge.svg"></a>
|
||||||
|
|
||||||
### F-Droid
|
### F-Droid
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven {
|
||||||
|
url "https://www.jitpack.io"
|
||||||
|
content {
|
||||||
|
includeModule 'com.github.UnifiedPush', 'android-connector'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.0.0'
|
classpath 'com.android.tools.build:gradle:8.0.0'
|
||||||
|
|||||||
1
img/saunarepo-badge.svg
Normal file
1
img/saunarepo-badge.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="124.25" height="28" role="img" aria-label="SAUNAREPO"><title>SAUNAREPO</title><g shape-rendering="crispEdges"><rect width="124.25" height="28" fill="#fb8441"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><image x="9" y="7" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHRpdGxlPkFuZHJvaWQ8L3RpdGxlPjxwYXRoIGQ9Ik0xNy41MjMgMTUuMzQxNGMtLjU1MTEgMC0uOTk5My0uNDQ4Ni0uOTk5My0uOTk5N3MuNDQ4My0uOTk5My45OTkzLS45OTkzYy41NTExIDAgLjk5OTMuNDQ4My45OTkzLjk5OTMuMDAwMS41NTExLS40NDgyLjk5OTctLjk5OTMuOTk5N20tMTEuMDQ2IDBjLS41NTExIDAtLjk5OTMtLjQ0ODYtLjk5OTMtLjk5OTdzLjQ0ODItLjk5OTMuOTk5My0uOTk5M2MuNTUxMSAwIC45OTkzLjQ0ODMuOTk5My45OTkzIDAgLjU1MTEtLjQ0ODMuOTk5Ny0uOTk5My45OTk3bTExLjQwNDUtNi4wMmwxLjk5NzMtMy40NTkyYS40MTYuNDE2IDAgMDAtLjE1MjEtLjU2NzYuNDE2LjQxNiAwIDAwLS41Njc2LjE1MjFsLTIuMDIyMyAzLjUwM0MxNS41OTAyIDguMjQzOSAxMy44NTMzIDcuODUwOCAxMiA3Ljg1MDhzLTMuNTkwMi4zOTMxLTUuMTM2NyAxLjA5ODlMNC44NDEgNS40NDY3YS40MTYxLjQxNjEgMCAwMC0uNTY3Ny0uMTUyMS40MTU3LjQxNTcgMCAwMC0uMTUyMS41Njc2bDEuOTk3MyAzLjQ1OTJDMi42ODg5IDExLjE4NjcuMzQzMiAxNC42NTg5IDAgMTguNzYxaDI0Yy0uMzQzNS00LjEwMjEtMi42ODkyLTcuNTc0My02LjExODUtOS40Mzk2Ii8+PC9zdmc+"/><text transform="scale(.1)" x="721.25" y="175" textLength="802.5" fill="#fff" font-weight="bold">SAUNAREPO</text></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 96
|
versionCode 110
|
||||||
versionName "2.0.1+fork.96"
|
versionName "2.1.6+fork.110"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ android {
|
|||||||
applicationIdSuffix '.debug'
|
applicationIdSuffix '.debug'
|
||||||
}
|
}
|
||||||
githubRelease { initWith release }
|
githubRelease { initWith release }
|
||||||
playRelease { initWith release }
|
fdroidRelease { initWith release }
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_17
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
@@ -69,7 +69,7 @@ dependencies {
|
|||||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||||
implementation 'me.grishka.litex:palette:1.0.0'
|
implementation 'me.grishka.litex:palette:1.0.0'
|
||||||
implementation 'me.grishka.appkit:appkit:1.2.9'
|
implementation 'me.grishka.appkit:appkit:1.2.14'
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
implementation 'org.jsoup:jsoup:1.14.3'
|
implementation 'org.jsoup:jsoup:1.14.3'
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
@@ -78,6 +78,7 @@ dependencies {
|
|||||||
implementation 'com.github.bottom-software-foundation:bottom-java:2.1.0'
|
implementation 'com.github.bottom-software-foundation:bottom-java:2.1.0'
|
||||||
annotationProcessor 'org.parceler:parceler:1.1.12'
|
annotationProcessor 'org.parceler:parceler:1.1.12'
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||||
|
implementation 'com.github.UnifiedPush:android-connector:2.1.1'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:core:1.5.0'
|
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||||
|
|||||||
@@ -257,5 +257,9 @@ public class UiUtilsTest {
|
|||||||
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
||||||
makeField("pronouns", "-- * (asterisk) --")
|
makeField("pronouns", "-- * (asterisk) --")
|
||||||
)).orElseThrow());
|
)).orElseThrow());
|
||||||
|
|
||||||
|
assertEquals("they/(she?)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
||||||
|
makeField("pronouns", "they/(she?)...")
|
||||||
|
)).orElseThrow());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
@@ -36,18 +37,6 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter android:autoVerify="true">
|
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
<data android:scheme="https" android:host="mastodon.social" android:pathPrefix="/@"/>
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter android:autoVerify="true">
|
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
<data android:scheme="https" android:host="mastodon.online" android:pathPrefix="/@"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".PanicResponderActivity"
|
android:name=".PanicResponderActivity"
|
||||||
@@ -57,7 +46,6 @@
|
|||||||
android:theme="@android:style/Theme.NoDisplay">
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
@@ -100,6 +88,15 @@
|
|||||||
<category android:name="me.grishka.fcmtest"/>
|
<category android:name="me.grishka.fcmtest"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
<receiver android:exported="true" android:enabled="true" android:name=".UnifiedPushNotificationReceiver"
|
||||||
|
tools:ignore="ExportedReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>
|
||||||
|
<action android:name="org.unifiedpush.android.connector.UNREGISTERED"/>
|
||||||
|
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT"/>
|
||||||
|
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +1,43 @@
|
|||||||
13bells.com
|
13bells.com
|
||||||
1611.social
|
1611.social
|
||||||
4aem.com
|
4aem.com
|
||||||
|
5dollah.click
|
||||||
adachi.party
|
adachi.party
|
||||||
anime.website
|
adtension.com
|
||||||
annihilation.social
|
annihilation.social
|
||||||
anon-kenkai.com
|
anon-kenkai.com
|
||||||
asbestos.cafe
|
asbestos.cafe
|
||||||
bae.st
|
bae.st
|
||||||
bajax.us
|
|
||||||
banepo.st
|
banepo.st
|
||||||
baraag.net
|
|
||||||
bassam.social
|
bassam.social
|
||||||
|
battlepenguin.video
|
||||||
beefyboys.win
|
beefyboys.win
|
||||||
beepboop.ga
|
|
||||||
berserker.town
|
|
||||||
bikeshed.party
|
|
||||||
boks.moe
|
|
||||||
boymoder.biz
|
boymoder.biz
|
||||||
brainsoap.net
|
brainsoap.net
|
||||||
breastmilk.club
|
breastmilk.club
|
||||||
brighteon.social
|
brighteon.social
|
||||||
bungle.online
|
cachapa.xyz
|
||||||
|
canary.fedinuke.example.com
|
||||||
|
catgirl.life
|
||||||
cawfee.club
|
cawfee.club
|
||||||
|
childlove.space
|
||||||
clew.lol
|
clew.lol
|
||||||
clubcyberia.co
|
clubcyberia.co
|
||||||
collapsitarian.io
|
|
||||||
comfyboy.club
|
|
||||||
contrapointsfan.club
|
contrapointsfan.club
|
||||||
|
crucible.world
|
||||||
cum.camp
|
cum.camp
|
||||||
cum.salon
|
cum.salon
|
||||||
darknight-coffee.org
|
|
||||||
decayable.ink
|
decayable.ink
|
||||||
dembased.xyz
|
dembased.xyz
|
||||||
desupost.soy
|
|
||||||
detroitriotcity.com
|
detroitriotcity.com
|
||||||
eatthebugs.social
|
djsumdog.com
|
||||||
eientei.org
|
eientei.org
|
||||||
elementality.org
|
|
||||||
eveningzoo.club
|
eveningzoo.club
|
||||||
firedragonstudios.com
|
|
||||||
firefaithfellowship.com
|
|
||||||
fluf.club
|
fluf.club
|
||||||
foxfam.club
|
|
||||||
freak.university
|
freak.university
|
||||||
freeatlantis.com
|
freeatlantis.com
|
||||||
freedomstrike.org
|
|
||||||
freesoftwareextremist.com
|
|
||||||
freespeech.group
|
|
||||||
freespeechextremist.com
|
freespeechextremist.com
|
||||||
freetalklive.com
|
|
||||||
froth.zone
|
froth.zone
|
||||||
fulltermprivacy.com
|
|
||||||
gameliberty.club
|
gameliberty.club
|
||||||
gearlandia.haus
|
gearlandia.haus
|
||||||
genderheretics.xyz
|
genderheretics.xyz
|
||||||
@@ -59,42 +46,34 @@ gleasonator.com
|
|||||||
glee.li
|
glee.li
|
||||||
glindr.org
|
glindr.org
|
||||||
goyim.app
|
goyim.app
|
||||||
goyslop.cafe
|
h5q.net
|
||||||
haeder.net
|
haeder.net
|
||||||
handholding.io
|
handholding.io
|
||||||
hitchhiker.social
|
hitchhiker.social
|
||||||
hunk.city
|
|
||||||
iddqd.social
|
iddqd.social
|
||||||
intkos.link
|
|
||||||
justicewarrior.social
|
|
||||||
kawa-kun.com
|
|
||||||
kitsunemimi.club
|
kitsunemimi.club
|
||||||
kiwifarms.cc
|
kiwifarms.cc
|
||||||
kompost.cz
|
|
||||||
kurosawa.moe
|
kurosawa.moe
|
||||||
|
kyaruc.moe
|
||||||
leafposter.club
|
leafposter.club
|
||||||
leftychan.net
|
|
||||||
lewdieheaven.com
|
lewdieheaven.com
|
||||||
liberdon.com
|
liberdon.com
|
||||||
ligma.pro
|
ligma.pro
|
||||||
lolicon.rocks
|
lolicon.rocks
|
||||||
|
lolison.network
|
||||||
lolison.top
|
lolison.top
|
||||||
lovingexpressions.net
|
lovingexpressions.net
|
||||||
mahodou.moe
|
|
||||||
makemysarcophagus.com
|
makemysarcophagus.com
|
||||||
maladaptive.art
|
|
||||||
marsey.moe
|
marsey.moe
|
||||||
masochi.st
|
|
||||||
mastinator.com
|
mastinator.com
|
||||||
merovingian.club
|
merovingian.club
|
||||||
midwaytrades.com
|
midwaytrades.com
|
||||||
mirr0r.city
|
mirr0r.city
|
||||||
moa.st
|
morale.ch
|
||||||
mouse.services
|
mouse.services
|
||||||
mugicha.club
|
mugicha.club
|
||||||
narrativerry.xyz
|
narrativerry.xyz
|
||||||
natehiggers.online
|
natehiggers.online
|
||||||
neckbeard.xyz
|
|
||||||
needs.vodka
|
needs.vodka
|
||||||
neenster.org
|
neenster.org
|
||||||
nicecrew.digital
|
nicecrew.digital
|
||||||
@@ -103,18 +82,18 @@ noagendasocial.com
|
|||||||
noagendasocial.nl
|
noagendasocial.nl
|
||||||
noagendatube.com
|
noagendatube.com
|
||||||
nobodyhasthe.biz
|
nobodyhasthe.biz
|
||||||
nukem.biz
|
norwoodzero.net
|
||||||
obo.sh
|
nyanide.com
|
||||||
onionfarms.org
|
onionfarms.org
|
||||||
pawlicker.com
|
pawlicker.com
|
||||||
pawoo.net
|
pawoo.net
|
||||||
pedo.school
|
pedo.school
|
||||||
|
peervideo.club
|
||||||
piazza.today
|
piazza.today
|
||||||
pibvt.net
|
pibvt.net
|
||||||
pieville.net
|
pieville.net
|
||||||
pisskey.io
|
pisskey.io
|
||||||
plagu.ee
|
plagu.ee
|
||||||
pmth.us
|
|
||||||
poa.st
|
poa.st
|
||||||
poast.org
|
poast.org
|
||||||
poast.tv
|
poast.tv
|
||||||
@@ -123,17 +102,18 @@ prospeech.space
|
|||||||
quodverum.com
|
quodverum.com
|
||||||
r18.social
|
r18.social
|
||||||
rakket.app
|
rakket.app
|
||||||
|
rapemeat.express
|
||||||
rapemeat.solutions
|
rapemeat.solutions
|
||||||
rdrama.cc
|
rayci.st
|
||||||
rebelbase.site
|
rebelbase.site
|
||||||
retardedniggers.forsale
|
|
||||||
rojogato.com
|
|
||||||
ryona.agency
|
ryona.agency
|
||||||
|
sad.cab
|
||||||
schwartzwelt.xyz
|
schwartzwelt.xyz
|
||||||
seal.cafe
|
seal.cafe
|
||||||
|
shaw.app
|
||||||
shigusegubu.club
|
shigusegubu.club
|
||||||
shitpost.cloud
|
shitpost.cloud
|
||||||
shota.house
|
shortstacksran.ch
|
||||||
silliness.observer
|
silliness.observer
|
||||||
skinheads.eu
|
skinheads.eu
|
||||||
skinheads.io
|
skinheads.io
|
||||||
@@ -148,23 +128,20 @@ sneed.social
|
|||||||
sonichu.com
|
sonichu.com
|
||||||
spinster.xyz
|
spinster.xyz
|
||||||
springbo.cc
|
springbo.cc
|
||||||
starnix.network
|
|
||||||
strelizia.net
|
strelizia.net
|
||||||
syspxl.xyz
|
|
||||||
tastingtraffic.net
|
tastingtraffic.net
|
||||||
teci.world
|
teci.world
|
||||||
theapex.social
|
theapex.social
|
||||||
|
thechimp.zone
|
||||||
|
thenobody.club
|
||||||
thepostearthdestination.com
|
thepostearthdestination.com
|
||||||
tkammer.de
|
tkammer.de
|
||||||
trumpislovetrumpis.life
|
trumpislovetrumpis.life
|
||||||
truthsocial.co.in
|
truthsocial.co.in
|
||||||
urchan.org
|
usualsuspects.lol
|
||||||
varishangout.net
|
varishangout.net
|
||||||
whinge.house
|
vtuberfan.social
|
||||||
whinge.town
|
|
||||||
wideboys.org
|
|
||||||
wolfgirl.bar
|
wolfgirl.bar
|
||||||
xn--p1abe3d.xn--80asehdb
|
xn--p1abe3d.xn--80asehdb
|
||||||
yggdrasil.social
|
yggdrasil.social
|
||||||
youjo.love
|
youjo.love
|
||||||
zztails.gay
|
|
||||||
|
|||||||
202
mastodon/src/main/java/name/fraser/neil/plaintext/LICENSE
Normal file
202
mastodon/src/main/java/name/fraser/neil/plaintext/LICENSE
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -169,7 +169,8 @@ public class AudioPlayerService extends Service{
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateNotification(false, false);
|
updateNotification(false, false);
|
||||||
getSystemService(AudioManager.class).requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
|
int audiofocus = GlobalUserPreferences.overlayMedia ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK : AudioManager.AUDIOFOCUS_GAIN;
|
||||||
|
getSystemService(AudioManager.class).requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, audiofocus);
|
||||||
|
|
||||||
player=new MediaPlayer();
|
player=new MediaPlayer();
|
||||||
player.setOnPreparedListener(this::onPlayerPrepared);
|
player.setOnPreparedListener(this::onPlayerPrepared);
|
||||||
@@ -265,7 +266,7 @@ public class AudioPlayerService extends Service{
|
|||||||
private void updateNotification(boolean dismissable, boolean removeNotification){
|
private void updateNotification(boolean dismissable, boolean removeNotification){
|
||||||
Notification.Builder bldr=new Notification.Builder(this)
|
Notification.Builder bldr=new Notification.Builder(this)
|
||||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||||
.setContentTitle(status.account.displayName)
|
.setContentTitle(status.account.getDisplayName())
|
||||||
.setContentText(HtmlParser.strip(status.content))
|
.setContentText(HtmlParser.strip(status.content))
|
||||||
.setOngoing(!dismissable)
|
.setOngoing(!dismissable)
|
||||||
.setShowWhen(false)
|
.setShowWhen(false)
|
||||||
@@ -280,7 +281,7 @@ public class AudioPlayerService extends Service{
|
|||||||
|
|
||||||
if(playerReady){
|
if(playerReady){
|
||||||
boolean isPlaying=player.isPlaying();
|
boolean isPlaying=player.isPlaying();
|
||||||
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_pause_24 : R.drawable.ic_play_24),
|
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_fluent_pause_24_filled : R.drawable.ic_fluent_play_24_filled),
|
||||||
getString(isPlaying ? R.string.pause : R.string.play),
|
getString(isPlaying ? R.string.pause : R.string.play),
|
||||||
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
|
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
|
||||||
.build());
|
.build());
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
UiUtils.setUserPreferredTheme(this);
|
UiUtils.setUserPreferredTheme(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
|
|
||||||
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
||||||
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
|
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
|
||||||
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
|
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package org.joinmastodon.android;
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
||||||
|
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.MATERIAL3;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
|
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.ContentType;
|
import org.joinmastodon.android.model.ContentType;
|
||||||
@@ -51,12 +51,17 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean collapseLongPosts;
|
public static boolean collapseLongPosts;
|
||||||
public static boolean spectatorMode;
|
public static boolean spectatorMode;
|
||||||
public static boolean autoHideFab;
|
public static boolean autoHideFab;
|
||||||
public static boolean compactReblogReplyLine;
|
|
||||||
public static boolean allowRemoteLoading;
|
public static boolean allowRemoteLoading;
|
||||||
public static boolean forwardReportDefault;
|
public static boolean forwardReportDefault;
|
||||||
public static AutoRevealMode autoRevealEqualSpoilers;
|
public static AutoRevealMode autoRevealEqualSpoilers;
|
||||||
public static ColorPreference color;
|
|
||||||
public static boolean disableM3PillActiveIndicator;
|
public static boolean disableM3PillActiveIndicator;
|
||||||
|
public static boolean showNavigationLabels;
|
||||||
|
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
|
||||||
|
public static boolean overlayMedia;
|
||||||
|
public static boolean showSuicideHelp;
|
||||||
|
public static boolean underlinedLinks;
|
||||||
|
public static ColorPreference color;
|
||||||
|
public static boolean likeIcon;
|
||||||
|
|
||||||
private static SharedPreferences getPrefs(){
|
private static SharedPreferences getPrefs(){
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
@@ -106,11 +111,19 @@ public class GlobalUserPreferences{
|
|||||||
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
||||||
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
||||||
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
||||||
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
|
||||||
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
||||||
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
||||||
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
|
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
|
||||||
disableM3PillActiveIndicator=prefs.getBoolean("disableM3PillActiveIndicator", false);
|
disableM3PillActiveIndicator=prefs.getBoolean("disableM3PillActiveIndicator", false);
|
||||||
|
showNavigationLabels=prefs.getBoolean("showNavigationLabels", true);
|
||||||
|
displayPronounsInTimelines=prefs.getBoolean("displayPronounsInTimelines", true);
|
||||||
|
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
|
||||||
|
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
|
||||||
|
overlayMedia=prefs.getBoolean("overlayMedia", false);
|
||||||
|
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
|
||||||
|
underlinedLinks=prefs.getBoolean("underlinedLinks", true);
|
||||||
|
color=ColorPreference.valueOf(prefs.getString("color", MATERIAL3.name()));
|
||||||
|
likeIcon=prefs.getBoolean("likeIcon", false);
|
||||||
|
|
||||||
if (prefs.contains("prefixRepliesWithRe")) {
|
if (prefs.contains("prefixRepliesWithRe")) {
|
||||||
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
||||||
@@ -121,14 +134,11 @@ public class GlobalUserPreferences{
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE);
|
||||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
if(migrationLevel < 61)
|
||||||
} catch (IllegalArgumentException|ClassCastException ignored) {
|
migrateToUpstreamVersion61();
|
||||||
// invalid color name or color was previously saved as integer
|
if(migrationLevel < BuildConfig.VERSION_CODE)
|
||||||
color=ColorPreference.PINK;
|
prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply();
|
||||||
}
|
|
||||||
|
|
||||||
if(prefs.getInt("migrationLevel", 0) < 61) migrateToUpstreamVersion61();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void save(){
|
public static void save(){
|
||||||
@@ -158,15 +168,43 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("collapseLongPosts", collapseLongPosts)
|
.putBoolean("collapseLongPosts", collapseLongPosts)
|
||||||
.putBoolean("spectatorMode", spectatorMode)
|
.putBoolean("spectatorMode", spectatorMode)
|
||||||
.putBoolean("autoHideFab", autoHideFab)
|
.putBoolean("autoHideFab", autoHideFab)
|
||||||
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
|
||||||
.putString("color", color.name())
|
|
||||||
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||||
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
||||||
.putBoolean("forwardReportDefault", forwardReportDefault)
|
.putBoolean("forwardReportDefault", forwardReportDefault)
|
||||||
.putBoolean("disableM3PillActiveIndicator", disableM3PillActiveIndicator)
|
.putBoolean("disableM3PillActiveIndicator", disableM3PillActiveIndicator)
|
||||||
|
.putBoolean("showNavigationLabels", showNavigationLabels)
|
||||||
|
.putBoolean("displayPronounsInTimelines", displayPronounsInTimelines)
|
||||||
|
.putBoolean("displayPronounsInThreads", displayPronounsInThreads)
|
||||||
|
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
|
||||||
|
.putBoolean("overlayMedia", overlayMedia)
|
||||||
|
.putBoolean("showSuicideHelp", showSuicideHelp)
|
||||||
|
.putBoolean("underlinedLinks", underlinedLinks)
|
||||||
|
.putString("color", color.name())
|
||||||
|
.putBoolean("likeIcon", likeIcon)
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ThemePreference{
|
||||||
|
AUTO,
|
||||||
|
LIGHT,
|
||||||
|
DARK
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AutoRevealMode {
|
||||||
|
NEVER,
|
||||||
|
THREADS,
|
||||||
|
DISCUSSIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PrefixRepliesMode {
|
||||||
|
NEVER,
|
||||||
|
ALWAYS,
|
||||||
|
TO_OTHERS
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//region preferences migrations
|
||||||
|
|
||||||
private static void migrateToUpstreamVersion61(){
|
private static void migrateToUpstreamVersion61(){
|
||||||
Log.d(TAG, "Migrating preferences to upstream version 61!!");
|
Log.d(TAG, "Migrating preferences to upstream version 61!!");
|
||||||
|
|
||||||
@@ -213,49 +251,8 @@ public class GlobalUserPreferences{
|
|||||||
|
|
||||||
localPrefs.save();
|
localPrefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs.edit().putInt("migrationLevel", 61).apply();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ColorPreference{
|
//endregion
|
||||||
MATERIAL3,
|
|
||||||
PINK,
|
|
||||||
PURPLE,
|
|
||||||
GREEN,
|
|
||||||
BLUE,
|
|
||||||
BROWN,
|
|
||||||
RED,
|
|
||||||
YELLOW;
|
|
||||||
|
|
||||||
public @StringRes int getName() {
|
|
||||||
return switch(this){
|
|
||||||
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
|
||||||
case PINK -> R.string.sk_color_palette_pink;
|
|
||||||
case PURPLE -> R.string.sk_color_palette_purple;
|
|
||||||
case GREEN -> R.string.sk_color_palette_green;
|
|
||||||
case BLUE -> R.string.sk_color_palette_blue;
|
|
||||||
case BROWN -> R.string.sk_color_palette_brown;
|
|
||||||
case RED -> R.string.sk_color_palette_red;
|
|
||||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ThemePreference{
|
|
||||||
AUTO,
|
|
||||||
LIGHT,
|
|
||||||
DARK
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum AutoRevealMode {
|
|
||||||
NEVER,
|
|
||||||
THREADS,
|
|
||||||
DISCUSSIONS
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PrefixRepliesMode {
|
|
||||||
NEVER,
|
|
||||||
ALWAYS,
|
|
||||||
TO_OTHERS
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,62 +31,46 @@ import org.joinmastodon.android.utils.ProvidesAssistContent;
|
|||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
||||||
|
private static final String TAG="MainActivity";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||||
UiUtils.setUserPreferredTheme(this);
|
AccountSession session=getCurrentSession();
|
||||||
|
UiUtils.setUserPreferredTheme(this, session);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if(savedInstanceState==null){
|
Thread.UncaughtExceptionHandler defaultHandler=Thread.getDefaultUncaughtExceptionHandler();
|
||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
Thread.setDefaultUncaughtExceptionHandler((t, e)->{
|
||||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
File file=new File(MastodonApp.context.getFilesDir(), "crash.log");
|
||||||
}else{
|
try(FileOutputStream out=new FileOutputStream(file)){
|
||||||
AccountSession session;
|
PrintWriter writer=new PrintWriter(out);
|
||||||
Bundle args=new Bundle();
|
writer.println(BuildConfig.VERSION_NAME+" ("+BuildConfig.VERSION_CODE+")");
|
||||||
Intent intent=getIntent();
|
writer.println(Instant.now().toString());
|
||||||
if(intent.hasExtra("fromExternalShare")) {
|
writer.println();
|
||||||
AccountSessionManager.getInstance()
|
e.printStackTrace(writer);
|
||||||
.setLastActiveAccountID(intent.getStringExtra("account"));
|
writer.flush();
|
||||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
|
}catch(IOException x){
|
||||||
AccountSessionManager.getInstance().getLastActiveAccount());
|
Log.e(TAG, "Error writing crash.log", x);
|
||||||
showFragmentForExternalShare(intent.getExtras());
|
}finally{
|
||||||
return;
|
defaultHandler.uncaughtException(t, e);
|
||||||
}
|
|
||||||
|
|
||||||
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
|
||||||
boolean hasNotification = intent.hasExtra("notification");
|
|
||||||
if(fromNotification){
|
|
||||||
String accountID=intent.getStringExtra("accountID");
|
|
||||||
try{
|
|
||||||
session=AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
if(!hasNotification) args.putString("tab", "notifications");
|
|
||||||
}catch(IllegalStateException x){
|
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
|
||||||
}
|
|
||||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
|
|
||||||
args.putString("account", session.getID());
|
|
||||||
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
if(fromNotification && hasNotification){
|
|
||||||
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
|
||||||
showFragmentForNotification(notification, session.getID());
|
|
||||||
} else if (intent.getBooleanExtra("compose", false)){
|
|
||||||
showCompose();
|
|
||||||
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
|
|
||||||
handleURL(intent.getData(), null);
|
|
||||||
} else {
|
|
||||||
showFragmentClearingBackStack(fragment);
|
|
||||||
maybeRequestNotificationsPermission();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(savedInstanceState==null){
|
||||||
|
restartHomeFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
@@ -143,7 +127,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
|
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
|
||||||
new GetSearchResults(q, null, true)
|
new GetSearchResults(q, null, true, null, 0, 0)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
@@ -259,4 +243,81 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
Fragment fragment = getCurrentFragment();
|
Fragment fragment = getCurrentFragment();
|
||||||
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccountSession getCurrentSession(){
|
||||||
|
AccountSession session;
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
Intent intent=getIntent();
|
||||||
|
if(intent.hasExtra("fromExternalShare")) {
|
||||||
|
return AccountSessionManager.getInstance()
|
||||||
|
.getAccount(intent.getStringExtra("account"));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
||||||
|
boolean hasNotification = intent.hasExtra("notification");
|
||||||
|
if(fromNotification){
|
||||||
|
String accountID=intent.getStringExtra("accountID");
|
||||||
|
try{
|
||||||
|
session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
if(!hasNotification) args.putString("tab", "notifications");
|
||||||
|
}catch(IllegalStateException x){
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restartActivity(){
|
||||||
|
finish();
|
||||||
|
startActivity(new Intent(this, MainActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void restartHomeFragment(){
|
||||||
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||||
|
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
||||||
|
}else{
|
||||||
|
AccountSession session;
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
Intent intent=getIntent();
|
||||||
|
if(intent.hasExtra("fromExternalShare")) {
|
||||||
|
AccountSessionManager.getInstance()
|
||||||
|
.setLastActiveAccountID(intent.getStringExtra("account"));
|
||||||
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
|
||||||
|
AccountSessionManager.getInstance().getLastActiveAccount());
|
||||||
|
showFragmentForExternalShare(intent.getExtras());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
||||||
|
boolean hasNotification = intent.hasExtra("notification");
|
||||||
|
if(fromNotification){
|
||||||
|
String accountID=intent.getStringExtra("accountID");
|
||||||
|
try{
|
||||||
|
session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
if(!hasNotification) args.putString("tab", "notifications");
|
||||||
|
}catch(IllegalStateException x){
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
|
}
|
||||||
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
|
||||||
|
args.putString("account", session.getID());
|
||||||
|
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
||||||
|
fragment.setArguments(args);
|
||||||
|
if(fromNotification && hasNotification){
|
||||||
|
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
||||||
|
showFragmentForNotification(notification, session.getID());
|
||||||
|
} else if (intent.getBooleanExtra("compose", false)){
|
||||||
|
showCompose();
|
||||||
|
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
|
||||||
|
handleURL(intent.getData(), null);
|
||||||
|
} else {
|
||||||
|
showFragmentClearingBackStack(fragment);
|
||||||
|
maybeRequestNotificationsPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
|
|
||||||
private static final int SUMMARY_ID = 791;
|
private static final int SUMMARY_ID = 791;
|
||||||
private static int notificationId = 0;
|
private static int notificationId = 0;
|
||||||
private static Map<String, Integer> notificationIdsForAccounts = new HashMap<>();
|
private static final Map<String, Integer> notificationIdsForAccounts = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent){
|
public void onReceive(Context context, Intent intent){
|
||||||
@@ -148,6 +148,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void notifyUnifiedPush(Context context, String accountID, org.joinmastodon.android.model.Notification notification) {
|
||||||
|
// push notifications are only created from the official push notification, so we create a fake from by transforming the notification
|
||||||
|
PushNotificationReceiver.this.notify(context, PushNotification.fromNotification(context, notification), accountID, notification);
|
||||||
|
}
|
||||||
|
|
||||||
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
|
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
|
||||||
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
||||||
AccountSession session=AccountSessionManager.get(accountID);
|
AccountSession session=AccountSessionManager.get(accountID);
|
||||||
@@ -169,6 +174,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
|
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
|
||||||
.map(type->{
|
.map(type->{
|
||||||
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
|
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
channel.setLightColor(context.getColor(R.color.primary_700));
|
||||||
|
channel.enableLights(true);
|
||||||
channel.setGroup(accountID);
|
channel.setGroup(accountID);
|
||||||
return channel;
|
return channel;
|
||||||
})
|
})
|
||||||
@@ -198,11 +205,12 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
|
.setLights(UiUtils.getThemeColor(context, android.R.attr.colorAccent), 500, 1000)
|
||||||
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
|
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
|
||||||
|
|
||||||
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
||||||
builder.setSmallIcon(switch (pn.notificationType) {
|
builder.setSmallIcon(switch (pn.notificationType) {
|
||||||
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
|
case FAVORITE -> GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled;
|
||||||
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
|
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
|
||||||
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
|
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
|
||||||
case MENTION -> R.drawable.ic_fluent_mention_24_filled;
|
case MENTION -> R.drawable.ic_fluent_mention_24_filled;
|
||||||
@@ -314,11 +322,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
|
|
||||||
CreateStatus.Request req=new CreateStatus.Request();
|
CreateStatus.Request req=new CreateStatus.Request();
|
||||||
req.status = initialText + input.toString();
|
req.status = initialText + input.toString();
|
||||||
req.language = preferences.postingDefaultLanguage;
|
req.language = notification.status.language;
|
||||||
req.visibility = preferences.postingDefaultVisibility;
|
req.visibility = notification.status.visibility;
|
||||||
req.inReplyToId = notification.status.id;
|
req.inReplyToId = notification.status.id;
|
||||||
|
|
||||||
if (!notification.status.spoilerText.isEmpty() &&
|
if (notification.status.hasSpoiler() &&
|
||||||
(GlobalUserPreferences.prefixReplies == ALWAYS
|
(GlobalUserPreferences.prefixReplies == ALWAYS
|
||||||
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(notification.status.account.id)))
|
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(notification.status.account.id)))
|
||||||
&& !notification.status.spoilerText.startsWith("re: ")) {
|
&& !notification.status.spoilerText.startsWith("re: ")) {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.model.Notification;
|
||||||
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
|
import org.unifiedpush.android.connector.MessagingReceiver;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
|
public class UnifiedPushNotificationReceiver extends MessagingReceiver{
|
||||||
|
private static final String TAG="UnifiedPushNotificationReceiver";
|
||||||
|
|
||||||
|
public UnifiedPushNotificationReceiver() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewEndpoint(@NotNull Context context, @NotNull String endpoint, @NotNull String instance) {
|
||||||
|
// Called when a new endpoint be used for sending push messages
|
||||||
|
Log.d(TAG, "onNewEndpoint: New Endpoint " + endpoint + " for "+ instance);
|
||||||
|
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||||
|
if (account != null)
|
||||||
|
account.getPushSubscriptionManager().registerAccountForPush(null, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistrationFailed(@NotNull Context context, @NotNull String instance) {
|
||||||
|
// called when the registration is not possible, eg. no network
|
||||||
|
Log.d(TAG, "onRegistrationFailed: " + instance);
|
||||||
|
//re-register for gcm
|
||||||
|
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||||
|
if (account != null)
|
||||||
|
account.getPushSubscriptionManager().registerAccountForPush(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnregistered(@NotNull Context context, @NotNull String instance) {
|
||||||
|
// called when this application is unregistered from receiving push messages
|
||||||
|
Log.d(TAG, "onUnregistered: " + instance);
|
||||||
|
//re-register for gcm
|
||||||
|
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||||
|
if (account != null)
|
||||||
|
account.getPushSubscriptionManager().registerAccountForPush(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(@NotNull Context context, @NotNull byte[] message, @NotNull String instance) {
|
||||||
|
// Called when a new message is received. The message contains the full POST body of the push message
|
||||||
|
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//this is stupid
|
||||||
|
// Mastodon stores the info to decrypt the message in the HTTP headers, which are not accessible in UnifiedPush,
|
||||||
|
// thus it is not possible to decrypt them. SO we need to re-request them from the server and transform them later on
|
||||||
|
// The official uses fcm and moves the headers to extra data, see
|
||||||
|
// https://github.com/mastodon/webpush-fcm-relay/blob/cac95b28d5364b0204f629283141ac3fb749e0c5/webpush-fcm-relay.go#L116
|
||||||
|
// https://github.com/tuskyapp/Tusky/pull/2303#issue-1112080540
|
||||||
|
account.getCacheController().getNotifications(null, 1, false, false, true, new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||||
|
result.items
|
||||||
|
.stream()
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(value->MastodonAPIController.runInBackground(()->new PushNotificationReceiver().notifyUnifiedPush(context, instance, value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
//professional error handling
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -69,12 +70,11 @@ public class CacheController{
|
|||||||
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
||||||
status.postprocess();
|
status.postprocess();
|
||||||
int flags=cursor.getInt(1);
|
int flags=cursor.getInt(1);
|
||||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0) ? status.id : null;
|
||||||
newMaxID=status.id;
|
newMaxID=status.id;
|
||||||
result.add(status);
|
result.add(status);
|
||||||
}while(cursor.moveToNext());
|
}while(cursor.moveToNext());
|
||||||
String _newMaxID=newMaxID;
|
String _newMaxID=newMaxID;
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
|
||||||
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,9 +86,7 @@ public class CacheController{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
ArrayList<Status> filtered=new ArrayList<>(result);
|
callback.onSuccess(new CacheablePaginatedResponse<>(result, result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||||
AccountSessionManager.get(accountID).filterStatuses(filtered, FilterContext.HOME);
|
|
||||||
callback.onSuccess(new CacheablePaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
|
||||||
putHomeTimeline(result, maxID==null);
|
putHomeTimeline(result, maxID==null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +114,14 @@ public class CacheController{
|
|||||||
values.put("id", s.id);
|
values.put("id", s.id);
|
||||||
values.put("json", MastodonAPIController.gson.toJson(s));
|
values.put("json", MastodonAPIController.gson.toJson(s));
|
||||||
int flags=0;
|
int flags=0;
|
||||||
if(s.hasGapAfter)
|
if(Objects.equals(s.hasGapAfter, s.id))
|
||||||
flags|=POST_FLAG_GAP_AFTER;
|
flags|=POST_FLAG_GAP_AFTER;
|
||||||
values.put("flags", flags);
|
values.put("flags", flags);
|
||||||
values.put("time", s.createdAt.getEpochSecond());
|
values.put("time", s.createdAt.getEpochSecond());
|
||||||
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
}
|
}
|
||||||
|
if(!clear)
|
||||||
|
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"1000"});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +273,28 @@ public class CacheController{
|
|||||||
|
|
||||||
public void deleteStatus(String id){
|
public void deleteStatus(String id){
|
||||||
runOnDbThread((db)->{
|
runOnDbThread((db)->{
|
||||||
|
String gapId=null;
|
||||||
|
int gapFlags=0;
|
||||||
|
// select to-be-removed and newer row
|
||||||
|
try(Cursor cursor=db.query("home_timeline", new String[]{"id", "flags"}, "`time`>=(SELECT `time` FROM `home_timeline` WHERE `id`=?)", new String[]{id}, null, null, "`time` ASC", "2")){
|
||||||
|
boolean hadGapAfter=false;
|
||||||
|
// always either one or two iterations (only one if there's no newer post)
|
||||||
|
while(cursor.moveToNext()){
|
||||||
|
String currentId=cursor.getString(0);
|
||||||
|
int currentFlags=cursor.getInt(1);
|
||||||
|
if(currentId.equals(id)){
|
||||||
|
hadGapAfter=((currentFlags & POST_FLAG_GAP_AFTER)!=0);
|
||||||
|
}else if(hadGapAfter){
|
||||||
|
gapFlags=currentFlags|POST_FLAG_GAP_AFTER;
|
||||||
|
gapId=currentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(gapId!=null){
|
||||||
|
ContentValues values=new ContentValues();
|
||||||
|
values.put("flags", gapFlags);
|
||||||
|
db.update("home_timeline", values, "`id`=?", new String[]{gapId});
|
||||||
|
}
|
||||||
db.delete("home_timeline", "`id`=?", new String[]{id});
|
db.delete("home_timeline", "`id`=?", new String[]{id});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class MastodonAPIController{
|
|||||||
.create();
|
.create();
|
||||||
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
||||||
private static OkHttpClient httpClient=new OkHttpClient.Builder()
|
private static OkHttpClient httpClient=new OkHttpClient.Builder()
|
||||||
.readTimeout(5, TimeUnit.MINUTES)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
@@ -113,13 +113,13 @@ public class MastodonAPIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Request hreq=builder.build();
|
Request hreq=builder.build();
|
||||||
Call call=httpClient.newCall(hreq);
|
OkHttpClient client=req.timeout>0
|
||||||
|
? httpClient.newBuilder().readTimeout(req.timeout, TimeUnit.MILLISECONDS).build()
|
||||||
|
: httpClient;
|
||||||
|
Call call=client.newCall(hreq);
|
||||||
synchronized(req){
|
synchronized(req){
|
||||||
req.okhttpCall=call;
|
req.okhttpCall=call;
|
||||||
}
|
}
|
||||||
if(req.timeout>0){
|
|
||||||
call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);
|
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);
|
||||||
|
|||||||
@@ -121,13 +121,13 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
.orElseGet(() -> this.execNoAuth(domain));
|
.orElseGet(() -> this.execNoAuth(domain));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
public MastodonAPIRequest<T> wrapProgress(Context context, @StringRes int message, boolean cancelable){
|
||||||
return wrapProgress(activity, message, cancelable, null);
|
return wrapProgress(context, message, cancelable, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable, Consumer<ProgressDialog> transform){
|
public MastodonAPIRequest<T> wrapProgress(Context context, @StringRes int message, boolean cancelable, Consumer<ProgressDialog> transform){
|
||||||
progressDialog=new ProgressDialog(activity);
|
progressDialog=new ProgressDialog(context);
|
||||||
progressDialog.setMessage(activity.getString(message));
|
progressDialog.setMessage(context.getString(message));
|
||||||
progressDialog.setCancelable(cancelable);
|
progressDialog.setCancelable(cancelable);
|
||||||
if (transform != null) transform.accept(progressDialog);
|
if (transform != null) transform.accept(progressDialog);
|
||||||
if(cancelable){
|
if(cancelable){
|
||||||
@@ -153,8 +153,9 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
headers.put(key, value);
|
headers.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setTimeout(long timeout){
|
public MastodonAPIRequest<T> setTimeout(long timeout){
|
||||||
this.timeout=timeout;
|
this.timeout=timeout;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getPathPrefix(){
|
protected String getPathPrefix(){
|
||||||
|
|||||||
@@ -120,9 +120,21 @@ public class PushSubscriptionManager{
|
|||||||
return !TextUtils.isEmpty(deviceToken);
|
return !TextUtils.isEmpty(deviceToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void registerAccountForPush(PushSubscription subscription){
|
public void registerAccountForPush(PushSubscription subscription){
|
||||||
if(TextUtils.isEmpty(deviceToken))
|
// this function is used for registering push notifications using FCM
|
||||||
throw new IllegalStateException("No device push token available");
|
// to avoid NonFreeNet in F-Droid, this registration is disabled in it
|
||||||
|
// see https://github.com/LucasGGamerM/moshidon/issues/206 for more context
|
||||||
|
if(BuildConfig.BUILD_TYPE.equals("fdroidRelease") || TextUtils.isEmpty(deviceToken)){
|
||||||
|
Log.d(TAG, "Skipping registering for FCM push notifications");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/";
|
||||||
|
registerAccountForPush(subscription, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerAccountForPush(PushSubscription subscription, String endpoint){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
Log.d(TAG, "registerAccountForPush: started for "+accountID);
|
Log.d(TAG, "registerAccountForPush: started for "+accountID);
|
||||||
String encodedPublicKey, encodedAuthKey, pushAccountID;
|
String encodedPublicKey, encodedAuthKey, pushAccountID;
|
||||||
@@ -151,12 +163,17 @@ public class PushSubscriptionManager{
|
|||||||
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
|
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new RegisterForPushNotifications(deviceToken,
|
|
||||||
|
//work-around for adding the randomAccountId
|
||||||
|
String newEndpoint = endpoint;
|
||||||
|
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
|
||||||
|
newEndpoint += pushAccountID;
|
||||||
|
|
||||||
|
new RegisterForPushNotifications(newEndpoint,
|
||||||
encodedPublicKey,
|
encodedPublicKey,
|
||||||
encodedAuthKey,
|
encodedAuthKey,
|
||||||
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
|
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
|
||||||
subscription==null ? PushSubscription.Policy.ALL : subscription.policy,
|
subscription==null ? PushSubscription.Policy.ALL : subscription.policy)
|
||||||
pushAccountID)
|
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(PushSubscription result){
|
public void onSuccess(PushSubscription result){
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import org.joinmastodon.android.MastodonApp;
|
|||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||||
|
import org.joinmastodon.android.events.ReblogDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@ public class StatusInteractionController{
|
|||||||
runningFavoriteRequests.remove(status.id);
|
runningFavoriteRequests.remove(status.id);
|
||||||
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,13 +60,13 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.favourited=!favorited;
|
status.favourited=!favorited;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningFavoriteRequests.put(status.id, req);
|
runningFavoriteRequests.put(status.id, req);
|
||||||
status.favourited=favorited;
|
status.favourited=favorited;
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
||||||
@@ -78,11 +81,15 @@ public class StatusInteractionController{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status reblog){
|
public void onSuccess(Status reblog){
|
||||||
Status result = reblog.getContentStatus();
|
Status result=reblog.getContentStatus();
|
||||||
runningReblogRequests.remove(status.id);
|
runningReblogRequests.remove(status.id);
|
||||||
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
|
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if(updateCounters){
|
||||||
|
E.post(new StatusCountersUpdatedEvent(result));
|
||||||
|
if(reblogged) E.post(new StatusCreatedEvent(reblog, accountID));
|
||||||
|
else E.post(new ReblogDeletedEvent(status.id, accountID));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -91,13 +98,13 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.reblogged=!reblogged;
|
status.reblogged=!reblogged;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningReblogRequests.put(status.id, req);
|
runningReblogRequests.put(status.id, req);
|
||||||
status.reblogged=reblogged;
|
status.reblogged=reblogged;
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBookmarked(Status status, boolean bookmarked){
|
public void setBookmarked(Status status, boolean bookmarked){
|
||||||
@@ -118,7 +125,7 @@ public class StatusInteractionController{
|
|||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
runningBookmarkRequests.remove(status.id);
|
runningBookmarkRequests.remove(status.id);
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -127,12 +134,12 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.bookmarked=!bookmarked;
|
status.bookmarked=!bookmarked;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningBookmarkRequests.put(status.id, req);
|
runningBookmarkRequests.put(status.id, req);
|
||||||
status.bookmarked=bookmarked;
|
status.bookmarked=bookmarked;
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
|
||||||
|
public class GetAccountBlocks extends HeaderPaginationRequest<Account>{
|
||||||
|
public GetAccountBlocks(String maxID, int limit){
|
||||||
|
super(HttpMethod.GET, "/blocks", new TypeToken<>(){});
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", limit+"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
|
||||||
|
public class GetAccountMutes extends HeaderPaginationRequest<Account>{
|
||||||
|
public GetAccountMutes(String maxID, int limit){
|
||||||
|
super(HttpMethod.GET, "/mutes/", new TypeToken<>(){});
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", limit+"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,15 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
|||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
|
||||||
public class SetAccountMuted extends MastodonAPIRequest<Relationship>{
|
public class SetAccountMuted extends MastodonAPIRequest<Relationship>{
|
||||||
public SetAccountMuted(String id, boolean muted){
|
public SetAccountMuted(String id, boolean muted, long duration){
|
||||||
super(HttpMethod.POST, "/accounts/"+id+"/"+(muted ? "mute" : "unmute"), Relationship.class);
|
super(HttpMethod.POST, "/accounts/"+id+"/"+(muted ? "mute" : "unmute"), Relationship.class);
|
||||||
setRequestBody(new Object());
|
setRequestBody(new Request(duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Request{
|
||||||
|
public long duration;
|
||||||
|
public Request(long duration){
|
||||||
|
this.duration=duration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.accounts;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
|
||||||
|
public class SetPrivateNote extends MastodonAPIRequest<Relationship>{
|
||||||
|
public SetPrivateNote(String id, String comment){
|
||||||
|
super(MastodonAPIRequest.HttpMethod.POST, "/accounts/"+id+"/note", Relationship.class);
|
||||||
|
Request req = new Request(comment);
|
||||||
|
setRequestBody(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Request{
|
||||||
|
public String comment;
|
||||||
|
public Request(String comment){
|
||||||
|
this.comment=comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,18 +6,19 @@ import org.joinmastodon.android.model.Preferences;
|
|||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
|
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
|
||||||
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){
|
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable, Boolean indexable){
|
||||||
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
|
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
|
||||||
setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
|
setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Request{
|
private static class Request{
|
||||||
public Boolean locked, discoverable;
|
public Boolean locked, discoverable, indexable;
|
||||||
public RequestSource source;
|
public RequestSource source;
|
||||||
|
|
||||||
public Request(Boolean locked, Boolean discoverable, RequestSource source){
|
public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){
|
||||||
this.locked=locked;
|
this.locked=locked;
|
||||||
this.discoverable=discoverable;
|
this.discoverable=discoverable;
|
||||||
|
this.indexable=indexable;
|
||||||
this.source=source;
|
this.source=source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.announcements;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
public class AddAnnouncementReaction extends MastodonAPIRequest<Object> {
|
||||||
|
public AddAnnouncementReaction(String id, String emoji) {
|
||||||
|
super(HttpMethod.PUT, "/announcements/" + id + "/reactions/" + emoji, Object.class);
|
||||||
|
setRequestBody(new Object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.announcements;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
|
||||||
|
public class DeleteAnnouncementReaction extends MastodonAPIRequest<Object> {
|
||||||
|
public DeleteAnnouncementReaction(String id, String emoji) {
|
||||||
|
super(HttpMethod.DELETE, "/announcements/" + id + "/reactions/" + emoji, Object.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@ import org.joinmastodon.android.model.FilterContext;
|
|||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
class FilterRequest{
|
class FilterRequest{
|
||||||
public String title;
|
public String title;
|
||||||
public EnumSet<FilterContext> context;
|
public EnumSet<FilterContext> context;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import okhttp3.MultipartBody;
|
|||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
|
|
||||||
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
|
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
|
||||||
private String maxID;
|
private final String maxID;
|
||||||
public PleromaMarkNotificationsRead(String maxID) {
|
public PleromaMarkNotificationsRead(String maxID) {
|
||||||
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
|
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
|
||||||
this.maxID = maxID;
|
this.maxID = maxID;
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
|||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
|
||||||
public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscription>{
|
public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscription>{
|
||||||
public RegisterForPushNotifications(String deviceToken, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy, String accountID){
|
public RegisterForPushNotifications(String endpoint, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
super(HttpMethod.POST, "/push/subscription", PushSubscription.class);
|
super(HttpMethod.POST, "/push/subscription", PushSubscription.class);
|
||||||
Request r=new Request();
|
Request r=new Request();
|
||||||
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
r.subscription.endpoint=endpoint;
|
||||||
r.data.alerts=alerts;
|
r.data.alerts=alerts;
|
||||||
r.policy=policy;
|
r.policy=policy;
|
||||||
r.subscription.keys.p256dh=encryptionKey;
|
r.subscription.keys.p256dh=encryptionKey;
|
||||||
|
|||||||
@@ -4,13 +4,19 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
|||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
|
|
||||||
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
|
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
|
||||||
public GetSearchResults(String query, Type type, boolean resolve){
|
public GetSearchResults(String query, Type type, boolean resolve, String maxID, int offset, int count){
|
||||||
super(HttpMethod.GET, "/search", SearchResults.class);
|
super(HttpMethod.GET, "/search", SearchResults.class);
|
||||||
addQueryParameter("q", query);
|
addQueryParameter("q", query);
|
||||||
if(type!=null)
|
if(type!=null)
|
||||||
addQueryParameter("type", type.name().toLowerCase());
|
addQueryParameter("type", type.name().toLowerCase());
|
||||||
if(resolve)
|
if(resolve)
|
||||||
addQueryParameter("resolve", "true");
|
addQueryParameter("resolve", "true");
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(offset>0)
|
||||||
|
addQueryParameter("offset", String.valueOf(offset));
|
||||||
|
if(count>0)
|
||||||
|
addQueryParameter("limit", String.valueOf(count));
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetSearchResults limit(int limit){
|
public GetSearchResults limit(int limit){
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
public class AddStatusReaction extends MastodonAPIRequest<Status> {
|
||||||
|
public AddStatusReaction(String id, String emoji) {
|
||||||
|
super(HttpMethod.POST, "/statuses/" + id + "/react/" + emoji, Status.class);
|
||||||
|
setRequestBody(new Object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.AkkomaTranslation;
|
||||||
|
|
||||||
|
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
|
||||||
|
public AkkomaTranslateStatus(String id, String lang){
|
||||||
|
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -11,13 +11,11 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class CreateStatus extends MastodonAPIRequest<Status>{
|
public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||||
public static final Instant DRAFTS_AFTER_INSTANT = Instant.ofEpochMilli(253370764799999L) /* end of 9998 */;
|
public static long EPOCH_OF_THE_YEAR_FIVE_THOUSAND=95617584000000L;
|
||||||
private static final float draftFactor = 31536000000f /* one year */ / 253370764799999f /* end of 9998 */;
|
public static final Instant DRAFTS_AFTER_INSTANT=Instant.ofEpochMilli(EPOCH_OF_THE_YEAR_FIVE_THOUSAND - 1) /* end of 4999 */;
|
||||||
|
|
||||||
public static Instant getDraftInstant() {
|
public static Instant getDraftInstant() {
|
||||||
// returns an instant between 9999-01-01 00:00:00 and 9999-12-31 23:59:59
|
return DRAFTS_AFTER_INSTANT.plusMillis(System.currentTimeMillis());
|
||||||
// yes, this is a weird implementation for something that hardly matters
|
|
||||||
return DRAFTS_AFTER_INSTANT.plusMillis(1 + (long) (System.currentTimeMillis() * draftFactor));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CreateStatus(CreateStatus.Request req, String uuid){
|
public CreateStatus(CreateStatus.Request req, String uuid){
|
||||||
@@ -36,6 +34,7 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
|||||||
|
|
||||||
public static class Request{
|
public static class Request{
|
||||||
public String status;
|
public String status;
|
||||||
|
public List<MediaAttribute> mediaAttributes;
|
||||||
public List<String> mediaIds;
|
public List<String> mediaIds;
|
||||||
public Poll poll;
|
public Poll poll;
|
||||||
public String inReplyToId;
|
public String inReplyToId;
|
||||||
@@ -49,11 +48,25 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
|||||||
public String quoteId;
|
public String quoteId;
|
||||||
public ContentType contentType;
|
public ContentType contentType;
|
||||||
|
|
||||||
|
public boolean preview;
|
||||||
|
|
||||||
public static class Poll{
|
public static class Poll{
|
||||||
public ArrayList<String> options=new ArrayList<>();
|
public ArrayList<String> options=new ArrayList<>();
|
||||||
public int expiresIn;
|
public int expiresIn;
|
||||||
public boolean multiple;
|
public boolean multiple;
|
||||||
public boolean hideTotals;
|
public boolean hideTotals;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class MediaAttribute{
|
||||||
|
public String id;
|
||||||
|
public String description;
|
||||||
|
public String focus;
|
||||||
|
|
||||||
|
public MediaAttribute(String id, String description, String focus){
|
||||||
|
this.id=id;
|
||||||
|
this.description=description;
|
||||||
|
this.focus=focus;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
public class DeleteStatusReaction extends MastodonAPIRequest<Status> {
|
||||||
|
public DeleteStatusReaction(String id, String emoji) {
|
||||||
|
super(HttpMethod.POST, "/statuses/" + id + "/unreact/" + emoji, Status.class);
|
||||||
|
setRequestBody(new Object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,8 @@ public class GetStatusEditHistory extends MastodonAPIRequest<List<Status>>{
|
|||||||
s.visibility=StatusPrivacy.PUBLIC;
|
s.visibility=StatusPrivacy.PUBLIC;
|
||||||
s.mentions=Collections.emptyList();
|
s.mentions=Collections.emptyList();
|
||||||
s.tags=Collections.emptyList();
|
s.tags=Collections.emptyList();
|
||||||
|
if (s.poll != null)
|
||||||
|
s.poll.id="fakeID"+i;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
public class PleromaAddStatusReaction extends MastodonAPIRequest<Status> {
|
||||||
|
public PleromaAddStatusReaction(String id, String emoji) {
|
||||||
|
super(HttpMethod.PUT, "/pleroma/statuses/" + id + "/reactions/" + emoji, Status.class);
|
||||||
|
setRequestBody(new Object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
public class PleromaDeleteStatusReaction extends MastodonAPIRequest<Status> {
|
||||||
|
public PleromaDeleteStatusReaction(String id, String emoji) {
|
||||||
|
super(HttpMethod.DELETE, "/pleroma/statuses/" + id + "/reactions/" + emoji, Status.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.EmojiReaction;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PleromaGetStatusReactions extends MastodonAPIRequest<List<EmojiReaction>> {
|
||||||
|
public PleromaGetStatusReactions(String id, String emoji) {
|
||||||
|
super(HttpMethod.GET, "/pleroma/statuses/" + id + "/reactions/" + (emoji != null ? emoji : ""), new TypeToken<>(){});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.TranslatedStatus;
|
import org.joinmastodon.android.model.Translation;
|
||||||
|
|
||||||
public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> {
|
import java.util.Map;
|
||||||
public TranslateStatus(String id) {
|
|
||||||
super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class);
|
public class TranslateStatus extends MastodonAPIRequest<Translation>{
|
||||||
setRequestBody(new Object());
|
public TranslateStatus(String id, String lang){
|
||||||
}
|
super(HttpMethod.POST, "/statuses/"+id+"/translate", Translation.class);
|
||||||
|
setRequestBody(Map.of("lang", lang));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.tags;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
|
||||||
|
public class GetTag extends MastodonAPIRequest<Hashtag>{
|
||||||
|
public GetTag(String tag){
|
||||||
|
super(HttpMethod.GET, "/tags/"+tag, Hashtag.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.tags;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
|
||||||
|
public class SetTagFollowed extends MastodonAPIRequest<Hashtag>{
|
||||||
|
public SetTagFollowed(String tag, boolean followed){
|
||||||
|
super(HttpMethod.POST, "/tags/"+tag+(followed ? "/follow" : "/unfollow"), Hashtag.class);
|
||||||
|
setRequestBody(new Object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,18 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
|||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.model.ContentType;
|
import org.joinmastodon.android.model.ContentType;
|
||||||
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class AccountLocalPreferences{
|
public class AccountLocalPreferences{
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
@@ -37,9 +41,14 @@ public class AccountLocalPreferences{
|
|||||||
public String publishButtonText;
|
public String publishButtonText;
|
||||||
public String timelineReplyVisibility; // akkoma-only
|
public String timelineReplyVisibility; // akkoma-only
|
||||||
public boolean keepOnlyLatestNotification;
|
public boolean keepOnlyLatestNotification;
|
||||||
|
public boolean emojiReactionsEnabled;
|
||||||
|
public ShowEmojiReactions showEmojiReactions;
|
||||||
|
public ColorPreference color;
|
||||||
|
public ArrayList<Emoji> recentCustomEmoji;
|
||||||
|
|
||||||
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
|
private final static Type recentLanguagesType=new TypeToken<ArrayList<String>>() {}.getType();
|
||||||
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
|
private final static Type timelinesType=new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
|
||||||
|
private final static Type recentCustomEmojiType=new TypeToken<ArrayList<Emoji>>() {}.getType();
|
||||||
|
|
||||||
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
|
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
|
||||||
this.prefs=prefs;
|
this.prefs=prefs;
|
||||||
@@ -62,6 +71,10 @@ public class AccountLocalPreferences{
|
|||||||
publishButtonText=prefs.getString("publishButtonText", null);
|
publishButtonText=prefs.getString("publishButtonText", null);
|
||||||
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
||||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||||
|
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
|
||||||
|
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
||||||
|
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
|
||||||
|
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getNotificationsPauseEndTime(){
|
public long getNotificationsPauseEndTime(){
|
||||||
@@ -72,6 +85,10 @@ public class AccountLocalPreferences{
|
|||||||
prefs.edit().putLong("notificationsPauseTime", time).apply();
|
prefs.edit().putLong("notificationsPauseTime", time).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ColorPreference getCurrentColor(){
|
||||||
|
return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3;
|
||||||
|
}
|
||||||
|
|
||||||
public void save(){
|
public void save(){
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putBoolean("interactionCounts", showInteractionCounts)
|
.putBoolean("interactionCounts", showInteractionCounts)
|
||||||
@@ -93,6 +110,40 @@ public class AccountLocalPreferences{
|
|||||||
.putString("publishButtonText", publishButtonText)
|
.putString("publishButtonText", publishButtonText)
|
||||||
.putString("timelineReplyVisibility", timelineReplyVisibility)
|
.putString("timelineReplyVisibility", timelineReplyVisibility)
|
||||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||||
|
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
|
||||||
|
.putString("showEmojiReactions", showEmojiReactions.name())
|
||||||
|
.putString("color", color!=null ? color.name() : null)
|
||||||
|
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ColorPreference{
|
||||||
|
MATERIAL3,
|
||||||
|
PINK,
|
||||||
|
PURPLE,
|
||||||
|
GREEN,
|
||||||
|
BLUE,
|
||||||
|
BROWN,
|
||||||
|
RED,
|
||||||
|
YELLOW;
|
||||||
|
|
||||||
|
public @StringRes int getName() {
|
||||||
|
return switch(this){
|
||||||
|
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
||||||
|
case PINK -> R.string.sk_color_palette_pink;
|
||||||
|
case PURPLE -> R.string.sk_color_palette_purple;
|
||||||
|
case GREEN -> R.string.sk_color_palette_green;
|
||||||
|
case BLUE -> R.string.sk_color_palette_blue;
|
||||||
|
case BROWN -> R.string.sk_color_palette_brown;
|
||||||
|
case RED -> R.string.sk_color_palette_red;
|
||||||
|
case YELLOW -> R.string.sk_color_palette_yellow;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ShowEmojiReactions{
|
||||||
|
HIDE_EMPTY,
|
||||||
|
ONLY_OPENED,
|
||||||
|
ALWAYS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import java.util.Objects;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
@@ -146,6 +145,9 @@ public class AccountSession{
|
|||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
Log.w(TAG, "Failed to load preferences for account "+getID()+": "+error);
|
Log.w(TAG, "Failed to load preferences for account "+getID()+": "+error);
|
||||||
|
if (preferences==null)
|
||||||
|
preferences=new Preferences();
|
||||||
|
preferencesFromAccountSource(self);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(getID());
|
.exec(getID());
|
||||||
@@ -216,7 +218,7 @@ public class AccountSession{
|
|||||||
|
|
||||||
public void savePreferencesIfPending(){
|
public void savePreferencesIfPending(){
|
||||||
if(preferencesNeedSaving){
|
if(preferencesNeedSaving){
|
||||||
new UpdateAccountCredentialsPreferences(preferences, null, null)
|
new UpdateAccountCredentialsPreferences(preferences, self.locked, self.discoverable, self.source.indexable)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
@@ -252,52 +254,77 @@ public class AccountSession{
|
|||||||
filterStatusContainingObjects(objects, extractor, context, null);
|
filterStatusContainingObjects(objects, extractor, context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
|
private boolean statusIsOnOwnProfile(Status s, Account profile){
|
||||||
Predicate<Status> statusIsOnOwnProfile = (s) -> self != null && profile != null && s.account != null
|
return self != null && profile != null && s.account != null
|
||||||
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
|
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
|
||||||
|
}
|
||||||
|
|
||||||
if(getLocalPreferences().serverSideFiltersSupported){
|
private boolean isFilteredType(Status s){
|
||||||
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
|
AccountLocalPreferences localPreferences = getLocalPreferences();
|
||||||
objects.removeIf(o->{
|
return (!localPreferences.showReplies && s.inReplyToId != null)
|
||||||
Status s=extractor.apply(o);
|
|| (!localPreferences.showBoosts && s.reblog != null);
|
||||||
if(s==null)
|
}
|
||||||
return false;
|
|
||||||
if(s.filtered==null)
|
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
|
||||||
return false;
|
AccountLocalPreferences localPreferences = getLocalPreferences();
|
||||||
// don't hide own posts in own profile
|
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
|
||||||
if (statusIsOnOwnProfile.test(s))
|
|
||||||
return false;
|
|
||||||
for(FilterResult filter:s.filtered){
|
|
||||||
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(wordFilters==null)
|
|
||||||
return;
|
|
||||||
for(T obj:objects){
|
|
||||||
Status s=extractor.apply(obj);
|
Status s=extractor.apply(obj);
|
||||||
if(s!=null && s.filtered!=null){
|
if(s!=null && s.filtered!=null){
|
||||||
getLocalPreferences().serverSideFiltersSupported=true;
|
localPreferences.serverSideFiltersSupported=true;
|
||||||
getLocalPreferences().save();
|
localPreferences.save();
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
objects.removeIf(o->{
|
|
||||||
Status s=extractor.apply(o);
|
List<T> removeUs=new ArrayList<>();
|
||||||
if(s==null)
|
for(int i=0; i<objects.size(); i++){
|
||||||
return false;
|
T o=objects.get(i);
|
||||||
// don't hide own posts in own profile
|
if(filterStatusContainingObject(o, extractor, context, profile)){
|
||||||
if (statusIsOnOwnProfile.test(s))
|
Status s=extractor.apply(o);
|
||||||
return false;
|
removeUs.add(o);
|
||||||
for(LegacyFilter filter:wordFilters){
|
if(s!=null && s.hasGapAfter!=null && i>0){
|
||||||
|
// oops, we're about to remove an item that has a gap after...
|
||||||
|
// gotta find the previous status that's not also about to be removed
|
||||||
|
for(int j=i-1; j>=0; j--){
|
||||||
|
T p=objects.get(j);
|
||||||
|
Status prev=extractor.apply(objects.get(j));
|
||||||
|
if(prev!=null && !removeUs.contains(p)){
|
||||||
|
prev.hasGapAfter=s.hasGapAfter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objects.removeAll(removeUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> boolean filterStatusContainingObject(T object, Function<T, Status> extractor, FilterContext context, Account profile){
|
||||||
|
Status s=extractor.apply(object);
|
||||||
|
if(s==null)
|
||||||
|
return false;
|
||||||
|
// don't hide own posts in own profile
|
||||||
|
if(statusIsOnOwnProfile(s, profile))
|
||||||
|
return false;
|
||||||
|
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
|
||||||
|
return true;
|
||||||
|
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
|
||||||
|
if(getLocalPreferences().serverSideFiltersSupported){
|
||||||
|
for(FilterResult filter : s.filtered){
|
||||||
|
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}else if(wordFilters!=null){
|
||||||
|
for(LegacyFilter filter : wordFilters){
|
||||||
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
|
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
});
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateAccountInfo(){
|
||||||
|
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Instance> getInstance() {
|
public Optional<Instance> getInstance() {
|
||||||
@@ -310,4 +337,10 @@ public class AccountSession{
|
|||||||
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDefaultAvatarUrl() {
|
||||||
|
return getInstance()
|
||||||
|
.map(instance->"https://"+domain+(instance.isAkkoma() ? "/images/avi.png" : "/avatars/original/missing.png"))
|
||||||
|
.orElse("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.joinmastodon.android.api.session;
|
package org.joinmastodon.android.api.session;
|
||||||
|
|
||||||
|
import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -34,6 +36,7 @@ import org.joinmastodon.android.model.EmojiCategory;
|
|||||||
import org.joinmastodon.android.model.LegacyFilter;
|
import org.joinmastodon.android.model.LegacyFilter;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
import org.unifiedpush.android.connector.UnifiedPush;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@@ -101,6 +104,7 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
||||||
|
Context context = MastodonApp.context;
|
||||||
instances.put(instance.uri, instance);
|
instances.put(instance.uri, instance);
|
||||||
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
|
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
|
||||||
sessions.put(session.getID(), session);
|
sessions.put(session.getID(), session);
|
||||||
@@ -113,7 +117,14 @@ public class AccountSessionManager{
|
|||||||
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
|
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
|
||||||
|
|
||||||
updateMoreInstanceInfo(instance, instance.uri);
|
updateMoreInstanceInfo(instance, instance.uri);
|
||||||
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
if (!UnifiedPush.getDistributor(context).isEmpty()) {
|
||||||
|
UnifiedPush.registerApp(
|
||||||
|
context,
|
||||||
|
session.getID(),
|
||||||
|
new ArrayList<>(),
|
||||||
|
context.getPackageName()
|
||||||
|
);
|
||||||
|
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||||
}
|
}
|
||||||
maybeUpdateShortcuts();
|
maybeUpdateShortcuts();
|
||||||
@@ -303,8 +314,7 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*package*/ void updateSessionLocalInfo(AccountSession session){
|
||||||
private void updateSessionLocalInfo(AccountSession session){
|
|
||||||
new GetOwnAccount()
|
new GetOwnAccount()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import org.joinmastodon.android.model.EmojiReaction;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class EmojiReactionsUpdatedEvent{
|
||||||
|
public final String id;
|
||||||
|
public final List<EmojiReaction> reactions;
|
||||||
|
public final boolean updateTextPadding;
|
||||||
|
public RecyclerView.ViewHolder viewHolder;
|
||||||
|
|
||||||
|
public EmojiReactionsUpdatedEvent(String id, List<EmojiReaction> reactions, boolean updateTextPadding, RecyclerView.ViewHolder viewHolder){
|
||||||
|
this.id=id;
|
||||||
|
this.reactions=reactions;
|
||||||
|
this.updateTextPadding=updateTextPadding;
|
||||||
|
this.viewHolder=viewHolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class ReblogDeletedEvent{
|
||||||
|
public final String statusID;
|
||||||
|
public final String accountID;
|
||||||
|
|
||||||
|
public ReblogDeletedEvent(String statusID, String accountID){
|
||||||
|
this.statusID=statusID;
|
||||||
|
this.accountID=accountID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.joinmastodon.android.events;
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
|
||||||
|
|
||||||
public class ScheduledStatusDeletedEvent{
|
public class ScheduledStatusDeletedEvent{
|
||||||
public final String id;
|
public final String id;
|
||||||
public final String accountID;
|
public final String accountID;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.joinmastodon.android.events;
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.CacheController;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
public class StatusCountersUpdatedEvent{
|
public class StatusCountersUpdatedEvent{
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ public class StatusCreatedEvent{
|
|||||||
public StatusCreatedEvent(Status status, String accountID){
|
public StatusCreatedEvent(Status status, String accountID){
|
||||||
this.status=status;
|
this.status=status;
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
|
status.fromStatusCreated=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,16 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
|
||||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
@@ -55,15 +52,14 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
|
currentRequest=new GetAccountStatuses(user.id, getMaxID(), null, count, filter)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(getActivity()==null) return;
|
if(getActivity()==null) return;
|
||||||
AccountSessionManager asm = AccountSessionManager.getInstance();
|
boolean more=applyMaxID(result);
|
||||||
boolean empty=result.isEmpty();
|
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
|
||||||
onDataLoaded(result, !empty);
|
onDataLoaded(result, more);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import org.joinmastodon.android.model.HeaderPaginationList;
|
|||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
@@ -69,10 +71,12 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
|||||||
Status fakeStatus = a.toStatus();
|
Status fakeStatus = a.toStatus();
|
||||||
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
|
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
|
||||||
textItem.textSelectable = true;
|
textItem.textSelectable = true;
|
||||||
return List.of(
|
|
||||||
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
|
List<StatusDisplayItem> items=new ArrayList<>();
|
||||||
textItem
|
items.add(HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead));
|
||||||
);
|
items.add(textItem);
|
||||||
|
if(!isInstanceAkkoma()) items.add(new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true));
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMarkAsRead(String id) {
|
public void onMarkAsRead(String id) {
|
||||||
@@ -93,7 +97,7 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Announcement> result){
|
public void onSuccess(List<Announcement> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
|
|
||||||
// get unread items first
|
// get unread items first
|
||||||
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList());
|
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList());
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import android.graphics.Rect;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
@@ -20,16 +19,22 @@ import android.widget.Toolbar;
|
|||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.AkkomaTranslateStatus;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.AkkomaTranslation;
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
import org.joinmastodon.android.model.Poll;
|
import org.joinmastodon.android.model.Poll;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.Translation;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
@@ -45,6 +50,7 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
|||||||
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||||
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
@@ -54,8 +60,10 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -85,6 +93,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected Rect tmpRect=new Rect();
|
protected Rect tmpRect=new Rect();
|
||||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||||
protected boolean currentlyScrolling;
|
protected boolean currentlyScrolling;
|
||||||
|
protected String maxID;
|
||||||
|
|
||||||
public BaseStatusListFragment(){
|
public BaseStatusListFragment(){
|
||||||
super(20);
|
super(20);
|
||||||
@@ -151,6 +160,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected String getMaxID(){
|
protected String getMaxID(){
|
||||||
|
if(refreshing) return null;
|
||||||
|
if(maxID!=null) return maxID;
|
||||||
if(!preloadedData.isEmpty())
|
if(!preloadedData.isEmpty())
|
||||||
return preloadedData.get(preloadedData.size()-1).getID();
|
return preloadedData.get(preloadedData.size()-1).getID();
|
||||||
else if(!data.isEmpty())
|
else if(!data.isEmpty())
|
||||||
@@ -159,6 +170,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean applyMaxID(List<Status> result){
|
||||||
|
boolean empty=result.isEmpty();
|
||||||
|
if(!empty) maxID=result.get(result.size()-1).id;
|
||||||
|
return !empty;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
|
protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
|
||||||
protected abstract void addAccountToKnown(T s);
|
protected abstract void addAccountToKnown(T s);
|
||||||
|
|
||||||
@@ -346,12 +363,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
list.addItemDecoration(new StatusListItemDecoration());
|
list.addItemDecoration(new StatusListItemDecoration());
|
||||||
|
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||||
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
||||||
private Rect tmpRect=new Rect();
|
private Rect tmpRect=new Rect();
|
||||||
@Override
|
@Override
|
||||||
public void getSelectorBounds(View view, Rect outRect){
|
public void getSelectorBounds(View view, Rect outRect){
|
||||||
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
|
if(list!=view.getParent()) return;
|
||||||
int lastIndex = -1, firstIndex = -1;
|
boolean hasDescendant=false, hasAncestor=false, isWarning=false;
|
||||||
|
int lastIndex=-1, firstIndex=-1;
|
||||||
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
|
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
|
||||||
list.getDecoratedBoundsWithMargins(view, outRect);
|
list.getDecoratedBoundsWithMargins(view, outRect);
|
||||||
}else{
|
}else{
|
||||||
@@ -456,10 +475,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected void updatePoll(String itemID, Status status, Poll poll){
|
protected void updatePoll(String itemID, Status status, Poll poll){
|
||||||
status.poll=poll;
|
status.poll=poll;
|
||||||
int firstOptionIndex=-1, footerIndex=-1;
|
int firstOptionIndex=-1, footerIndex=-1;
|
||||||
|
int spoilerFirstOptionIndex=-1, spoilerFooterIndex=-1;
|
||||||
|
SpoilerStatusDisplayItem spoilerItem=null;
|
||||||
int i=0;
|
int i=0;
|
||||||
for(StatusDisplayItem item:displayItems){
|
for(StatusDisplayItem item:displayItems){
|
||||||
if(item.parentID.equals(itemID)){
|
if(item.parentID.equals(itemID)){
|
||||||
if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
|
if(item instanceof SpoilerStatusDisplayItem){
|
||||||
|
spoilerItem=(SpoilerStatusDisplayItem) item;
|
||||||
|
}else if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
|
||||||
firstOptionIndex=i;
|
firstOptionIndex=i;
|
||||||
}else if(item instanceof PollFooterStatusDisplayItem){
|
}else if(item instanceof PollFooterStatusDisplayItem){
|
||||||
footerIndex=i;
|
footerIndex=i;
|
||||||
@@ -472,8 +495,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
throw new IllegalStateException("Can't find all poll items in displayItems");
|
throw new IllegalStateException("Can't find all poll items in displayItems");
|
||||||
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
|
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
|
||||||
int prevSize=pollItems.size();
|
int prevSize=pollItems.size();
|
||||||
|
if(spoilerItem!=null){
|
||||||
|
spoilerFirstOptionIndex=spoilerItem.contentItems.indexOf(pollItems.get(0));
|
||||||
|
spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1));
|
||||||
|
}
|
||||||
pollItems.clear();
|
pollItems.clear();
|
||||||
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems);
|
StatusDisplayItem.buildPollItems(itemID, this, poll, status, pollItems);
|
||||||
|
if(spoilerItem!=null){
|
||||||
|
spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear();
|
||||||
|
spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems);
|
||||||
|
}
|
||||||
if(prevSize!=pollItems.size()){
|
if(prevSize!=pollItems.size()){
|
||||||
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
|
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
|
||||||
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
|
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
|
||||||
@@ -540,37 +571,39 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
|
|
||||||
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
|
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
|
||||||
Status status=holder.getItem().status;
|
Status status=holder.getItem().status;
|
||||||
toggleSpoiler(status, holder.getItemID());
|
boolean isForQuote=holder.getItem().isForQuote;
|
||||||
|
toggleSpoiler(status, isForQuote, holder.getItemID());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
|
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
|
||||||
Status status = holder.getItem().status;
|
Status status = holder.getItem().status;
|
||||||
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
|
if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false);
|
||||||
if (mediaGrid != null) {
|
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
|
||||||
if (!status.sensitiveRevealed) mediaGrid.revealSensitive();
|
if(mediaGrid!=null){
|
||||||
|
if(!status.sensitiveRevealed) mediaGrid.revealSensitive();
|
||||||
else mediaGrid.hideSensitive();
|
else mediaGrid.hideSensitive();
|
||||||
} else {
|
}else{
|
||||||
// media grid's methods normally change the status' state - we still want to be able
|
status.sensitiveRevealed=false;
|
||||||
// to do this if the media grid is not bound, tho - so, doing it ourselves here
|
notifyItemChangedAfter(holder.getItem(), MediaGridStatusDisplayItem.class);
|
||||||
status.sensitiveRevealed = !status.sensitiveRevealed;
|
|
||||||
}
|
}
|
||||||
holder.rebind();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
|
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
|
||||||
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
||||||
if(header != null) header.rebind();
|
if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true);
|
||||||
|
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void toggleSpoiler(Status status, String itemID){
|
protected void toggleSpoiler(Status status, boolean isForQuote, String itemID){
|
||||||
status.spoilerRevealed=!status.spoilerRevealed;
|
status.spoilerRevealed=!status.spoilerRevealed;
|
||||||
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
|
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
|
||||||
status.sensitiveRevealed = false;
|
status.sensitiveRevealed = false;
|
||||||
|
|
||||||
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
|
List<SpoilerStatusDisplayItem.Holder> spoilers=findAllHoldersOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
|
||||||
if(spoiler!=null)
|
SpoilerStatusDisplayItem.Holder spoiler=spoilers.size() > 1 && isForQuote ? spoilers.get(1) : spoilers.get(0);
|
||||||
spoiler.rebind();
|
if(spoiler!=null) spoiler.rebind();
|
||||||
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
|
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
|
||||||
|
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(spoiler.getItem());
|
||||||
|
|
||||||
int index=displayItems.indexOf(spoilerItem);
|
int index=displayItems.indexOf(spoilerItem);
|
||||||
if(status.spoilerRevealed){
|
if(status.spoilerRevealed){
|
||||||
@@ -581,33 +614,32 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
|
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
notifyItemChanged(itemID, TextStatusDisplayItem.class);
|
||||||
if(text!=null)
|
|
||||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
|
||||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||||
if(header!=null)
|
if(header!=null) header.rebind();
|
||||||
header.rebind();
|
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
|
||||||
|
|
||||||
list.invalidateItemDecorations();
|
list.invalidateItemDecorations();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
|
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
|
||||||
if (holder.getItem().status.textExpandable != expandable && list != null) {
|
Status s=holder.getItem().status;
|
||||||
holder.getItem().status.textExpandable = expandable;
|
if(s.textExpandable!=expandable && list!=null) {
|
||||||
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
s.textExpandable=expandable;
|
||||||
if (header != null) header.rebind();
|
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
||||||
|
if(header!=null) header.bindCollapseButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onToggleExpanded(Status status, String itemID) {
|
public void onToggleExpanded(Status status, String itemID) {
|
||||||
status.textExpanded = !status.textExpanded;
|
status.textExpanded = !status.textExpanded;
|
||||||
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
notifyItemChanged(itemID, TextStatusDisplayItem.class);
|
||||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||||
if (text != null) text.rebind();
|
if(header!=null) header.animateExpandToggle();
|
||||||
if (header != null) header.rebind();
|
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
|
||||||
|
|
||||||
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
||||||
int startPos = warning.getAbsoluteAdapterPosition();
|
int startPos = warning.getAbsoluteAdapterPosition();
|
||||||
@@ -663,9 +695,61 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this as a fallback if findHolderOfType fails to find the ViewHolder.
|
||||||
|
* It might still be bound but off-screen and therefore not a child of the RecyclerView -
|
||||||
|
* resulting in the ViewHolder displaying an outdated state once scrolled back into view.
|
||||||
|
*/
|
||||||
|
protected <I extends StatusDisplayItem> int notifyItemChanged(String id, Class<I> type){
|
||||||
|
boolean encounteredParent=false;
|
||||||
|
for(int i=0; i<displayItems.size(); i++){
|
||||||
|
StatusDisplayItem item=displayItems.get(i);
|
||||||
|
boolean idEquals=id.equals(item.parentID);
|
||||||
|
if(!encounteredParent && idEquals) encounteredParent=true; // reached top of the parent
|
||||||
|
else if(encounteredParent && !idEquals) break; // passed by bottom of the parent. man muss ja wissen wann schluss is
|
||||||
|
if(idEquals && type.isInstance(item)){
|
||||||
|
adapter.notifyItemChanged(i);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <I extends StatusDisplayItem> int notifyItemChangedAfter(StatusDisplayItem afterThis, Class<I> type){
|
||||||
|
int startIndex=displayItems.indexOf(afterThis);
|
||||||
|
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem");
|
||||||
|
String parentID=afterThis.parentID;
|
||||||
|
for(int i=startIndex; i<displayItems.size(); i++){
|
||||||
|
StatusDisplayItem item=displayItems.get(i);
|
||||||
|
if(!parentID.equals(item.parentID)) break; // didn't find anything
|
||||||
|
if(type.isInstance(item)){
|
||||||
|
// found it
|
||||||
|
adapter.notifyItemChanged(i);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <I extends StatusDisplayItem> int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class<I> type){
|
||||||
|
int startIndex=displayItems.indexOf(beforeThis);
|
||||||
|
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem");
|
||||||
|
String parentID=beforeThis.parentID;
|
||||||
|
for(int i=startIndex; i>=0; i--){
|
||||||
|
StatusDisplayItem item=displayItems.get(i);
|
||||||
|
if(!parentID.equals(item.parentID)) break; // didn't find anything
|
||||||
|
if(type.isInstance(item)){
|
||||||
|
// found it
|
||||||
|
adapter.notifyItemChanged(i);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
|
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0; i<list.getChildCount(); i++){
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
|
if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
|
||||||
return type.cast(holder);
|
return type.cast(holder);
|
||||||
@@ -762,6 +846,94 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void togglePostTranslation(Status status, String itemID){
|
||||||
|
switch(status.translationState){
|
||||||
|
case LOADING -> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case SHOWN -> {
|
||||||
|
status.translationState=Status.TranslationState.HIDDEN;
|
||||||
|
}
|
||||||
|
case HIDDEN -> {
|
||||||
|
if(status.translation!=null){
|
||||||
|
status.translationState=Status.TranslationState.SHOWN;
|
||||||
|
}else{
|
||||||
|
status.translationState=Status.TranslationState.LOADING;
|
||||||
|
Consumer<Translation> successCallback=(result)->{
|
||||||
|
status.translation=result;
|
||||||
|
status.translationState=Status.TranslationState.SHOWN;
|
||||||
|
updateTranslation(itemID);
|
||||||
|
};
|
||||||
|
MastodonAPIRequest<?> req=isInstanceAkkoma()
|
||||||
|
? new AkkomaTranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(AkkomaTranslation result){
|
||||||
|
if(getActivity()!=null) successCallback.accept(result.toTranslation());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
if(getActivity()!=null) translationCallbackError(status, itemID);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Translation result){
|
||||||
|
if(getActivity()!=null) successCallback.accept(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
if(getActivity()!=null) translationCallbackError(status, itemID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1 minute
|
||||||
|
req.setTimeout(60000).exec(accountID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateTranslation(itemID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void translationCallbackError(Status status, String itemID) {
|
||||||
|
status.translationState=Status.TranslationState.HIDDEN;
|
||||||
|
updateTranslation(itemID);
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.translation_failed)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTranslation(String itemID) {
|
||||||
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
|
if(text!=null){
|
||||||
|
text.updateTranslation(true);
|
||||||
|
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
|
||||||
|
}else{
|
||||||
|
notifyItemChanged(itemID, TextStatusDisplayItem.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isInstanceAkkoma())
|
||||||
|
return;
|
||||||
|
|
||||||
|
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
|
||||||
|
if(spoiler!=null){
|
||||||
|
spoiler.rebind();
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaGridStatusDisplayItem.Holder media=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
|
||||||
|
if (media!=null) {
|
||||||
|
media.rebind();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
|
if(list.getChildViewHolder(list.getChildAt(i)) instanceof PollOptionStatusDisplayItem.Holder item){
|
||||||
|
item.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void rebuildAllDisplayItems(){
|
public void rebuildAllDisplayItems(){
|
||||||
displayItems.clear();
|
displayItems.clear();
|
||||||
for(T item:data){
|
for(T item:data){
|
||||||
@@ -777,11 +949,15 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
if(getContext()==null) return;
|
if(getContext()==null) return;
|
||||||
super.onDataLoaded(d, more);
|
super.onDataLoaded(d, more);
|
||||||
// more available, but the page isn't even full yet? seems wrong, let's load some more
|
// more available, but the page isn't even full yet? seems wrong, let's load some more
|
||||||
if(more && d.size() < itemsPerPage){
|
if(more && data.size() < itemsPerPage){
|
||||||
preloader.onScrolledToLastItem();
|
preloader.onScrolledToLastItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void scrollBy(int x, int y) {
|
||||||
|
list.scrollBy(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
public DisplayItemsAdapter(){
|
public DisplayItemsAdapter(){
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Status> result){
|
public void onSuccess(HeaderPaginationList<Status> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -110,11 +110,11 @@ import java.time.format.DateTimeFormatter;
|
|||||||
import java.time.format.FormatStyle;
|
import java.time.format.FormatStyle;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -150,7 +150,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
public LinearLayout mainLayout;
|
public LinearLayout mainLayout;
|
||||||
private SizeListenerLinearLayout contentView;
|
private SizeListenerLinearLayout contentView;
|
||||||
private TextView selfName, selfUsername, selfExtraText, extraText, pronouns;
|
private TextView selfName, selfUsername, selfExtraText, extraText;
|
||||||
private ImageView selfAvatar;
|
private ImageView selfAvatar;
|
||||||
private Account self;
|
private Account self;
|
||||||
private String instanceDomain;
|
private String instanceDomain;
|
||||||
@@ -161,7 +161,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private int charCount, charLimit, trimmedCharCount;
|
private int charCount, charLimit, trimmedCharCount;
|
||||||
|
|
||||||
private Button publishButton, languageButton, scheduleTimeBtn;
|
private Button publishButton, languageButton, scheduleTimeBtn;
|
||||||
private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup;
|
private PopupMenu contentTypePopup, visibilityPopup, draftOptionsPopup;
|
||||||
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn;
|
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn;
|
||||||
private View sensitiveBtn;
|
private View sensitiveBtn;
|
||||||
private TextView replyText;
|
private TextView replyText;
|
||||||
@@ -200,7 +200,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
public Instance instance;
|
public Instance instance;
|
||||||
|
|
||||||
public Status editingStatus;
|
public Status editingStatus;
|
||||||
private ScheduledStatus scheduledStatus;
|
public ScheduledStatus scheduledStatus;
|
||||||
private boolean redraftStatus;
|
private boolean redraftStatus;
|
||||||
|
|
||||||
private ContentType contentType;
|
private ContentType contentType;
|
||||||
@@ -214,7 +214,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
private BackgroundColorSpan overLimitBG;
|
private BackgroundColorSpan overLimitBG;
|
||||||
private ForegroundColorSpan overLimitFG;
|
private ForegroundColorSpan overLimitFG;
|
||||||
|
|
||||||
public ComposeFragment(){
|
public ComposeFragment(){
|
||||||
super(R.layout.toolbar_fragment_with_progressbar);
|
super(R.layout.toolbar_fragment_with_progressbar);
|
||||||
}
|
}
|
||||||
@@ -293,13 +293,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
creatingView=true;
|
creatingView=true;
|
||||||
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), customEmojis, instanceDomain);
|
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), accountID, customEmojis, instanceDomain);
|
||||||
emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){
|
emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){
|
||||||
@Override
|
@Override
|
||||||
public void onEmojiSelected(Emoji emoji){
|
public void onEmojiSelected(Emoji emoji){
|
||||||
onCustomEmojiClick(emoji);
|
onCustomEmojiClick(emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEmojiSelected(String emoji){
|
||||||
|
if(getActivity().getCurrentFocus() instanceof EditText edit && edit == mainEditText){
|
||||||
|
edit.getText().replace(edit.getSelectionStart(), edit.getSelectionEnd(), emoji);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackspace(){
|
public void onBackspace(){
|
||||||
getActivity().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
|
getActivity().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
|
||||||
@@ -319,7 +326,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
selfUsername=view.findViewById(R.id.self_username);
|
selfUsername=view.findViewById(R.id.self_username);
|
||||||
selfAvatar=view.findViewById(R.id.self_avatar);
|
selfAvatar=view.findViewById(R.id.self_avatar);
|
||||||
selfExtraText=view.findViewById(R.id.self_extra_text);
|
selfExtraText=view.findViewById(R.id.self_extra_text);
|
||||||
HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis);
|
HtmlParser.setTextWithCustomEmoji(selfName, self.getDisplayName(), self.emojis);
|
||||||
selfUsername.setText('@'+self.username+'@'+instanceDomain);
|
selfUsername.setText('@'+self.username+'@'+instanceDomain);
|
||||||
if(self.avatar!=null)
|
if(self.avatar!=null)
|
||||||
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
|
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
|
||||||
@@ -412,7 +419,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
hasSpoiler=true;
|
hasSpoiler=true;
|
||||||
spoilerWrap.setVisibility(View.VISIBLE);
|
spoilerWrap.setVisibility(View.VISIBLE);
|
||||||
spoilerBtn.setSelected(true);
|
spoilerBtn.setSelected(true);
|
||||||
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
|
}else if(editingStatus!=null && editingStatus.hasSpoiler()){
|
||||||
hasSpoiler=true;
|
hasSpoiler=true;
|
||||||
spoilerWrap.setVisibility(View.VISIBLE);
|
spoilerWrap.setVisibility(View.VISIBLE);
|
||||||
spoilerEdit.setText(getArguments().getString("sourceSpoiler", editingStatus.spoilerText));
|
spoilerEdit.setText(getArguments().getString("sourceSpoiler", editingStatus.spoilerText));
|
||||||
@@ -449,7 +456,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
} else if (getArguments().containsKey("sourceContentType")) {
|
} else if (getArguments().containsKey("sourceContentType")) {
|
||||||
try {
|
try {
|
||||||
String val = getArguments().getString("sourceContentType");
|
String val = getArguments().getString("sourceContentType");
|
||||||
contentType = val == null ? null : ContentType.valueOf(val);
|
if (val != null) contentType = ContentType.valueOf(val);
|
||||||
} catch (IllegalArgumentException ignored) {}
|
} catch (IllegalArgumentException ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,7 +626,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
});
|
});
|
||||||
View originalPost=view.findViewById(R.id.original_post);
|
View originalPost=view.findViewById(R.id.original_post);
|
||||||
extraText=view.findViewById(R.id.extra_text);
|
extraText=view.findViewById(R.id.extra_text);
|
||||||
pronouns=view.findViewById(R.id.pronouns);
|
|
||||||
originalPost.setVisibility(View.VISIBLE);
|
originalPost.setVisibility(View.VISIBLE);
|
||||||
originalPost.setOnClickListener(v->{
|
originalPost.setOnClickListener(v->{
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
@@ -659,18 +665,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
moreBtn.setBackground(null);
|
moreBtn.setBackground(null);
|
||||||
|
|
||||||
TextView name = view.findViewById(R.id.name);
|
TextView name = view.findViewById(R.id.name);
|
||||||
name.setText(HtmlParser.parseCustomEmoji(status.account.displayName, status.account.emojis));
|
name.setText(HtmlParser.parseCustomEmoji(status.account.getDisplayName(), status.account.emojis));
|
||||||
UiUtils.loadCustomEmojiInTextView(name);
|
UiUtils.loadCustomEmojiInTextView(name);
|
||||||
|
|
||||||
String time = status==null || status.editedAt==null
|
String time = status==null || status.editedAt==null
|
||||||
? UiUtils.formatRelativeTimestamp(getContext(), status.createdAt)
|
? UiUtils.formatRelativeTimestamp(getContext(), status.createdAt)
|
||||||
: getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), status.editedAt));
|
: getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), status.editedAt));
|
||||||
|
|
||||||
String sepp = getString(R.string.sk_separator);
|
((TextView) view.findViewById(R.id.username)).setText(status.account.getDisplayUsername());
|
||||||
String username = status.account.getDisplayUsername();
|
view.findViewById(R.id.separator).setVisibility(time==null ? View.GONE : View.VISIBLE);
|
||||||
((TextView) view.findViewById(R.id.time_and_username)).setText(time == null ? username :
|
view.findViewById(R.id.time).setVisibility(time==null ? View.GONE : View.VISIBLE);
|
||||||
username + " " + sepp + " " + time);
|
if(time!=null) ((TextView) view.findViewById(R.id.time)).setText(time);
|
||||||
if (status.spoilerText != null && !status.spoilerText.isBlank()) {
|
|
||||||
|
if (status.hasSpoiler()) {
|
||||||
TextView replyToSpoiler = view.findViewById(R.id.reply_to_spoiler);
|
TextView replyToSpoiler = view.findViewById(R.id.reply_to_spoiler);
|
||||||
replyToSpoiler.setVisibility(View.VISIBLE);
|
replyToSpoiler.setVisibility(View.VISIBLE);
|
||||||
replyToSpoiler.setText(status.spoilerText);
|
replyToSpoiler.setText(status.spoilerText);
|
||||||
@@ -692,7 +699,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
|
.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
|
||||||
}
|
}
|
||||||
|
|
||||||
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.displayName));
|
replyText.setText(HtmlParser.parseCustomEmoji(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.getDisplayName()), status.account.emojis));
|
||||||
|
UiUtils.loadCustomEmojiInTextView(replyText);
|
||||||
int visibilityNameRes = switch (status.visibility) {
|
int visibilityNameRes = switch (status.visibility) {
|
||||||
case PUBLIC -> R.string.visibility_public;
|
case PUBLIC -> R.string.visibility_public;
|
||||||
case UNLISTED -> R.string.sk_visibility_unlisted;
|
case UNLISTED -> R.string.sk_visibility_unlisted;
|
||||||
@@ -700,7 +708,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
case DIRECT -> R.string.visibility_private;
|
case DIRECT -> R.string.visibility_private;
|
||||||
case LOCAL -> R.string.sk_local_only;
|
case LOCAL -> R.string.sk_local_only;
|
||||||
};
|
};
|
||||||
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.displayName) + ", " + getString(visibilityNameRes));
|
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.getDisplayName()) + ", " + getString(visibilityNameRes));
|
||||||
replyText.setOnClickListener(v->{
|
replyText.setOnClickListener(v->{
|
||||||
scrollView.smoothScrollTo(0, 0);
|
scrollView.smoothScrollTo(0, 0);
|
||||||
});
|
});
|
||||||
@@ -728,7 +736,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|
||||||
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
|
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
|
||||||
&& !status.spoilerText.startsWith("re: ") ? "re: " : "";
|
&& !status.spoilerText.startsWith("re: ") ? "re: " : "";
|
||||||
spoilerEdit.setText(prefix + replyTo.spoilerText);
|
spoilerEdit.setText(prefix + status.spoilerText);
|
||||||
spoilerBtn.setSelected(true);
|
spoilerBtn.setSelected(true);
|
||||||
}
|
}
|
||||||
if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language);
|
if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language);
|
||||||
@@ -789,6 +797,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
|
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
|
||||||
@@ -798,32 +807,58 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
actionItem.setActionView(wrap);
|
actionItem.setActionView(wrap);
|
||||||
actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
|
||||||
draftsBtn = wrap.findViewById(R.id.drafts_btn);
|
draftsBtn=wrap.findViewById(R.id.drafts_btn);
|
||||||
draftOptionsPopup = new PopupMenu(getContext(), draftsBtn);
|
draftOptionsPopup=new PopupMenu(getContext(), draftsBtn);
|
||||||
draftOptionsPopup.inflate(R.menu.compose_more);
|
draftOptionsPopup.inflate(R.menu.compose_more);
|
||||||
draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft);
|
Menu draftOptionsMenu=draftOptionsPopup.getMenu();
|
||||||
undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft);
|
draftMenuItem=draftOptionsMenu.findItem(R.id.draft);
|
||||||
scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule);
|
undraftMenuItem=draftOptionsMenu.findItem(R.id.undraft);
|
||||||
unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule);
|
scheduleMenuItem=draftOptionsMenu.findItem(R.id.schedule);
|
||||||
|
unscheduleMenuItem=draftOptionsMenu.findItem(R.id.unschedule);
|
||||||
|
draftOptionsMenu.findItem(R.id.preview).setVisible(isInstanceAkkoma());
|
||||||
draftOptionsPopup.setOnMenuItemClickListener(i->{
|
draftOptionsPopup.setOnMenuItemClickListener(i->{
|
||||||
int id = i.getItemId();
|
int id=i.getItemId();
|
||||||
if (id == R.id.draft) updateScheduledAt(getDraftInstant());
|
if(id==R.id.draft) updateScheduledAt(getDraftInstant());
|
||||||
else if (id == R.id.schedule) pickScheduledDateTime();
|
else if(id==R.id.schedule) pickScheduledDateTime();
|
||||||
else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null);
|
else if(id==R.id.unschedule || id==R.id.undraft) updateScheduledAt(null);
|
||||||
else navigateToUnsentPosts();
|
else if(id==R.id.drafts) navigateToUnsentPosts();
|
||||||
|
else if(id==R.id.preview) publish(true);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
|
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
|
||||||
|
|
||||||
publishButton = wrap.findViewById(R.id.publish_btn);
|
publishButton=wrap.findViewById(R.id.publish_btn);
|
||||||
languageButton = wrap.findViewById(R.id.language_btn);
|
languageButton=wrap.findViewById(R.id.language_btn);
|
||||||
languageButton.setOnClickListener(v->showLanguageAlert());
|
languageButton.setOnClickListener(v->showLanguageAlert());
|
||||||
|
languageButton.setOnLongClickListener(v->{
|
||||||
|
if(!getLocalPrefs().bottomEncoding){
|
||||||
|
getLocalPrefs().bottomEncoding=true;
|
||||||
|
getLocalPrefs().save();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
|
||||||
|
|
||||||
publishButton.setOnClickListener(v -> {
|
publishButton.setOnClickListener(v->{
|
||||||
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
|
Consumer<Boolean> draftCheckComplete=(isDraft)->{
|
||||||
checkAltTextsAndPublish();
|
if(GlobalUserPreferences.altTextReminders && !isDraft) checkAltTextsAndPublish();
|
||||||
else
|
else publish();
|
||||||
publish();
|
};
|
||||||
|
|
||||||
|
boolean isAlreadyDraft=scheduledAt!=null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
|
||||||
|
if(editingStatus!=null && scheduledAt!=null && isAlreadyDraft) {
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_save_draft)
|
||||||
|
.setMessage(R.string.sk_save_draft_message)
|
||||||
|
.setPositiveButton(R.string.save, (d, w)->draftCheckComplete.accept(isAlreadyDraft))
|
||||||
|
.setNegativeButton(R.string.publish, (d, w)->{
|
||||||
|
updateScheduledAt(null);
|
||||||
|
draftCheckComplete.accept(false);
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}else{
|
||||||
|
draftCheckComplete.accept(isAlreadyDraft);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
|
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
|
||||||
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
|
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
|
||||||
@@ -835,8 +870,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
? languageResolver.fromOrFallback(prefs.postingDefaultLanguage)
|
? languageResolver.fromOrFallback(prefs.postingDefaultLanguage)
|
||||||
: languageResolver.getDefault());
|
: languageResolver.getDefault());
|
||||||
|
|
||||||
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
|
if(isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
|
||||||
if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) {
|
if(isInstancePixelfed() || (editingStatus!=null && !redraftStatus)) {
|
||||||
// editing an already published post
|
// editing an already published post
|
||||||
draftsBtn.setVisibility(View.GONE);
|
draftsBtn.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@@ -960,7 +995,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getNavigationIconDrawableResource(){
|
protected int getNavigationIconDrawableResource(){
|
||||||
return R.drawable.ic_baseline_close_24;
|
return R.drawable.ic_fluent_dismiss_24_regular;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1019,6 +1054,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void publish(){
|
private void publish(){
|
||||||
|
publish(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publish(boolean preview){
|
||||||
sendingOverlay=new View(getActivity());
|
sendingOverlay=new View(getActivity());
|
||||||
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
|
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
|
||||||
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
|
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
|
||||||
@@ -1032,23 +1071,22 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
publishButton.setEnabled(false);
|
publishButton.setEnabled(false);
|
||||||
V.setVisibilityAnimated(sendProgress, View.VISIBLE);
|
V.setVisibilityAnimated(sendProgress, View.VISIBLE);
|
||||||
|
|
||||||
mediaViewController.saveAltTextsBeforePublishing(this::actuallyPublish, this::handlePublishError);
|
mediaViewController.saveAltTextsBeforePublishing(
|
||||||
|
()->actuallyPublish(preview),
|
||||||
|
this::handlePublishError);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void actuallyPublish(){
|
private void actuallyPublish(boolean preview){
|
||||||
actuallyPublish(false);
|
|
||||||
}
|
|
||||||
private void actuallyPublish(boolean force){
|
|
||||||
String text=mainEditText.getText().toString();
|
String text=mainEditText.getText().toString();
|
||||||
CreateStatus.Request req=new CreateStatus.Request();
|
CreateStatus.Request req=new CreateStatus.Request();
|
||||||
if ("bottom".equals(postLang.encoding)) {
|
if("bottom".equals(postLang.encoding)){
|
||||||
text = new StatusTextEncoder(Bottom::encode).encode(text);
|
text=new StatusTextEncoder(Bottom::encode).encode(text);
|
||||||
req.spoilerText = "bottom-encoded emoji spam";
|
req.spoilerText="bottom-encoded emoji spam";
|
||||||
}
|
}
|
||||||
if (localOnly &&
|
if(localOnly &&
|
||||||
AccountSessionManager.get(accountID).getLocalPreferences().glitchInstance &&
|
AccountSessionManager.get(accountID).getLocalPreferences().glitchInstance &&
|
||||||
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()) {
|
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()){
|
||||||
text += " " + GLITCH_LOCAL_ONLY_SUFFIX;
|
text+=" "+GLITCH_LOCAL_ONLY_SUFFIX;
|
||||||
}
|
}
|
||||||
req.status=text;
|
req.status=text;
|
||||||
req.localOnly=localOnly;
|
req.localOnly=localOnly;
|
||||||
@@ -1056,21 +1094,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
req.sensitive=sensitive;
|
req.sensitive=sensitive;
|
||||||
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
|
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
|
||||||
req.scheduledAt=scheduledAt;
|
req.scheduledAt=scheduledAt;
|
||||||
|
req.preview=preview;
|
||||||
if(!mediaViewController.isEmpty()){
|
if(!mediaViewController.isEmpty()){
|
||||||
req.mediaIds=mediaViewController.getAttachmentIDs();
|
req.mediaIds=mediaViewController.getAttachmentIDs();
|
||||||
}
|
if(editingStatus != null){
|
||||||
// ask whether to publish now when editing an existing draft
|
req.mediaAttributes=mediaViewController.getAttachmentAttributes();
|
||||||
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
|
}
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.sk_save_draft)
|
|
||||||
.setMessage(R.string.sk_save_draft_message)
|
|
||||||
.setPositiveButton(R.string.save, (d, w) -> actuallyPublish(true))
|
|
||||||
.setNegativeButton(R.string.publish, (d, w) -> {
|
|
||||||
updateScheduledAt(null);
|
|
||||||
actuallyPublish();
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
|
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
|
||||||
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
|
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
|
||||||
@@ -1093,7 +1122,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
Callback<Status> resCallback=new Callback<>(){
|
Callback<Status> resCallback=new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
maybeDeleteScheduledPost(() -> {
|
if(preview){
|
||||||
|
openPreview(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeDeleteScheduledPost(()->{
|
||||||
wm.removeView(sendingOverlay);
|
wm.removeView(sendingOverlay);
|
||||||
sendingOverlay=null;
|
sendingOverlay=null;
|
||||||
if(editingStatus==null || redraftStatus){
|
if(editingStatus==null || redraftStatus){
|
||||||
@@ -1115,10 +1149,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
E.post(new StatusUpdatedEvent(editedStatus));
|
E.post(new StatusUpdatedEvent(editedStatus));
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
|
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()){
|
||||||
Nav.finish(ComposeFragment.this);
|
Nav.finish(ComposeFragment.this);
|
||||||
}
|
}
|
||||||
if (getArguments().getBoolean("navigateToStatus", false)) {
|
if(getArguments().getBoolean("navigateToStatus", false)){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelable("status", Parcels.wrap(result));
|
args.putParcelable("status", Parcels.wrap(result));
|
||||||
@@ -1134,11 +1168,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(editingStatus!=null && !redraftStatus){
|
if(editingStatus!=null && !redraftStatus && !preview){
|
||||||
new EditStatus(req, editingStatus.id)
|
new EditStatus(req, editingStatus.id)
|
||||||
.setCallback(resCallback)
|
.setCallback(resCallback)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}else if(req.scheduledAt == null){
|
}else if(req.scheduledAt == null || preview){
|
||||||
new CreateStatus(req, uuid)
|
new CreateStatus(req, uuid)
|
||||||
.setCallback(resCallback)
|
.setCallback(resCallback)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -1191,6 +1225,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openPreview(Status result){
|
||||||
|
result.preview=true;
|
||||||
|
wm.removeView(sendingOverlay);
|
||||||
|
sendingOverlay=null;
|
||||||
|
publishButton.setEnabled(true);
|
||||||
|
V.setVisibilityAnimated(sendProgress, View.GONE);
|
||||||
|
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
||||||
|
imm.hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||||
|
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("status", Parcels.wrap(result));
|
||||||
|
if(replyTo!=null){
|
||||||
|
args.putParcelable("inReplyTo", Parcels.wrap(replyTo));
|
||||||
|
args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo.account));
|
||||||
|
}
|
||||||
|
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
private void updateRecentLanguages() {
|
private void updateRecentLanguages() {
|
||||||
if (postLang == null || postLang.language == null) return;
|
if (postLang == null || postLang.language == null) return;
|
||||||
String language = postLang.language.getLanguage();
|
String language = postLang.language.getLanguage();
|
||||||
@@ -1266,20 +1319,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void confirmDiscardDraftAndFinish(){
|
private void confirmDiscardDraftAndFinish(){
|
||||||
boolean attachmentsPending = mediaViewController.areAnyAttachmentsNotDone();
|
boolean attachmentsPending=mediaViewController.areAnyAttachmentsNotDone();
|
||||||
if (attachmentsPending) new M3AlertDialogBuilder(getActivity())
|
if(attachmentsPending) new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(R.string.sk_unfinished_attachments)
|
.setTitle(R.string.sk_unfinished_attachments)
|
||||||
.setMessage(R.string.sk_unfinished_attachments_message)
|
.setMessage(R.string.sk_unfinished_attachments_message)
|
||||||
.setPositiveButton(R.string.edit, (d, w) -> {})
|
.setPositiveButton(R.string.ok, (d, w)->{})
|
||||||
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
|
.setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
|
||||||
.show();
|
.show();
|
||||||
else new M3AlertDialogBuilder(getActivity())
|
else new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
|
.setTitle(editingStatus!=null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
|
||||||
.setPositiveButton(R.string.save, (d, w) -> {
|
.setPositiveButton(R.string.save, (d, w)->{
|
||||||
updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt);
|
updateScheduledAt(scheduledAt==null ? getDraftInstant() : scheduledAt);
|
||||||
publish();
|
publish();
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
|
.setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1297,7 +1350,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
|
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
|
||||||
if(usePhotoPicker){
|
if(usePhotoPicker){
|
||||||
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||||
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
|
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
|
||||||
|
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
|
||||||
}else{
|
}else{
|
||||||
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
@@ -1435,8 +1489,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateHeaders() {
|
private void updateHeaders() {
|
||||||
UiUtils.setExtraTextInfo(getContext(), selfExtraText, null, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null);
|
UiUtils.setExtraTextInfo(getContext(), selfExtraText, false, false, localOnly, null);
|
||||||
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, pronouns, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
|
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildVisibilityPopup(View v){
|
private void buildVisibilityPopup(View v){
|
||||||
@@ -1508,6 +1562,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentTypePopup.setOnMenuItemClickListener(i->{
|
contentTypePopup.setOnMenuItemClickListener(i->{
|
||||||
|
uuid=null;
|
||||||
int index=i.getItemId();
|
int index=i.getItemId();
|
||||||
contentType=ContentType.values()[index];
|
contentType=ContentType.values()[index];
|
||||||
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());
|
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ import android.widget.ImageView;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
import org.joinmastodon.android.ui.utils.ColorPalette;
|
import org.joinmastodon.android.ui.utils.ColorPalette;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
@@ -54,16 +54,17 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
|
||||||
attachmentID=getArguments().getString("attachment");
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
attachmentID=getArguments().getString("attachment");
|
||||||
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
||||||
ColorPalette.palettes.get(GlobalUserPreferences.color).apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
|
ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().getCurrentColor())
|
||||||
|
.apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
|
||||||
setTitle(R.string.add_alt_text);
|
setTitle(R.string.add_alt_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,412 +60,422 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private TimelinesAdapter adapter;
|
private TimelinesAdapter adapter;
|
||||||
private final ItemTouchHelper itemTouchHelper;
|
private final ItemTouchHelper itemTouchHelper;
|
||||||
private Menu optionsMenu;
|
private Menu optionsMenu;
|
||||||
private boolean updated;
|
private boolean updated;
|
||||||
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>();
|
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem=new HashMap<>();
|
||||||
private final List<ListTimeline> listTimelines = new ArrayList<>();
|
private final List<ListTimeline> listTimelines=new ArrayList<>();
|
||||||
private final List<Hashtag> hashtags = new ArrayList<>();
|
private final List<Hashtag> hashtags=new ArrayList<>();
|
||||||
private MenuItem addHashtagItem;
|
private MenuItem addHashtagItem;
|
||||||
|
|
||||||
public EditTimelinesFragment() {
|
public EditTimelinesFragment(){
|
||||||
super(10);
|
super(10);
|
||||||
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ;
|
ItemTouchHelper.SimpleCallback itemTouchCallback=new ItemTouchHelperCallback();
|
||||||
itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
|
itemTouchHelper=new ItemTouchHelper(itemTouchCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
setTitle(R.string.sk_timelines);
|
setTitle(R.string.sk_timelines);
|
||||||
accountID = getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
|
|
||||||
new GetLists().setCallback(new Callback<>() {
|
new GetLists().setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<ListTimeline> result) {
|
public void onSuccess(List<ListTimeline> result){
|
||||||
listTimelines.addAll(result);
|
listTimelines.addAll(result);
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error) {
|
public void onError(ErrorResponse error){
|
||||||
error.showToast(getContext());
|
error.showToast(getContext());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
|
|
||||||
new GetFollowedHashtags().setCallback(new Callback<>() {
|
new GetFollowedHashtags().setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> result) {
|
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
||||||
hashtags.addAll(result);
|
hashtags.addAll(result);
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error) {
|
public void onError(ErrorResponse error){
|
||||||
error.showToast(getContext());
|
error.showToast(getContext());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onShown(){
|
protected void onShown(){
|
||||||
super.onShown();
|
super.onShown();
|
||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
|
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
itemTouchHelper.attachToRecyclerView(list);
|
itemTouchHelper.attachToRecyclerView(list);
|
||||||
refreshLayout.setEnabled(false);
|
refreshLayout.setEnabled(false);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
this.optionsMenu = menu;
|
this.optionsMenu=menu;
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
if (item.getItemId() == R.id.menu_back) {
|
if(item.getItemId()==R.id.menu_back){
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
|
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
TimelineDefinition tl = timelineByMenuItem.get(item);
|
TimelineDefinition tl=timelineByMenuItem.get(item);
|
||||||
if (tl != null) {
|
if(tl!=null){
|
||||||
addTimeline(tl);
|
addTimeline(tl);
|
||||||
} else if (item == addHashtagItem) {
|
}else if(item==addHashtagItem){
|
||||||
makeTimelineEditor(null, (hashtag) -> {
|
makeTimelineEditor(null, (hashtag)->{
|
||||||
if (hashtag != null) addTimeline(hashtag);
|
if(hashtag!=null) addTimeline(hashtag);
|
||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTimeline(TimelineDefinition tl) {
|
private void addTimeline(TimelineDefinition tl){
|
||||||
data.add(tl.copy());
|
data.add(tl.copy());
|
||||||
adapter.notifyItemInserted(data.size());
|
adapter.notifyItemInserted(data.size());
|
||||||
saveTimelines();
|
saveTimelines();
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
|
private void addTimelineToOptions(TimelineDefinition tl, Menu menu){
|
||||||
if (data.contains(tl)) return;
|
if(data.contains(tl)) return;
|
||||||
MenuItem item = addOptionsItem(menu, tl.getTitle(getContext()), tl.getIcon().iconRes);
|
MenuItem item=addOptionsItem(menu, tl.getTitle(getContext()), tl.getIcon().iconRes);
|
||||||
timelineByMenuItem.put(item, tl);
|
timelineByMenuItem.put(item, tl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon) {
|
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon){
|
||||||
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, name);
|
MenuItem item=menu.add(0, View.generateViewId(), Menu.NONE, name);
|
||||||
item.setIcon(icon);
|
item.setIcon(icon);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOptionsMenu() {
|
private void updateOptionsMenu(){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
optionsMenu.clear();
|
optionsMenu.clear();
|
||||||
timelineByMenuItem.clear();
|
timelineByMenuItem.clear();
|
||||||
|
|
||||||
SubMenu menu = optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
|
SubMenu menu=optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
|
||||||
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
|
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
|
||||||
|
|
||||||
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline);
|
SubMenu timelinesMenu=menu.addSubMenu(R.string.sk_timeline);
|
||||||
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
|
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
|
||||||
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list);
|
SubMenu listsMenu=menu.addSubMenu(R.string.sk_list);
|
||||||
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
|
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
|
||||||
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag);
|
SubMenu hashtagsMenu=menu.addSubMenu(R.string.sk_hashtag);
|
||||||
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
||||||
|
|
||||||
makeBackItem(timelinesMenu);
|
makeBackItem(timelinesMenu);
|
||||||
makeBackItem(listsMenu);
|
makeBackItem(listsMenu);
|
||||||
makeBackItem(hashtagsMenu);
|
makeBackItem(hashtagsMenu);
|
||||||
|
|
||||||
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl->addTimelineToOptions(tl, timelinesMenu));
|
||||||
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl->addTimelineToOptions(tl, listsMenu));
|
||||||
addHashtagItem = addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
|
addHashtagItem=addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
|
||||||
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl->addTimelineToOptions(tl, hashtagsMenu));
|
||||||
|
|
||||||
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0);
|
timelinesMenu.getItem().setVisible(timelinesMenu.size()>0);
|
||||||
listsMenu.getItem().setVisible(listsMenu.size() > 0);
|
listsMenu.getItem().setVisible(listsMenu.size()>0);
|
||||||
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0);
|
hashtagsMenu.getItem().setVisible(hashtagsMenu.size()>0);
|
||||||
|
|
||||||
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveTimelines() {
|
private void saveTimelines(){
|
||||||
updated=true;
|
updated=true;
|
||||||
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
||||||
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
|
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
|
||||||
prefs.timelines=data;
|
prefs.timelines=data;
|
||||||
prefs.save();
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeTimeline(int position) {
|
private void removeTimeline(int position){
|
||||||
data.remove(position);
|
data.remove(position);
|
||||||
adapter.notifyItemRemoved(position);
|
adapter.notifyItemRemoved(position);
|
||||||
saveTimelines();
|
saveTimelines();
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
onDataLoaded(AccountSessionManager.get(accountID).getLocalPreferences().timelines);
|
onDataLoaded(AccountSessionManager.get(accountID).getLocalPreferences().timelines);
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() {
|
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter(){
|
||||||
return adapter = new TimelinesAdapter();
|
return adapter=new TimelinesAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop() {
|
public void scrollToTop(){
|
||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy(){
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (updated) UiUtils.restartApp();
|
if(updated) UiUtils.restartApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
|
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags){
|
||||||
if (tags == null || tags.isEmpty()) return false;
|
if(tags==null || tags.isEmpty()) return false;
|
||||||
editText.setText(String.join(",", tags));
|
editText.setText(tags);
|
||||||
editText.chipifyAllUnterminatedTokens();
|
editText.chipifyAllUnterminatedTokens();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NachoTextView prepareChipTextView(NachoTextView nacho) {
|
private NachoTextView prepareChipTextView(NachoTextView nacho){
|
||||||
nacho.addChipTerminator(',', BEHAVIOR_CHIPIFY_ALL);
|
//I’ll Be Back
|
||||||
nacho.addChipTerminator('\n', BEHAVIOR_CHIPIFY_ALL);
|
nacho.setChipTerminators(
|
||||||
nacho.addChipTerminator(' ', BEHAVIOR_CHIPIFY_ALL);
|
Map.of(
|
||||||
nacho.addChipTerminator(';', BEHAVIOR_CHIPIFY_ALL);
|
',', BEHAVIOR_CHIPIFY_ALL,
|
||||||
nacho.enableEditChipOnTouch(true, true);
|
'\n', BEHAVIOR_CHIPIFY_ALL,
|
||||||
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
|
' ', BEHAVIOR_CHIPIFY_ALL,
|
||||||
return nacho;
|
';', BEHAVIOR_CHIPIFY_ALL
|
||||||
}
|
)
|
||||||
|
);
|
||||||
|
nacho.enableEditChipOnTouch(true, true);
|
||||||
|
nacho.setOnFocusChangeListener((v, hasFocus)->nacho.chipifyAllUnterminatedTokens());
|
||||||
|
return nacho;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove) {
|
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove){
|
||||||
Context ctx = getContext();
|
Context ctx=getContext();
|
||||||
View view = getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
|
View view=getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
|
||||||
|
|
||||||
View divider = view.findViewById(R.id.divider);
|
View divider=view.findViewById(R.id.divider);
|
||||||
Button advancedBtn = view.findViewById(R.id.advanced);
|
Button advancedBtn=view.findViewById(R.id.advanced);
|
||||||
EditText editText = view.findViewById(R.id.input);
|
EditText editText=view.findViewById(R.id.input);
|
||||||
if (item != null) editText.setText(item.getCustomTitle());
|
if(item!=null) editText.setText(item.getCustomTitle());
|
||||||
editText.setHint(item != null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
|
editText.setHint(item!=null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
|
||||||
|
|
||||||
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap);
|
LinearLayout tagWrap=view.findViewById(R.id.tag_wrap);
|
||||||
boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG;
|
boolean hashtagOptionsAvailable=item==null || item.getType()==TimelineDefinition.TimelineType.HASHTAG;
|
||||||
advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE);
|
advancedBtn.setVisibility(hashtagOptionsAvailable ? View.VISIBLE : View.GONE);
|
||||||
advancedBtn.setOnClickListener(l -> {
|
advancedBtn.setOnClickListener(l->{
|
||||||
advancedBtn.setSelected(!advancedBtn.isSelected());
|
advancedBtn.setSelected(!advancedBtn.isSelected());
|
||||||
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
|
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
|
||||||
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
||||||
tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
||||||
UiUtils.beginLayoutTransition((ViewGroup) view);
|
UiUtils.beginLayoutTransition((ViewGroup) view);
|
||||||
});
|
});
|
||||||
|
|
||||||
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch);
|
Switch localOnlySwitch=view.findViewById(R.id.local_only_switch);
|
||||||
view.findViewById(R.id.local_only)
|
view.findViewById(R.id.local_only).setOnClickListener(l->localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
|
||||||
.setOnClickListener(l -> localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
|
|
||||||
|
|
||||||
EditText tagMain = view.findViewById(R.id.tag_main);
|
EditText tagMain=view.findViewById(R.id.tag_main);
|
||||||
NachoTextView tagsAny = prepareChipTextView(view.findViewById(R.id.tags_any));
|
NachoTextView tagsAny=prepareChipTextView(view.findViewById(R.id.tags_any));
|
||||||
NachoTextView tagsAll = prepareChipTextView(view.findViewById(R.id.tags_all));
|
NachoTextView tagsAll=prepareChipTextView(view.findViewById(R.id.tags_all));
|
||||||
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none));
|
NachoTextView tagsNone=prepareChipTextView(view.findViewById(R.id.tags_none));
|
||||||
if (item != null) {
|
|
||||||
tagMain.setText(item.getHashtagName());
|
if(item!=null && hashtagOptionsAvailable){
|
||||||
boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
|
tagMain.setText(item.getHashtagName());
|
||||||
hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
|
boolean hasAdvanced=!TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
|
||||||
hasAdvanced = setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
|
hasAdvanced=setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
|
||||||
hasAdvanced = setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
|
hasAdvanced=setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
|
||||||
if (item.isHashtagLocalOnly()) {
|
hasAdvanced=setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
|
||||||
localOnlySwitch.setChecked(true);
|
if(item.isHashtagLocalOnly()){
|
||||||
hasAdvanced = true;
|
localOnlySwitch.setChecked(true);
|
||||||
}
|
hasAdvanced=true;
|
||||||
if (hasAdvanced) {
|
}
|
||||||
advancedBtn.setSelected(true);
|
if(hasAdvanced){
|
||||||
advancedBtn.setText(R.string.sk_advanced_options_hide);
|
advancedBtn.setSelected(true);
|
||||||
|
advancedBtn.setText(R.string.sk_advanced_options_hide);
|
||||||
tagWrap.setVisibility(View.VISIBLE);
|
tagWrap.setVisibility(View.VISIBLE);
|
||||||
divider.setVisibility(View.VISIBLE);
|
divider.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton btn = view.findViewById(R.id.button);
|
ImageButton btn=view.findViewById(R.id.button);
|
||||||
PopupMenu popup = new PopupMenu(ctx, btn);
|
PopupMenu popup=new PopupMenu(ctx, btn);
|
||||||
TimelineDefinition.Icon currentIcon = item != null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
|
TimelineDefinition.Icon currentIcon=item!=null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
|
||||||
btn.setImageResource(currentIcon.iconRes);
|
btn.setImageResource(currentIcon.iconRes);
|
||||||
btn.setTag(currentIcon.ordinal());
|
btn.setTag(currentIcon.ordinal());
|
||||||
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
|
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
|
||||||
btn.setOnTouchListener(popup.getDragToOpenListener());
|
btn.setOnTouchListener(popup.getDragToOpenListener());
|
||||||
btn.setOnClickListener(l -> popup.show());
|
btn.setOnClickListener(l->popup.show());
|
||||||
|
|
||||||
Menu menu = popup.getMenu();
|
Menu menu=popup.getMenu();
|
||||||
TimelineDefinition.Icon defaultIcon = item != null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
|
TimelineDefinition.Icon defaultIcon=item!=null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
|
||||||
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
|
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
|
||||||
if (!currentIcon.equals(defaultIcon)) {
|
if(!currentIcon.equals(defaultIcon)){
|
||||||
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
|
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
|
||||||
}
|
}
|
||||||
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
|
for(TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()){
|
||||||
if (icon.hidden || icon.ordinal() == (int) btn.getTag()) continue;
|
if(icon.hidden || icon.ordinal()==(int) btn.getTag()) continue;
|
||||||
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
|
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
|
||||||
}
|
}
|
||||||
UiUtils.enablePopupMenuIcons(ctx, popup);
|
UiUtils.enablePopupMenuIcons(ctx, popup);
|
||||||
|
|
||||||
popup.setOnMenuItemClickListener(menuItem -> {
|
popup.setOnMenuItemClickListener(menuItem->{
|
||||||
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
|
TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[menuItem.getItemId()];
|
||||||
btn.setImageResource(icon.iconRes);
|
btn.setImageResource(icon.iconRes);
|
||||||
btn.setTag(menuItem.getItemId());
|
btn.setTag(menuItem.getItemId());
|
||||||
btn.setContentDescription(ctx.getString(icon.nameRes));
|
btn.setContentDescription(ctx.getString(icon.nameRes));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
AlertDialog.Builder builder = new M3AlertDialogBuilder(ctx)
|
AlertDialog.Builder builder=new M3AlertDialogBuilder(ctx)
|
||||||
.setTitle(item == null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
|
.setTitle(item==null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
|
||||||
.setView(view)
|
.setView(view)
|
||||||
.setPositiveButton(R.string.save, (d, which) -> {
|
.setPositiveButton(R.string.save, (d, which)->{
|
||||||
tagsAny.chipifyAllUnterminatedTokens();
|
String name=editText.getText().toString().trim();
|
||||||
tagsAll.chipifyAllUnterminatedTokens();
|
|
||||||
tagsNone.chipifyAllUnterminatedTokens();
|
|
||||||
String name = editText.getText().toString().trim();
|
|
||||||
String mainHashtag = tagMain.getText().toString().trim();
|
|
||||||
if (TextUtils.isEmpty(mainHashtag)) {
|
|
||||||
mainHashtag = name;
|
|
||||||
name = null;
|
|
||||||
}
|
|
||||||
if (TextUtils.isEmpty(mainHashtag)) {
|
|
||||||
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
|
|
||||||
onSave.accept(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimelineDefinition tl = item != null ? item : TimelineDefinition.ofHashtag(name);
|
String mainHashtag=tagMain.getText().toString().trim();
|
||||||
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[(int) btn.getTag()];
|
if(item.getType()==TimelineDefinition.TimelineType.HASHTAG){
|
||||||
tl.setIcon(icon);
|
tagsAny.chipifyAllUnterminatedTokens();
|
||||||
tl.setTitle(name);
|
tagsAll.chipifyAllUnterminatedTokens();
|
||||||
tl.setTagOptions(
|
tagsNone.chipifyAllUnterminatedTokens();
|
||||||
mainHashtag,
|
if(TextUtils.isEmpty(mainHashtag)){
|
||||||
tagsAny.getChipValues(),
|
mainHashtag=name;
|
||||||
tagsAll.getChipValues(),
|
name=null;
|
||||||
tagsNone.getChipValues(),
|
}
|
||||||
localOnlySwitch.isChecked()
|
if(TextUtils.isEmpty(mainHashtag) && (item!=null && item.getType()==TimelineDefinition.TimelineType.HASHTAG)){
|
||||||
);
|
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
|
||||||
onSave.accept(tl);
|
onSave.accept(null);
|
||||||
})
|
return;
|
||||||
.setNegativeButton(R.string.cancel, (d, which) -> {});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (onRemove != null) builder.setNeutralButton(R.string.sk_remove, (d, which) -> onRemove.run());
|
TimelineDefinition tl=item!=null ? item : TimelineDefinition.ofHashtag(name);
|
||||||
|
TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[(int) btn.getTag()];
|
||||||
|
tl.setIcon(icon);
|
||||||
|
tl.setTitle(name);
|
||||||
|
if(item.getType()==TimelineDefinition.TimelineType.HASHTAG){
|
||||||
|
tl.setTagOptions(
|
||||||
|
mainHashtag,
|
||||||
|
tagsAny.getChipValues(),
|
||||||
|
tagsAll.getChipValues(),
|
||||||
|
tagsNone.getChipValues(),
|
||||||
|
localOnlySwitch.isChecked()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
onSave.accept(tl);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, (d, which)->{});
|
||||||
|
|
||||||
builder.show();
|
if(onRemove!=null) builder.setNeutralButton(R.string.sk_remove, (d, which)->onRemove.run());
|
||||||
btn.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
|
builder.show();
|
||||||
@NonNull
|
btn.requestFocus();
|
||||||
@Override
|
}
|
||||||
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new TimelineViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
|
||||||
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) {
|
@NonNull
|
||||||
holder.bind(data.get(position));
|
@Override
|
||||||
}
|
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new TimelineViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position){
|
||||||
return data.size();
|
holder.bind(data.get(position));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
|
@Override
|
||||||
private final TextView title;
|
public int getItemCount(){
|
||||||
private final ImageView dragger;
|
return data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TimelineViewHolder(){
|
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
|
||||||
super(getActivity(), R.layout.item_text, list);
|
private final TextView title;
|
||||||
title=findViewById(R.id.title);
|
private final ImageView dragger;
|
||||||
dragger=findViewById(R.id.dragger_thingy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
public TimelineViewHolder(){
|
||||||
@Override
|
super(getActivity(), R.layout.item_text, list);
|
||||||
public void onBind(TimelineDefinition item) {
|
title=findViewById(R.id.title);
|
||||||
title.setText(item.getTitle(getContext()));
|
dragger=findViewById(R.id.dragger_thingy);
|
||||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
|
}
|
||||||
dragger.setVisibility(View.VISIBLE);
|
|
||||||
dragger.setOnTouchListener((View v, MotionEvent event) -> {
|
|
||||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
|
||||||
itemTouchHelper.startDrag(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSave(TimelineDefinition tl) {
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
saveTimelines();
|
@Override
|
||||||
rebind();
|
public void onBind(TimelineDefinition item){
|
||||||
}
|
title.setText(item.getTitle(getContext()));
|
||||||
|
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
|
||||||
|
dragger.setVisibility(View.VISIBLE);
|
||||||
|
dragger.setOnTouchListener((View v, MotionEvent event)->{
|
||||||
|
if(event.getAction()==MotionEvent.ACTION_DOWN){
|
||||||
|
itemTouchHelper.startDrag(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void onRemove() {
|
private void onSave(TimelineDefinition tl){
|
||||||
removeTimeline(getAbsoluteAdapterPosition());
|
saveTimelines();
|
||||||
}
|
rebind();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
private void onRemove(){
|
||||||
@Override
|
removeTimeline(getAbsoluteAdapterPosition());
|
||||||
public void onClick() {
|
}
|
||||||
makeTimelineEditor(item, this::onSave, this::onRemove);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
public ItemTouchHelperCallback() {
|
@Override
|
||||||
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
|
public void onClick(){
|
||||||
}
|
makeTimelineEditor(item, this::onSave, this::onRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback{
|
||||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
public ItemTouchHelperCallback(){
|
||||||
int fromPosition = viewHolder.getAbsoluteAdapterPosition();
|
super(ItemTouchHelper.UP|ItemTouchHelper.DOWN, ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT);
|
||||||
int toPosition = target.getAbsoluteAdapterPosition();
|
}
|
||||||
if (Math.max(fromPosition, toPosition) >= data.size() || Math.min(fromPosition, toPosition) < 0) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
Collections.swap(data, fromPosition, toPosition);
|
|
||||||
adapter.notifyItemMoved(fromPosition, toPosition);
|
|
||||||
saveTimelines();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target){
|
||||||
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
|
int fromPosition=viewHolder.getAbsoluteAdapterPosition();
|
||||||
viewHolder.itemView.animate().alpha(0.65f);
|
int toPosition=target.getAbsoluteAdapterPosition();
|
||||||
}
|
if(Math.max(fromPosition, toPosition)>=data.size() || Math.min(fromPosition, toPosition)<0){
|
||||||
}
|
return false;
|
||||||
|
}else{
|
||||||
|
Collections.swap(data, fromPosition, toPosition);
|
||||||
|
adapter.notifyItemMoved(fromPosition, toPosition);
|
||||||
|
saveTimelines();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
||||||
super.clearView(recyclerView, viewHolder);
|
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG && viewHolder!=null){
|
||||||
viewHolder.itemView.animate().alpha(1f);
|
viewHolder.itemView.animate().alpha(0.65f);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
|
||||||
int position = viewHolder.getAbsoluteAdapterPosition();
|
super.clearView(recyclerView, viewHolder);
|
||||||
removeTimeline(position);
|
viewHolder.itemView.animate().alpha(1f);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction){
|
||||||
|
int position=viewHolder.getAbsoluteAdapterPosition();
|
||||||
|
removeTimeline(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class FavoritedStatusListFragment extends StatusListFragment{
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Status> result){
|
public void onSuccess(HeaderPaginationList<Status> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(String id){
|
public void onItemClick(String id){
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, id, data.stream().filter(h -> Objects.equals(h.name, id)).findAny().map(h -> h.following).orElse(null));
|
UiUtils.openHashtagTimeline(getActivity(), accountID, Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
@@ -81,7 +82,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
public void onSuccess(HeaderPaginationList<Account> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
@@ -201,7 +202,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
}
|
}
|
||||||
|
|
||||||
// literally the same as AccountCardStatusDisplayItem and DiscoverAccountsFragment. code should be generalized
|
// literally the same as AccountCardStatusDisplayItem and DiscoverAccountsFragment. code should be generalized
|
||||||
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.DisableableClickable{
|
||||||
private final ImageView cover, avatar;
|
private final ImageView cover, avatar;
|
||||||
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
|
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
|
||||||
private final ProgressBarButton actionButton, acceptButton, rejectButton;
|
private final ProgressBarButton actionButton, acceptButton, rejectButton;
|
||||||
@@ -233,15 +234,24 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
rejectProgress=findViewById(R.id.reject_progress);
|
rejectProgress=findViewById(R.id.reject_progress);
|
||||||
rejectWrap=findViewById(R.id.reject_btn_wrap);
|
rejectWrap=findViewById(R.id.reject_btn_wrap);
|
||||||
|
|
||||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(6));
|
avatar.setOutlineProvider(OutlineProviders.roundedRect(15));
|
||||||
itemView.setClipToOutline(true);
|
|
||||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
|
||||||
avatar.setClipToOutline(true);
|
avatar.setClipToOutline(true);
|
||||||
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
|
View border=findViewById(R.id.avatar_border);
|
||||||
|
border.setOutlineProvider(OutlineProviders.roundedRect(17));
|
||||||
|
border.setClipToOutline(true);
|
||||||
|
cover.setOutlineProvider(OutlineProviders.roundedRect(9));
|
||||||
cover.setClipToOutline(true);
|
cover.setClipToOutline(true);
|
||||||
|
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||||
|
itemView.setClipToOutline(true);
|
||||||
actionButton.setOnClickListener(this::onActionButtonClick);
|
actionButton.setOnClickListener(this::onActionButtonClick);
|
||||||
acceptButton.setOnClickListener(this::onFollowRequestButtonClick);
|
acceptButton.setOnClickListener(this::onFollowRequestButtonClick);
|
||||||
rejectButton.setOnClickListener(this::onFollowRequestButtonClick);
|
rejectButton.setOnClickListener(this::onFollowRequestButtonClick);
|
||||||
|
itemView.setOnClickListener(v->this.onClick());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(){
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -254,26 +264,23 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||||
postsLabel.setText(getResources().getQuantityString(R.plurals.x_posts, (int)(item.account.statusesCount%1000), item.account.statusesCount));
|
postsLabel.setText(getResources().getQuantityString(R.plurals.sk_posts_count_label, (int)(item.account.statusesCount%1000), item.account.statusesCount));
|
||||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
relationship=relationships.get(item.account.id);
|
relationship=relationships.get(item.account.id);
|
||||||
if(relationship == null || !relationship.followedBy){
|
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account);
|
||||||
|
|
||||||
|
if(relationship==null || !relationship.followedBy){
|
||||||
actionWrap.setVisibility(View.GONE);
|
actionWrap.setVisibility(View.GONE);
|
||||||
acceptWrap.setVisibility(View.VISIBLE);
|
acceptWrap.setVisibility(View.VISIBLE);
|
||||||
rejectWrap.setVisibility(View.VISIBLE);
|
rejectWrap.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// i hate that i wasn't able to do this in xml
|
|
||||||
acceptButton.setCompoundDrawableTintList(acceptButton.getTextColors());
|
acceptButton.setCompoundDrawableTintList(acceptButton.getTextColors());
|
||||||
acceptProgress.setIndeterminateTintList(acceptButton.getTextColors());
|
acceptProgress.setIndeterminateTintList(acceptButton.getTextColors());
|
||||||
rejectButton.setCompoundDrawableTintList(rejectButton.getTextColors());
|
rejectButton.setCompoundDrawableTintList(rejectButton.getTextColors());
|
||||||
rejectProgress.setIndeterminateTintList(rejectButton.getTextColors());
|
rejectProgress.setIndeterminateTintList(rejectButton.getTextColors());
|
||||||
}else if(relationship==null){
|
|
||||||
actionWrap.setVisibility(View.GONE);
|
|
||||||
acceptWrap.setVisibility(View.GONE);
|
|
||||||
rejectWrap.setVisibility(View.GONE);
|
|
||||||
}else{
|
}else{
|
||||||
actionWrap.setVisibility(View.VISIBLE);
|
actionWrap.setVisibility(View.VISIBLE);
|
||||||
acceptWrap.setVisibility(View.GONE);
|
acceptWrap.setVisibility(View.GONE);
|
||||||
@@ -351,15 +358,16 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
|
|
||||||
public AccountWrapper(Account account){
|
public AccountWrapper(Account account){
|
||||||
this.account=account;
|
this.account=account;
|
||||||
if(!TextUtils.isEmpty(account.avatar))
|
avaRequest=new UrlImageLoaderRequest(
|
||||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(getAccountID()).getDefaultAvatarUrl() : account.avatar,
|
||||||
|
V.dp(50), V.dp(50));
|
||||||
if(!TextUtils.isEmpty(account.header))
|
if(!TextUtils.isEmpty(account.header))
|
||||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
if(account.emojis.isEmpty()){
|
if(account.emojis.isEmpty()){
|
||||||
parsedName=account.displayName;
|
parsedName= account.getDisplayName();
|
||||||
}else{
|
}else{
|
||||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis);
|
||||||
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
@@ -121,7 +121,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
|
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,65 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.tags.GetHashtag;
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
import org.joinmastodon.android.api.requests.tags.GetTag;
|
||||||
|
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
||||||
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
import org.joinmastodon.android.ui.text.SpacerSpan;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
||||||
private String hashtag;
|
private Hashtag hashtag;
|
||||||
|
private String hashtagName;
|
||||||
|
private TextView headerTitle, headerSubtitle;
|
||||||
|
private ProgressBarButton followButton;
|
||||||
|
private ProgressBar followProgress;
|
||||||
|
private MenuItem followMenuItem, pinMenuItem;
|
||||||
|
private boolean followRequestRunning;
|
||||||
|
private boolean toolbarContentVisible;
|
||||||
|
|
||||||
private List<String> any;
|
private List<String> any;
|
||||||
private List<String> all;
|
private List<String> all;
|
||||||
private List<String> none;
|
private List<String> none;
|
||||||
private boolean following;
|
private boolean following;
|
||||||
private boolean localOnly;
|
private boolean localOnly;
|
||||||
private MenuItem followButton;
|
private Menu optionsMenu;
|
||||||
|
private MenuInflater optionsMenuInflater;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean wantsComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
@@ -50,89 +69,36 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
updateTitle(getArguments().getString("hashtag"));
|
|
||||||
following=getArguments().getBoolean("following", false);
|
following=getArguments().getBoolean("following", false);
|
||||||
localOnly=getArguments().getBoolean("localOnly", false);
|
localOnly=getArguments().getBoolean("localOnly", false);
|
||||||
any=getArguments().getStringArrayList("any");
|
any=getArguments().getStringArrayList("any");
|
||||||
all=getArguments().getStringArrayList("all");
|
all=getArguments().getStringArrayList("all");
|
||||||
none=getArguments().getStringArrayList("none");
|
none=getArguments().getStringArrayList("none");
|
||||||
setHasOptionsMenu(true);
|
if(getArguments().containsKey("hashtag")){
|
||||||
}
|
hashtag=Parcels.unwrap(getArguments().getParcelable("hashtag"));
|
||||||
|
hashtagName=hashtag.name;
|
||||||
private void updateTitle(String hashtagName) {
|
}else{
|
||||||
hashtag = hashtagName;
|
hashtagName=getArguments().getString("hashtagName");
|
||||||
setTitle('#'+hashtag);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateFollowingState(boolean newFollowing) {
|
|
||||||
this.following = newFollowing;
|
|
||||||
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
|
|
||||||
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
|
||||||
E.post(new HashtagUpdatedEvent(hashtag, following));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.hashtag_timeline, menu);
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
followButton = menu.findItem(R.id.follow_hashtag);
|
|
||||||
updateFollowingState(following);
|
|
||||||
|
|
||||||
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Hashtag hashtag) {
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
updateTitle(hashtag.name);
|
|
||||||
updateFollowingState(hashtag.following);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (super.onOptionsItemSelected(item)) return true;
|
|
||||||
if (item.getItemId() == R.id.follow_hashtag) {
|
|
||||||
updateFollowingState(!following);
|
|
||||||
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
|
||||||
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Hashtag i) {
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
if (i.following == following) Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show();
|
|
||||||
updateFollowingState(i.following);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getActivity());
|
|
||||||
updateFollowingState(!following);
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
setTitle('#'+hashtagName);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TimelineDefinition makeTimelineDefinition() {
|
protected TimelineDefinition makeTimelineDefinition() {
|
||||||
return TimelineDefinition.ofHashtag(hashtag);
|
return TimelineDefinition.ofHashtag(hashtagName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetHashtagTimeline(hashtagName, getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
boolean more=applyMaxID(result);
|
||||||
onDataLoaded(result, !result.isEmpty());
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
||||||
|
onDataLoaded(result, more);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -146,15 +112,40 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onFabLongClick(View v) {
|
public void loadData(){
|
||||||
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
|
reloadTag();
|
||||||
|
super.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
fab=view.findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
|
||||||
|
if(getParentFragment() instanceof HomeTabFragment) return;
|
||||||
|
|
||||||
|
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||||
|
View topChild=recyclerView.getChildAt(0);
|
||||||
|
int firstChildPos=recyclerView.getChildAdapterPosition(topChild);
|
||||||
|
float newAlpha=firstChildPos>0 ? 1f : Math.min(1f, -topChild.getTop()/(float)headerTitle.getHeight());
|
||||||
|
toolbarTitleView.setAlpha(newAlpha);
|
||||||
|
boolean newToolbarVisibility=newAlpha>0.5f;
|
||||||
|
if(newToolbarVisibility!=toolbarContentVisible){
|
||||||
|
toolbarContentVisible=newToolbarVisibility;
|
||||||
|
createOptionsMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFabClick(View v){
|
public void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putString("prefilledText", '#'+hashtag+' ');
|
args.putString("prefilledText", '#'+hashtagName+' ');
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +161,202 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
|
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags/") + hashtag).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
|
View header=getActivity().getLayoutInflater().inflate(R.layout.header_hashtag_timeline, list, false);
|
||||||
|
headerTitle=header.findViewById(R.id.title);
|
||||||
|
headerSubtitle=header.findViewById(R.id.subtitle);
|
||||||
|
followButton=header.findViewById(R.id.profile_action_btn);
|
||||||
|
followProgress=header.findViewById(R.id.action_progress);
|
||||||
|
|
||||||
|
headerTitle.setText("#"+hashtagName);
|
||||||
|
followButton.setVisibility(View.GONE);
|
||||||
|
followButton.setOnClickListener(v->{
|
||||||
|
if(hashtag==null)
|
||||||
|
return;
|
||||||
|
setFollowed(!hashtag.following);
|
||||||
|
});
|
||||||
|
followButton.setOnLongClickListener(v->{
|
||||||
|
if(hashtag==null) return false;
|
||||||
|
UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> {
|
||||||
|
new SetTagFollowed(hashtagName, true).setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Hashtag hashtag) {
|
||||||
|
Toast.makeText(
|
||||||
|
getActivity(),
|
||||||
|
getString(R.string.sk_followed_as, session.self.getShortUsername()),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
}).exec(session.getID());
|
||||||
|
}, null);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
updateHeader();
|
||||||
|
|
||||||
|
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
|
||||||
|
if(!(getParentFragment() instanceof HomeTabFragment)){
|
||||||
|
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(header));
|
||||||
|
}
|
||||||
|
mergeAdapter.addAdapter(super.getAdapter());
|
||||||
|
return mergeAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getMainAdapterOffset(){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createOptionsMenu(){
|
||||||
|
optionsMenu.clear();
|
||||||
|
optionsMenuInflater.inflate(R.menu.hashtag_timeline, optionsMenu);
|
||||||
|
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
|
||||||
|
pinMenuItem=optionsMenu.findItem(R.id.pin);
|
||||||
|
followMenuItem.setVisible(toolbarContentVisible);
|
||||||
|
pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
super.updatePinButton(pinMenuItem);
|
||||||
|
if(toolbarContentVisible){
|
||||||
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu);
|
||||||
|
}else{
|
||||||
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updatePinButton(MenuItem pin){
|
||||||
|
super.updatePinButton(pin);
|
||||||
|
if(toolbarContentVisible) UiUtils.insetPopupMenuIcon(getContext(), pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
|
inflater.inflate(R.menu.hashtag_timeline, menu);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
optionsMenu=menu;
|
||||||
|
optionsMenuInflater=inflater;
|
||||||
|
createOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
|
if (super.onOptionsItemSelected(item)) return true;
|
||||||
|
if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) {
|
||||||
|
setFollowed(!hashtag.following);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onUpdateToolbar(){
|
||||||
|
super.onUpdateToolbar();
|
||||||
|
toolbarTitleView.setAlpha(toolbarContentVisible ? 1f : 0f);
|
||||||
|
createOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateHeader(){
|
||||||
|
if(hashtag==null || getActivity()==null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(hashtag.history!=null && !hashtag.history.isEmpty()){
|
||||||
|
int weekPosts=hashtag.history.stream().mapToInt(h->h.uses).sum();
|
||||||
|
int todayPosts=hashtag.history.get(0).uses;
|
||||||
|
int numAccounts=hashtag.history.stream().mapToInt(h->h.accounts).sum();
|
||||||
|
int hSpace=V.dp(8);
|
||||||
|
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||||
|
ssb.append(getResources().getQuantityString(R.plurals.x_posts, weekPosts, weekPosts));
|
||||||
|
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
|
||||||
|
ssb.append('·');
|
||||||
|
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
|
||||||
|
ssb.append(getResources().getQuantityString(R.plurals.x_participants, numAccounts, numAccounts));
|
||||||
|
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
|
||||||
|
ssb.append('·');
|
||||||
|
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
|
||||||
|
ssb.append(getResources().getQuantityString(R.plurals.x_posts_today, todayPosts, todayPosts));
|
||||||
|
headerSubtitle.setText(ssb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int styleRes;
|
||||||
|
followButton.setVisibility(View.VISIBLE);
|
||||||
|
if(hashtag.following){
|
||||||
|
followButton.setText(R.string.button_following);
|
||||||
|
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal;
|
||||||
|
}else{
|
||||||
|
followButton.setText(R.string.button_follow);
|
||||||
|
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
|
||||||
|
}
|
||||||
|
TypedArray ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
|
||||||
|
followButton.setBackground(ta.getDrawable(0));
|
||||||
|
ta.recycle();
|
||||||
|
ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
|
||||||
|
followButton.setTextColor(ta.getColorStateList(0));
|
||||||
|
followProgress.setIndeterminateTintList(ta.getColorStateList(0));
|
||||||
|
ta.recycle();
|
||||||
|
|
||||||
|
followButton.setTextVisible(true);
|
||||||
|
followProgress.setVisibility(View.GONE);
|
||||||
|
if(followMenuItem!=null){
|
||||||
|
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
|
||||||
|
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||||
|
UiUtils.insetPopupMenuIcon(getContext(), followMenuItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadTag(){
|
||||||
|
new GetTag(hashtagName)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Hashtag result){
|
||||||
|
hashtag=result;
|
||||||
|
updateHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFollowed(boolean followed){
|
||||||
|
if(followRequestRunning)
|
||||||
|
return;
|
||||||
|
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||||
|
followButton.setTextVisible(false);
|
||||||
|
followProgress.setVisibility(View.VISIBLE);
|
||||||
|
followRequestRunning=true;
|
||||||
|
new SetTagFollowed(hashtagName, followed)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Hashtag result){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
hashtag=result;
|
||||||
|
updateHeader();
|
||||||
|
followRequestRunning=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
if(error instanceof MastodonErrorResponse er && "Duplicate record".equals(er.error)){
|
||||||
|
hashtag.following=true;
|
||||||
|
}else{
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
updateHeader();
|
||||||
|
followRequestRunning=false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
|
|||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
|
import android.graphics.drawable.RippleDrawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
@@ -31,7 +32,6 @@ import org.joinmastodon.android.events.NotificationsMarkerUpdatedEvent;
|
|||||||
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
|
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
|
||||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
@@ -70,14 +70,12 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
private TextView notificationsBadge;
|
private TextView notificationsBadge;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private boolean isAkkoma;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
setTitle(R.string.sk_app_name);
|
setTitle(R.string.sk_app_name);
|
||||||
isAkkoma = getInstance().map(Instance::isAkkoma).orElse(false);
|
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
@@ -88,7 +86,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
homeTabFragment=new HomeTabFragment();
|
homeTabFragment=new HomeTabFragment();
|
||||||
homeTabFragment.setArguments(args);
|
homeTabFragment.setArguments(args);
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
args.putBoolean("disableDiscover", isAkkoma);
|
|
||||||
args.putBoolean("noAutoLoad", true);
|
args.putBoolean("noAutoLoad", true);
|
||||||
discoverFragment=new DiscoverFragment();
|
discoverFragment=new DiscoverFragment();
|
||||||
discoverFragment.setArguments(args);
|
discoverFragment.setArguments(args);
|
||||||
@@ -126,12 +123,31 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
tabBarWrap=content.findViewById(R.id.tabbar_wrap);
|
tabBarWrap=content.findViewById(R.id.tabbar_wrap);
|
||||||
|
|
||||||
// this one's for the pill haters (https://m3.material.io/components/navigation-bar/overview)
|
// this one's for the pill haters (https://m3.material.io/components/navigation-bar/overview)
|
||||||
if (GlobalUserPreferences.disableM3PillActiveIndicator) {
|
if(GlobalUserPreferences.disableM3PillActiveIndicator){
|
||||||
for(int i=0; i<tabBar.getChildCount(); i++){
|
tabBar.findViewById(R.id.tab_home_pill).setBackground(null);
|
||||||
ViewGroup f=(ViewGroup) tabBar.getChildAt(i);
|
tabBar.findViewById(R.id.tab_search_pill).setBackground(null);
|
||||||
f.setBackgroundResource(R.drawable.bg_tabbar_tab_ripple);
|
tabBar.findViewById(R.id.tab_notifications_pill).setBackground(null);
|
||||||
|
tabBar.findViewById(R.id.tab_profile_pill).setBackgroundResource(R.drawable.bg_tab_profile);
|
||||||
|
|
||||||
|
View[] tabs={
|
||||||
|
tabBar.findViewById(R.id.tab_home),
|
||||||
|
tabBar.findViewById(R.id.tab_search),
|
||||||
|
tabBar.findViewById(R.id.tab_notifications),
|
||||||
|
tabBar.findViewById(R.id.tab_profile)
|
||||||
|
};
|
||||||
|
|
||||||
|
for(View tab : tabs){
|
||||||
|
tab.setBackgroundResource(R.drawable.bg_tabbar_tab_ripple);
|
||||||
|
((RippleDrawable) tab.getBackground())
|
||||||
|
.setRadius(V.dp(GlobalUserPreferences.showNavigationLabels ? 56 : 42));
|
||||||
}
|
}
|
||||||
tabBar.findViewById(R.id.tab_profile).setBackgroundResource(R.drawable.bg_tab_profile);
|
}
|
||||||
|
|
||||||
|
if(!GlobalUserPreferences.showNavigationLabels){
|
||||||
|
tabBar.findViewById(R.id.tab_home_label).setVisibility(View.GONE);
|
||||||
|
tabBar.findViewById(R.id.tab_search_label).setVisibility(View.GONE);
|
||||||
|
tabBar.findViewById(R.id.tab_notifications_label).setVisibility(View.GONE);
|
||||||
|
tabBar.findViewById(R.id.tab_profile_label).setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
tabBarAvatar=tabBar.findViewById(R.id.tab_profile_ava);
|
tabBarAvatar=tabBar.findViewById(R.id.tab_profile_ava);
|
||||||
@@ -164,6 +180,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tabBar.selectTab(currentTab);
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
@@ -275,7 +292,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
if(tab==R.id.tab_profile){
|
if(tab==R.id.tab_profile){
|
||||||
ArrayList<String> options=new ArrayList<>();
|
ArrayList<String> options=new ArrayList<>();
|
||||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||||
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
options.add(session.self.getDisplayName()+"\n("+session.self.username+"@"+session.domain+")");
|
||||||
}
|
}
|
||||||
new AccountSwitcherSheet(getActivity(), this).show();
|
new AccountSwitcherSheet(getActivity(), this).show();
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
|||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -232,21 +233,25 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
|
|
||||||
ViewTreeObserver vto = getToolbar().getViewTreeObserver();
|
ViewTreeObserver vto = getToolbar().getViewTreeObserver();
|
||||||
if (vto.isAlive()) {
|
if (vto.isAlive()) {
|
||||||
vto.addOnGlobalLayoutListener(() -> {
|
vto.addOnGlobalLayoutListener(()->{
|
||||||
Toolbar t = getToolbar();
|
Toolbar t=getToolbar();
|
||||||
if (t == null) return;
|
if(t==null) return;
|
||||||
int toolbarWidth = t.getWidth();
|
int toolbarWidth=t.getWidth();
|
||||||
if (toolbarWidth == 0) return;
|
if(toolbarWidth==0) return;
|
||||||
|
|
||||||
int toolbarFrameWidth = toolbarFrame.getWidth();
|
int toolbarFrameWidth=toolbarFrame.getWidth();
|
||||||
int padding = toolbarWidth - toolbarFrameWidth;
|
int actionsWidth=toolbarWidth-toolbarFrameWidth;
|
||||||
FrameLayout parent = ((FrameLayout) toolbarShowNewPostsBtn.getParent());
|
// margin (4) + padding (12) + icon (24) + margin (8) + chevron (16) + padding (12)
|
||||||
if (padding == parent.getPaddingStart()) return;
|
int switcherWidth=V.dp(76);
|
||||||
|
FrameLayout parent=((FrameLayout) toolbarShowNewPostsBtn.getParent());
|
||||||
|
if(actionsWidth==parent.getPaddingStart()) return;
|
||||||
|
int paddingMax=Math.max(actionsWidth, switcherWidth);
|
||||||
|
int paddingEnd=(Math.max(0, switcherWidth-actionsWidth));
|
||||||
|
|
||||||
// toolbar frame goes from screen edge to beginning of right-aligned option buttons.
|
// toolbar frame goes from screen edge to beginning of right-aligned option buttons.
|
||||||
// centering button by applying the same space on the left
|
// centering button by applying the same space on the left
|
||||||
parent.setPaddingRelative(padding, 0, 0, 0);
|
parent.setPaddingRelative(paddingMax, 0, paddingEnd, 0);
|
||||||
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth - padding * 2);
|
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth-paddingMax*2);
|
||||||
|
|
||||||
switcher.setPivotX(V.dp(28)); // padding + half of icon
|
switcher.setPivotX(V.dp(28)); // padding + half of icon
|
||||||
switcher.setPivotY(switcher.getHeight() / 2f);
|
switcher.setPivotY(switcher.getHeight() / 2f);
|
||||||
@@ -286,7 +291,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
new GetAnnouncements(false).setCallback(new Callback<>() {
|
new GetAnnouncements(false).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Announcement> result) {
|
public void onSuccess(List<Announcement> result) {
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
if (result.stream().anyMatch(a -> !a.read)) {
|
if (result.stream().anyMatch(a -> !a.read)) {
|
||||||
announcementsBadged = true;
|
announcementsBadged = true;
|
||||||
announcements.setVisible(false);
|
announcements.setVisible(false);
|
||||||
@@ -336,11 +341,13 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
hashtagsMenu.clear();
|
hashtagsMenu.clear();
|
||||||
hashtagsMenu.getItem().setVisible(hashtagsItems.size() > 0);
|
hashtagsMenu.getItem().setVisible(hashtagsItems.size() > 0);
|
||||||
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(hashtagsMenu));
|
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(hashtagsMenu));
|
||||||
hashtagsItems.forEach((id, hashtag) -> {
|
hashtagsItems.entrySet().stream()
|
||||||
MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name);
|
.sorted(Comparator.comparing(x -> x.getValue().name, String.CASE_INSENSITIVE_ORDER))
|
||||||
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
.forEach(entry -> {
|
||||||
UiUtils.insetPopupMenuIcon(ctx, item);
|
MenuItem item = hashtagsMenu.add(Menu.NONE, entry.getKey(), Menu.NONE, entry.getValue().name);
|
||||||
});
|
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
||||||
|
UiUtils.insetPopupMenuIcon(ctx, item);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateToolbarLogo(){
|
public void updateToolbarLogo(){
|
||||||
@@ -378,7 +385,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateOverflowMenu() {
|
private void updateOverflowMenu() {
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
Menu m = overflowPopup.getMenu();
|
Menu m = overflowPopup.getMenu();
|
||||||
m.clear();
|
m.clear();
|
||||||
overflowPopup.inflate(R.menu.home_overflow);
|
overflowPopup.inflate(R.menu.home_overflow);
|
||||||
@@ -397,9 +404,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
addListsToOverflowMenu();
|
addListsToOverflowMenu();
|
||||||
addHashtagsToOverflowMenu();
|
addHashtagsToOverflowMenu();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI())
|
||||||
m.setGroupDividerEnabled(true);
|
m.setGroupDividerEnabled(true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -521,9 +527,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
||||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||||
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
||||||
args.putString("hashtag", hashtag.name);
|
UiUtils.openHashtagTimeline(getContext(), accountID, hashtag);
|
||||||
args.putBoolean("following", hashtag.following);
|
|
||||||
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,27 +11,26 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineMarkers;
|
import org.joinmastodon.android.model.TimelineMarkers;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class HomeTimelineFragment extends StatusListFragment {
|
public class HomeTimelineFragment extends StatusListFragment {
|
||||||
private HomeTabFragment parent;
|
private HomeTabFragment parent;
|
||||||
@@ -50,16 +49,6 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean typeFilterPredicate(Status s) {
|
|
||||||
AccountLocalPreferences lp=getLocalPrefs();
|
|
||||||
return (lp.showReplies || s.inReplyToId == null) &&
|
|
||||||
(lp.showBoosts || s.reblog == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Status> filterPosts(List<Status> items) {
|
|
||||||
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
AccountSessionManager.getInstance()
|
AccountSessionManager.getInstance()
|
||||||
@@ -67,11 +56,12 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
List<Status> filteredItems = filterPosts(result.items);
|
boolean empty=result.items.isEmpty();
|
||||||
maxID=result.maxID;
|
maxID=result.maxID;
|
||||||
onDataLoaded(filteredItems, !result.items.isEmpty());
|
AccountSessionManager.get(accountID).filterStatuses(result.items, getFilterContext());
|
||||||
if(result.isFromCache())
|
onDataLoaded(result.items, !empty);
|
||||||
|
if(result.isFromCache() && GlobalUserPreferences.loadNewPosts)
|
||||||
loadNewPosts();
|
loadNewPosts();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -84,7 +74,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||||
if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
if(parent!=null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
||||||
parent.hideNewPostsButton();
|
parent.hideNewPostsButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +87,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
if(!getArguments().getBoolean("noAutoLoad")){
|
if(!getArguments().getBoolean("noAutoLoad")){
|
||||||
if(!loaded && !dataLoading){
|
if(!loaded && !dataLoading){
|
||||||
loadData();
|
loadData();
|
||||||
}else if(!dataLoading){
|
}else if(!dataLoading && GlobalUserPreferences.loadNewPosts){
|
||||||
loadNewPosts();
|
loadNewPosts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,38 +117,46 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onStatusCreated(Status status){
|
public void onStatusCreated(Status status){
|
||||||
|
if(status.reblog!=null) return;
|
||||||
prependItems(Collections.singletonList(status), true);
|
prependItems(Collections.singletonList(status), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadNewPosts(){
|
private void loadNewPosts(){
|
||||||
if (!GlobalUserPreferences.loadNewPosts) return;
|
|
||||||
dataLoading=true;
|
dataLoading=true;
|
||||||
|
// we only care about the data that was actually retrieved from the timeline api since
|
||||||
|
// user-created statuses are probably in the wrong position
|
||||||
|
List<Status> dataFromTimeline=data.stream().filter(s->!s.fromStatusCreated).collect(Collectors.toList());
|
||||||
// The idea here is that we request the timeline such that if there are fewer than `limit` posts,
|
// The idea here is that we request the timeline such that if there are fewer than `limit` posts,
|
||||||
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
||||||
// between the existing and newly loaded parts of the timeline.
|
// between the existing and newly loaded parts of the timeline.
|
||||||
String sinceID=data.size()>1 ? data.get(1).id : "1";
|
String sinceID=dataFromTimeline.size()>1 ? dataFromTimeline.get(1).id : "1";
|
||||||
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
result = filterPosts(result);
|
refreshDone();
|
||||||
if(result.isEmpty() || getActivity()==null)
|
if(result.isEmpty() || getActivity()==null)
|
||||||
return;
|
return;
|
||||||
Status last=result.get(result.size()-1);
|
Status last=result.get(result.size()-1);
|
||||||
List<Status> toAdd;
|
List<Status> toAdd;
|
||||||
if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one
|
if(!dataFromTimeline.isEmpty() && last.id.equals(dataFromTimeline.get(0).id)){ // This part intersects with the existing one
|
||||||
toAdd=result.subList(0, result.size()-1); // Remove the already known last post
|
toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post
|
||||||
}else{
|
}else{
|
||||||
result.get(result.size()-1).hasGapAfter=true;
|
last.hasGapAfter=last.id;
|
||||||
toAdd=result;
|
toAdd=result;
|
||||||
}
|
}
|
||||||
|
if(!toAdd.isEmpty())
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(new ArrayList<>(toAdd), false);
|
||||||
|
// removing statuses that come up as duplicates (hopefully only posts and boosts that were locally created
|
||||||
|
// and thus were already prepended to the timeline earlier)
|
||||||
|
List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList());
|
||||||
|
toAdd.removeIf(s->existingIds.contains(s.getID()));
|
||||||
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
|
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
|
||||||
if(!toAdd.isEmpty()){
|
if(!toAdd.isEmpty()){
|
||||||
prependItems(toAdd, true);
|
prependItems(toAdd, true);
|
||||||
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
|
if(parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +164,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
|
refreshDone();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -176,15 +175,23 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onGapClick(GapStatusDisplayItem.Holder item){
|
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){
|
||||||
if(dataLoading)
|
if(dataLoading)
|
||||||
return;
|
return;
|
||||||
item.getItem().loading=true;
|
|
||||||
V.setVisibilityAnimated(item.progress, View.VISIBLE);
|
|
||||||
V.setVisibilityAnimated(item.text, View.GONE);
|
|
||||||
GapStatusDisplayItem gap=item.getItem();
|
GapStatusDisplayItem gap=item.getItem();
|
||||||
|
gap.loading=true;
|
||||||
dataLoading=true;
|
dataLoading=true;
|
||||||
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
|
|
||||||
|
String maxID=null;
|
||||||
|
String minID=null;
|
||||||
|
if (downwards) {
|
||||||
|
maxID=item.getItem().getMaxID();
|
||||||
|
} else {
|
||||||
|
int gapPos=displayItems.indexOf(gap);
|
||||||
|
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
|
||||||
|
minID=nextItem.parentID;
|
||||||
|
}
|
||||||
|
currentRequest=new GetHomeTimeline(maxID, minID, 20, null, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
@@ -195,61 +202,108 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
int gapPos=displayItems.indexOf(gap);
|
int gapPos=displayItems.indexOf(gap);
|
||||||
if(gapPos==-1)
|
if(gapPos==-1)
|
||||||
return;
|
return;
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
||||||
if(result.isEmpty()){
|
if(result.isEmpty()){
|
||||||
displayItems.remove(gapPos);
|
displayItems.remove(gapPos);
|
||||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||||
Status gapStatus=getStatusByID(gap.parentID);
|
Status gapStatus=getStatusByID(gap.parentID);
|
||||||
if(gapStatus!=null){
|
if(gapStatus!=null){
|
||||||
gapStatus.hasGapAfter=false;
|
gapStatus.hasGapAfter=null;
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
Set<String> idsBelowGap=new HashSet<>();
|
// TODO: refactor this code. it's too long. incomprehensible, even
|
||||||
boolean belowGap=false;
|
if(downwards) {
|
||||||
int gapPostIndex=0;
|
Set<String> idsBelowGap=new HashSet<>();
|
||||||
for(Status s:data){
|
boolean belowGap=false;
|
||||||
if(belowGap){
|
int gapPostIndex=0;
|
||||||
idsBelowGap.add(s.id);
|
for(Status s:data){
|
||||||
}else if(s.id.equals(gap.parentID)){
|
if(belowGap){
|
||||||
belowGap=true;
|
idsBelowGap.add(s.id);
|
||||||
s.hasGapAfter=false;
|
}else if(s.id.equals(gap.parentID)){
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
belowGap=true;
|
||||||
}else{
|
s.hasGapAfter=null;
|
||||||
gapPostIndex++;
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||||
|
}else{
|
||||||
|
gapPostIndex++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
int endIndex=0;
|
||||||
int endIndex=0;
|
for(Status s:result){
|
||||||
for(Status s:result){
|
endIndex++;
|
||||||
endIndex++;
|
if(idsBelowGap.contains(s.id))
|
||||||
if(idsBelowGap.contains(s.id))
|
break;
|
||||||
break;
|
}
|
||||||
}
|
if(endIndex==result.size()){
|
||||||
if(endIndex==result.size()){
|
Status last=result.get(result.size()-1);
|
||||||
result.get(result.size()-1).hasGapAfter=true;
|
last.hasGapAfter=last.id;
|
||||||
}else{
|
}else{
|
||||||
result=result.subList(0, endIndex);
|
result=result.subList(0, endIndex);
|
||||||
}
|
}
|
||||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||||
targetList.clear();
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
targetList.clear();
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
for(Status s:result){
|
for(Status s:result){
|
||||||
if(idsBelowGap.contains(s.id))
|
if(idsBelowGap.contains(s.id))
|
||||||
break;
|
break;
|
||||||
if(typeFilterPredicate(s) && filterPredicate.test(s)){
|
|
||||||
targetList.addAll(buildDisplayItems(s));
|
targetList.addAll(buildDisplayItems(s));
|
||||||
insertedPosts.add(s);
|
insertedPosts.add(s);
|
||||||
}
|
}
|
||||||
|
if(targetList.isEmpty()){
|
||||||
|
// oops. We didn't add new posts, but at least we know there are none.
|
||||||
|
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||||
|
}else{
|
||||||
|
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||||
|
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||||
|
}
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||||
|
} else {
|
||||||
|
String aboveGapID = gap.parentID;
|
||||||
|
int gapPostIndex = 0;
|
||||||
|
for (;gapPostIndex<data.size();gapPostIndex++){
|
||||||
|
if (Objects.equals(aboveGapID, data.get(gapPostIndex).id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// find if there's an overlap between the new data and the current data
|
||||||
|
int indexOfGapInResponse = 0;
|
||||||
|
for (;indexOfGapInResponse<result.size();indexOfGapInResponse++){
|
||||||
|
if (Objects.equals(aboveGapID, result.get(indexOfGapInResponse).id)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// there is an overlap between new and current data
|
||||||
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
|
if(indexOfGapInResponse<result.size()){
|
||||||
|
result=result.subList(indexOfGapInResponse+1,result.size());
|
||||||
|
Optional<Status> gapStatus=data.stream()
|
||||||
|
.filter(s->Objects.equals(s.id, gap.parentID))
|
||||||
|
.findFirst();
|
||||||
|
if (gapStatus.isPresent()) {
|
||||||
|
gapStatus.get().hasGapAfter=null;
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
|
||||||
|
}
|
||||||
|
targetList.clear();
|
||||||
|
} else {
|
||||||
|
gap.loading=false;
|
||||||
|
}
|
||||||
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
|
for(Status s:result){
|
||||||
|
targetList.addAll(buildDisplayItems(s));
|
||||||
|
insertedPosts.add(s);
|
||||||
|
}
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, FilterContext.HOME);
|
||||||
|
if(targetList.isEmpty()){
|
||||||
|
// oops. We didn't add new posts, but at least we know there are none.
|
||||||
|
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||||
|
}else{
|
||||||
|
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||||
|
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||||
|
}
|
||||||
|
list.scrollToPosition(getMainAdapterOffset()+gapPos+targetList.size());
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||||
}
|
}
|
||||||
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
|
|
||||||
if(targetList.isEmpty()){
|
|
||||||
// oops. We didn't add new posts, but at least we know there are none.
|
|
||||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
|
||||||
}else{
|
|
||||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
|
||||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
|
||||||
}
|
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +332,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
}
|
}
|
||||||
if (parent != null) parent.hideNewPostsButton();
|
if(parent!=null) parent.hideNewPostsButton();
|
||||||
super.onRefresh();
|
super.onRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.lists.GetList;
|
import org.joinmastodon.android.api.requests.lists.GetList;
|
||||||
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
@@ -25,10 +26,8 @@ import org.joinmastodon.android.model.TimelineDefinition;
|
|||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ListEditor;
|
import org.joinmastodon.android.ui.views.ListEditor;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -63,7 +62,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
new GetList(listID).setCallback(new Callback<>() {
|
new GetList(listID).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(ListTimeline listTimeline) {
|
public void onSuccess(ListTimeline listTimeline) {
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
// TODO: save updated info
|
// TODO: save updated info
|
||||||
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
|
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
|
||||||
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
|
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
|
||||||
@@ -101,7 +100,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(ListTimeline list) {
|
public void onSuccess(ListTimeline list) {
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
setTitle(list.title);
|
setTitle(list.title);
|
||||||
listTitle = list.title;
|
listTitle = list.title;
|
||||||
repliesPolicy = list.repliesPolicy;
|
repliesPolicy = list.repliesPolicy;
|
||||||
@@ -134,13 +133,14 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count) {
|
protected void doLoadData(int offset, int count) {
|
||||||
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetListTimeline(listID, getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this) {
|
.setCallback(new SimpleCallback<>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result) {
|
public void onSuccess(List<Status> result) {
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
boolean more=applyMaxID(result);
|
||||||
onDataLoaded(result, !result.isEmpty());
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
||||||
|
onDataLoaded(result, more);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
|
|||||||
.setCallback(new SimpleCallback<>(this) {
|
.setCallback(new SimpleCallback<>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<ListTimeline> lists) {
|
public void onSuccess(List<ListTimeline> lists) {
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
||||||
userInList.putAll(userInListBefore);
|
userInList.putAll(userInListBefore);
|
||||||
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
||||||
@@ -149,7 +149,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
|
|||||||
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
|
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<ListTimeline> allLists) {
|
public void onSuccess(List<ListTimeline> allLists) {
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
List<ListTimeline> newLists = new ArrayList<>();
|
List<ListTimeline> newLists = new ArrayList<>();
|
||||||
for (ListTimeline l : allLists) {
|
for (ListTimeline l : allLists) {
|
||||||
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Account> accounts) {
|
public void onSuccess(HeaderPaginationList<Account> accounts) {
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
|
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import android.view.View;
|
|||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
@@ -22,14 +23,15 @@ import org.joinmastodon.android.model.Notification;
|
|||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
|
||||||
import org.joinmastodon.android.utils.ObjectIdComparator;
|
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
@@ -43,7 +45,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
|
||||||
|
|
||||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification> {
|
public class NotificationsListFragment extends BaseStatusListFragment<Notification> {
|
||||||
private boolean onlyMentions;
|
private boolean onlyMentions;
|
||||||
@@ -95,11 +96,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
if (n.type == Notification.Type.FOLLOW_REQUEST) {
|
if (n.type == Notification.Type.FOLLOW_REQUEST) {
|
||||||
ArrayList<StatusDisplayItem> items = new ArrayList<>();
|
ArrayList<StatusDisplayItem> items = new ArrayList<>();
|
||||||
items.add(titleItem);
|
items.add(titleItem);
|
||||||
items.add(new AccountCardStatusDisplayItem(n.id, this, n.account, n));
|
items.add(new AccountCardStatusDisplayItem(n.id, this, accountID, n.account, n));
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET); // | StatusDisplayItem.FLAG_NO_HEADER);
|
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS); // | StatusDisplayItem.FLAG_NO_HEADER);
|
||||||
|
if (GlobalUserPreferences.spectatorMode)
|
||||||
|
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
|
||||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, null, flags);
|
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, null, flags);
|
||||||
if(titleItem!=null)
|
if(titleItem!=null)
|
||||||
items.add(0, titleItem);
|
items.add(0, titleItem);
|
||||||
@@ -131,6 +134,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
return;
|
return;
|
||||||
maxID=result.maxID;
|
maxID=result.maxID;
|
||||||
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
|
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
|
||||||
|
if(bannerHelper!=null) bannerHelper.onBannerBecameVisible();
|
||||||
reloadingFromCache=false;
|
reloadingFromCache=false;
|
||||||
if (getParentFragment() instanceof NotificationsFragment nf) {
|
if (getParentFragment() instanceof NotificationsFragment nf) {
|
||||||
nf.updateMarkAllReadButton();
|
nf.updateMarkAllReadButton();
|
||||||
@@ -170,7 +174,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
|
||||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
private Paint paint=new Paint();
|
private Paint paint=new Paint();
|
||||||
private Rect tmpRect=new Rect();
|
private Rect tmpRect=new Rect();
|
||||||
@@ -234,7 +237,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
continue;
|
continue;
|
||||||
Status contentStatus=ntf.status.getContentStatus();
|
Status contentStatus=ntf.status.getContentStatus();
|
||||||
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
|
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
|
||||||
updatePoll(ntf.id, ntf.status, ev.poll);
|
updatePoll(ntf.id, contentStatus, ev.poll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,8 +247,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
@Subscribe
|
@Subscribe
|
||||||
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||||
for(Notification n:data){
|
for(Notification n:data){
|
||||||
if (n.status == null) continue;
|
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||||
if(n.status.getContentStatus().id.equals(ev.id)){
|
|
||||||
n.status.getContentStatus().update(ev);
|
n.status.getContentStatus().update(ev);
|
||||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
@@ -259,8 +261,31 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(Notification n:preloadedData){
|
for(Notification n:preloadedData){
|
||||||
if (n.status == null) continue;
|
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||||
if(n.status.getContentStatus().id.equals(ev.id)){
|
n.status.getContentStatus().update(ev);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||||
|
for(Notification n : data){
|
||||||
|
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||||
|
n.status.getContentStatus().update(ev);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||||
|
for(int i=0; i<list.getChildCount(); i++){
|
||||||
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
|
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
|
||||||
|
reactions.rebind();
|
||||||
|
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
|
||||||
|
text.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Notification n : preloadedData){
|
||||||
|
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||||
n.status.getContentStatus().update(ev);
|
n.status.getContentStatus().update(ev);
|
||||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
@@ -35,6 +36,8 @@ public class PinnedPostsListFragment extends StatusListFragment{
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
|
if(getActivity()==null) return;
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
||||||
onDataLoaded(result, false);
|
onDataLoaded(result, false);
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import static org.joinmastodon.android.fragments.ProfileAboutFragment.MAX_FIELDS;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -21,9 +21,11 @@ import android.graphics.drawable.LayerDrawable;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.InputType;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ImageSpan;
|
import android.text.TextWatcher;
|
||||||
import android.transition.ChangeBounds;
|
import android.transition.ChangeBounds;
|
||||||
import android.transition.Fade;
|
import android.transition.Fade;
|
||||||
import android.transition.TransitionManager;
|
import android.transition.TransitionManager;
|
||||||
@@ -57,11 +59,14 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
|
||||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.fragments.account_list.BlockedAccountsListFragment;
|
||||||
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
||||||
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
|
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
|
||||||
|
import org.joinmastodon.android.fragments.account_list.MutedAccountsListFragment;
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||||
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -132,7 +137,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private ImageView avatar;
|
private ImageView avatar;
|
||||||
private CoverImageView cover;
|
private CoverImageView cover;
|
||||||
private View avatarBorder;
|
private View avatarBorder;
|
||||||
|
private View usernameWrap;
|
||||||
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
|
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
|
||||||
|
private ImageView lockIcon, botIcon;
|
||||||
private ProgressBarButton actionButton, notifyButton;
|
private ProgressBarButton actionButton, notifyButton;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
private NestedRecyclerScrollView scrollView;
|
private NestedRecyclerScrollView scrollView;
|
||||||
@@ -142,7 +149,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private SwipeRefreshLayout refreshLayout;
|
private SwipeRefreshLayout refreshLayout;
|
||||||
private View followersBtn, followingBtn;
|
private View followersBtn, followingBtn;
|
||||||
private EditText nameEdit, bioEdit;
|
private EditText nameEdit, bioEdit;
|
||||||
private ProgressBar actionProgress, notifyProgress;
|
private ProgressBar actionProgress, notifyProgress, noteSaveProgress;
|
||||||
private FrameLayout[] tabViews;
|
private FrameLayout[] tabViews;
|
||||||
private TabLayoutMediator tabLayoutMediator;
|
private TabLayoutMediator tabLayoutMediator;
|
||||||
private TextView followsYouView;
|
private TextView followsYouView;
|
||||||
@@ -175,12 +182,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private MenuItem editSaveMenuItem;
|
private MenuItem editSaveMenuItem;
|
||||||
private boolean savingEdits;
|
private boolean savingEdits;
|
||||||
|
|
||||||
|
private int maxFields = ProfileAboutFragment.MAX_FIELDS;
|
||||||
|
|
||||||
// from ProfileAboutFragment
|
// from ProfileAboutFragment
|
||||||
public UsableRecyclerView list;
|
public UsableRecyclerView list;
|
||||||
private AboutAdapter adapter;
|
private AboutAdapter adapter;
|
||||||
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
|
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
|
||||||
private ListImageLoaderWrapper imgLoader;
|
private ListImageLoaderWrapper imgLoader;
|
||||||
|
|
||||||
|
// profile note
|
||||||
|
private FrameLayout noteWrap;
|
||||||
|
private ImageButton noteSaveBtn;
|
||||||
|
private EditText noteEdit;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -200,6 +214,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
loaded=true;
|
loaded=true;
|
||||||
if(!isOwnProfile)
|
if(!isOwnProfile)
|
||||||
loadRelationship();
|
loadRelationship();
|
||||||
|
else if (isInstanceAkkoma()) {
|
||||||
|
maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
profileAccountID=getArguments().getString("profileAccountID");
|
profileAccountID=getArguments().getString("profileAccountID");
|
||||||
if(!getArguments().getBoolean("noAutoLoad", false))
|
if(!getArguments().getBoolean("noAutoLoad", false))
|
||||||
@@ -226,7 +243,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
cover=content.findViewById(R.id.cover);
|
cover=content.findViewById(R.id.cover);
|
||||||
avatarBorder=content.findViewById(R.id.avatar_border);
|
avatarBorder=content.findViewById(R.id.avatar_border);
|
||||||
name=content.findViewById(R.id.name);
|
name=content.findViewById(R.id.name);
|
||||||
|
usernameWrap=content.findViewById(R.id.username_wrap);
|
||||||
username=content.findViewById(R.id.username);
|
username=content.findViewById(R.id.username);
|
||||||
|
lockIcon=content.findViewById(R.id.lock_icon);
|
||||||
|
botIcon=content.findViewById(R.id.bot_icon);
|
||||||
bio=content.findViewById(R.id.bio);
|
bio=content.findViewById(R.id.bio);
|
||||||
followersCount=content.findViewById(R.id.followers_count);
|
followersCount=content.findViewById(R.id.followers_count);
|
||||||
followersLabel=content.findViewById(R.id.followers_label);
|
followersLabel=content.findViewById(R.id.followers_label);
|
||||||
@@ -246,6 +266,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
bioEditWrap=content.findViewById(R.id.bio_edit_wrap);
|
bioEditWrap=content.findViewById(R.id.bio_edit_wrap);
|
||||||
actionProgress=content.findViewById(R.id.action_progress);
|
actionProgress=content.findViewById(R.id.action_progress);
|
||||||
notifyProgress=content.findViewById(R.id.notify_progress);
|
notifyProgress=content.findViewById(R.id.notify_progress);
|
||||||
|
noteSaveProgress=content.findViewById(R.id.note_save_progress);
|
||||||
fab=content.findViewById(R.id.fab);
|
fab=content.findViewById(R.id.fab);
|
||||||
followsYouView=content.findViewById(R.id.follows_you);
|
followsYouView=content.findViewById(R.id.follows_you);
|
||||||
countersLayout=content.findViewById(R.id.profile_counters);
|
countersLayout=content.findViewById(R.id.profile_counters);
|
||||||
@@ -255,9 +276,56 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
list=content.findViewById(R.id.metadata);
|
list=content.findViewById(R.id.metadata);
|
||||||
rolesView=content.findViewById(R.id.roles);
|
rolesView=content.findViewById(R.id.roles);
|
||||||
|
|
||||||
|
avatarBorder.setOutlineProvider(OutlineProviders.roundedRect(26));
|
||||||
|
avatarBorder.setClipToOutline(true);
|
||||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
|
avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
|
||||||
avatar.setClipToOutline(true);
|
avatar.setClipToOutline(true);
|
||||||
|
|
||||||
|
noteEdit=content.findViewById(R.id.note_edit);
|
||||||
|
noteWrap=content.findViewById(R.id.note_edit_wrap);
|
||||||
|
noteSaveBtn=content.findViewById(R.id.note_save_btn);
|
||||||
|
|
||||||
|
noteSaveBtn.setOnClickListener((v->{
|
||||||
|
savePrivateNote(noteEdit.getText().toString());
|
||||||
|
InputMethodManager imm=(InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
|
||||||
|
noteEdit.clearFocus();
|
||||||
|
noteSaveBtn.clearFocus();
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
noteEdit.setOnFocusChangeListener((v, hasFocus)->{
|
||||||
|
if(hasFocus){
|
||||||
|
hideFab();
|
||||||
|
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
|
||||||
|
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
|
||||||
|
}else if(!noteSaveBtn.hasFocus()){
|
||||||
|
showFab();
|
||||||
|
hideNoteSaveBtnIfNotDirty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
noteEdit.addTextChangedListener(new TextWatcher(){
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||||
|
if(relationship!=null && noteSaveBtn.getVisibility()!=View.VISIBLE && !s.toString().equals(relationship.note))
|
||||||
|
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s){}
|
||||||
|
});
|
||||||
|
|
||||||
|
noteSaveBtn.setOnFocusChangeListener((v, hasFocus)->{
|
||||||
|
if(!hasFocus && !noteEdit.hasFocus()){
|
||||||
|
showFab();
|
||||||
|
hideNoteSaveBtnIfNotDirty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
FrameLayout sizeWrapper=new FrameLayout(getActivity()){
|
FrameLayout sizeWrapper=new FrameLayout(getActivity()){
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||||
@@ -294,6 +362,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
|
|
||||||
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
|
||||||
tabbar.setTabTextSize(V.dp(14));
|
tabbar.setTabTextSize(V.dp(14));
|
||||||
|
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){
|
||||||
|
case 0 -> R.string.profile_featured;
|
||||||
|
case 1 -> R.string.profile_timeline;
|
||||||
|
case 2 -> R.string.profile_about;
|
||||||
|
default -> throw new IllegalStateException();
|
||||||
|
}));
|
||||||
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||||
@Override
|
@Override
|
||||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||||
@@ -307,6 +381,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if (position == 4) tab.view.setVisibility(View.GONE);
|
if (position == 4) tab.view.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
||||||
|
@Override
|
||||||
|
public void onTabSelected(TabLayout.Tab tab){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabUnselected(TabLayout.Tab tab){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabReselected(TabLayout.Tab tab){
|
||||||
|
if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt)
|
||||||
|
stt.scrollToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
cover.setOutlineProvider(new ViewOutlineProvider(){
|
cover.setOutlineProvider(new ViewOutlineProvider(){
|
||||||
@Override
|
@Override
|
||||||
@@ -342,7 +429,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||||
|
|
||||||
username.setOnClickListener(v->{
|
content.findViewById(R.id.username_wrap).setOnClickListener(v->{
|
||||||
try {
|
try {
|
||||||
new GetInstance()
|
new GetInstance()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@@ -363,11 +450,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
.execRemote(Uri.parse(account.url).getHost());
|
.execRemote(Uri.parse(account.url).getHost());
|
||||||
} catch (NullPointerException ignored) {
|
} catch (NullPointerException ignored) {
|
||||||
// maybe the url was malformed?
|
// maybe the url was malformed?
|
||||||
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT);
|
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
username.setOnLongClickListener(v->{
|
content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{
|
||||||
String usernameString=account.acct;
|
String usernameString=account.acct;
|
||||||
if(!usernameString.contains("@")){
|
if(!usernameString.contains("@")){
|
||||||
usernameString+="@"+domain;
|
usernameString+="@"+domain;
|
||||||
@@ -403,6 +490,46 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return sizeWrapper;
|
return sizeWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hideNoteSaveBtnIfNotDirty(){
|
||||||
|
if(noteEdit.getText().toString().equals(relationship.note)){
|
||||||
|
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPrivateNote(){
|
||||||
|
noteWrap.setVisibility(View.VISIBLE);
|
||||||
|
noteEdit.setText(relationship.note);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hidePrivateNote(){
|
||||||
|
noteWrap.setVisibility(View.GONE);
|
||||||
|
noteEdit.setText(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePrivateNote(String note){
|
||||||
|
if(note!=null && note.equals(relationship.note)){
|
||||||
|
updateRelationship();
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
V.setVisibilityAnimated(noteSaveProgress, View.VISIBLE);
|
||||||
|
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
|
||||||
|
new SetPrivateNote(profileAccountID, note).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Relationship result) {
|
||||||
|
updateRelationship(result);
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
V.setVisibilityAnimated(noteSaveProgress, View.GONE);
|
||||||
|
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
private void onAccountLoaded(Account result) {
|
private void onAccountLoaded(Account result) {
|
||||||
account=result;
|
account=result;
|
||||||
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
@@ -450,7 +577,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
onAccountLoaded(result);
|
onAccountLoaded(result);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -587,12 +714,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
private void bindHeaderView(){
|
private void bindHeaderView(){
|
||||||
setTitle(account.displayName);
|
setTitle(account.getDisplayName());
|
||||||
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
|
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
|
||||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(
|
||||||
|
TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() :
|
||||||
|
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
|
||||||
|
V.dp(100), V.dp(100)));
|
||||||
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
SpannableStringBuilder ssb=new SpannableStringBuilder(account.getDisplayName());
|
||||||
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
||||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||||
name.setText(ssb);
|
name.setText(ssb);
|
||||||
@@ -619,19 +750,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
String acct = ((isSelf || account.isRemote)
|
String acct = ((isSelf || account.isRemote)
|
||||||
? account.getFullyQualifiedName()
|
? account.getFullyQualifiedName()
|
||||||
: account.acct);
|
: account.acct);
|
||||||
if(account.locked){
|
|
||||||
ssb=new SpannableStringBuilder("@");
|
username.setText('@'+acct);
|
||||||
ssb.append(acct);
|
|
||||||
ssb.append(" ");
|
lockIcon.setVisibility(account.locked ? View.VISIBLE : View.GONE);
|
||||||
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
|
lockIcon.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor()));
|
||||||
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
|
||||||
lock.setTint(username.getCurrentTextColor());
|
botIcon.setVisibility(account.bot ? View.VISIBLE : View.GONE);
|
||||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BASELINE), 0);
|
botIcon.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor()));
|
||||||
username.setText(ssb);
|
|
||||||
}else{
|
|
||||||
// noinspection SetTextI18n
|
|
||||||
username.setText('@'+acct);
|
|
||||||
}
|
|
||||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
if(TextUtils.isEmpty(parsedBio)){
|
if(TextUtils.isEmpty(parsedBio)){
|
||||||
bio.setVisibility(View.GONE);
|
bio.setVisibility(View.GONE);
|
||||||
@@ -723,20 +850,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
|
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
|
||||||
MenuItem openWithAccounts = menu.findItem(R.id.open_with_account);
|
MenuItem openWithAccounts = menu.findItem(R.id.open_with_account);
|
||||||
openWithAccounts.setVisible(hasMultipleAccounts);
|
openWithAccounts.setVisible(hasMultipleAccounts);
|
||||||
SubMenu accountsMenu = openWithAccounts.getSubMenu();
|
SubMenu accountsMenu=openWithAccounts.getSubMenu();
|
||||||
if (hasMultipleAccounts) {
|
if(hasMultipleAccounts){
|
||||||
accountsMenu.clear();
|
accountsMenu.clear();
|
||||||
UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL(
|
UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL(
|
||||||
getActivity(), s.getID(), account.url, false
|
getActivity(), s.getID(), account.url, false
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.share).setTitle(R.string.share_user);
|
|
||||||
if(isOwnProfile) {
|
if(isOwnProfile) {
|
||||||
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
|
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem mute = menu.findItem(R.id.mute);
|
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||||
|
MenuItem mute=menu.findItem(R.id.mute);
|
||||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||||
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||||
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
||||||
@@ -744,19 +872,24 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
||||||
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
|
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
|
||||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
||||||
|
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
||||||
if (relationship.following) {
|
if (relationship.following) {
|
||||||
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
|
|
||||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
||||||
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
|
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
|
||||||
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
||||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
hideBoosts.setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
menu.findItem(R.id.hide_boosts).setVisible(false);
|
hideBoosts.setVisible(false);
|
||||||
}
|
}
|
||||||
if(!account.isLocal())
|
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||||
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
if(!account.isLocal()){
|
||||||
else
|
blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||||
menu.findItem(R.id.block_domain).setVisible(false);
|
blockDomain.setVisible(true);
|
||||||
|
}else{
|
||||||
|
blockDomain.setVisible(false);
|
||||||
|
}
|
||||||
|
menu.findItem(R.id.edit_note).setTitle(noteWrap.getVisibility()==View.GONE && (relationship.note==null || relationship.note.isEmpty())
|
||||||
|
? R.string.sk_add_note : R.string.sk_delete_note);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -768,11 +901,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
intent.putExtra(Intent.EXTRA_TEXT, account.url);
|
intent.putExtra(Intent.EXTRA_TEXT, account.url);
|
||||||
startActivity(Intent.createChooser(intent, item.getTitle()));
|
startActivity(Intent.createChooser(intent, item.getTitle()));
|
||||||
}else if(id==R.id.mute){
|
}else if(id==R.id.mute){
|
||||||
confirmToggleMuted();
|
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
||||||
}else if(id==R.id.block){
|
}else if(id==R.id.block){
|
||||||
confirmToggleBlocked();
|
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
||||||
}else if(id==R.id.soft_block){
|
}else if(id==R.id.soft_block){
|
||||||
confirmSoftBlockUser();
|
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
|
||||||
}else if(id==R.id.report){
|
}else if(id==R.id.report){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -817,6 +950,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||||
}
|
}
|
||||||
Nav.go(getActivity(), ListsFragment.class, args);
|
Nav.go(getActivity(), ListsFragment.class, args);
|
||||||
|
}else if(id==R.id.muted_accounts){
|
||||||
|
final Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||||
|
Nav.go(getActivity(), MutedAccountsListFragment.class, args);
|
||||||
|
}else if(id==R.id.blocked_accounts){
|
||||||
|
final Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||||
|
Nav.go(getActivity(), BlockedAccountsListFragment.class, args);
|
||||||
}else if(id==R.id.followed_hashtags){
|
}else if(id==R.id.followed_hashtags){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -828,6 +971,26 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}else if(id==R.id.save){
|
}else if(id==R.id.save){
|
||||||
if(isInEditMode)
|
if(isInEditMode)
|
||||||
saveAndExitEditMode();
|
saveAndExitEditMode();
|
||||||
|
}else if(id==R.id.edit_note){
|
||||||
|
if(noteWrap.getVisibility()==View.GONE){
|
||||||
|
showPrivateNote();
|
||||||
|
UiUtils.beginLayoutTransition(scrollableContent);
|
||||||
|
noteEdit.requestFocus();
|
||||||
|
noteEdit.postDelayed(()->{
|
||||||
|
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
||||||
|
imm.showSoftInput(noteEdit, 0);
|
||||||
|
}, 100);
|
||||||
|
}else if(relationship.note.isEmpty()){
|
||||||
|
hidePrivateNote();
|
||||||
|
UiUtils.beginLayoutTransition(scrollableContent);
|
||||||
|
}else{
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setMessage(getContext().getString(R.string.sk_private_note_confirm_delete, account.getDisplayUsername()))
|
||||||
|
.setPositiveButton(R.string.delete, (dlg, btn)->savePrivateNote(null))
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -852,16 +1015,23 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateRelationship(){
|
private void updateRelationship(){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
|
if(relationship.note!=null && !relationship.note.isEmpty()) showPrivateNote();
|
||||||
|
else hidePrivateNote();
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
actionButton.setVisibility(View.VISIBLE);
|
actionButton.setVisibility(View.VISIBLE);
|
||||||
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
|
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
|
||||||
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||||
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
|
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
|
||||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
||||||
|
noteSaveProgress.setIndeterminateTintList(noteEdit.getTextColors());
|
||||||
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
||||||
notifyButton.setSelected(relationship.notifying);
|
notifyButton.setSelected(relationship.notifying);
|
||||||
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
||||||
|
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
|
V.setVisibilityAnimated(noteSaveProgress, View.GONE);
|
||||||
|
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
|
||||||
|
UiUtils.beginLayoutTransition(scrollableContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageButton getFab() {
|
public ImageButton getFab() {
|
||||||
@@ -1054,12 +1224,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
actionButton.setText(R.string.save_changes);
|
actionButton.setText(R.string.save_changes);
|
||||||
pager.setVisibility(View.GONE);
|
pager.setVisibility(View.GONE);
|
||||||
tabbar.setVisibility(View.GONE);
|
tabbar.setVisibility(View.GONE);
|
||||||
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate();
|
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay, getActivity().getTheme()).mutate();
|
||||||
avatar.setForeground(overlay);
|
avatar.setForeground(overlay);
|
||||||
updateMetadataHeight();
|
updateMetadataHeight();
|
||||||
|
|
||||||
Toolbar toolbar=getToolbar();
|
Toolbar toolbar=getToolbar();
|
||||||
Drawable close=getToolbarContext().getDrawable(R.drawable.ic_baseline_close_24).mutate();
|
Drawable close=getToolbarContext().getDrawable(R.drawable.ic_fluent_dismiss_24_regular).mutate();
|
||||||
close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant));
|
close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant));
|
||||||
toolbar.setNavigationIcon(close);
|
toolbar.setNavigationIcon(close);
|
||||||
toolbar.setNavigationContentDescription(R.string.discard);
|
toolbar.setNavigationContentDescription(R.string.discard);
|
||||||
@@ -1073,7 +1243,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
);
|
);
|
||||||
|
|
||||||
name.setVisibility(View.GONE);
|
name.setVisibility(View.GONE);
|
||||||
username.setVisibility(View.GONE);
|
rolesView.setVisibility(View.GONE);
|
||||||
|
usernameWrap.setVisibility(View.GONE);
|
||||||
bio.setVisibility(View.GONE);
|
bio.setVisibility(View.GONE);
|
||||||
countersLayout.setVisibility(View.GONE);
|
countersLayout.setVisibility(View.GONE);
|
||||||
|
|
||||||
@@ -1121,7 +1292,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
nameEditWrap.setVisibility(View.GONE);
|
nameEditWrap.setVisibility(View.GONE);
|
||||||
bioEditWrap.setVisibility(View.GONE);
|
bioEditWrap.setVisibility(View.GONE);
|
||||||
name.setVisibility(View.VISIBLE);
|
name.setVisibility(View.VISIBLE);
|
||||||
username.setVisibility(View.VISIBLE);
|
rolesView.setVisibility(View.VISIBLE);
|
||||||
|
usernameWrap.setVisibility(View.VISIBLE);
|
||||||
bio.setVisibility(View.VISIBLE);
|
bio.setVisibility(View.VISIBLE);
|
||||||
countersLayout.setVisibility(View.VISIBLE);
|
countersLayout.setVisibility(View.VISIBLE);
|
||||||
refreshLayout.setEnabled(true);
|
refreshLayout.setEnabled(true);
|
||||||
@@ -1133,6 +1305,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
|
imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
|
||||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||||
bindHeaderView();
|
bindHeaderView();
|
||||||
|
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAndExitEditMode(){
|
private void saveAndExitEditMode(){
|
||||||
@@ -1147,7 +1320,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
savingEdits=false;
|
savingEdits=false;
|
||||||
account=result;
|
account=result;
|
||||||
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
exitEditMode();
|
exitEditMode();
|
||||||
setActionProgressVisible(false);
|
setActionProgressVisible(false);
|
||||||
}
|
}
|
||||||
@@ -1162,18 +1335,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmToggleMuted(){
|
|
||||||
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void confirmToggleBlocked(){
|
|
||||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void confirmSoftBlockUser(){
|
|
||||||
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRelationship(Relationship r){
|
private void updateRelationship(Relationship r){
|
||||||
relationship=r;
|
relationship=r;
|
||||||
updateRelationship();
|
updateRelationship();
|
||||||
@@ -1220,7 +1381,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(ava==null)
|
if(ava==null)
|
||||||
return;
|
return;
|
||||||
int radius=V.dp(25);
|
int radius=V.dp(25);
|
||||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
|
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : account.avatar, ava), 0,
|
||||||
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
|
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1292,9 +1453,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
FrameLayout view=tabViews[viewType];
|
FrameLayout view=new FrameLayout(parent.getContext());
|
||||||
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
|
|
||||||
view.setVisibility(View.VISIBLE);
|
|
||||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
return new SimpleViewHolder(view);
|
return new SimpleViewHolder(view);
|
||||||
}
|
}
|
||||||
@@ -1302,8 +1461,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||||
Fragment fragment=getFragmentForPage(position);
|
Fragment fragment=getFragmentForPage(position);
|
||||||
|
FrameLayout fragmentView=tabViews[position];
|
||||||
|
fragmentView.setVisibility(View.VISIBLE);
|
||||||
|
if(fragmentView.getParent() instanceof ViewGroup parent)
|
||||||
|
parent.removeView(fragmentView);
|
||||||
|
((FrameLayout)holder.itemView).addView(fragmentView);
|
||||||
if(!fragment.isAdded()){
|
if(!fragment.isAdded()){
|
||||||
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
|
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit();
|
||||||
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreDraw(){
|
public boolean onPreDraw(){
|
||||||
@@ -1385,7 +1549,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
if(isInEditMode){
|
if(isInEditMode){
|
||||||
int size=fields.size();
|
int size=fields.size();
|
||||||
if(size<MAX_FIELDS)
|
if(size<maxFields)
|
||||||
size++;
|
size++;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
@@ -1421,16 +1585,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder {
|
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder{
|
||||||
private final TextView title;
|
private final TextView title;
|
||||||
private final LinkedTextView value;
|
private final LinkedTextView value;
|
||||||
// private final ImageView verifiedIcon;
|
|
||||||
|
|
||||||
public AboutViewHolder(){
|
public AboutViewHolder(){
|
||||||
super(R.layout.item_profile_about);
|
super(R.layout.item_profile_about);
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
value=findViewById(R.id.value);
|
value=findViewById(R.id.value);
|
||||||
// verifiedIcon=findViewById(R.id.verified_icon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1438,7 +1600,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
super.onBind(item);
|
super.onBind(item);
|
||||||
title.setText(item.parsedName);
|
title.setText(item.parsedName);
|
||||||
value.setText(item.parsedValue);
|
value.setText(item.parsedValue);
|
||||||
// verifiedIcon.setVisibility(item.verifiedAt!=null ? View.VISIBLE : View.GONE);
|
if(item.verifiedAt!=null){
|
||||||
|
int textColor=UiUtils.getThemeColor(getContext(), R.attr.colorM3Success);
|
||||||
|
value.setTextColor(textColor);
|
||||||
|
value.setLinkTextColor(textColor);
|
||||||
|
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate();
|
||||||
|
check.setTint(textColor);
|
||||||
|
value.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, check, null);
|
||||||
|
}else{
|
||||||
|
value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||||
|
value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent));
|
||||||
|
value.setCompoundDrawables(null, null, null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1510,7 +1683,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
fields.add(new AccountField());
|
fields.add(new AccountField());
|
||||||
if(fields.size()==MAX_FIELDS){ // replace this row with new row
|
if(fields.size()==maxFields){ // replace this row with new row
|
||||||
adapter.notifyItemChanged(fields.size()-1);
|
adapter.notifyItemChanged(fields.size()-1);
|
||||||
}else{
|
}else{
|
||||||
adapter.notifyItemInserted(fields.size()-1);
|
adapter.notifyItemInserted(fields.size()-1);
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
@@ -26,6 +29,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
||||||
private String nextMaxID;
|
private String nextMaxID;
|
||||||
@@ -80,7 +84,10 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, true, null);
|
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, null,
|
||||||
|
StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS |
|
||||||
|
StatusDisplayItem.FLAG_NO_FOOTER |
|
||||||
|
StatusDisplayItem.FLAG_NO_TRANSLATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -112,6 +119,15 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
|
super.onShown();
|
||||||
|
// because, for some reason, when navigating back from compose fragment,
|
||||||
|
// match_parent would otherwise be incorrect (leaving a gap for the keyboard
|
||||||
|
// where there is none)
|
||||||
|
list.post(list::requestLayout);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
|
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
|
||||||
@@ -122,7 +138,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
nextMaxID=null;
|
nextMaxID=null;
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
onDataLoaded(result, nextMaxID!=null);
|
onDataLoaded(result, nextMaxID!=null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -183,6 +199,21 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
|
if(contentView!=null){
|
||||||
|
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||||
|
int insetBottom=insets.getSystemWindowInsetBottom();
|
||||||
|
((ViewGroup.MarginLayoutParams) list.getLayoutParams()).bottomMargin=insetBottom;
|
||||||
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+insetBottom;
|
||||||
|
insets=insets.inset(0, 0, 0, insetBottom);
|
||||||
|
}else{
|
||||||
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onApplyWindowInsets(insets);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
// TODO: adapt when frontends finally implement a scheduled posts list
|
// TODO: adapt when frontends finally implement a scheduled posts list
|
||||||
|
|||||||
@@ -47,13 +47,12 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
private ProgressBarButton defaultServerButton;
|
private ProgressBarButton defaultServerButton;
|
||||||
private ProgressBar defaultServerProgress;
|
private ProgressBar defaultServerProgress;
|
||||||
private String chosenDefaultServer=DEFAULT_SERVER;
|
private String chosenDefaultServer=DEFAULT_SERVER;
|
||||||
private boolean loadingDefaultServer;
|
private boolean loadingDefaultServer, loadedDefaultServer;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
||||||
loadAndChooseDefaultServer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -101,6 +100,8 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if(!loadedDefaultServer && !loadingDefaultServer)
|
||||||
|
loadAndChooseDefaultServer();
|
||||||
|
|
||||||
return contentView;
|
return contentView;
|
||||||
}
|
}
|
||||||
@@ -239,6 +240,7 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
private void setChosenDefaultServer(String domain){
|
private void setChosenDefaultServer(String domain){
|
||||||
chosenDefaultServer=domain;
|
chosenDefaultServer=domain;
|
||||||
loadingDefaultServer=false;
|
loadingDefaultServer=false;
|
||||||
|
loadedDefaultServer=true;
|
||||||
if(defaultServerButton!=null && getActivity()!=null){
|
if(defaultServerButton!=null && getActivity()!=null){
|
||||||
defaultServerButton.setTextVisible(true);
|
defaultServerButton.setTextVisible(true);
|
||||||
defaultServerProgress.setVisibility(View.GONE);
|
defaultServerProgress.setVisibility(View.GONE);
|
||||||
|
|||||||
@@ -9,20 +9,25 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import name.fraser.neil.plaintext.diff_match_patch;
|
||||||
|
|
||||||
public class StatusEditHistoryFragment extends StatusListFragment{
|
public class StatusEditHistoryFragment extends StatusListFragment{
|
||||||
private String id, url;
|
private String id, url;
|
||||||
@@ -47,8 +52,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
|
if(getActivity()==null) return;
|
||||||
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
|
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
|
||||||
if (getActivity() == null) return;
|
|
||||||
onDataLoaded(result, false);
|
onDataLoaded(result, false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -57,7 +62,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET);
|
List<StatusDisplayItem> items=new ArrayList<>();
|
||||||
int idx=data.indexOf(s);
|
int idx=data.indexOf(s);
|
||||||
if(idx>=0){
|
if(idx>=0){
|
||||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||||
@@ -82,8 +87,11 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class);
|
EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class);
|
||||||
Status prev=data.get(idx+1);
|
Status prev=data.get(idx+1);
|
||||||
|
|
||||||
if(!Objects.equals(s.content, prev.content)){
|
// if only formatting was changed, don't even try to create a diff text
|
||||||
|
if(!Objects.equals(HtmlParser.text(s.content), HtmlParser.text(prev.content))){
|
||||||
changes.add(StatusEditChangeType.TEXT_CHANGED);
|
changes.add(StatusEditChangeType.TEXT_CHANGED);
|
||||||
|
//update status content to display a diffs
|
||||||
|
s.content=createDiffText(prev.content, s.content);
|
||||||
}
|
}
|
||||||
if(!Objects.equals(s.spoilerText, prev.spoilerText)){
|
if(!Objects.equals(s.spoilerText, prev.spoilerText)){
|
||||||
if(s.spoilerText==null){
|
if(s.spoilerText==null){
|
||||||
@@ -143,17 +151,13 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String sep = getString(R.string.sk_separator);
|
String sep = getString(R.string.sk_separator);
|
||||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
|
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s));
|
||||||
|
items.add(1, new DummyStatusDisplayItem(s.id, this));
|
||||||
}
|
}
|
||||||
|
items.addAll(StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER|StatusDisplayItem.FLAG_INSET|StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS));
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isItemEnabled(String id){
|
public boolean isItemEnabled(String id){
|
||||||
return false;
|
return false;
|
||||||
@@ -168,4 +172,28 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
return Uri.parse(url);
|
return Uri.parse(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String createDiffText(String original, String modified) {
|
||||||
|
diff_match_patch dmp=new diff_match_patch();
|
||||||
|
LinkedList<diff_match_patch.Diff> diffs=dmp.diff_main(original, modified);
|
||||||
|
dmp.diff_cleanupSemantic(diffs);
|
||||||
|
|
||||||
|
StringBuilder stringBuilder=new StringBuilder();
|
||||||
|
for(diff_match_patch.Diff diff : diffs){
|
||||||
|
switch(diff.operation){
|
||||||
|
case DELETE->{
|
||||||
|
stringBuilder.append("<edit-diff-delete>");
|
||||||
|
stringBuilder.append(diff.text);
|
||||||
|
stringBuilder.append("</edit-diff-delete>");
|
||||||
|
}
|
||||||
|
case INSERT->{
|
||||||
|
stringBuilder.append("<edit-diff-insert>");
|
||||||
|
stringBuilder.append(diff.text);
|
||||||
|
stringBuilder.append("</edit-diff-insert>");
|
||||||
|
}
|
||||||
|
default->stringBuilder.append(diff.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ShowEmojiReactions.ONLY_OPENED;
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
@@ -7,8 +9,12 @@ import com.squareup.otto.Subscribe;
|
|||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.api.CacheController;
|
||||||
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.events.ReblogDeletedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
@@ -16,13 +22,21 @@ import org.joinmastodon.android.events.StatusDeletedEvent;
|
|||||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -33,9 +47,16 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
protected EventListener eventListener=new EventListener();
|
protected EventListener eventListener=new EventListener();
|
||||||
|
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
|
boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id);
|
||||||
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
|
int flags = 0;
|
||||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), addFooter ? 0 : StatusDisplayItem.FLAG_NO_FOOTER);
|
AccountLocalPreferences lp=getLocalPrefs();
|
||||||
|
if(GlobalUserPreferences.spectatorMode)
|
||||||
|
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
|
||||||
|
if(!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
|
||||||
|
flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS;
|
||||||
|
if(GlobalUserPreferences.translateButtonOpenedOnly)
|
||||||
|
flags |= StatusDisplayItem.FLAG_NO_TRANSLATE;
|
||||||
|
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract FilterContext getFilterContext();
|
protected abstract FilterContext getFilterContext();
|
||||||
@@ -63,8 +84,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
@Override
|
@Override
|
||||||
public void onItemClick(String id){
|
public void onItemClick(String id){
|
||||||
Status status=getContentStatusByID(id);
|
Status status=getContentStatusByID(id);
|
||||||
if(status==null)
|
if(status==null || status.preview) return;
|
||||||
return;
|
|
||||||
status.filterRevealed=true;
|
status.filterRevealed=true;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -129,12 +149,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Status getContentStatusByID(String id){
|
public Status getContentStatusByID(String id){
|
||||||
Status s=getStatusByID(id);
|
Status s=getStatusByID(id);
|
||||||
return s==null ? null : s.getContentStatus();
|
return s==null ? null : s.getContentStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Status getStatusByID(String id){
|
public Status getStatusByID(String id){
|
||||||
for(Status s:data){
|
for(Status s:data){
|
||||||
if(s.id.equals(id)){
|
if(s.id.equals(id)){
|
||||||
return s;
|
return s;
|
||||||
@@ -161,41 +181,73 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void removeStatus(Status status){
|
private boolean removeStatusDisplayItems(String parentID, int firstIndex, int ancestorFirstIndex, int ancestorLastIndex){
|
||||||
data.remove(status);
|
|
||||||
preloadedData.remove(status);
|
|
||||||
int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
|
|
||||||
for(int i=0;i<displayItems.size();i++){
|
|
||||||
StatusDisplayItem item = displayItems.get(i);
|
|
||||||
if(status.id.equals(item.parentID)){
|
|
||||||
index=i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (item.parentID.equals(status.inReplyToId)) {
|
|
||||||
if (ancestorFirstIndex == -1) ancestorFirstIndex = i;
|
|
||||||
ancestorLastIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// did we find an ancestor that is also the status' neighbor?
|
// did we find an ancestor that is also the status' neighbor?
|
||||||
if (ancestorFirstIndex >= 0 && ancestorLastIndex == index - 1) {
|
if(ancestorFirstIndex>=0 && ancestorLastIndex==firstIndex-1){
|
||||||
for (int i = ancestorFirstIndex; i <= ancestorLastIndex; i++) {
|
// update ancestor to have no descendant anymore
|
||||||
StatusDisplayItem item = displayItems.get(i);
|
displayItems.subList(ancestorFirstIndex, ancestorLastIndex+1).forEach(i->i.hasDescendantNeighbor=false);
|
||||||
// update ancestor to have no descendant anymore
|
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex-ancestorFirstIndex+1);
|
||||||
if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
|
|
||||||
}
|
|
||||||
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(index==-1)
|
if(firstIndex==-1) return false;
|
||||||
return;
|
int lastIndex=firstIndex;
|
||||||
int lastIndex;
|
while(lastIndex<displayItems.size()){
|
||||||
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
StatusDisplayItem item=displayItems.get(lastIndex);
|
||||||
if(!displayItems.get(lastIndex).parentID.equals(status.id))
|
if(!item.parentID.equals(parentID) || item instanceof GapStatusDisplayItem) break;
|
||||||
break;
|
lastIndex++;
|
||||||
}
|
}
|
||||||
displayItems.subList(index, lastIndex).clear();
|
int count=lastIndex-firstIndex;
|
||||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
if(count<1) return false;
|
||||||
|
displayItems.subList(firstIndex, lastIndex).clear();
|
||||||
|
adapter.notifyItemRangeRemoved(firstIndex, count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeStatus(Status status){
|
||||||
|
final AccountSessionManager asm=AccountSessionManager.getInstance();
|
||||||
|
final CacheController cache=AccountSessionManager.get(accountID).getCacheController();
|
||||||
|
final boolean unReblogging=status.reblog!=null && asm.isSelf(accountID, status.account);
|
||||||
|
final Predicate<Status> isToBeRemovedReblog=item->item!=null && item.reblog!=null
|
||||||
|
&& item.reblog.id.equals(status.reblog.id)
|
||||||
|
&& asm.isSelf(accountID, item.account);
|
||||||
|
final BiPredicate<String, Supplier<String>> isToBeRemovedContent=(parentId, contentIdSupplier)->
|
||||||
|
parentId.equals(status.id) || contentIdSupplier.get().equals(status.id);
|
||||||
|
|
||||||
|
int ancestorFirstIndex=-1, ancestorLastIndex=-1;
|
||||||
|
for(int i=0;i<displayItems.size();i++){
|
||||||
|
StatusDisplayItem item=displayItems.get(i);
|
||||||
|
// we found a status that the to-be-removed status replies to!
|
||||||
|
// storing indices to maybe update its display items
|
||||||
|
if(item.parentID.equals(status.inReplyToId)){
|
||||||
|
if(ancestorFirstIndex==-1) ancestorFirstIndex=i;
|
||||||
|
ancestorLastIndex=i;
|
||||||
|
}
|
||||||
|
// if we're un-reblogging, we compare the reblogged status's id with the current status's
|
||||||
|
if(unReblogging
|
||||||
|
? isToBeRemovedReblog.test(getStatusByID(item.parentID))
|
||||||
|
: isToBeRemovedContent.test(item.parentID, item::getContentStatusID)){
|
||||||
|
// if statuses are removed from index i, the next iteration should be on the same index again
|
||||||
|
if(removeStatusDisplayItems(item.parentID, i, ancestorFirstIndex, ancestorLastIndex)) i--;
|
||||||
|
// resetting in case we find another occurrence of the same status that also has ancestors
|
||||||
|
// (we won't - unless the timeline is being especially weird)
|
||||||
|
ancestorFirstIndex=-1; ancestorLastIndex=-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Consumer<List<Status>> removeStatusFromData=(list)->{
|
||||||
|
Iterator<Status> it=list.iterator();
|
||||||
|
while(it.hasNext()){
|
||||||
|
Status s=it.next();
|
||||||
|
if(unReblogging
|
||||||
|
? isToBeRemovedReblog.test(s)
|
||||||
|
: isToBeRemovedContent.test(s.id, s::getContentStatusID)){
|
||||||
|
it.remove();
|
||||||
|
cache.deleteStatus(s.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
removeStatusFromData.accept(data);
|
||||||
|
removeStatusFromData.accept(preloadedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -230,6 +282,30 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||||
|
for(Status s:data){
|
||||||
|
if(s.getContentStatus().id.equals(ev.id)){
|
||||||
|
s.getContentStatus().update(ev);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||||
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
|
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
|
||||||
|
reactions.rebind();
|
||||||
|
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
|
||||||
|
text.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Status s:preloadedData){
|
||||||
|
if(s.getContentStatus().id.equals(ev.id)){
|
||||||
|
s.getContentStatus().update(ev);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onStatusDeleted(StatusDeletedEvent ev){
|
public void onStatusDeleted(StatusDeletedEvent ev){
|
||||||
if(!ev.accountID.equals(accountID))
|
if(!ev.accountID.equals(accountID))
|
||||||
@@ -240,6 +316,22 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
removeStatus(status);
|
removeStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onReblogDeleted(ReblogDeletedEvent ev){
|
||||||
|
AccountSessionManager asm=AccountSessionManager.getInstance();
|
||||||
|
if(!ev.accountID.equals(accountID))
|
||||||
|
return;
|
||||||
|
for(Status item : data){
|
||||||
|
boolean itemIsOwnReblog=item.reblog!=null
|
||||||
|
&& item.getContentStatusID().equals(ev.statusID)
|
||||||
|
&& asm.isSelf(accountID, item.account);
|
||||||
|
if(itemIsOwnReblog){
|
||||||
|
removeStatus(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onStatusCreated(StatusCreatedEvent ev){
|
public void onStatusCreated(StatusCreatedEvent ev){
|
||||||
if(!ev.accountID.equals(accountID))
|
if(!ev.accountID.equals(accountID))
|
||||||
@@ -260,6 +352,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
Status contentStatus=status.getContentStatus();
|
Status contentStatus=status.getContentStatus();
|
||||||
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
|
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
|
||||||
updatePoll(status.id, contentStatus, ev.poll);
|
updatePoll(status.id, contentStatus, ev.poll);
|
||||||
|
AccountSessionManager.get(accountID).getCacheController().updateStatus(contentStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
|
||||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
@@ -24,13 +24,13 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
|
|||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
@@ -50,21 +50,25 @@ import me.grishka.appkit.api.SimpleCallback;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
||||||
protected Status mainStatus, updatedStatus;
|
protected Status mainStatus, updatedStatus, replyTo;
|
||||||
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
|
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
|
||||||
private StatusContext result;
|
private StatusContext result;
|
||||||
protected boolean contextInitiallyRendered, transitionFinished;
|
protected boolean contextInitiallyRendered, transitionFinished, preview;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mainStatus=Parcels.unwrap(getArguments().getParcelable("status"));
|
mainStatus=Parcels.unwrap(getArguments().getParcelable("status"));
|
||||||
|
replyTo=Parcels.unwrap(getArguments().getParcelable("inReplyTo"));
|
||||||
Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount"));
|
Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount"));
|
||||||
|
refreshing=contextInitiallyRendered=getArguments().getBoolean("refresh", false);
|
||||||
if(inReplyToAccount!=null)
|
if(inReplyToAccount!=null)
|
||||||
knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
|
knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
|
||||||
data.add(mainStatus);
|
data.add(mainStatus);
|
||||||
onAppendItems(Collections.singletonList(mainStatus));
|
onAppendItems(Collections.singletonList(mainStatus));
|
||||||
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
|
preview=mainStatus.preview;
|
||||||
|
if(preview) setRefreshEnabled(false);
|
||||||
|
setTitle(preview ? getString(R.string.sk_post_preview) : HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
|
||||||
transitionFinished = getArguments().getBoolean("noTransition", false);
|
transitionFinished = getArguments().getBoolean("noTransition", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +109,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
text.textSelectable=true;
|
text.textSelectable=true;
|
||||||
else if(item instanceof FooterStatusDisplayItem footer)
|
else if(item instanceof FooterStatusDisplayItem footer)
|
||||||
footer.hideCounts=true;
|
footer.hideCounts=true;
|
||||||
|
else if(item instanceof SpoilerStatusDisplayItem spoiler){
|
||||||
|
for(StatusDisplayItem subItem:spoiler.contentItems){
|
||||||
|
if(subItem instanceof TextStatusDisplayItem text)
|
||||||
|
text.textSelectable=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,11 +133,21 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
if (refreshing) loadMainStatus();
|
if(preview && replyTo==null){
|
||||||
currentRequest=new GetStatusContext(mainStatus.id)
|
result=new StatusContext();
|
||||||
|
result.descendants=Collections.emptyList();
|
||||||
|
result.ancestors=Collections.emptyList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(refreshing && !preview) loadMainStatus();
|
||||||
|
currentRequest=new GetStatusContext(preview ? replyTo.id : mainStatus.id)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(StatusContext result){
|
public void onSuccess(StatusContext result){
|
||||||
|
if(preview){
|
||||||
|
result.descendants=Collections.emptyList();
|
||||||
|
result.ancestors.add(replyTo);
|
||||||
|
}
|
||||||
ThreadFragment.this.result = result;
|
ThreadFragment.this.result = result;
|
||||||
maybeApplyContext();
|
maybeApplyContext();
|
||||||
}
|
}
|
||||||
@@ -188,8 +208,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
// TODO: figure out how this code works
|
// TODO: figure out how this code works
|
||||||
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||||
|
|
||||||
result.descendants=filterStatuses(result.descendants);
|
filterStatuses(result.descendants);
|
||||||
result.ancestors=filterStatuses(result.ancestors);
|
filterStatuses(result.ancestors);
|
||||||
restoreStatusStates(result.descendants, oldData);
|
restoreStatusStates(result.descendants, oldData);
|
||||||
restoreStatusStates(result.ancestors, oldData);
|
restoreStatusStates(result.ancestors, oldData);
|
||||||
|
|
||||||
@@ -325,11 +345,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> filterStatuses(List<Status> statuses){
|
private void filterStatuses(List<Status> statuses){
|
||||||
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext());
|
AccountSessionManager.get(accountID).filterStatuses(statuses, getFilterContext());
|
||||||
return statuses.stream()
|
|
||||||
.filter(statusFilterPredicate)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -417,6 +434,10 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Status getMainStatus(){
|
||||||
|
return mainStatus;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isItemEnabled(String id){
|
public boolean isItemEnabled(String id){
|
||||||
return !id.equals(mainStatus.id) || !mainStatus.filterRevealed;
|
return !id.equals(mainStatus.id) || !mainStatus.filterRevealed;
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
|
|||||||
for(Relationship rel:result){
|
for(Relationship rel:result){
|
||||||
relationships.put(rel.id, rel);
|
relationships.put(rel.id, rel);
|
||||||
}
|
}
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
if(list==null)
|
if(list==null)
|
||||||
return;
|
return;
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetAccountBlocks;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
|
|
||||||
|
public class BlockedAccountsListFragment extends AccountRelatedAccountListFragment{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.sk_blocked_accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
|
return new GetAccountBlocks(maxID, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||||
|
super.onConfigureViewHolder(holder);
|
||||||
|
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return super.getWebUri(base).buildUpon()
|
||||||
|
.appendPath("/blocks").build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
|||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
refreshing=true;
|
refreshing=true;
|
||||||
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
|
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetAccountMutes;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
|
|
||||||
|
public class MutedAccountsListFragment extends AccountRelatedAccountListFragment{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setTitle(R.string.sk_muted_accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
|
return new GetAccountMutes(maxID, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||||
|
super.onConfigureViewHolder(holder);
|
||||||
|
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return super.getWebUri(base).buildUpon()
|
||||||
|
.appendPath("/mutes").build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -132,10 +133,14 @@ public abstract class PaginatedAccountListFragment<T> extends BaseAccountListFra
|
|||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
nextMaxID=null;
|
nextMaxID=null;
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
List<AccountViewModel> items = result.stream()
|
List<AccountViewModel> items = result.stream()
|
||||||
.filter(a -> d.size() > 1000 || d.stream()
|
.filter(a -> d.size() > 1000 || d.stream()
|
||||||
.noneMatch(i -> i.account.url.equals(a.url)))
|
.noneMatch(i -> i.account.url.equals(a.url)))
|
||||||
|
.peek(account ->{
|
||||||
|
if (account.getDomainFromURL().equals(getRemoteDomain()))
|
||||||
|
account.acct=account.getFullyQualifiedName();
|
||||||
|
})
|
||||||
.map(a->new AccountViewModel(a, accountID))
|
.map(a->new AccountViewModel(a, accountID))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.PleromaGetStatusReactions;
|
||||||
|
import org.joinmastodon.android.model.Emoji;
|
||||||
|
import org.joinmastodon.android.model.EmojiReaction;
|
||||||
|
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
|
public class StatusEmojiReactionsListFragment extends BaseAccountListFragment {
|
||||||
|
private String id;
|
||||||
|
private String emojiName;
|
||||||
|
private String url;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
id = getArguments().getString("statusID");
|
||||||
|
emojiName = getArguments().getString("emoji");
|
||||||
|
url = getArguments().getString("url");
|
||||||
|
count = getArguments().getInt("count");
|
||||||
|
|
||||||
|
SpannableStringBuilder title = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.sk_users_reacted_with, count,
|
||||||
|
count, url == null ? emojiName : ":"+emojiName+":"));
|
||||||
|
if (url != null) {
|
||||||
|
Emoji emoji = new Emoji();
|
||||||
|
emoji.shortcode = emojiName;
|
||||||
|
emoji.url = url;
|
||||||
|
HtmlParser.parseCustomEmoji(title, Collections.singletonList(emoji));
|
||||||
|
}
|
||||||
|
setTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
if (url != null) {
|
||||||
|
UiUtils.loadCustomEmojiInTextView(toolbarTitleView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dataLoaded() {
|
||||||
|
super.dataLoaded();
|
||||||
|
footerProgress.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
currentRequest = new PleromaGetStatusReactions(id, emojiName)
|
||||||
|
.setCallback(new SimpleCallback<>(StatusEmojiReactionsListFragment.this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<EmojiReaction> result) {
|
||||||
|
if (getActivity() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<AccountViewModel> items = result.get(0).accounts.stream()
|
||||||
|
.map(a -> new AccountViewModel(a, accountID))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
onDataLoaded(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
super.onError(error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume(){
|
||||||
|
super.onResume();
|
||||||
|
if(!loaded && !dataLoading)
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,21 +6,19 @@ import android.os.Bundle;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
|
||||||
public class BubbleTimelineFragment extends StatusListFragment {
|
public class BubbleTimelineFragment extends StatusListFragment {
|
||||||
private DiscoverInfoBannerHelper bannerHelper;
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
private String maxID;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -36,15 +34,15 @@ public class BubbleTimelineFragment extends StatusListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetBubbleTimeline(refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetBubbleTimeline(getMaxID(), count, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(!result.isEmpty())
|
if(getActivity()==null) return;
|
||||||
maxID=result.get(result.size()-1).id;
|
boolean more=applyMaxID(result);
|
||||||
if (getActivity() == null) return;
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
onDataLoaded(result, more);
|
||||||
onDataLoaded(result, !result.isEmpty());
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.graphics.Rect;
|
|||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -16,6 +17,7 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
@@ -64,6 +66,8 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
|||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
|
setRetainInstance(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -76,7 +80,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<FollowSuggestion> result){
|
public void onSuccess(List<FollowSuggestion> result){
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
|
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
|
||||||
loadRelationships();
|
loadRelationships();
|
||||||
}
|
}
|
||||||
@@ -111,7 +115,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
|||||||
public void onSuccess(List<Relationship> result){
|
public void onSuccess(List<Relationship> result){
|
||||||
relationshipsRequest=null;
|
relationshipsRequest=null;
|
||||||
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
||||||
if (getActivity() == null) return;
|
if(getActivity()==null) return;
|
||||||
if(list==null)
|
if(list==null)
|
||||||
return;
|
return;
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
@@ -197,7 +201,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.DisableableClickable{
|
||||||
private final ImageView cover, avatar;
|
private final ImageView cover, avatar;
|
||||||
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
|
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
|
||||||
private final ProgressBarButton actionButton;
|
private final ProgressBarButton actionButton;
|
||||||
@@ -223,13 +227,22 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
|||||||
actionProgress=findViewById(R.id.action_progress);
|
actionProgress=findViewById(R.id.action_progress);
|
||||||
actionWrap=findViewById(R.id.action_btn_wrap);
|
actionWrap=findViewById(R.id.action_btn_wrap);
|
||||||
|
|
||||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(6));
|
avatar.setOutlineProvider(OutlineProviders.roundedRect(15));
|
||||||
itemView.setClipToOutline(true);
|
|
||||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
|
||||||
avatar.setClipToOutline(true);
|
avatar.setClipToOutline(true);
|
||||||
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
|
View border=findViewById(R.id.avatar_border);
|
||||||
|
border.setOutlineProvider(OutlineProviders.roundedRect(17));
|
||||||
|
border.setClipToOutline(true);
|
||||||
|
cover.setOutlineProvider(OutlineProviders.roundedRect(9));
|
||||||
cover.setClipToOutline(true);
|
cover.setClipToOutline(true);
|
||||||
|
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||||
|
itemView.setClipToOutline(true);
|
||||||
actionButton.setOnClickListener(this::onActionButtonClick);
|
actionButton.setOnClickListener(this::onActionButtonClick);
|
||||||
|
itemView.setOnClickListener(v->this.onClick());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(){
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -242,12 +255,14 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
|||||||
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
|
||||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||||
postsLabel.setText(getResources().getQuantityString(R.plurals.x_posts, (int)(item.account.statusesCount%1000), item.account.statusesCount));
|
postsLabel.setText(getResources().getQuantityString(R.plurals.sk_posts_count_label, (int)(item.account.statusesCount%1000), item.account.statusesCount));
|
||||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
relationship=relationships.get(item.account.id);
|
relationship=relationships.get(item.account.id);
|
||||||
|
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account);
|
||||||
|
|
||||||
if(relationship==null){
|
if(relationship==null){
|
||||||
actionWrap.setVisibility(View.GONE);
|
actionWrap.setVisibility(View.GONE);
|
||||||
}else{
|
}else{
|
||||||
@@ -308,15 +323,16 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
|||||||
|
|
||||||
public AccountWrapper(Account account){
|
public AccountWrapper(Account account){
|
||||||
this.account=account;
|
this.account=account;
|
||||||
if(!TextUtils.isEmpty(account.avatar))
|
avaRequest=new UrlImageLoaderRequest(
|
||||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.getInstance().getAccount(accountID).getDefaultAvatarUrl() : account.avatar,
|
||||||
|
V.dp(50), V.dp(50));
|
||||||
if(!TextUtils.isEmpty(account.header))
|
if(!TextUtils.isEmpty(account.header))
|
||||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
if(account.emojis.isEmpty()){
|
if(account.emojis.isEmpty()){
|
||||||
parsedName=account.displayName;
|
parsedName= account.getDisplayName();
|
||||||
}else{
|
}else{
|
||||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis);
|
||||||
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
@@ -54,6 +56,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
private String accountID;
|
private String accountID;
|
||||||
private String currentQuery;
|
private String currentQuery;
|
||||||
|
|
||||||
|
private boolean disableDiscover;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -94,8 +98,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||||
@Override
|
@Override
|
||||||
public void onPageSelected(int position){
|
public void onPageSelected(int position){
|
||||||
if(position==0)
|
|
||||||
return;
|
|
||||||
Fragment _page=getFragmentForPage(position);
|
Fragment _page=getFragmentForPage(position);
|
||||||
if(_page instanceof BaseRecyclerFragment<?> page){
|
if(_page instanceof BaseRecyclerFragment<?> page){
|
||||||
if(!page.loaded && !page.isDataLoading())
|
if(!page.loaded && !page.isDataLoading())
|
||||||
@@ -155,6 +157,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
disableDiscover=AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false);
|
||||||
searchView=view.findViewById(R.id.search_fragment);
|
searchView=view.findViewById(R.id.search_fragment);
|
||||||
if(searchFragment==null){
|
if(searchFragment==null){
|
||||||
searchFragment=new SearchFragment();
|
searchFragment=new SearchFragment();
|
||||||
@@ -170,8 +173,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
searchBack.setOnClickListener(v->{
|
searchBack.setOnClickListener(v->{
|
||||||
if(searchActive) exitSearch(); else openSearch();
|
if(searchActive) exitSearch(); else openSearch();
|
||||||
});
|
});
|
||||||
if(searchActive){
|
if(searchActive) searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
|
||||||
searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
|
else searchBack.setEnabled(false);
|
||||||
|
if(searchActive || disableDiscover){
|
||||||
pager.setVisibility(View.GONE);
|
pager.setVisibility(View.GONE);
|
||||||
tabLayout.setVisibility(View.GONE);
|
tabLayout.setVisibility(View.GONE);
|
||||||
searchView.setVisibility(View.VISIBLE);
|
searchView.setVisibility(View.VISIBLE);
|
||||||
@@ -211,8 +215,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadData(){
|
public void loadData(){
|
||||||
if(postsFragment!=null && !postsFragment.loaded && !postsFragment.dataLoading)
|
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
|
||||||
postsFragment.loadData();
|
hashtagsFragment.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enterSearch(){
|
private void enterSearch(){
|
||||||
@@ -232,15 +236,18 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
if(!searchActive)
|
if(!searchActive)
|
||||||
return;
|
return;
|
||||||
searchActive=false;
|
searchActive=false;
|
||||||
pager.setVisibility(View.VISIBLE);
|
|
||||||
tabLayout.setVisibility(View.VISIBLE);
|
|
||||||
searchView.setVisibility(View.GONE);
|
|
||||||
searchText.setText(R.string.sk_search_fediverse);
|
searchText.setText(R.string.sk_search_fediverse);
|
||||||
searchBack.setImageResource(R.drawable.ic_fluent_search_24_regular);
|
searchBack.setImageResource(R.drawable.ic_fluent_search_24_regular);
|
||||||
searchBack.setEnabled(false);
|
searchBack.setEnabled(false);
|
||||||
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||||
tabsDivider.setVisibility(View.VISIBLE);
|
|
||||||
currentQuery=null;
|
currentQuery=null;
|
||||||
|
searchFragment.clear();
|
||||||
|
|
||||||
|
if(disableDiscover) return;
|
||||||
|
pager.setVisibility(View.VISIBLE);
|
||||||
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
|
searchView.setVisibility(View.GONE);
|
||||||
|
tabsDivider.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Fragment getFragmentForPage(int page){
|
private Fragment getFragmentForPage(int page){
|
||||||
@@ -282,15 +289,19 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
FrameLayout view=tabViews[viewType];
|
FrameLayout view=new FrameLayout(parent.getContext());
|
||||||
((ViewGroup)view.getParent()).removeView(view);
|
|
||||||
view.setVisibility(View.VISIBLE);
|
|
||||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
return new SimpleViewHolder(view);
|
return new SimpleViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||||
|
FrameLayout view=tabViews[position];
|
||||||
|
if(view.getParent() instanceof ViewGroup parent)
|
||||||
|
parent.removeView(view);
|
||||||
|
view.setVisibility(View.VISIBLE);
|
||||||
|
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package org.joinmastodon.android.fragments.discover;
|
|||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@@ -14,14 +14,12 @@ import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
|||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Card;
|
import org.joinmastodon.android.model.Card;
|
||||||
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
import org.joinmastodon.android.model.viewmodel.CardViewModel;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -32,11 +30,9 @@ import me.grishka.appkit.api.SimpleCallback;
|
|||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.ListImageLoaderAdapter;
|
|
||||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||||
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
@@ -60,6 +56,8 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS, accountID);
|
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS, accountID);
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
|
setRetainInstance(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
@@ -17,6 +18,7 @@ import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|||||||
|
|
||||||
public class DiscoverPostsFragment extends StatusListFragment{
|
public class DiscoverPostsFragment extends StatusListFragment{
|
||||||
private DiscoverInfoBannerHelper bannerHelper;
|
private DiscoverInfoBannerHelper bannerHelper;
|
||||||
|
private int offset;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -25,12 +27,17 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int o, int count){
|
||||||
|
if(refreshing) offset=0;
|
||||||
currentRequest=new GetTrendingStatuses(offset, count)
|
currentRequest=new GetTrendingStatuses(offset, count)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
onDataLoaded(result, !result.isEmpty());
|
if(getActivity()==null) return;
|
||||||
|
boolean empty=result.isEmpty();
|
||||||
|
offset+=result.size();
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
||||||
|
onDataLoaded(result, !empty);
|
||||||
bannerHelper.onBannerBecameVisible();
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
|
|||||||
@@ -29,15 +29,14 @@ public class FederatedTimelineFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetPublicTimeline(false, false, getMaxID(), count, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(!result.isEmpty())
|
if(getActivity()==null) return;
|
||||||
maxID=result.get(result.size()-1).id;
|
boolean more=applyMaxID(result);
|
||||||
boolean empty=result.isEmpty();
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
onDataLoaded(result, more);
|
||||||
onDataLoaded(result, !empty);
|
|
||||||
bannerHelper.onBannerBecameVisible();
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package org.joinmastodon.android.fragments.discover;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
@@ -30,15 +29,14 @@ public class LocalTimelineFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetPublicTimeline(true, false, getMaxID(), count, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(!result.isEmpty())
|
if(getActivity()==null) return;
|
||||||
maxID=result.get(result.size()-1).id;
|
boolean more=applyMaxID(result);
|
||||||
boolean empty=result.isEmpty();
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
|
onDataLoaded(result, more);
|
||||||
onDataLoaded(result, !empty);
|
|
||||||
bannerHelper.onBannerBecameVisible();
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.os.Bundle;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
@@ -22,7 +23,6 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
@@ -30,12 +30,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||||
@@ -54,7 +55,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
setEmptyText(R.string.no_search_results);
|
setEmptyText(R.string.sk_recent_searches_placeholder);
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
args.putParcelable("profileAccount", Parcels.wrap(res.account));
|
args.putParcelable("profileAccount", Parcels.wrap(res.account));
|
||||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||||
}
|
}
|
||||||
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
|
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
|
||||||
case STATUS -> {
|
case STATUS -> {
|
||||||
Status status=res.status.getContentStatus();
|
Status status=res.status.getContentStatus();
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
@@ -112,7 +113,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int _offset, int count){
|
||||||
GetSearchResults.Type type;
|
GetSearchResults.Type type;
|
||||||
if(currentFilter.size()==1){
|
if(currentFilter.size()==1){
|
||||||
type=switch(currentFilter.iterator().next()){
|
type=switch(currentFilter.iterator().next()){
|
||||||
@@ -127,8 +128,22 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
dataLoaded();
|
dataLoaded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentRequest=new GetSearchResults(currentQuery, type, true)
|
String maxID=null;
|
||||||
.setCallback(new Callback<>(){
|
// TODO server-side bug
|
||||||
|
/*int offset=0;
|
||||||
|
if(_offset>0){
|
||||||
|
if(type==GetSearchResults.Type.STATUSES){
|
||||||
|
if(!preloadedData.isEmpty())
|
||||||
|
maxID=preloadedData.get(preloadedData.size()-1).status.id;
|
||||||
|
else if(!data.isEmpty())
|
||||||
|
maxID=data.get(data.size()-1).status.id;
|
||||||
|
}else{
|
||||||
|
offset=_offset;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
int offset=_offset;
|
||||||
|
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
ArrayList<SearchResult> results=new ArrayList<>();
|
ArrayList<SearchResult> results=new ArrayList<>();
|
||||||
@@ -141,23 +156,18 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
results.add(new SearchResult(tag));
|
results.add(new SearchResult(tag));
|
||||||
}
|
}
|
||||||
if(result.statuses!=null){
|
if(result.statuses!=null){
|
||||||
for(Status status:result.statuses)
|
Set<String> alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet());
|
||||||
results.add(new SearchResult(status));
|
for(Status status:result.statuses){
|
||||||
|
if(!alreadyLoadedStatuses.contains(status.id))
|
||||||
|
results.add(new SearchResult(status));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
prevDisplayItems=new ArrayList<>(displayItems);
|
||||||
unfilteredResults=results;
|
unfilteredResults=results;
|
||||||
onDataLoaded(filterSearchResults(results), false);
|
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
currentRequest=null;
|
|
||||||
Activity a=getActivity();
|
|
||||||
if(a==null)
|
|
||||||
return;
|
|
||||||
error.showToast(a);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.setTimeout(180000) // 3 minutes (searches can take a long time)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,13 +183,16 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setQuery(String q, SearchResult.Type filter){
|
public void setQuery(String q, SearchResult.Type filter){
|
||||||
if(q.isBlank())
|
if(q.isBlank()) {
|
||||||
|
setEmptyText(R.string.sk_recent_searches_placeholder);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
if(currentRequest!=null){
|
if(currentRequest!=null){
|
||||||
currentRequest.cancel();
|
currentRequest.cancel();
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
}
|
}
|
||||||
currentQuery=q;
|
currentQuery=q;
|
||||||
|
setEmptyText(R.string.no_search_results);
|
||||||
if(filter==null)
|
if(filter==null)
|
||||||
currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
||||||
else
|
else
|
||||||
@@ -221,6 +234,13 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
data.clear();
|
||||||
|
preloadedData.clear();
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
V.setVisibilityAnimated(content, View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
Uri.Builder searchUri = base.path("/search");
|
Uri.Builder searchUri = base.path("/search");
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.graphics.Outline;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.LayerDrawable;
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -26,7 +25,7 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
@@ -38,19 +37,16 @@ import org.joinmastodon.android.model.SearchResults;
|
|||||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||||
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
|
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.SearchViewHelper;
|
import org.joinmastodon.android.ui.SearchViewHelper;
|
||||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||||
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -119,14 +115,14 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
onDataLoaded(results.stream().map(sr->{
|
onDataLoaded(results.stream().map(sr->{
|
||||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
|
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
|
||||||
if(sr.type==SearchResult.Type.HASHTAG){
|
if(sr.type==SearchResult.Type.HASHTAG){
|
||||||
vm.hashtagItem.onClick=()->openHashtag(sr);
|
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
||||||
}
|
}
|
||||||
return vm;
|
return vm;
|
||||||
}).collect(Collectors.toList()), false);
|
}).collect(Collectors.toList()), false);
|
||||||
recentsHeader.setVisible(!data.isEmpty());
|
recentsHeader.setVisible(!data.isEmpty());
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
currentRequest=new GetSearchResults(currentQuery, null, false)
|
currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0)
|
||||||
.limit(2)
|
.limit(2)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
@@ -136,7 +132,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
.map(sr->{
|
.map(sr->{
|
||||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
|
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
|
||||||
if(sr.type==SearchResult.Type.HASHTAG){
|
if(sr.type==SearchResult.Type.HASHTAG){
|
||||||
vm.hashtagItem.onClick=()->openHashtag(sr);
|
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
||||||
}
|
}
|
||||||
return vm;
|
return vm;
|
||||||
})
|
})
|
||||||
@@ -381,30 +377,72 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void openHashtag(SearchResult res){
|
private void openHashtag(SearchResult res){
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
|
wrapSuicideDialog(()->{
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
|
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isInRecentMode(){
|
private boolean isInRecentMode(){
|
||||||
return TextUtils.isEmpty(currentQuery);
|
return TextUtils.isEmpty(currentQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSearchViewEnter(){
|
private void wrapSuicideDialog(Runnable r){
|
||||||
deliverResult(currentQuery, null);
|
if(!GlobalUserPreferences.showSuicideHelp || currentQuery==null){
|
||||||
|
r.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] terms=getContext().getString(R.string.sk_suicide_search_terms).toLowerCase().split(",");
|
||||||
|
String query=currentQuery.trim().toLowerCase();
|
||||||
|
boolean termMatches=false;
|
||||||
|
for(String term : terms){
|
||||||
|
if(query.contains(term)){
|
||||||
|
termMatches=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!termMatches){
|
||||||
|
r.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url=getContext().getString(R.string.sk_suicide_helplines_url);
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_search_suicide_title)
|
||||||
|
.setMessage(R.string.sk_search_suicide_message)
|
||||||
|
.setNegativeButton(R.string.sk_do_not_show_again, (dialog, which)->{
|
||||||
|
GlobalUserPreferences.showSuicideHelp = false;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
r.run();
|
||||||
|
})
|
||||||
|
.setNeutralButton(R.string.sk_search_suicide_hotlines, (dialog, which)->UiUtils.launchWebBrowser(getContext(), url))
|
||||||
|
.setPositiveButton(R.string.ok, (dialog, which)->r.run())
|
||||||
|
.setOnDismissListener((dialog)->{})
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOpenURLClick(){
|
private void onSearchViewEnter(){
|
||||||
|
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty())
|
||||||
|
return;
|
||||||
|
wrapSuicideDialog(()->deliverResult(currentQuery, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onOpenURLClick(ListItem<?> item_){
|
||||||
UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false);
|
UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToHashtagClick(){
|
private void onGoToHashtagClick(ListItem<?> item_){
|
||||||
String q=searchViewHelper.getQuery();
|
wrapSuicideDialog(()->{
|
||||||
if(q.startsWith("#"))
|
String q=searchViewHelper.getQuery();
|
||||||
q=q.substring(1);
|
if(q.startsWith("#"))
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
|
q=q.substring(1);
|
||||||
|
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToAccountClick(){
|
private void onGoToAccountClick(ListItem<?> item_){
|
||||||
String q=searchViewHelper.getQuery();
|
String q=searchViewHelper.getQuery();
|
||||||
if(!q.startsWith("@")){
|
if(!q.startsWith("@")){
|
||||||
q="@"+q;
|
q="@"+q;
|
||||||
@@ -421,12 +459,12 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
}).ifPresent(progress -> progress.wrapProgress((Activity) getContext(), R.string.loading, true));
|
}).ifPresent(progress -> progress.wrapProgress((Activity) getContext(), R.string.loading, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToStatusSearchClick(){
|
private void onGoToStatusSearchClick(ListItem<?> item_){
|
||||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
|
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onGoToAccountSearchClick(){
|
private void onGoToAccountSearchClick(ListItem<?> item_){
|
||||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
|
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onClearRecentClick(){
|
private void onClearRecentClick(){
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments.discover;
|
package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -9,8 +10,6 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.HashtagChartView;
|
import org.joinmastodon.android.ui.views.HashtagChartView;
|
||||||
|
|
||||||
@@ -34,6 +33,8 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
|||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
|
setRetainInstance(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -105,7 +106,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
|
UiUtils.openHashtagTimeline(getActivity(), accountID, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,9 +165,7 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
private void tryGetAccount(){
|
private void tryGetAccount(){
|
||||||
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
|
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
|
||||||
uiHandler.removeCallbacks(pollRunnable);
|
uiHandler.removeCallbacks(pollRunnable);
|
||||||
getActivity().finish();
|
((MainActivity)getActivity()).restartHomeFragment();
|
||||||
Intent intent=new Intent(getActivity(), MainActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentRequest=new GetOwnAccount()
|
currentRequest=new GetOwnAccount()
|
||||||
|
|||||||
@@ -83,9 +83,10 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateFilteredList(){
|
protected void updateFilteredList(){
|
||||||
boolean addFakeInstance = currentSearchQuery.length()>0 && currentSearchQuery.matches("^\\S+\\.[^\\.]+$");
|
String query=getCurrentSearchQuery();
|
||||||
|
boolean addFakeInstance=query.length()>0 && query.matches("^\\S+\\.[^\\.]+$");
|
||||||
if(addFakeInstance){
|
if(addFakeInstance){
|
||||||
fakeInstance.domain=fakeInstance.normalizedDomain=currentSearchQuery;
|
fakeInstance.domain=fakeInstance.normalizedDomain=query;
|
||||||
fakeInstance.description=getString(R.string.loading_instance);
|
fakeInstance.description=getString(R.string.loading_instance);
|
||||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||||
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
|
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
|
||||||
@@ -99,12 +100,12 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
|||||||
}
|
}
|
||||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||||
filteredData.clear();
|
filteredData.clear();
|
||||||
if(currentSearchQuery.length()>0){
|
if(query.length()>0){
|
||||||
boolean foundExactMatch=false;
|
boolean foundExactMatch=false;
|
||||||
for(CatalogInstance inst:data){
|
for(CatalogInstance inst:data){
|
||||||
if(inst.normalizedDomain.contains(currentSearchQuery)){
|
if(inst.normalizedDomain.contains(query)){
|
||||||
filteredData.add(inst);
|
filteredData.add(inst);
|
||||||
if(inst.normalizedDomain.equals(currentSearchQuery))
|
if(inst.normalizedDomain.equals(query))
|
||||||
foundExactMatch=true;
|
foundExactMatch=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +140,9 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
|||||||
headerView.findViewById(R.id.more).setVisibility(View.GONE);
|
headerView.findViewById(R.id.more).setVisibility(View.GONE);
|
||||||
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
||||||
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
|
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
|
||||||
((TextView) headerView.findViewById(R.id.time_and_username)).setText(R.string.sk_app_username);
|
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
|
||||||
|
headerView.findViewById(R.id.time).setVisibility(View.GONE);
|
||||||
|
((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
|
||||||
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
||||||
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
|
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
|
||||||
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
|
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
|
||||||
|
|||||||
@@ -93,10 +93,10 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
|
|||||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||||
updateFilteredList();
|
updateFilteredList();
|
||||||
searchEdit.removeCallbacks(searchDebouncer);
|
searchEdit.removeCallbacks(searchDebouncer);
|
||||||
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
|
Instance instance=instancesCache.get(normalizeInstanceDomain(getCurrentSearchQuery()));
|
||||||
if(instance==null){
|
if(instance==null){
|
||||||
showProgressDialog();
|
showProgressDialog();
|
||||||
loadInstanceInfo(currentSearchQuery, false);
|
loadInstanceInfo(getCurrentSearchQuery(), false);
|
||||||
}else{
|
}else{
|
||||||
proceedWithAuthOrSignup(instance);
|
proceedWithAuthOrSignup(instance);
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
|
|||||||
protected void onSearchChangedDebounced(){
|
protected void onSearchChangedDebounced(){
|
||||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||||
updateFilteredList();
|
updateFilteredList();
|
||||||
loadInstanceInfo(currentSearchQuery, false);
|
loadInstanceInfo(getCurrentSearchQuery(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){
|
protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){
|
||||||
@@ -126,9 +126,16 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
|
|||||||
instanceProgressDialog.show();
|
instanceProgressDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getCurrentSearchQuery(){
|
||||||
|
String[] parts=currentSearchQuery.split("@");
|
||||||
|
return parts.length>0 ? parts[parts.length-1] : "";
|
||||||
|
}
|
||||||
|
|
||||||
protected String normalizeInstanceDomain(String _domain){
|
protected String normalizeInstanceDomain(String _domain){
|
||||||
if(TextUtils.isEmpty(_domain))
|
if(TextUtils.isEmpty(_domain))
|
||||||
return null;
|
return null;
|
||||||
|
String[] parts=_domain.split("@");
|
||||||
|
_domain=parts[parts.length - 1];
|
||||||
if(_domain.contains(":")){
|
if(_domain.contains(":")){
|
||||||
try{
|
try{
|
||||||
_domain=Uri.parse(_domain).getAuthority();
|
_domain=Uri.parse(_domain).getAuthority();
|
||||||
@@ -198,7 +205,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
|
|||||||
instanceProgressDialog=null;
|
instanceProgressDialog=null;
|
||||||
proceedWithAuthOrSignup(result);
|
proceedWithAuthOrSignup(result);
|
||||||
}
|
}
|
||||||
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
if(Objects.equals(domain, getCurrentSearchQuery()) || Objects.equals(getCurrentSearchQuery(), redirects.get(domain)) || Objects.equals(getCurrentSearchQuery(), redirectsInverse.get(domain))){
|
||||||
boolean found=false;
|
boolean found=false;
|
||||||
for(CatalogInstance ci:filteredData){
|
for(CatalogInstance ci:filteredData){
|
||||||
if(ci.domain.equals(domain) && ci!=fakeInstance){
|
if(ci.domain.equals(domain) && ci!=fakeInstance){
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments.onboarding;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void tail(Node node, int depth){
|
public void tail(Node node, int depth){
|
||||||
if(node instanceof Element){
|
if(node instanceof Element){
|
||||||
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments.report;
|
package org.joinmastodon.android.fragments.report;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -25,6 +23,7 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.CheckableHeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.CheckableHeaderStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
@@ -83,10 +82,11 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES)
|
currentRequest=new GetAccountStatuses(reportAccount.id, getMaxID(), null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
|
if(getActivity()==null) return;
|
||||||
for(Status s:result){
|
for(Status s:result){
|
||||||
s.sensitive=true;
|
s.sensitive=true;
|
||||||
}
|
}
|
||||||
@@ -96,23 +96,20 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void onToggleItem(String id){
|
||||||
public void onItemClick(String id){
|
|
||||||
if(selectedIDs.contains(id))
|
if(selectedIDs.contains(id))
|
||||||
selectedIDs.remove(id);
|
selectedIDs.remove(id);
|
||||||
else
|
else
|
||||||
selectedIDs.add(id);
|
selectedIDs.add(id);
|
||||||
btn.setEnabled(!selectedIDs.isEmpty());
|
|
||||||
CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class);
|
CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class);
|
||||||
if(holder!=null)
|
if(holder!=null) holder.rebind();
|
||||||
holder.rebind();
|
else notifyItemChanged(id, CheckableHeaderStatusDisplayItem.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
btn=view.findViewById(R.id.btn_next);
|
btn=view.findViewById(R.id.btn_next);
|
||||||
btn.setEnabled(!selectedIDs.isEmpty());
|
|
||||||
btn.setOnClickListener(this::onButtonClick);
|
btn.setOnClickListener(this::onButtonClick);
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
|
||||||
@@ -122,13 +119,20 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||||
if(holder.getAbsoluteAdapterPosition()==0 || holder instanceof CheckableHeaderStatusDisplayItem.Holder)
|
if(holder.getAbsoluteAdapterPosition()==0 || holder instanceof CheckableHeaderStatusDisplayItem.Holder)
|
||||||
return;
|
return;
|
||||||
outRect.left=V.dp(40);
|
boolean isRTL=parent.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL;
|
||||||
|
if(isRTL) outRect.right=V.dp(40);
|
||||||
|
else outRect.left=V.dp(40);
|
||||||
if(holder instanceof AudioStatusDisplayItem.Holder){
|
if(holder instanceof AudioStatusDisplayItem.Holder){
|
||||||
outRect.bottom=V.dp(16);
|
outRect.bottom=V.dp(16);
|
||||||
}else if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
|
}else if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
|
||||||
outRect.bottom=V.dp(16);
|
outRect.bottom=V.dp(8);
|
||||||
outRect.left+=V.dp(16);
|
if(isRTL){
|
||||||
outRect.right=V.dp(16);
|
outRect.right+=V.dp(16);
|
||||||
|
outRect.left=V.dp(16);
|
||||||
|
}else{
|
||||||
|
outRect.left+=V.dp(16);
|
||||||
|
outRect.right=V.dp(16);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -156,9 +160,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
return adapter;
|
return adapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onButtonClick(View v){
|
private void onButtonClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -202,7 +203,9 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN);
|
||||||
|
items.add(new DummyStatusDisplayItem(s.getID(), this));
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import me.grishka.appkit.api.Callback;
|
|||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ReportDoneFragment extends MastodonToolbarFragment{
|
public class ReportDoneFragment extends MastodonToolbarFragment{
|
||||||
@@ -227,7 +226,7 @@ public class ReportDoneFragment extends MastodonToolbarFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getNavigationIconDrawableResource(){
|
protected int getNavigationIconDrawableResource(){
|
||||||
return R.drawable.ic_baseline_close_24;
|
return R.drawable.ic_fluent_dismiss_24_regular;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user