Compare commits
1837 Commits
v1.1.5+for
...
1.1.4+fork
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a9d187727 | ||
|
|
affd69c577 | ||
|
|
deaca28fe8 | ||
|
|
37432ef21c | ||
|
|
79f500098b | ||
|
|
b5acaaf369 | ||
|
|
6aeca9ba26 | ||
|
|
95b28d30f7 | ||
|
|
5b4be77ded | ||
|
|
462457cf09 | ||
|
|
cc9012516a | ||
|
|
d716891453 | ||
|
|
71ffaad660 | ||
|
|
210349523f | ||
|
|
b883ed1e40 | ||
|
|
f4431fb00f | ||
|
|
b877488aa9 | ||
|
|
a4e3774739 | ||
|
|
c8580979e8 | ||
|
|
0aac29545f | ||
|
|
94a3d994fd | ||
|
|
043526f883 | ||
|
|
1a1f9e5cb1 | ||
|
|
3f2dd91749 | ||
|
|
ac1e000645 | ||
|
|
acdd203c26 | ||
|
|
caf07a60ea | ||
|
|
a26c835134 | ||
|
|
363ceed2ad | ||
|
|
46c9bd9807 | ||
|
|
4d23daf461 | ||
|
|
31b50d705f | ||
|
|
66932ccd4c | ||
|
|
d0cddff330 | ||
|
|
94a5807619 | ||
|
|
2c1be165f4 | ||
|
|
ddc4a8c46d | ||
|
|
c255fb5e58 | ||
|
|
d6705ba991 | ||
|
|
c3c9af872b | ||
|
|
7a71aad676 | ||
|
|
e34d4cf04c | ||
|
|
55b32111da | ||
|
|
00b081b20b | ||
|
|
8d08ff397a | ||
|
|
57a1c1b198 | ||
|
|
6be2978857 | ||
|
|
facde9ea15 | ||
|
|
02e9c5f577 | ||
|
|
d04b09f75f | ||
|
|
ec0f6edc41 | ||
|
|
a900c79e2c | ||
|
|
97dbd3499f | ||
|
|
f07e0906dd | ||
|
|
8933c0647e | ||
|
|
333c38c64d | ||
|
|
ca5827e3f8 | ||
|
|
4884667484 | ||
|
|
a2f687898c | ||
|
|
defd038064 | ||
|
|
f88b65f479 | ||
|
|
f65d56361f | ||
|
|
255155b55a | ||
|
|
ee2e39462a | ||
|
|
32b459ae77 | ||
|
|
c1b79da4a7 | ||
|
|
65dfd8667d | ||
|
|
4f3e2f69d6 | ||
|
|
ec01da10f5 | ||
|
|
3015fa9b97 | ||
|
|
0ca2c4acb2 | ||
|
|
1180ac2d9f | ||
|
|
bc8fce60ec | ||
|
|
41f0679102 | ||
|
|
2a0d0d0811 | ||
|
|
6f234fddf7 | ||
|
|
83c850b782 | ||
|
|
2780363dab | ||
|
|
7273040c4d | ||
|
|
f7de52dfe7 | ||
|
|
fffa86d1b3 | ||
|
|
cae456278a | ||
|
|
d2808e9599 | ||
|
|
07f35723b6 | ||
|
|
5739b26eb2 | ||
|
|
9da7d899a6 | ||
|
|
b6a7649b94 | ||
|
|
8e2270d720 | ||
|
|
f92dcac291 | ||
|
|
e5cf8e3511 | ||
|
|
38b0cc3320 | ||
|
|
5fdd476277 | ||
|
|
c05f1d45fb | ||
|
|
01135937cc | ||
|
|
1e4cc8edb1 | ||
|
|
aa7beef17a | ||
|
|
f8e117fff1 | ||
|
|
494fab52fa | ||
|
|
12558c3c18 | ||
|
|
dae347a29f | ||
|
|
c51be5f199 | ||
|
|
fc1bd14f70 | ||
|
|
bd39ed3754 | ||
|
|
50029c7f73 | ||
|
|
e2c907eb10 | ||
|
|
937747e11b | ||
|
|
e5ad2db1ad | ||
|
|
3520ebafc9 | ||
|
|
f7c85d7d53 | ||
|
|
fb812d5d88 | ||
|
|
fde44e61bc | ||
|
|
34e072a1b5 | ||
|
|
fe244112d4 | ||
|
|
3fcf8818c0 | ||
|
|
0b9b51294e | ||
|
|
85b6bc79a3 | ||
|
|
ec9d41fbbd | ||
|
|
847d966daa | ||
|
|
2783806f97 | ||
|
|
0ee99fa709 | ||
|
|
618840c76a | ||
|
|
33d856562d | ||
|
|
9873e9ede5 | ||
|
|
63dad42bf3 | ||
|
|
dad58f8245 | ||
|
|
647a7d70cd | ||
|
|
f49c7dff00 | ||
|
|
72f638c96c | ||
|
|
e223bdd2bd | ||
|
|
c28a7d6029 | ||
|
|
b814c7eab9 | ||
|
|
ecfa255d75 | ||
|
|
979977aec6 | ||
|
|
a487a45828 | ||
|
|
0afcf10c74 | ||
|
|
43c82ed99b | ||
|
|
28302f3a4b | ||
|
|
d6ad1cbfbf | ||
|
|
fd08a1f845 | ||
|
|
67b2a82058 | ||
|
|
f7ab8bd27e | ||
|
|
5df8318200 | ||
|
|
0fcaa08ed0 | ||
|
|
2ce3167db0 | ||
|
|
2c8ecbd996 | ||
|
|
90aa8c5762 | ||
|
|
bb0ca63a03 | ||
|
|
35e0897869 | ||
|
|
e22cb07d63 | ||
|
|
e8a8691b03 | ||
|
|
7fe3d97347 | ||
|
|
a2e932934c | ||
|
|
e82e51ca88 | ||
|
|
ef561b6724 | ||
|
|
2bdff65c13 | ||
|
|
286b642101 | ||
|
|
27ed78c293 | ||
|
|
562a5aae7d | ||
|
|
4940eff7f9 | ||
|
|
cfb9854a8e | ||
|
|
594e49cf64 | ||
|
|
c2df989217 | ||
|
|
31d0bfb434 | ||
|
|
14e639aa8a | ||
|
|
6c24e06157 | ||
|
|
53ce4276f6 | ||
|
|
423e919e16 | ||
|
|
ad2895e297 | ||
|
|
dae2632c18 | ||
|
|
c6cd424f30 | ||
|
|
e282d54f99 | ||
|
|
29ad08f2ea | ||
|
|
97f09d4569 | ||
|
|
ac1dbc0f90 | ||
|
|
cf67175826 | ||
|
|
dc8f81f447 | ||
|
|
b11b4c95d0 | ||
|
|
20dc664242 | ||
|
|
5cabab368f | ||
|
|
e512a0b327 | ||
|
|
4a96392f11 | ||
|
|
74a28cb881 | ||
|
|
cd712f5109 | ||
|
|
11d5a65f04 | ||
|
|
efc1a0b4e3 | ||
|
|
492d9e90b8 | ||
|
|
0928b5808f | ||
|
|
99c2555a88 | ||
|
|
5c2f72a706 | ||
|
|
b153a64373 | ||
|
|
ede4137935 | ||
|
|
b625ed7aec | ||
|
|
e7e3d94add | ||
|
|
03722868b1 | ||
|
|
a2a0c9801b | ||
|
|
9a55f847b9 | ||
|
|
0473062bc2 | ||
|
|
9393e845b7 | ||
|
|
3dc56286cc | ||
|
|
e22ea06d28 | ||
|
|
6fd5b77e51 | ||
|
|
372de91be9 | ||
|
|
ce3f8ce1b5 | ||
|
|
7c01809eb3 | ||
|
|
29cc73e84e | ||
|
|
c3567bcbff | ||
|
|
bd4ade0852 | ||
|
|
62649ffe60 | ||
|
|
b0a405337a | ||
|
|
18b6e33862 | ||
|
|
acb5b01cbd | ||
|
|
754c41389e | ||
|
|
d26ba6df4a | ||
|
|
cc9b97338d | ||
|
|
f714a00fc4 | ||
|
|
7097b5371e | ||
|
|
ad6ebf9e23 | ||
|
|
5436a45c5f | ||
|
|
4ba3baf354 | ||
|
|
9160a5b0e2 | ||
|
|
fdac91363e | ||
|
|
70a9c91f8a | ||
|
|
45ca3d1b48 | ||
|
|
9506d55149 | ||
|
|
16808b404b | ||
|
|
97a4cade1f | ||
|
|
55f3d80f3a | ||
|
|
10d66b732c | ||
|
|
e62c70268a | ||
|
|
cbb10ac480 | ||
|
|
33de46130c | ||
|
|
d1bd3a423c | ||
|
|
2e24e443c5 | ||
|
|
040b613d68 | ||
|
|
6659ca5f60 | ||
|
|
adaa09790a | ||
|
|
699bcc0377 | ||
|
|
b359a2bfa1 | ||
|
|
f821709afc | ||
|
|
a9899e8f9a | ||
|
|
e1b1ae717f | ||
|
|
0fd2894a8b | ||
|
|
05ddd2f8d9 | ||
|
|
6d9639b042 | ||
|
|
f3ee87a815 | ||
|
|
59c5ac0dd1 | ||
|
|
e647b37143 | ||
|
|
d94c3e3d9b | ||
|
|
9b508bd9ca | ||
|
|
fc49c20fff | ||
|
|
7cc791ccd9 | ||
|
|
b527ab6e84 | ||
|
|
a5fb358998 | ||
|
|
daa0497ce8 | ||
|
|
2d3f852fd6 | ||
|
|
cf288c9ba9 | ||
|
|
4847eff116 | ||
|
|
36430c6557 | ||
|
|
3fb8470899 | ||
|
|
e1f7feb875 | ||
|
|
0ec005d595 | ||
|
|
b2bed8e6b5 | ||
|
|
30c6a56f25 | ||
|
|
2ba575ecb8 | ||
|
|
d67cfe1d28 | ||
|
|
cab36e8f79 | ||
|
|
d30eb0bd36 | ||
|
|
31443d59ca | ||
|
|
79ccc4b14a | ||
|
|
6ce1dec467 | ||
|
|
81cac94fac | ||
|
|
4301c82856 | ||
|
|
2856a5e6d4 | ||
|
|
a06414e3a1 | ||
|
|
747edce53c | ||
|
|
c407b5d504 | ||
|
|
997c277beb | ||
|
|
2e392f3bad | ||
|
|
db133ad8d2 | ||
|
|
015479e6e0 | ||
|
|
8b26103049 | ||
|
|
8d095e7901 | ||
|
|
945b57681e | ||
|
|
1dcaa51f2c | ||
|
|
c7b377829e | ||
|
|
63ffd53b09 | ||
|
|
631f5f8691 | ||
|
|
89976d1894 | ||
|
|
ff7305da8b | ||
|
|
6593c43c1c | ||
|
|
cf36c6ca83 | ||
|
|
92fb0272f0 | ||
|
|
aad7b5d703 | ||
|
|
eb5f82af2a | ||
|
|
cf8cfd8093 | ||
|
|
2538c8b689 | ||
|
|
36d78abd1e | ||
|
|
072e78a89a | ||
|
|
58dee464e5 | ||
|
|
21f816860f | ||
|
|
84e5983c4c | ||
|
|
1a6ad6c029 | ||
|
|
f8ecbb8936 | ||
|
|
1c98bba7d2 | ||
|
|
b94e927439 | ||
|
|
eb0fcda3cc | ||
|
|
d7feb78197 | ||
|
|
acf1988827 | ||
|
|
3822263b1c | ||
|
|
1af82fde61 | ||
|
|
6062510844 | ||
|
|
27c34461bf | ||
|
|
abf801742f | ||
|
|
186604931d | ||
|
|
a726bf6101 | ||
|
|
0008b07072 | ||
|
|
527267b315 | ||
|
|
396836760a | ||
|
|
a5a293be78 | ||
|
|
ea8107d33e | ||
|
|
430dd6164c | ||
|
|
7588143ec6 | ||
|
|
1f97fa9447 | ||
|
|
fff7949e51 | ||
|
|
ce6b928964 | ||
|
|
0604326f3f | ||
|
|
338de2699f | ||
|
|
0b0fe98e2d | ||
|
|
84d412645f | ||
|
|
c565f6779d | ||
|
|
1733b1b065 | ||
|
|
49cff20785 | ||
|
|
65232b3b1e | ||
|
|
ac6177f3de | ||
|
|
48e6a6f624 | ||
|
|
73db5907fe | ||
|
|
69114b9f57 | ||
|
|
114283846c | ||
|
|
e0dd2e3a08 | ||
|
|
648c3a0c0d | ||
|
|
dd3bf1d1e0 | ||
|
|
5280ba1930 | ||
|
|
5c8c888024 | ||
|
|
b9a997730a | ||
|
|
446a2f8206 | ||
|
|
ea4c7dc51d | ||
|
|
16c0ff2f0b | ||
|
|
8941c2bc1d | ||
|
|
aa4a1648af | ||
|
|
5e27a43471 | ||
|
|
12a51eb51f | ||
|
|
5fd17778f4 | ||
|
|
a6028e06ea | ||
|
|
8a0f95affe | ||
|
|
1221a5622f | ||
|
|
b776b0bc1b | ||
|
|
09bf0f2c3c | ||
|
|
42dc6b7425 | ||
|
|
1b31284a95 | ||
|
|
672ed86630 | ||
|
|
862a23af5d | ||
|
|
d49733695d | ||
|
|
d79c4ad650 | ||
|
|
6e314f0ea5 | ||
|
|
e5a6dc33b4 | ||
|
|
8c6ac9e22b | ||
|
|
8d827054bf | ||
|
|
55a2ae8748 | ||
|
|
55640dddb9 | ||
|
|
9a2df814c5 | ||
|
|
0fcbf02a2c | ||
|
|
bc9edf1f69 | ||
|
|
50baffefbe | ||
|
|
3622eba057 | ||
|
|
9df738831f | ||
|
|
7939380172 | ||
|
|
b36b1b2a28 | ||
|
|
b63c0010a9 | ||
|
|
901f1763ab | ||
|
|
cf22096e35 | ||
|
|
c96e5cde7b | ||
|
|
5538d5af6c | ||
|
|
662fbbf8b6 | ||
|
|
247dea3af0 | ||
|
|
efff075bbc | ||
|
|
45832355a3 | ||
|
|
51d4fd63db | ||
|
|
ececa7aa2f | ||
|
|
6cf8793efe | ||
|
|
57251d58cb | ||
|
|
cf1f8e8d1a | ||
|
|
c77cb14602 | ||
|
|
de864edb33 | ||
|
|
699925ac9b | ||
|
|
e367b7711f | ||
|
|
b0e4f707aa | ||
|
|
1124486f1f | ||
|
|
2749ffb6f2 | ||
|
|
47f57bab17 | ||
|
|
f1b0f828ac | ||
|
|
afbf13ef95 | ||
|
|
8e4cff17a5 | ||
|
|
da954ed3fd | ||
|
|
9d48beaebb | ||
|
|
e607118347 | ||
|
|
5f6dafb763 | ||
|
|
052a000d3c | ||
|
|
59315f81ec | ||
|
|
c757b1ffea | ||
|
|
932655eeb6 | ||
|
|
21d57b25c9 | ||
|
|
bed572f343 | ||
|
|
c7483a6b20 | ||
|
|
cdb1e26a4d | ||
|
|
ce1a450ccb | ||
|
|
dfc244ff41 | ||
|
|
9c3e2f5deb | ||
|
|
452b286352 | ||
|
|
6deca645de | ||
|
|
49fd1aba76 | ||
|
|
7bc951ba67 | ||
|
|
a70e73a8cb | ||
|
|
cf345356a5 | ||
|
|
3da3967afa | ||
|
|
a12f09a38a | ||
|
|
a7302cc3e1 | ||
|
|
9aed2a96dc | ||
|
|
dbe49134e1 | ||
|
|
089d176704 | ||
|
|
4e482ef6fa | ||
|
|
c64397a613 | ||
|
|
6c0d4778b7 | ||
|
|
e9cd29c59e | ||
|
|
b94c1f4a82 | ||
|
|
a29a072e53 | ||
|
|
4f435c6957 | ||
|
|
2a6115f6d9 | ||
|
|
4cbc1e3664 | ||
|
|
1053d2ac0c | ||
|
|
0123b17602 | ||
|
|
1dace6ead9 | ||
|
|
3287cf69c1 | ||
|
|
a2854524a9 | ||
|
|
71c06c0762 | ||
|
|
e30df6067d | ||
|
|
912a354b1c | ||
|
|
71f830ea82 | ||
|
|
bd109a9139 | ||
|
|
c82b4445ff | ||
|
|
9a7d149dae | ||
|
|
c66e576461 | ||
|
|
2a47d2fe77 | ||
|
|
331d490f4f | ||
|
|
8d722a2130 | ||
|
|
6863363452 | ||
|
|
10e66a58eb | ||
|
|
3a5c27eadc | ||
|
|
8c6bce4f73 | ||
|
|
17e1cd1fe9 | ||
|
|
d7aceffc8f | ||
|
|
bcb3e217cd | ||
|
|
229c19664c | ||
|
|
8bdbb2adef | ||
|
|
327426e443 | ||
|
|
907c5a2ca1 | ||
|
|
2a2bfebf48 | ||
|
|
eba59549ec | ||
|
|
9478a71693 | ||
|
|
b01d7a417a | ||
|
|
80c77292ed | ||
|
|
488e6dda04 | ||
|
|
689f676668 | ||
|
|
a06db9a3ab | ||
|
|
a7283cbed8 | ||
|
|
bc70d5e212 | ||
|
|
441686740a | ||
|
|
ac0df083f2 | ||
|
|
e10762d5fa | ||
|
|
0ca4663c29 | ||
|
|
7efd9341b1 | ||
|
|
3d8693b2bd | ||
|
|
a3b5f3c926 | ||
|
|
f9f4a1d1ef | ||
|
|
dd536002d0 | ||
|
|
4f8e381c84 | ||
|
|
3b6b212c9e | ||
|
|
bf429ee263 | ||
|
|
e7b1301b71 | ||
|
|
6726e9523c | ||
|
|
3ffcc7cef2 | ||
|
|
e6232f6d3b | ||
|
|
5efc431192 | ||
|
|
c3b75782b1 | ||
|
|
ec6f3f0cc3 | ||
|
|
136c3cfb4a | ||
|
|
79d1dbd3b7 | ||
|
|
f2e1663c41 | ||
|
|
5c73f37599 | ||
|
|
7f239abf2f | ||
|
|
a07f7c232a | ||
|
|
4a60a5190f | ||
|
|
69986fd869 | ||
|
|
e2ca572d45 | ||
|
|
746e41fdbc | ||
|
|
091f1f1e8c | ||
|
|
844ec185a6 | ||
|
|
5622eaed83 | ||
|
|
dbf25da1db | ||
|
|
d35a416084 | ||
|
|
098acb85e4 | ||
|
|
9d67337913 | ||
|
|
914861775a | ||
|
|
c12a6eaee6 | ||
|
|
5de23581fe | ||
|
|
413141df1e | ||
|
|
5f48870a90 | ||
|
|
86e781cdea | ||
|
|
34ebd9219f | ||
|
|
41688c4670 | ||
|
|
0d30cd973e | ||
|
|
5ed80ca40a | ||
|
|
78958085c3 | ||
|
|
5b1188ce97 | ||
|
|
c11863388d | ||
|
|
a1798b6666 | ||
|
|
476b462e86 | ||
|
|
f69b308936 | ||
|
|
bdcafd2564 | ||
|
|
baa7dd6302 | ||
|
|
ba93e5bac3 | ||
|
|
2358d3c602 | ||
|
|
cf61626901 | ||
|
|
349a1115a6 | ||
|
|
8fa4980ba5 | ||
|
|
3455aab3ba | ||
|
|
099d0ccf94 | ||
|
|
35a1de7888 | ||
|
|
6fc850b5ba | ||
|
|
96f13defd4 | ||
|
|
36dd07aa38 | ||
|
|
6a831539ad | ||
|
|
c679f5529e | ||
|
|
9c8096274a | ||
|
|
7291b2da5a | ||
|
|
4ff98140cb | ||
|
|
aac4f412bd | ||
|
|
26831c3375 | ||
|
|
d7004824fb | ||
|
|
c2a993c5c1 | ||
|
|
53d93764b0 | ||
|
|
6a84462b79 | ||
|
|
739d30c887 | ||
|
|
aa3aa8a5f9 | ||
|
|
be48719f52 | ||
|
|
8e60d107fe | ||
|
|
c618feabe9 | ||
|
|
87164dc469 | ||
|
|
4ab1c61262 | ||
|
|
1b04440546 | ||
|
|
c0c276f03e | ||
|
|
d30b1f7bbd | ||
|
|
c0ee16cf08 | ||
|
|
a37fb33a68 | ||
|
|
59095e4ffe | ||
|
|
626614c03d | ||
|
|
58ab0c0fc1 | ||
|
|
32a8d38edf | ||
|
|
82534f7c4a | ||
|
|
c6d7242043 | ||
|
|
c4e23b0fe6 | ||
|
|
a5c753a9f8 | ||
|
|
e3520df57e | ||
|
|
66cede567e | ||
|
|
6916f435b3 | ||
|
|
dab0c560e9 | ||
|
|
1b23ef31d5 | ||
|
|
dd7af8b5d3 | ||
|
|
5914ef8fad | ||
|
|
a26ddfe70f | ||
|
|
cb067ca4fa | ||
|
|
3df9a3eecc | ||
|
|
987cbc86ec | ||
|
|
66dcaa9169 | ||
|
|
7162feea31 | ||
|
|
fe519f10a1 | ||
|
|
901c7c3806 | ||
|
|
211e6cdee2 | ||
|
|
394699c072 | ||
|
|
c9766defff | ||
|
|
737aa95bf5 | ||
|
|
1a51744807 | ||
|
|
38e035d792 | ||
|
|
f83a28a1b3 | ||
|
|
f5d4e2a0b5 | ||
|
|
4aaf0c4fa4 | ||
|
|
38e133bee4 | ||
|
|
87bc01d985 | ||
|
|
d5561674cd | ||
|
|
48ec9e9fc6 | ||
|
|
63775c6eb9 | ||
|
|
79a61f6865 | ||
|
|
5f7e03a562 | ||
|
|
c93c4efe1d | ||
|
|
69771269fc | ||
|
|
e7a28696c6 | ||
|
|
124ad1df06 | ||
|
|
3a6ace53d5 | ||
|
|
1e825c979c | ||
|
|
c67b2b35f3 | ||
|
|
8588ca8ae3 | ||
|
|
7bb280e8b8 | ||
|
|
ad1e1b112b | ||
|
|
29139a8f4d | ||
|
|
ddfeaabd44 | ||
|
|
51219bf98a | ||
|
|
335f734698 | ||
|
|
512cb70347 | ||
|
|
c7e0adfbd4 | ||
|
|
ad7a9626a4 | ||
|
|
92f37fdf16 | ||
|
|
900e8fb2e9 | ||
|
|
be4b032527 | ||
|
|
95cb04530f | ||
|
|
4aa750c05e | ||
|
|
c3e5f4d254 | ||
|
|
2e5ff452fd | ||
|
|
c397c08e40 | ||
|
|
4b6a0b71a0 | ||
|
|
187190c07e | ||
|
|
5142851f57 | ||
|
|
763c5fe2a7 | ||
|
|
7f0265fe24 | ||
|
|
f87827700b | ||
|
|
fb2c0c0ec2 | ||
|
|
ec40488ed1 | ||
|
|
88851a085e | ||
|
|
a4d2101f54 | ||
|
|
f956a17797 | ||
|
|
1c1d1772a3 | ||
|
|
4db87feec4 | ||
|
|
bef3c72513 | ||
|
|
4fa641b482 | ||
|
|
885b5d781a | ||
|
|
2f3bfb3e74 | ||
|
|
2be625fd76 | ||
|
|
134a371263 | ||
|
|
8b0eddb8e1 | ||
|
|
bd2d56b953 | ||
|
|
38e429f738 | ||
|
|
de8b15d447 | ||
|
|
0df1bcce31 | ||
|
|
4e17256cfa | ||
|
|
e12c3e2d68 | ||
|
|
aec2704f15 | ||
|
|
31f9173126 | ||
|
|
90196df65d | ||
|
|
6b9fa71806 | ||
|
|
130085f804 | ||
|
|
f4356e74a4 | ||
|
|
9c8a4b7a8e | ||
|
|
b7ccf1144c | ||
|
|
87d5b92a99 | ||
|
|
29f8260852 | ||
|
|
060745869b | ||
|
|
1aff3eacd8 | ||
|
|
0207ddb774 | ||
|
|
78d0add808 | ||
|
|
2fa042490a | ||
|
|
885f559092 | ||
|
|
77af7ceae3 | ||
|
|
09d4188d54 | ||
|
|
1ad03828e3 | ||
|
|
870ac2b946 | ||
|
|
394a3eebb1 | ||
|
|
95c10a9fea | ||
|
|
cc4483dea1 | ||
|
|
87c743886e | ||
|
|
f3cde5441b | ||
|
|
7a9534772d | ||
|
|
42faa62a5f | ||
|
|
f0e14c5a13 | ||
|
|
b79b69d961 | ||
|
|
5118a1fb1e | ||
|
|
18275183d0 | ||
|
|
6e718d6765 | ||
|
|
616049bff2 | ||
|
|
1a79bc0b61 | ||
|
|
b26d491eda | ||
|
|
abdbab9d7b | ||
|
|
d43cbe642f | ||
|
|
77cee4c46a | ||
|
|
0949ad1ce6 | ||
|
|
1e411c0c23 | ||
|
|
76d306aef7 | ||
|
|
7ff19ef481 | ||
|
|
6650bb946f | ||
|
|
bbbf1683aa | ||
|
|
5cdea99eb0 | ||
|
|
356426b5fc | ||
|
|
7577d60f42 | ||
|
|
8c7364d57d | ||
|
|
c0a2945378 | ||
|
|
1af9a71210 | ||
|
|
dc859fe91c | ||
|
|
d50c37af23 | ||
|
|
00c8a03b80 | ||
|
|
d15d222b72 | ||
|
|
34d134cb57 | ||
|
|
40eb3e2400 | ||
|
|
0af45e5f56 | ||
|
|
82e3250623 | ||
|
|
533a51fc77 | ||
|
|
6fc82cf26b | ||
|
|
6dcfdb9735 | ||
|
|
217d8348d4 | ||
|
|
9bce934944 | ||
|
|
a1ef3e1cae | ||
|
|
978c1cfdc4 | ||
|
|
9fb9ffc269 | ||
|
|
4f357637de | ||
|
|
1acd177e81 | ||
|
|
e4b1bf452f | ||
|
|
ce22cb4678 | ||
|
|
3c45215fca | ||
|
|
b17e63acae | ||
|
|
859ba5ebb9 | ||
|
|
164579cbb5 | ||
|
|
990f8189e4 | ||
|
|
4d0a642fd9 | ||
|
|
84b2994b99 | ||
|
|
efbca327c1 | ||
|
|
aa4b007d25 | ||
|
|
23dccef4b4 | ||
|
|
1ef96ed5e6 | ||
|
|
31e2a32233 | ||
|
|
168ae80743 | ||
|
|
8acf23ddac | ||
|
|
eb40211582 | ||
|
|
bf35161c9f | ||
|
|
456c50f69e | ||
|
|
587212cf46 | ||
|
|
fe800a259d | ||
|
|
c193741013 | ||
|
|
a7b752264f | ||
|
|
9075027f69 | ||
|
|
28faf4277a | ||
|
|
120ab8ca54 | ||
|
|
a3fc1a6a74 | ||
|
|
ec1fe07fea | ||
|
|
e3d054ae3e | ||
|
|
795d4b0801 | ||
|
|
0cf0f07f2d | ||
|
|
2cc5872ec7 | ||
|
|
6a151e00ac | ||
|
|
1e99862d40 | ||
|
|
3a1b12306b | ||
|
|
e31db6d506 | ||
|
|
b22c0a5d3d | ||
|
|
e7d856acf4 | ||
|
|
ee8b087b61 | ||
|
|
4e86314df5 | ||
|
|
cf5fbe3b55 | ||
|
|
952416fefc | ||
|
|
1645ce4486 | ||
|
|
2bde95b4d2 | ||
|
|
2de003c5bb | ||
|
|
a04e16c572 | ||
|
|
eb41d77d54 | ||
|
|
9325590319 | ||
|
|
d32a57a18d | ||
|
|
13b5462f63 | ||
|
|
480d4ad904 | ||
|
|
7fa52247e8 | ||
|
|
5a547015e6 | ||
|
|
513736f765 | ||
|
|
c6164b1bcd | ||
|
|
87342782d7 | ||
|
|
a4b18de72c | ||
|
|
3064b549cd | ||
|
|
6666f82329 | ||
|
|
ad87efa7e2 | ||
|
|
d06cf1bb1e | ||
|
|
096aa23f69 | ||
|
|
2464042329 | ||
|
|
928b04eda6 | ||
|
|
a31e33415e | ||
|
|
87ce6b8bb1 | ||
|
|
bb08d6585c | ||
|
|
94fa1133fd | ||
|
|
83822b8f69 | ||
|
|
9f3bd186ba | ||
|
|
58cb338cb2 | ||
|
|
6f447909eb | ||
|
|
af953e294d | ||
|
|
7504a1b9cb | ||
|
|
60ee781004 | ||
|
|
4b88ce5115 | ||
|
|
3842ecb0d1 | ||
|
|
3713063ce3 | ||
|
|
83b089457e | ||
|
|
ed9813f093 | ||
|
|
e45f3f30f3 | ||
|
|
df44d4cc4f | ||
|
|
b666048603 | ||
|
|
153542e1b4 | ||
|
|
57e0b96f36 | ||
|
|
ff65d150e3 | ||
|
|
88474ba826 | ||
|
|
dd92f1b66f | ||
|
|
af1c7194e6 | ||
|
|
8e507e7970 | ||
|
|
3b542730b1 | ||
|
|
b038f81718 | ||
|
|
e1206703cf | ||
|
|
924affee14 | ||
|
|
0c5da34cd6 | ||
|
|
b44e6b9f0a | ||
|
|
9d3369f601 | ||
|
|
f607ed314d | ||
|
|
2cdf642ca3 | ||
|
|
5d278eb5aa | ||
|
|
860c2826e3 | ||
|
|
3060c36cca | ||
|
|
a1b0632c75 | ||
|
|
14cbb1107f | ||
|
|
dd5f352f5e | ||
|
|
7c7f3cc42a | ||
|
|
734a8049a5 | ||
|
|
417faa66f9 | ||
|
|
7223a13d08 | ||
|
|
a55002da0c | ||
|
|
ce89733f2d | ||
|
|
18811ec32a | ||
|
|
8bce03fad3 | ||
|
|
d148883ab2 | ||
|
|
cfa93424cc | ||
|
|
ff575f75c7 | ||
|
|
6fec7a5205 | ||
|
|
0693495e12 | ||
|
|
04381d57f2 | ||
|
|
9f4adcab23 | ||
|
|
00dba5981c | ||
|
|
ae838fe4d7 | ||
|
|
2110861f1b | ||
|
|
4f6476c807 | ||
|
|
eccfa27128 | ||
|
|
460bce6174 | ||
|
|
d40790a85a | ||
|
|
75dc6fd019 | ||
|
|
581e2056f7 | ||
|
|
70d5100419 | ||
|
|
0285d9620e | ||
|
|
f6411052dd | ||
|
|
ff21c0c103 | ||
|
|
443c18ce13 | ||
|
|
7380da88f9 | ||
|
|
a7551ce9d9 | ||
|
|
dbb2c62702 | ||
|
|
eb2385afe4 | ||
|
|
6c63e7b833 | ||
|
|
d8eb1f280b | ||
|
|
82a90d5486 | ||
|
|
78a3c43b06 | ||
|
|
54c23b2d05 | ||
|
|
3fb350abe4 | ||
|
|
2e4bb98bb0 | ||
|
|
0d2d4dd1b2 | ||
|
|
f8b1695c61 | ||
|
|
b456973c6a | ||
|
|
7c4616568b | ||
|
|
1e93360778 | ||
|
|
e7db1fcfc1 | ||
|
|
8be50e126b | ||
|
|
a75611e707 | ||
|
|
1df643fc9a | ||
|
|
1e730df767 | ||
|
|
f4c097704e | ||
|
|
a86035a4ed | ||
|
|
5b57b4ca79 | ||
|
|
5e9dda72b5 | ||
|
|
4121346794 | ||
|
|
3ce24f72d8 | ||
|
|
5ee81a6416 | ||
|
|
0fb7402094 | ||
|
|
4de4617cf5 | ||
|
|
bc3ace42f4 | ||
|
|
7c9437b5d2 | ||
|
|
7d4b82f4ca | ||
|
|
f7ea6fb0dd | ||
|
|
afff61fe8c | ||
|
|
d60c82b21c | ||
|
|
7c9107f229 | ||
|
|
40eb686418 | ||
|
|
bf9f859827 | ||
|
|
cd51bca670 | ||
|
|
2048a49f9b | ||
|
|
ea00117844 | ||
|
|
f8df86ae6b | ||
|
|
924f792f8b | ||
|
|
02c9928a1f | ||
|
|
0fec486ce0 | ||
|
|
c6c90d61b5 | ||
|
|
9147b3b495 | ||
|
|
a9cca7f8db | ||
|
|
e8e8eef42d | ||
|
|
c1f31f3983 | ||
|
|
3df7123599 | ||
|
|
6dd4b202d9 | ||
|
|
71432fb87d | ||
|
|
1a0a09ddae | ||
|
|
94b0c8be08 | ||
|
|
5cb5f426d8 | ||
|
|
2b0b612191 | ||
|
|
6c4424bca4 | ||
|
|
b9e46339cd | ||
|
|
ded00f84f1 | ||
|
|
79b74a1960 | ||
|
|
25e8febc44 | ||
|
|
6cc2885050 | ||
|
|
a68053f3a5 | ||
|
|
deca8df309 | ||
|
|
e65e6163ba | ||
|
|
60edcfee1f | ||
|
|
9c3db24d2f | ||
|
|
19abbe199b | ||
|
|
eb1ab99262 | ||
|
|
b33003f7b0 | ||
|
|
e75d350b7a | ||
|
|
e9cfe3dee0 | ||
|
|
753914ca5a | ||
|
|
93e3097993 | ||
|
|
0916eddbb7 | ||
|
|
a4848f001b | ||
|
|
8952cd6f97 | ||
|
|
4a3e6888d6 | ||
|
|
b9bcb62cda | ||
|
|
9a5747efc8 | ||
|
|
d3491b5753 | ||
|
|
9e9f9357fd | ||
|
|
980503ed57 | ||
|
|
c2dd858de8 | ||
|
|
d2ef6fb567 | ||
|
|
9c996b3568 | ||
|
|
ec525bde6d | ||
|
|
ee19410cc6 | ||
|
|
0468ae246e | ||
|
|
9722cd9e12 | ||
|
|
85799a7d93 | ||
|
|
e222559bde | ||
|
|
2f3c7dc8f1 | ||
|
|
d86588bbe2 | ||
|
|
2bf787c8f2 | ||
|
|
82ab8bef56 | ||
|
|
cf024dc85f | ||
|
|
ddebe1b3c0 | ||
|
|
070e5637cc | ||
|
|
bdd3c849e7 | ||
|
|
21073b11d0 | ||
|
|
b4fa74b78f | ||
|
|
ab3a98fd60 | ||
|
|
6e6fdbccd5 | ||
|
|
1764e5f3d1 | ||
|
|
2387d84bc0 | ||
|
|
3bd69b5447 | ||
|
|
71f6311598 | ||
|
|
e808977717 | ||
|
|
8594e34bb5 | ||
|
|
4591f06d63 | ||
|
|
b9c3143c6f | ||
|
|
adefb0e567 | ||
|
|
5a8fed3c06 | ||
|
|
2c1b8da475 | ||
|
|
1cc6bf4971 | ||
|
|
54200991cb | ||
|
|
6bea10bdac | ||
|
|
6c615a4893 | ||
|
|
3dc338b3a5 | ||
|
|
37278ff52b | ||
|
|
707c51e4d6 | ||
|
|
1a075e32de | ||
|
|
17262ebdac | ||
|
|
836c493951 | ||
|
|
73869b6ea2 | ||
|
|
3980329112 | ||
|
|
6107b21d3b | ||
|
|
42a0b881af | ||
|
|
2076818220 | ||
|
|
eb45874546 | ||
|
|
f75520a1d8 | ||
|
|
b5392f0c2b | ||
|
|
d667b8fa98 | ||
|
|
6a1032cd61 | ||
|
|
513bea7959 | ||
|
|
a48dc18df5 | ||
|
|
c55639e966 | ||
|
|
1fc89d7448 | ||
|
|
4fcc07fcd6 | ||
|
|
b50b1c33da | ||
|
|
d72f8d3f9c | ||
|
|
25460191b0 | ||
|
|
cd95a75f8f | ||
|
|
1206ce6efc | ||
|
|
e7bb393cee | ||
|
|
9b74373c22 | ||
|
|
de0afdfa16 | ||
|
|
480b3f1902 | ||
|
|
3cdcffe03d | ||
|
|
b09aab4a1d | ||
|
|
d27fe4ebd1 | ||
|
|
ab2cee08fe | ||
|
|
1fa42fd20f | ||
|
|
e343131670 | ||
|
|
f157313d9a | ||
|
|
9bd4433942 | ||
|
|
c9cc8e23c1 | ||
|
|
786dbaf92c | ||
|
|
8dd22e1853 | ||
|
|
34a4dd6d1f | ||
|
|
6e13e592d0 | ||
|
|
be73ca188d | ||
|
|
d0ad55611d | ||
|
|
d47797bf7a | ||
|
|
54c29fd787 | ||
|
|
294595513a | ||
|
|
485ab4ee22 | ||
|
|
b8f101ead7 | ||
|
|
2614118d7d | ||
|
|
cbcbaaa9fa | ||
|
|
7c6f6816b3 | ||
|
|
59a8d1d462 | ||
|
|
f4e5baf94d | ||
|
|
a782160dd3 | ||
|
|
871c17cbe2 | ||
|
|
047e72ce9c | ||
|
|
6c1424055f | ||
|
|
369902ffe5 | ||
|
|
6c778d05ea | ||
|
|
747439999d | ||
|
|
4d836f8032 | ||
|
|
f97ab73c5d | ||
|
|
147fb94442 | ||
|
|
975dc94d41 | ||
|
|
a5c1053c58 | ||
|
|
1bfbb4bf38 | ||
|
|
c2950ace90 | ||
|
|
f0846465c2 | ||
|
|
ddc4512116 | ||
|
|
1c9e4fe561 | ||
|
|
35299a7b3f | ||
|
|
3e3ed050ba | ||
|
|
84179bc207 | ||
|
|
9dc795ded7 | ||
|
|
9c733d65b2 | ||
|
|
4d49890b1e | ||
|
|
f78c6978cf | ||
|
|
b58a3d89e8 | ||
|
|
cd0cfba7c0 | ||
|
|
713c95d597 | ||
|
|
15534ad42e | ||
|
|
32bb3fac69 | ||
|
|
e604da4ff4 | ||
|
|
2d9938e8b2 | ||
|
|
1e99940c1d | ||
|
|
2827bcffe3 | ||
|
|
3a1b71e95c | ||
|
|
ce4e762cd5 | ||
|
|
54ec1a6cf7 | ||
|
|
bed3e987b7 | ||
|
|
639ddb3f80 | ||
|
|
e645abb771 | ||
|
|
762adce054 | ||
|
|
263bde658e | ||
|
|
3951acf12e | ||
|
|
5cb640a387 | ||
|
|
c65b9ff873 | ||
|
|
13a80fb536 | ||
|
|
23e49c52e5 | ||
|
|
9fcc73984b | ||
|
|
67338b6c85 | ||
|
|
101e7efd74 | ||
|
|
a996a24b7f | ||
|
|
44dcc9fe2b | ||
|
|
58f79e06ef | ||
|
|
f197c8201d | ||
|
|
2fc5669203 | ||
|
|
0065b93060 | ||
|
|
3f9dbd6fe2 | ||
|
|
7ceff3eaa4 | ||
|
|
7cdddf06bc | ||
|
|
77e7b136ff | ||
|
|
c8d160fc35 | ||
|
|
ee0737c9c7 | ||
|
|
0f85be7114 | ||
|
|
34ba4ceb16 | ||
|
|
f2a536d0ea | ||
|
|
815c4d4cc9 | ||
|
|
969b91bba9 | ||
|
|
f24abde76e | ||
|
|
f486b1a9ce | ||
|
|
3dd6638ef3 | ||
|
|
bcfb63b57c | ||
|
|
c7f5f6827a | ||
|
|
8ed88b2b29 | ||
|
|
75db9f4623 | ||
|
|
b69015a25a | ||
|
|
557d535e5a | ||
|
|
1cdcc8794c | ||
|
|
a0c26b748a | ||
|
|
ce5e733c05 | ||
|
|
de485272c5 | ||
|
|
eab53b805e | ||
|
|
a60046f6ef | ||
|
|
b0d223c47c | ||
|
|
185a8c776b | ||
|
|
f510ee3b4d | ||
|
|
e7a29824e8 | ||
|
|
69b86dd98c | ||
|
|
55807dc7c6 | ||
|
|
9a6ee719c4 | ||
|
|
1b02af382c | ||
|
|
4fe87a9888 | ||
|
|
91995155e9 | ||
|
|
aae0ff5aa7 | ||
|
|
821d9b8a5e | ||
|
|
2070aed38f | ||
|
|
986979eefc | ||
|
|
ed3ce54b24 | ||
|
|
c80ebf2eda | ||
|
|
42fcd6df51 | ||
|
|
1f4031da61 | ||
|
|
d0ebee74ca | ||
|
|
e8ec042d96 | ||
|
|
bd61bf32b6 | ||
|
|
8b12fac766 | ||
|
|
ab7e6b3332 | ||
|
|
548da48615 | ||
|
|
0b6128bcdd | ||
|
|
198a7d5ad3 | ||
|
|
040f244e15 | ||
|
|
98bc6f14a9 | ||
|
|
b92e6d2c48 | ||
|
|
ac3875fe08 | ||
|
|
6fdbafc67b | ||
|
|
5aebdcaa6a | ||
|
|
60517b00f3 | ||
|
|
faf5e8e82b | ||
|
|
7264982761 | ||
|
|
fedf74258f | ||
|
|
def4960be6 | ||
|
|
2969a3e4fd | ||
|
|
e1fcf44aa6 | ||
|
|
ddbfe9de57 | ||
|
|
b2ee1527af | ||
|
|
c94706745f | ||
|
|
8aef61ff09 | ||
|
|
5d6e245ec2 | ||
|
|
8dd4d1a41d | ||
|
|
10f9230139 | ||
|
|
32c94c8948 | ||
|
|
4b947dd1f9 | ||
|
|
93a7d86f78 | ||
|
|
6ed3a57c58 | ||
|
|
ecdba3898f | ||
|
|
d82a9b25cf | ||
|
|
da4849e526 | ||
|
|
da1b47ea0d | ||
|
|
a617693f93 | ||
|
|
f4c573a95e | ||
|
|
d3476c1473 | ||
|
|
d58247d996 | ||
|
|
525cc69c70 | ||
|
|
7ed1b164b5 | ||
|
|
ee441d5b4a | ||
|
|
97a5c6f5cb | ||
|
|
b6566a2bcc | ||
|
|
af389f7a47 | ||
|
|
45577fc423 | ||
|
|
c00b9b3035 | ||
|
|
f17879783c | ||
|
|
08dfcdf508 | ||
|
|
c97e8fffc4 | ||
|
|
120585954f | ||
|
|
247669644c | ||
|
|
ff15bdeaea | ||
|
|
8b67320e20 | ||
|
|
48828ed1b7 | ||
|
|
1e615db77e | ||
|
|
629416ef2f | ||
|
|
d6b1c88085 | ||
|
|
3b7079be17 | ||
|
|
48d26017b6 | ||
|
|
8bdbe1c4d7 | ||
|
|
4093443bf8 | ||
|
|
c6709aba56 | ||
|
|
a817e03fa0 | ||
|
|
a757607f35 | ||
|
|
ecc9c45f6e | ||
|
|
2e157a9e68 | ||
|
|
8a39154bd3 | ||
|
|
b5e2aa8b7f | ||
|
|
aaea709201 | ||
|
|
c2a0f5e8bc | ||
|
|
2873a66450 | ||
|
|
7331a5a0cd | ||
|
|
c331cc95c2 | ||
|
|
91aa425f43 | ||
|
|
fd5165428e | ||
|
|
6df57aebf4 | ||
|
|
40b2e4c42e | ||
|
|
a762408df8 | ||
|
|
9fb6a0261b | ||
|
|
d90edc8cf1 | ||
|
|
eecc1242be | ||
|
|
6eedb0e156 | ||
|
|
519894a461 | ||
|
|
fe3052a359 | ||
|
|
245b91bebd | ||
|
|
2e47147367 | ||
|
|
8fec3fe56c | ||
|
|
c4d56179f3 | ||
|
|
64d24f6002 | ||
|
|
074efb0813 | ||
|
|
98b96c78d7 | ||
|
|
3a962c7c05 | ||
|
|
99e3658938 | ||
|
|
9cd7ad3601 | ||
|
|
96db2b7a8b | ||
|
|
0a9c31fb09 | ||
|
|
5ed6f97846 | ||
|
|
7dc195606c | ||
|
|
b2377a3353 | ||
|
|
0fdae0c775 | ||
|
|
113bbd960f | ||
|
|
a48e09e77b | ||
|
|
3c3f759d9a | ||
|
|
5f0986d03b | ||
|
|
579794d7e0 | ||
|
|
4956543eac | ||
|
|
87043f19bc | ||
|
|
375f8ceb27 | ||
|
|
a881f23253 | ||
|
|
54106c497b | ||
|
|
1554c6d422 | ||
|
|
dccd9dcb97 | ||
|
|
496ad6a442 | ||
|
|
83a09c4af2 | ||
|
|
fac6985d01 | ||
|
|
43670ba62b | ||
|
|
ef511349f8 | ||
|
|
eea0199a21 | ||
|
|
7e0f02ecc7 | ||
|
|
7b26a65ea0 | ||
|
|
c5b92d7162 | ||
|
|
f8387f0a81 | ||
|
|
8486326b00 | ||
|
|
8f64747e75 | ||
|
|
f75efdaa03 | ||
|
|
ee8d9d0c07 | ||
|
|
6791adef46 | ||
|
|
80bcb84acf | ||
|
|
7d6b0f9ca8 | ||
|
|
594f6d4fc5 | ||
|
|
e40acb9bf6 | ||
|
|
e372871108 | ||
|
|
1c01469a3e | ||
|
|
90c2e45be2 | ||
|
|
1a783d4faf | ||
|
|
d2944983a4 | ||
|
|
26459eecb0 | ||
|
|
90dea16222 | ||
|
|
8897a326b3 | ||
|
|
f3e69ddef1 | ||
|
|
8ecaa2c4d1 | ||
|
|
3a607a81f6 | ||
|
|
4b113e4a82 | ||
|
|
3e0e0c1484 | ||
|
|
676e166459 | ||
|
|
16c0866f7f | ||
|
|
db88de206b | ||
|
|
92f353e1c5 | ||
|
|
71c80dd381 | ||
|
|
d2be917bd4 | ||
|
|
ea3dc32e98 | ||
|
|
be86f1e96f | ||
|
|
d626d45f5c | ||
|
|
f007bdb39c | ||
|
|
a0cbfe9a36 | ||
|
|
21f99081f2 | ||
|
|
45952ef143 | ||
|
|
fedaaa6fc8 | ||
|
|
1eb08e40be | ||
|
|
6a4936853b | ||
|
|
b277eb4990 | ||
|
|
74efc5c332 | ||
|
|
6653dd97a6 | ||
|
|
d031acabf5 | ||
|
|
852f666b78 | ||
|
|
9f729e9ef0 | ||
|
|
027f19e710 | ||
|
|
14786f796b | ||
|
|
411e39a096 | ||
|
|
8a72c5b9ca | ||
|
|
9e88e28a59 | ||
|
|
df73c5ad8d | ||
|
|
98707dde76 | ||
|
|
444a9afabe | ||
|
|
315bcd5b1a | ||
|
|
c719ac22da | ||
|
|
f4596998aa | ||
|
|
5414f5cf41 | ||
|
|
d919827bc8 | ||
|
|
7b40bc157d | ||
|
|
c3ec64d1ff | ||
|
|
3464cb4a05 | ||
|
|
21f40e5b42 | ||
|
|
6d8ecab766 | ||
|
|
fc2e5112c0 | ||
|
|
8f6c57d5c9 | ||
|
|
8455dc7bd2 | ||
|
|
4677cef580 | ||
|
|
9ab6bf3da1 | ||
|
|
bc65fa6654 | ||
|
|
9fe5960449 | ||
|
|
b684dd5a3b | ||
|
|
5fff0cbdb0 | ||
|
|
99dc479d78 | ||
|
|
275eef912c | ||
|
|
b9de7632e6 | ||
|
|
7a000f2a44 | ||
|
|
9d91b8c0fb | ||
|
|
84d213bdd1 | ||
|
|
a2302ad318 | ||
|
|
42eacea4be | ||
|
|
664bfe895e | ||
|
|
9c1812ce08 | ||
|
|
49c83581f9 | ||
|
|
6aa0428879 | ||
|
|
838d0c678b | ||
|
|
35555d1362 | ||
|
|
6c6b5d7be9 | ||
|
|
037b8cc54e | ||
|
|
afb518cf8e | ||
|
|
b36c7375dd | ||
|
|
069356418d | ||
|
|
6b1c725ebd | ||
|
|
a46f8f1c20 | ||
|
|
f0a704b93b | ||
|
|
cfc528df9a | ||
|
|
9231ea1446 | ||
|
|
3b3d98c4be | ||
|
|
f2f9138435 | ||
|
|
624700497b | ||
|
|
5794a64da6 | ||
|
|
7e6e9d3dcd | ||
|
|
07634edfa3 | ||
|
|
3d4742e8f7 | ||
|
|
a6f1c21e1c | ||
|
|
a0b62ab434 | ||
|
|
4a03fbfbf0 | ||
|
|
0b8501e92b | ||
|
|
000cdb08ec | ||
|
|
9a0851cb06 | ||
|
|
e4340f5015 | ||
|
|
e0521b3c95 | ||
|
|
9547be89e1 | ||
|
|
40d44269fc | ||
|
|
3bf0903453 | ||
|
|
c3abf8c05c | ||
|
|
0cd5d12d42 | ||
|
|
014398e050 | ||
|
|
4f77370977 | ||
|
|
5937215d3a | ||
|
|
ade1ce8e05 | ||
|
|
a4581dc61b | ||
|
|
50d4130b3f | ||
|
|
1147087531 | ||
|
|
4653a22635 | ||
|
|
e79501857f | ||
|
|
a8c05f6a32 | ||
|
|
c1d98cad00 | ||
|
|
fb54948f86 | ||
|
|
26297fbb5b | ||
|
|
cd342d1034 | ||
|
|
029650ef2d | ||
|
|
ca5f189e70 | ||
|
|
ac24f636df | ||
|
|
1688168bc1 | ||
|
|
6220ce6780 | ||
|
|
6107698a76 | ||
|
|
46b842afc4 | ||
|
|
a3c3fec9b4 | ||
|
|
f3a9b19104 | ||
|
|
3f773a52cc | ||
|
|
48664bb580 | ||
|
|
d9ed6f600b | ||
|
|
8a6d86727c | ||
|
|
d9abf82918 | ||
|
|
094cd67728 | ||
|
|
3cd9020ee0 | ||
|
|
6d22a4d014 | ||
|
|
a70bd4f906 | ||
|
|
4adac359e3 | ||
|
|
9adaf12c00 | ||
|
|
8bbfa2e417 | ||
|
|
9d800106cc | ||
|
|
e1ca97f323 | ||
|
|
68bb23e3b4 | ||
|
|
68397bd487 | ||
|
|
3104ddb4b6 | ||
|
|
ccad5d40ec | ||
|
|
5b7b022d9f | ||
|
|
387139b5d3 | ||
|
|
0ae10d5fbe | ||
|
|
d53397d8f8 | ||
|
|
b1006d2a14 | ||
|
|
c5cf318cdd | ||
|
|
671338c16a | ||
|
|
bbaa70e396 | ||
|
|
536d6cf63e | ||
|
|
b564a297ab | ||
|
|
6d34ae1a50 | ||
|
|
9bddd6b274 | ||
|
|
0d2457f39e | ||
|
|
420505328c | ||
|
|
0e60d71006 | ||
|
|
0f1456819b | ||
|
|
677621f2da | ||
|
|
d40138dd99 | ||
|
|
3af89a6175 | ||
|
|
d957e8f2fc | ||
|
|
681c327306 | ||
|
|
80c9afec7b | ||
|
|
eea8041abe | ||
|
|
1309bfe1ee | ||
|
|
70d3ef9984 | ||
|
|
2fac871493 | ||
|
|
0166133b61 | ||
|
|
a9e0911796 | ||
|
|
213f341257 | ||
|
|
01cc2a2c67 | ||
|
|
6c7a040c02 | ||
|
|
4e40944b26 | ||
|
|
af859438b4 | ||
|
|
7ef80e87b8 | ||
|
|
f8e873cd78 | ||
|
|
5cc97f7cf1 | ||
|
|
44c56331fd | ||
|
|
1272c3962e | ||
|
|
52ac5f16e5 | ||
|
|
ea43070e6d | ||
|
|
14cd23c28b | ||
|
|
793668021e | ||
|
|
f0ea6ef43e | ||
|
|
2b2e4845a1 | ||
|
|
2dccec99cc | ||
|
|
b060894a6c | ||
|
|
92872edb58 | ||
|
|
c5fcf49eda | ||
|
|
d66a4c0920 | ||
|
|
e8a31cf867 | ||
|
|
c1f9a88ef4 | ||
|
|
e6200e186b | ||
|
|
5564502125 | ||
|
|
e2799fcdff | ||
|
|
8e4f402e21 | ||
|
|
10d2949647 | ||
|
|
30ef23308b | ||
|
|
564132ee82 | ||
|
|
1e527e87df | ||
|
|
9692a04275 | ||
|
|
6ece8eb0c1 | ||
|
|
1f33237e8a | ||
|
|
9209508aac | ||
|
|
f750e6ff7e | ||
|
|
f2eecf774b | ||
|
|
98de4e75e0 | ||
|
|
459e32caf8 | ||
|
|
c4ad325e5c | ||
|
|
f55bd6d6cd | ||
|
|
f1ce700c93 | ||
|
|
c18e1e8456 | ||
|
|
1fcd1924c3 | ||
|
|
a1767f0425 | ||
|
|
44d95ca25f | ||
|
|
7432540c29 | ||
|
|
c289f58351 | ||
|
|
711e041ff5 | ||
|
|
0ac3534585 | ||
|
|
57821e0860 | ||
|
|
0dae798c9a | ||
|
|
732e780fd9 | ||
|
|
5577124b7a | ||
|
|
81b82c75a2 | ||
|
|
b32e322749 | ||
|
|
3031cc4561 | ||
|
|
d60abc648c | ||
|
|
a9a0233bb3 | ||
|
|
f6fa9e5122 | ||
|
|
7947e7689c | ||
|
|
b53ada7ea2 | ||
|
|
584f28534a | ||
|
|
770fde7aac | ||
|
|
3d7f918132 | ||
|
|
29b8cedc7c | ||
|
|
33b65c3bf3 | ||
|
|
34ab1bcd9c | ||
|
|
cfdc88174b | ||
|
|
15123d8924 | ||
|
|
20086d76ce | ||
|
|
1ca4fb5c37 | ||
|
|
009016a835 | ||
|
|
33dfb2a30d | ||
|
|
1604c067fd | ||
|
|
0c7419e2b3 | ||
|
|
7cf30ccb98 | ||
|
|
aa2c8c5624 | ||
|
|
875695c239 | ||
|
|
61049a1302 | ||
|
|
28db90aa82 | ||
|
|
f0e7fc5e3b | ||
|
|
2169afa8e7 | ||
|
|
508ec06d93 | ||
|
|
9fb39d9403 | ||
|
|
4879d74f80 | ||
|
|
ba3f6c4f95 | ||
|
|
481241c4f6 | ||
|
|
5798587dc6 | ||
|
|
066e3e08a2 | ||
|
|
16d6c14633 | ||
|
|
80a4a3551c | ||
|
|
74f3bb8708 | ||
|
|
c3e398b3c2 | ||
|
|
dcfa812c83 | ||
|
|
065e65d708 | ||
|
|
bca0dab381 | ||
|
|
4a45c1055e | ||
|
|
7c789746ce | ||
|
|
f46ce5576c | ||
|
|
730e6fc1fa | ||
|
|
cb36cc042c | ||
|
|
5d586418f9 | ||
|
|
44f1d026d6 | ||
|
|
defaa1095c | ||
|
|
c67f2f8027 | ||
|
|
452128565f | ||
|
|
6322d3c984 | ||
|
|
d68c820e58 | ||
|
|
79f37b4813 | ||
|
|
29b4f7c91a | ||
|
|
0a9ee57233 | ||
|
|
94b69a9c1c | ||
|
|
f0209dd1cc | ||
|
|
366e432c18 | ||
|
|
0075c0e779 | ||
|
|
d99dfd4185 | ||
|
|
0309b3ad25 | ||
|
|
7a85532b73 | ||
|
|
7299d947f7 | ||
|
|
adec7b28f1 | ||
|
|
87460a2fb6 | ||
|
|
6774a642d9 | ||
|
|
c2563da056 | ||
|
|
64b9e53916 | ||
|
|
f0bd9233b7 | ||
|
|
66efe750a8 | ||
|
|
3658fc423b | ||
|
|
86ab6f7b3d | ||
|
|
3c3ad7447e | ||
|
|
b570064b99 | ||
|
|
d9f6ef69fe | ||
|
|
bdac1d5bb4 | ||
|
|
1eee1ead5e | ||
|
|
0ec51f5b34 | ||
|
|
6c6fb05a7a | ||
|
|
09a0faacba | ||
|
|
d0d1d15de5 | ||
|
|
e4e9516d5d | ||
|
|
05eceecbea | ||
|
|
71ba1bb0d5 | ||
|
|
2160a26648 | ||
|
|
5433eac9c9 | ||
|
|
ed02733524 | ||
|
|
a99af63f34 | ||
|
|
1d900a66fe | ||
|
|
f29acc217d | ||
|
|
0a68f86200 | ||
|
|
c91dae0346 | ||
|
|
e1df7e5077 | ||
|
|
0560b54559 | ||
|
|
c78db7e835 | ||
|
|
c837a2d4b6 | ||
|
|
70b91b7a9a | ||
|
|
27079a7ec5 | ||
|
|
9563df0574 | ||
|
|
638209cc13 | ||
|
|
224c731afa | ||
|
|
0bbf937531 | ||
|
|
3556c92c3e | ||
|
|
87c5b23196 | ||
|
|
c83910c885 | ||
|
|
586622e90d | ||
|
|
e5e2430e03 | ||
|
|
04bfdba50e | ||
|
|
7abf15e9e0 | ||
|
|
6b680831b8 | ||
|
|
6cbf100828 | ||
|
|
3e7bbebe7f | ||
|
|
56d344045a | ||
|
|
7ab634cc08 | ||
|
|
9f0db3ebb5 | ||
|
|
6415eb8590 | ||
|
|
87c77b84a4 | ||
|
|
0b7bb16f22 | ||
|
|
5164b5ba78 | ||
|
|
f3c28bc66a | ||
|
|
239f7eb9e7 | ||
|
|
d6daf7a553 | ||
|
|
dfb3b230e6 | ||
|
|
484a5c878f | ||
|
|
3f27cfb13b | ||
|
|
38e2ba6ccd | ||
|
|
3dad38e614 | ||
|
|
0865c9d1bd | ||
|
|
20a8783d84 | ||
|
|
0b96fb05fc | ||
|
|
8767d62de7 | ||
|
|
74fb04e2d4 | ||
|
|
2537460e16 | ||
|
|
be3dfde3be | ||
|
|
42025035ad | ||
|
|
6a667fdf32 | ||
|
|
bfafac3d4f | ||
|
|
0cafbe9f91 | ||
|
|
2fbf172729 | ||
|
|
bb9755f4af | ||
|
|
2a01377a8a | ||
|
|
61cc6d5d07 | ||
|
|
1d74a37f60 | ||
|
|
ef9645f9e7 | ||
|
|
6a103ca3f3 | ||
|
|
c22772121b | ||
|
|
de7bc69d2a | ||
|
|
2eccd572c9 | ||
|
|
824a62024b | ||
|
|
3a3cfda919 | ||
|
|
e29120cc51 | ||
|
|
197d5c6bc3 | ||
|
|
d143cc75db | ||
|
|
1635a06c54 | ||
|
|
76de0d8c70 | ||
|
|
402a995b8f | ||
|
|
f580ba7779 | ||
|
|
bc3869b920 | ||
|
|
020f4a5a1a | ||
|
|
b054caa967 | ||
|
|
82b7633650 | ||
|
|
33497864f2 | ||
|
|
4c9d1544fa | ||
|
|
bce2367cfc | ||
|
|
390ecc48fb | ||
|
|
9ed99edd6e | ||
|
|
4362490539 | ||
|
|
f5d225fc3e | ||
|
|
063e9287fd | ||
|
|
ba376908cd | ||
|
|
caddf0021c | ||
|
|
90645f4d90 | ||
|
|
1316fcae22 | ||
|
|
27dee7297b | ||
|
|
13ecba40ae | ||
|
|
e15dd0d8b3 | ||
|
|
1ab26bc665 | ||
|
|
e6758d8c01 | ||
|
|
4621787e34 | ||
|
|
10ad35a285 | ||
|
|
d10145a6ba | ||
|
|
c9792ced32 | ||
|
|
a3fb09a33c | ||
|
|
6d875fd890 | ||
|
|
5d87fb7b67 | ||
|
|
4cbb59850b | ||
|
|
a2022b25e5 | ||
|
|
0d168f93ed | ||
|
|
94ac5b9bb7 | ||
|
|
024d358213 | ||
|
|
5562c93855 | ||
|
|
98e897d6a8 | ||
|
|
4aac6aa4f4 | ||
|
|
2bb4616e40 | ||
|
|
56e8476d2e | ||
|
|
97d81eb1b2 | ||
|
|
ffa21b26af | ||
|
|
9917712f66 | ||
|
|
11cdce6c90 | ||
|
|
8e82cf1e99 | ||
|
|
9767b11626 | ||
|
|
0f95694083 | ||
|
|
7dfc7dd9ef | ||
|
|
0407e958f1 | ||
|
|
e6a5fa1c3f | ||
|
|
6f48a7c4a4 | ||
|
|
80c56d71cb | ||
|
|
f77d9dcee2 | ||
|
|
f7195c7787 | ||
|
|
ca92cc6dc1 | ||
|
|
cd31b2ae5a | ||
|
|
00bec7174a | ||
|
|
236acab54f | ||
|
|
ba362f4457 | ||
|
|
8ed93baf8d | ||
|
|
bf953e96fa | ||
|
|
6b89a747e2 | ||
|
|
2fa1d54268 | ||
|
|
02ef34b451 | ||
|
|
1701fc71c4 | ||
|
|
fe200996db | ||
|
|
659333342f | ||
|
|
1ca5b6def2 | ||
|
|
4e8e3ee440 | ||
|
|
86dd724222 | ||
|
|
8242995027 | ||
|
|
49962a4734 | ||
|
|
509b16aee1 | ||
|
|
f3f5e4a887 | ||
|
|
7aabc1fa76 | ||
|
|
dcb5e36041 | ||
|
|
e0c072ab9c | ||
|
|
0231903868 | ||
|
|
f63bbeee79 | ||
|
|
db9e427444 | ||
|
|
4474a584df | ||
|
|
ab00ad68f1 | ||
|
|
d1e77efa1c | ||
|
|
de00353864 | ||
|
|
feec459d47 | ||
|
|
ad68d7e4f2 | ||
|
|
cf27c6bbf3 | ||
|
|
0115656d67 | ||
|
|
002687d2b1 | ||
|
|
a3267f6cd3 | ||
|
|
0ca9c536cd | ||
|
|
382a23c0b6 | ||
|
|
1f51331f67 | ||
|
|
cce6ba0746 | ||
|
|
be3c12dfb3 | ||
|
|
bfd87cf94e | ||
|
|
857bb1e483 | ||
|
|
75a131b675 | ||
|
|
d98b1c5ee1 | ||
|
|
1eeab25b7d | ||
|
|
82cc0c3c09 | ||
|
|
e102faff6c | ||
|
|
34369bd7e9 | ||
|
|
c71b620402 | ||
|
|
21b4bf23a1 | ||
|
|
d034311f2d | ||
|
|
2deed69766 | ||
|
|
bfbd21b826 | ||
|
|
ba8683301d | ||
|
|
0ed178167b | ||
|
|
b34e34de51 | ||
|
|
ba38e21e07 | ||
|
|
90bef7fddb | ||
|
|
c1b382ef34 | ||
|
|
028b88aa24 | ||
|
|
9d0ce33f5e | ||
|
|
dbb23d952c | ||
|
|
7fe7e47d53 | ||
|
|
d0c93dfd4d | ||
|
|
acdccaf80a | ||
|
|
769293ce1a | ||
|
|
8d0fe18b70 | ||
|
|
6926432a6c | ||
|
|
83f12b0840 | ||
|
|
290b7db7e4 | ||
|
|
f352c20ed9 | ||
|
|
2ccbffa165 | ||
|
|
06cd80a352 | ||
|
|
de97493e6a | ||
|
|
3a24ff0d15 | ||
|
|
c463a3fc39 | ||
|
|
fc845685cc | ||
|
|
0ef0aa1a44 | ||
|
|
337689aa45 | ||
|
|
f7e3423f9c | ||
|
|
b465c09cc8 | ||
|
|
0dcdda75be | ||
|
|
ac6c0651d6 | ||
|
|
18af6f5a12 | ||
|
|
d11ee3a702 | ||
|
|
6d9f9ce2d2 | ||
|
|
ec1496a4cc | ||
|
|
41e19185e8 | ||
|
|
e15dd6024f | ||
|
|
e52dffeece | ||
|
|
5b85bb427d | ||
|
|
4d62388617 | ||
|
|
04b8055474 | ||
|
|
3c34b6a7d2 | ||
|
|
de4964c2cd | ||
|
|
fbcaa05c03 | ||
|
|
883f28696e | ||
|
|
df52230837 | ||
|
|
a90f26a37a | ||
|
|
8c1f76d7fa | ||
|
|
f384d44f8f | ||
|
|
4ab6ed55f5 | ||
|
|
cf99bf5152 | ||
|
|
10779717cf | ||
|
|
4e5c2a9ecf | ||
|
|
db4c1bfe47 | ||
|
|
27afba1cf2 | ||
|
|
4895425b40 | ||
|
|
004c414fba | ||
|
|
c8e38b134c | ||
|
|
de5a911286 | ||
|
|
606cd7442e | ||
|
|
3ebc972268 | ||
|
|
4e39bb381c | ||
|
|
b6178681b0 | ||
|
|
29abf70cec | ||
|
|
8d63be513d | ||
|
|
e63b9d0dd6 | ||
|
|
e9fe4a82df | ||
|
|
b1fda17ac7 | ||
|
|
bad44b145c | ||
|
|
77669cedf6 | ||
|
|
19238c389f | ||
|
|
1747ff98b5 | ||
|
|
8fa5824e3e | ||
|
|
6a674d7a7e | ||
|
|
dad3b8cd6b | ||
|
|
9179d2198d | ||
|
|
d096bef234 | ||
|
|
f2c47a1b84 | ||
|
|
bc2ac4e915 | ||
|
|
ff215412c8 |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,9 +1,8 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: LucasGGamerM
|
||||||
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
|
||||||
|
|||||||
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -8,25 +8,33 @@ assignees: ''
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
|
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
**To Reproduce**
|
**To reproduce**
|
||||||
|
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
|
**Does this happen in the official app?**
|
||||||
|
|
||||||
|
Does this issue also occur with the respective upstream release?
|
||||||
|
> No / Yes
|
||||||
|
|
||||||
|
> In case it does, please consider filing an [upstream bug report](https://github.com/mastodon/mastodon-android/issues) instead.
|
||||||
|
> If this bug is seriously impacting your usage or you think I might want to try to fix it for Megalodon, feel free to still create this issue!
|
||||||
|
|
||||||
**Screenshots and screen recordings**
|
**Screenshots and screen recordings**
|
||||||
|
|
||||||
If applicable, add screenshots (and screen recordings, if possible) to help explain your problem.
|
If applicable, add screenshots (and screen recordings, if possible) to help explain your problem.
|
||||||
|
|
||||||
**Version**
|
**Version**
|
||||||
Megalodon version: [e.g. v1.1.4+fork.#]
|
|
||||||
|
|
||||||
**Additional context**
|
Moshidon version: [e.g. v1.1.4+fork.#]
|
||||||
- Does this issue also occur with the respective upstream release? (Please test using the respective `upstream-xxxxxx.apk` provided in [Releases](https://github.com/sk22/megalodon/releases)) No / Yes (`mastodon#…`)
|
|
||||||
|
|
||||||
> In this case, please consider filing an [upstream bug report](https://github.com/mastodon/mastodon-android/issues) instead. If this bug is seriously impacting your usage or you think I might want to try to fix it for Megalodon, feel free to still create this issue!
|
|
||||||
|
|
||||||
**Crash log**
|
**Crash log**
|
||||||
|
|
||||||
If you know your way around Android development tools, please consider attaching a crash log, if possible.
|
If you know your way around Android development tools, please consider attaching a crash log, if possible.
|
||||||
|
|||||||
132
README.md
132
README.md
@@ -1,23 +1,38 @@
|
|||||||

|

|
||||||
|
|
||||||
# Megalodon
|
# Moshidon, the material you mastodon client!
|
||||||
|
|
||||||
[](https://translate.codeberg.org/engage/megalodon/)
|
> A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly won’t ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
|
||||||
|
|
||||||
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
|
||||||
|
|
||||||
<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://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk"><img height="50" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a>
|
|
||||||
|
|
||||||
> A fork of the [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly won’t ever be implemented, such as the federated timeline, unlisted posting and an image description viewer.
|
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
||||||
|
|
||||||
|
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
|
||||||
|
alt="Get it on IzzyOnDroid"
|
||||||
|
height="80">](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda)
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
## Key features
|
## Key features
|
||||||
|
|
||||||
|
### **Material you theme support on Android 12+ devices!**
|
||||||
|
|
||||||
|
### **Show posts filtered with a warning!**
|
||||||
|
|
||||||
|
**Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:**
|
||||||
|
|
||||||
|
Before | After
|
||||||
|
:-------------------------:|:-------------------------:
|
||||||
|
 | 
|
||||||
|
|
||||||
|
|
||||||
|
### **Color themes**
|
||||||
|
|
||||||
|
**Allows you to change theme within the app. Supports Purple, pink, green, blue, orange and yellow!**
|
||||||
|
|
||||||
### **Unlisted posting**
|
### **Unlisted posting**
|
||||||
|
|
||||||
**Allows you to post publicly without having your post show up in trends, hashtags or public timelines (i.e., in the tabs “Community”, “Federated” and “Posts”).**
|
**Allows you to post publicly without having your post show up in trends, hashtags or public timelines (i.e., in the tabs “Local”, “Community” and “Posts”).**
|
||||||
|
|
||||||
When posting with Unlisted visibility, your posts will still be publicly accessible in your profile. They will also be shown in people’s Home timelines, but only if they follow you or someone they follow reposted/replied to your post.
|
When posting with Unlisted visibility, your posts will still be publicly accessible in your profile. They will also be shown in people’s Home timelines, but only if they follow you or someone they follow reposted/replied to your post.
|
||||||
|
|
||||||
@@ -43,63 +58,29 @@ This is important to **ensure the content you’re sharing is as accessible as p
|
|||||||
|
|
||||||
On the Fediverse, it’s quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
|
On the Fediverse, it’s quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
|
||||||
|
|
||||||
|
### **Bookmarks**
|
||||||
|
|
||||||
|
**They allow for quickly saving posts and viewing them through the Bookmarks button on the top right of your profile.**
|
||||||
|
|
||||||
|
To bookmark a post, press the button between the Favorite and Share buttons on the bottom of the post. Bookmarks are saved privately, so the post authors won’t know you saved their post – the list of bookmarked posts is only visible to you.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### From app stores
|
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Moshidon will automatically notify you about new updates inside the app.**
|
||||||
|
|
||||||
* **[Izzy's F-Droid repository](https://apt.izzysoft.de/fdroid/repo)**: [apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
|
To install this app on your Android device, download the [latest release from GitHub](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||||
|
|
||||||
Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first:
|
Moshidon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Megalodon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
||||||
|
|
||||||
`https://apt.izzysoft.de/fdroid/repo`
|
|
||||||
|
|
||||||
* **[Google Play Store](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)**: [play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
|
|
||||||
|
|
||||||
* **[F-Droid.org](https://f-droid.org)?** Not yet, sorry!
|
|
||||||
|
|
||||||
If you want, you can help me figure out if something's missing in the [Issue #47: F-Droid.org](https://github.com/sk22/megalodon/issues/47)
|
|
||||||
|
|
||||||
### Directly from GitHub
|
|
||||||
|
|
||||||
Press the download button to download the APK. Open the downloaded file on your Android device to install it. Megalodon will automatically notify you about new updates inside the app.
|
|
||||||
|
|
||||||
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
|
||||||
|
|
||||||
You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/sk22/megalodon/releases) page.
|
|
||||||
|
|
||||||
Megalodon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Megalodon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
Moshidon is also available in [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda), compatible with all F-Droid clients. The APK provided here is the same as the one included in the Releases.
|
||||||
|
|
||||||
## Release variants
|
## Release variants
|
||||||
|
|
||||||
All downloads can be found on the [Releases](https://github.com/sk22/megalodon/releases) page.
|
All downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||||
|
|
||||||
**`megalodon.apk`**
|
**`moshidon.apk`**
|
||||||
|
|
||||||
Variant with an integrated updater. If you download Megalodon from here (and not from an app store), just download the regular `megalodon.apk`.
|
|
||||||
|
|
||||||
**`upstream-1234abc.apk`**
|
|
||||||
|
|
||||||
This is an **unmodified version** of the official [Mastodon for Android](https://github.com/mastodon/mastodon-android) app the respective Megalodon release is based on. Should you find any bugs in Megalodon (which you will), try to see if it occurs with this variant, too. The last 7 digits of the file name are important to know which version of the official app you're using.
|
|
||||||
|
|
||||||
<!-- **`megalodon-fdroid.apk`**
|
|
||||||
|
|
||||||
Variant without the integrated updater. This is the variant to be published to F-Droid.org where an integrated updater is not necessary. -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contribution
|
|
||||||
|
|
||||||
### Translation
|
|
||||||
|
|
||||||
As with the source code, the translation is sourced from the official project, which you can contribute to on the official “**Mastodon for Android**” Crowdin project: https://crowdin.com/project/mastodon-for-android
|
|
||||||
|
|
||||||
There's also a handful of custom strings exclusive to this projects that would need to be translated. You can help translate **Megalodon** on Weblate: https://translate.codeberg.org/projects/megalodon/
|
|
||||||
|
|
||||||
[](https://translate.codeberg.org/engage/megalodon/)
|
|
||||||
|
|
||||||
|
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -108,9 +89,16 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
|
||||||
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
|
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
|
||||||
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
|
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
|
||||||
* [Add “Federation” tab and change Discover tab order](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/add-federated-timeline) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/8))
|
* Adding a useful private profile note box!*
|
||||||
|
* Auto hiding the compose button on scroll!*
|
||||||
|
* Adding the hability to remind yourself to add alt text to images!*
|
||||||
|
* An indicator for if an image has alt text or not*
|
||||||
|
* Adding the ability to have drafts!*
|
||||||
|
* Also adding the ability to view announcements from your instance!*
|
||||||
|
* Adding the ability to post for local timeline only (Only on instances that support it!)*
|
||||||
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
||||||
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
||||||
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
||||||
@@ -128,15 +116,11 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Show visibility of original post when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-reply-visibility)
|
* [Show visibility of original post when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-reply-visibility)
|
||||||
* [Clickable reply/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
|
* [Clickable reply/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
|
||||||
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
|
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
|
||||||
* [Add push notification setting for post notifications](https://github.com/sk22/megalodon/commit/b190480d7739be47f23543d9e7644660f9b4b4ee)
|
|
||||||
* [Add option to allow voting for multiple options on polls](https://github.com/sk22/megalodon/commit/5b28468efd49387b4f8b83f142f3adf3104ca60c)
|
|
||||||
* [Add translate function](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/translate-button)
|
|
||||||
* [Add language selector](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/language-selector)
|
|
||||||
* [Implement deleting notifications](https://github.com/sk22/megalodon/commit/b0f9ce081f69f29ad59658fc00ca41372cd2677d) (disabled by default)
|
|
||||||
|
|
||||||
|
|
||||||
### Behavior
|
### Behavior
|
||||||
|
|
||||||
|
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
|
||||||
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
||||||
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
||||||
* [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
|
* [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
|
||||||
@@ -144,17 +128,6 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
|
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
|
||||||
* [Option to always reveal content warnings](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
|
* [Option to always reveal content warnings](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
|
||||||
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
|
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
|
||||||
* [No ellipsis for long poll answers](https://github.com/mastodon/mastodon-android/commit/c9aae828e2518adccdc092e41f8d1f0489636271)
|
|
||||||
* [Show poll vote button for multiple and single answer polls](https://github.com/mastodon/mastodon-android/commit/e14dfda2fdf32f0fa3043504ac5831683a87559a)
|
|
||||||
* [Show own vote after voting](https://github.com/mastodon/mastodon-android/commit/4ab9e25fec4fd9c10b7a8ddd1be522b3cc12cf28) ([Closes issue](https://github.com/mastodon/mastodon-android/commit/4ab9e25fec4fd9c10b7a8ddd1be522b3cc12cf28))
|
|
||||||
* [Make inline emoji search case-insensitive and don't only search from start of emoji names](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:better-inline-emoji-search) ([Pull request](https://github.com/mastodon/mastodon-android/pull/445))
|
|
||||||
* [Include subject line when sharing e.g. a website to Megalodon](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:external-share-include-subject)
|
|
||||||
* [Improve semantics for voting on polls (radio buttons and checkboxes)](https://github.com/sk22/megalodon/commit/6fd58c96827cb1d2da329cebdc170a1425dd18d7)
|
|
||||||
* [Copy post URL when long-pressing share button](https://github.com/sk22/megalodon/commit/ba36347f03278763ecec617b1ce57ba89db7be72)
|
|
||||||
* [Add option to disable swiping between tabs](https://github.com/sk22/megalodon/commit/1f20b21fc84bf006c1ec14bd2229cbfad5215ec8)
|
|
||||||
* [Resolve Fediverse links in the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/open-urls-in-app)
|
|
||||||
* [Long-click boost button to "quote" a post](https://github.com/sk22/megalodon/commit/b25a237c20c6a924ed4d9b357999867c3a32b32b)
|
|
||||||
* [Preserve whitespaces in HTML](https://github.com/sk22/megalodon/commit/7d876bddc7a07d98f0fecbf62b13bdb9fcce3412)
|
|
||||||
|
|
||||||
|
|
||||||
### Visual
|
### Visual
|
||||||
@@ -162,13 +135,6 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
|
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
|
||||||
* [Improvements to the true black mode](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:true-black-improvements)
|
* [Improvements to the true black mode](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:true-black-improvements)
|
||||||
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)
|
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)
|
||||||
* [Custom color themes](https://github.com/sk22/megalodon/pull/124) by [@LucasGGamerM](https://github.com/LucasGGamerM)
|
|
||||||
* [Custom "megalodon" text logo](https://github.com/sk22/megalodon/commit/563afd487ca5c608cfbb00fa3909d3c27384acc0) by [@LucasGGamerM](https://github.com/LucasGGamerM)
|
|
||||||
* [Custom login screen](https://github.com/sk22/megalodon/commit/9bbf8c4618dbe13accaeb3b5482bf3fe88cac4c0)
|
|
||||||
* [More distinct filled boost icon](https://github.com/sk22/megalodon/commits/more-distinct-filled-boost-icon)
|
|
||||||
* Material You color theme by [@LucasGGamerM](https://github.com/LucasGGamerM)
|
|
||||||
* [Animations for interaction buttons](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/animate-buttons)
|
|
||||||
* [Dedicated icons for different notification types](https://github.com/sk22/megalodon/pull/178) by [@florian-obernberger](https://github.com/florian-obernberger)
|
|
||||||
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
@@ -185,4 +151,6 @@ This project is released under the [GPL-3 License](./LICENSE).
|
|||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
<a rel="me" href="https://floss.social/@megalodon">@megalodon<wbr>@floss.social</a>
|
[Official matrix chatroom:](https://matrix.to/#/#moshidon:matrix.org) https://matrix.to/#/#moshidon:matrix.org
|
||||||
|
|
||||||
|
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
title: Megalodon
|
title: Moshidon
|
||||||
layout: default
|
layout: default
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Megalodon</title>
|
<title>Moshidon</title>
|
||||||
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
||||||
<link rel="me" href="https://floss.social/@mastodon">
|
<link rel="me" href="https://floss.social/@mastodon">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
||||||
@@ -14,4 +14,4 @@
|
|||||||
{{ content }}
|
{{ content }}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
3
fix-metadata-markdown-lists.sh
Executable file
3
fix-metadata-markdown-lists.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
find metadata -name '*.txt' -exec sed -Ei 's/^[–—─•·*]\s+/- /' {} \;
|
||||||
@@ -16,4 +16,4 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# Automatically convert third-party libraries to use AndroidX
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
android.enableJetifier=true
|
android.enableJetifier=false
|
||||||
BIN
img/izzy-badge.png
Normal file
BIN
img/izzy-badge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -5,12 +5,12 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
compileSdk 33
|
compileSdk 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
archivesBaseName = "megalodon"
|
archivesBaseName = "moshidon"
|
||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.moshinda"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 62
|
versionCode 93
|
||||||
versionName "1.1.5+fork.62"
|
versionName "1.1.4+fork.93.moshinda"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,7 @@ dependencies {
|
|||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
implementation 'de.psdev:async-otto:1.0.3'
|
implementation 'de.psdev:async-otto:1.0.3'
|
||||||
implementation 'org.parceler:parceler-api:1.1.12'
|
implementation 'org.parceler:parceler-api:1.1.12'
|
||||||
|
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:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
|
|||||||
BIN
mastodon/src/github/ic_launcher-playstore.png
Normal file
BIN
mastodon/src/github/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -14,12 +14,14 @@ import android.os.Build;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
@@ -61,6 +63,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
info=new UpdateInfo();
|
info=new UpdateInfo();
|
||||||
info.version=prefs.getString("version", null);
|
info.version=prefs.getString("version", null);
|
||||||
info.size=prefs.getLong("apkSize", 0);
|
info.size=prefs.getLong("apkSize", 0);
|
||||||
|
info.changelog=prefs.getString("changelog", null);
|
||||||
downloadID=prefs.getLong("downloadID", 0);
|
downloadID=prefs.getLong("downloadID", 0);
|
||||||
if(downloadID==0 || !getUpdateApkFile().exists()){
|
if(downloadID==0 || !getUpdateApkFile().exists()){
|
||||||
state=UpdateState.UPDATE_AVAILABLE;
|
state=UpdateState.UPDATE_AVAILABLE;
|
||||||
@@ -84,6 +87,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
.remove("apkURL")
|
.remove("apkURL")
|
||||||
.remove("checkedByBuild")
|
.remove("checkedByBuild")
|
||||||
.remove("downloadID")
|
.remove("downloadID")
|
||||||
|
.remove("changelog")
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,61 +115,70 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
|
|
||||||
private void actuallyCheckForUpdates(){
|
private void actuallyCheckForUpdates(){
|
||||||
Request req=new Request.Builder()
|
Request req=new Request.Builder()
|
||||||
.url("https://api.github.com/repos/sk22/megalodon/releases/latest")
|
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases")
|
||||||
.build();
|
.build();
|
||||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||||
try(Response resp=call.execute()){
|
try(Response resp=call.execute()){
|
||||||
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
|
JsonArray arr=JsonParser.parseReader(resp.body().charStream()).getAsJsonArray();
|
||||||
String tag=obj.get("tag_name").getAsString();
|
for (JsonElement jsonElement : arr) {
|
||||||
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
JsonObject obj = jsonElement.getAsJsonObject();
|
||||||
Matcher matcher=pattern.matcher(tag);
|
if (obj.get("prerelease").getAsBoolean() && !GlobalUserPreferences.enablePreReleases) continue;
|
||||||
if(!matcher.find()){
|
|
||||||
Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int newMajor=Integer.parseInt(matcher.group(1)),
|
|
||||||
newMinor=Integer.parseInt(matcher.group(2)),
|
|
||||||
newRevision=Integer.parseInt(matcher.group(3)),
|
|
||||||
newForkNumber=Integer.parseInt(matcher.group(4));
|
|
||||||
matcher=pattern.matcher(BuildConfig.VERSION_NAME);
|
|
||||||
String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
|
|
||||||
if(!matcher.find()){
|
|
||||||
Log.w(TAG, "actuallyCheckForUpdates: current version has wrong format: "+BuildConfig.VERSION_NAME);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int curMajor=Integer.parseInt(matcher.group(1)),
|
|
||||||
curMinor=Integer.parseInt(matcher.group(2)),
|
|
||||||
curRevision=Integer.parseInt(matcher.group(3)),
|
|
||||||
curForkNumber=Integer.parseInt(matcher.group(4));
|
|
||||||
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
|
||||||
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
|
||||||
if(newVersion>curVersion || newForkNumber>curForkNumber){
|
|
||||||
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
|
||||||
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
|
||||||
for(JsonElement el:obj.getAsJsonArray("assets")){
|
|
||||||
JsonObject asset=el.getAsJsonObject();
|
|
||||||
if("megalodon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
|
||||||
long size=asset.get("size").getAsLong();
|
|
||||||
String url=asset.get("browser_download_url").getAsString();
|
|
||||||
|
|
||||||
UpdateInfo info=new UpdateInfo();
|
String tag=obj.get("tag_name").getAsString();
|
||||||
info.size=size;
|
String changelog=obj.get("body").getAsString();
|
||||||
info.version=version;
|
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
||||||
this.info=info;
|
Matcher matcher=pattern.matcher(tag);
|
||||||
|
if(!matcher.find()){
|
||||||
|
Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int newMajor=Integer.parseInt(matcher.group(1)),
|
||||||
|
newMinor=Integer.parseInt(matcher.group(2)),
|
||||||
|
newRevision=Integer.parseInt(matcher.group(3)),
|
||||||
|
newForkNumber=Integer.parseInt(matcher.group(4));
|
||||||
|
matcher=pattern.matcher(BuildConfig.VERSION_NAME);
|
||||||
|
String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
|
||||||
|
if(!matcher.find()){
|
||||||
|
Log.w(TAG, "actuallyCheckForUpdates: current version has wrong format: "+BuildConfig.VERSION_NAME);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int curMajor=Integer.parseInt(matcher.group(1)),
|
||||||
|
curMinor=Integer.parseInt(matcher.group(2)),
|
||||||
|
curRevision=Integer.parseInt(matcher.group(3)),
|
||||||
|
curForkNumber=Integer.parseInt(matcher.group(4));
|
||||||
|
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
||||||
|
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
||||||
|
if(newVersion>curVersion || newForkNumber>curForkNumber){
|
||||||
|
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
||||||
|
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
||||||
|
for(JsonElement el:obj.getAsJsonArray("assets")){
|
||||||
|
JsonObject asset=el.getAsJsonObject();
|
||||||
|
if("moshidon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
||||||
|
long size=asset.get("size").getAsLong();
|
||||||
|
String url=asset.get("browser_download_url").getAsString();
|
||||||
|
|
||||||
getPrefs().edit()
|
UpdateInfo info=new UpdateInfo();
|
||||||
.putLong("apkSize", size)
|
info.size=size;
|
||||||
.putString("version", version)
|
info.version=version;
|
||||||
.putString("apkURL", url)
|
info.changelog=changelog;
|
||||||
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
this.info=info;
|
||||||
.remove("downloadID")
|
|
||||||
.apply();
|
|
||||||
|
|
||||||
break;
|
getPrefs().edit()
|
||||||
|
.putLong("apkSize", size)
|
||||||
|
.putString("version", version)
|
||||||
|
.putString("apkURL", url)
|
||||||
|
.putString("changelog", changelog)
|
||||||
|
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
||||||
|
.remove("downloadID")
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
|
|
||||||
}catch(Exception x){
|
}catch(Exception x){
|
||||||
Log.w(TAG, "actuallyCheckForUpdates", x);
|
Log.w(TAG, "actuallyCheckForUpdates", x);
|
||||||
}finally{
|
}finally{
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||||
|
<path android:pathData="M3.897 4.054L3.97 3.97c0.266-0.267 0.683-0.29 0.976-0.073L5.03 3.97 10 8.939l4.97-4.97c0.266-0.266 0.683-0.29 0.976-0.072L16.03 3.97c0.267 0.266 0.29 0.683 0.073 0.976L16.03 5.03 11.061 10l4.97 4.97c0.266 0.266 0.29 0.683 0.072 0.976L16.03 16.03c-0.266 0.267-0.683 0.29-0.976 0.073L14.97 16.03 10 11.061l-4.97 4.97c-0.266 0.266-0.683 0.29-0.976 0.072L3.97 16.03c-0.267-0.266-0.29-0.683-0.073-0.976L3.97 14.97 8.939 10l-4.97-4.97C3.704 4.764 3.68 4.347 3.898 4.054L3.97 3.97 3.897 4.054z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M22 6.5c0 3.038-2.462 5.5-5.5 5.5S11 9.538 11 6.5 13.462 1 16.5 1 22 3.462 22 6.5zm-7.146-2.354c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708L15.793 6.5l-1.647 1.646c-0.195 0.196-0.195 0.512 0 0.707 0.196 0.196 0.512 0.196 0.708 0L16.5 7.208l1.646 1.647c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.707L17.207 6.5l1.647-1.646c0.195-0.196 0.195-0.512 0-0.708-0.196-0.195-0.512-0.195-0.708 0L16.5 5.793l-1.646-1.647zM19.5 14v-1.732c0.551-0.287 1.056-0.651 1.5-1.078v7.56c0 1.733-1.357 3.15-3.066 3.245L17.75 22H6.25c-1.733 0-3.15-1.357-3.245-3.066L3 18.75V7.25C3 5.517 4.356 4.1 6.066 4.005L6.25 4h4.248c-0.198 0.474-0.34 0.977-0.422 1.5H6.25c-0.918 0-1.671 0.707-1.744 1.606L4.5 7.25V14H9c0.38 0 0.694 0.282 0.743 0.648L9.75 14.75C9.75 15.993 10.757 17 12 17c1.19 0 2.166-0.925 2.245-2.096l0.005-0.154c0-0.38 0.282-0.694 0.648-0.743L15 14h4.5zm-15 1.5v3.25c0 0.918 0.707 1.671 1.606 1.744L6.25 20.5h11.5c0.918 0 1.671-0.707 1.744-1.607L19.5 18.75V15.5h-3.825c-0.335 1.648-1.75 2.904-3.475 2.995L12 18.5c-1.747 0-3.215-1.195-3.632-2.812L8.325 15.5H4.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||||
|
<path android:pathData="M26 7.5c0 3.59-2.91 6.5-6.5 6.5S13 11.09 13 7.5 15.91 1 19.5 1 26 3.91 26 7.5zm-9.146-3.354c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708L18.793 7.5l-2.647 2.646c-0.195 0.196-0.195 0.512 0 0.708 0.196 0.195 0.512 0.195 0.708 0L19.5 8.207l2.646 2.647c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.708L20.207 7.5l2.647-2.646c0.195-0.196 0.195-0.512 0-0.708-0.196-0.195-0.512-0.195-0.708 0L19.5 6.793l-2.646-2.647zM25 22.75V12.6c-0.443 0.476-0.947 0.896-1.5 1.245V16h-6l-0.102 0.007c-0.366 0.05-0.648 0.363-0.648 0.743 0 1.519-1.231 2.75-2.75 2.75s-2.75-1.231-2.75-2.75l-0.007-0.102C11.193 16.282 10.88 16 10.5 16h-6V7.25c0-0.966 0.784-1.75 1.75-1.75h6.02c0.145-0.525 0.345-1.028 0.595-1.5H6.25C4.455 4 3 5.455 3 7.25v15.5C3 24.545 4.455 26 6.25 26h15.5c1.795 0 3.25-1.455 3.25-3.25zm-20.5 0V17.5h5.316l0.041 0.204C10.291 19.592 11.982 21 14 21l0.215-0.005c1.994-0.1 3.627-1.574 3.969-3.495H23.5v5.25c0 0.966-0.784 1.75-1.75 1.75H6.25c-0.966 0-1.75-0.784-1.75-1.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||||
|
<path android:pathData="M10 2c4.418 0 8 3.582 8 8 0 2.706-1.142 4.5-3 4.5-1.226 0-2.14-0.781-2.62-2.09C11.784 13.393 10.781 14 9.5 14 7.36 14 6 12.307 6 10c0-2.337 1.313-4 3.5-4 1.052 0 1.901 0.385 2.5 1.044V6.5C12 6.224 12.224 6 12.5 6c0.245 0 0.45 0.177 0.492 0.41L13 6.5V10c0 2.223 0.813 3.5 2 3.5s2-1.277 2-3.5c0-3.866-3.134-7-7-7s-7 3.134-7 7 3.134 7 7 7c0.823 0 1.626-0.142 2.383-0.416 0.26-0.094 0.547 0.04 0.64 0.3 0.095 0.26-0.04 0.546-0.3 0.64C11.859 17.838 10.94 18 10 18c-4.418 0-8-3.582-8-8s3.582-8 8-8zM9.5 7C7.924 7 7 8.17 7 10c0 1.797 0.966 3 2.5 3s2.5-1.203 2.5-3c0-1.83-0.924-3-2.5-3z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M6.25 4.5C5.283 4.5 4.5 5.284 4.5 6.25v11.5c0 0.966 0.783 1.75 1.75 1.75h11.5c0.966 0 1.75-0.784 1.75-1.75v-4c0-0.414 0.335-0.75 0.75-0.75 0.414 0 0.75 0.336 0.75 0.75v4c0 1.795-1.456 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V6.25C3 4.455 4.455 3 6.25 3h4C10.664 3 11 3.336 11 3.75S10.664 4.5 10.25 4.5h-4zM13 3.75C13 3.336 13.335 3 13.75 3h6.5C20.664 3 21 3.336 21 3.75v6.5c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75V5.56l-5.22 5.22c-0.293 0.293-0.768 0.293-1.06 0-0.293-0.293-0.293-0.768 0-1.06l5.22-5.22h-4.69C13.335 4.5 13 4.164 13 3.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M8.502 11.5c0.554 0 1.002 0.448 1.002 1.002 0 0.553-0.448 1.002-1.002 1.002-0.553 0-1.002-0.449-1.002-1.002 0-0.554 0.449-1.003 1.002-1.003zM12 4.353v6.651h7.442L17.72 9.28c-0.267-0.266-0.29-0.683-0.073-0.977L17.72 8.22c0.266-0.266 0.683-0.29 0.976-0.072L18.78 8.22l2.997 2.998c0.266 0.266 0.29 0.682 0.073 0.976l-0.073 0.084-2.996 3.003c-0.293 0.294-0.767 0.294-1.06 0.002-0.267-0.266-0.292-0.683-0.075-0.977l0.073-0.084 1.713-1.717h-7.431L12 19.25c0 0.466-0.421 0.82-0.88 0.738l-8.5-1.501C2.26 18.424 2 18.112 2 17.748V5.75c0-0.368 0.266-0.681 0.628-0.74l8.5-1.396C11.585 3.539 12 3.89 12 4.354zm-1.5 0.883l-7 1.15v10.732l7 1.236V5.237zM13 18.5h0.765l0.102-0.007c0.366-0.05 0.649-0.364 0.648-0.744l-0.007-4.25H13v5zm0.002-8.502L13 8.726V5h0.745c0.38 0 0.693 0.281 0.743 0.647l0.007 0.101L14.502 10h-1.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M14.704 3.44C14.895 3.667 15 3.953 15 4.248V19.75c0 0.69-0.56 1.25-1.25 1.25-0.296 0-0.582-0.105-0.808-0.296l-4.967-4.206H4.25c-1.243 0-2.25-1.008-2.25-2.25v-4.5c0-1.243 1.007-2.25 2.25-2.25h3.725l4.968-4.204c0.526-0.446 1.315-0.38 1.761 0.147zM13.5 4.787l-4.975 4.21H4.25c-0.414 0-0.75 0.337-0.75 0.75v4.5c0 0.415 0.336 0.75 0.75 0.75h4.275L13.5 19.21V4.787z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||||
|
<path android:pathData="M16.5 4.814c0-1.094-1.307-1.66-2.105-0.912l-4.937 4.63C9.134 8.836 8.706 9.005 8.261 9.005H5.25C3.455 9.005 2 10.46 2 12.255v3.492c0 1.795 1.455 3.25 3.25 3.25h3.012c0.444 0 0.872 0.17 1.196 0.473l4.937 4.626c0.799 0.748 2.105 0.182 2.105-0.912V4.814zm-6.016 4.812L15 5.39v17.216l-4.516-4.232c-0.602-0.564-1.397-0.878-2.222-0.878H5.25c-0.966 0-1.75-0.784-1.75-1.75v-3.492c0-0.966 0.784-1.75 1.75-1.75h3.011c0.826 0 1.62-0.314 2.223-0.88z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06L6.438 7.5H4.25C3.007 7.499 2 8.506 2 9.749v4.497c0 1.243 1.007 2.25 2.25 2.25h3.68c0.183 0 0.36 0.068 0.498 0.19l4.491 3.994C13.725 21.396 15 20.824 15 19.746V16.06l5.72 5.72c0.292 0.292 0.767 0.292 1.06 0 0.293-0.293 0.293-0.768 0-1.061L3.28 2.22zM13.5 14.56v4.629l-4.075-3.624c-0.412-0.366-0.944-0.569-1.495-0.569H4.25c-0.414 0-0.75-0.335-0.75-0.75V9.75C3.5 9.335 3.836 9 4.25 9h3.688l5.562 5.56zm0-9.753v5.511l1.5 1.5V4.25c0-1.079-1.274-1.65-2.08-0.934l-3.4 3.022 1.063 1.063L13.5 4.807zm3.641 9.152l1.138 1.138C18.741 14.163 19 13.111 19 12c0-1.203-0.304-2.338-0.84-3.328-0.198-0.364-0.653-0.5-1.017-0.303-0.364 0.197-0.5 0.653-0.303 1.017 0.42 0.777 0.66 1.666 0.66 2.614 0 0.691-0.127 1.351-0.359 1.96zm2.247 2.247l1.093 1.094C21.445 15.763 22 13.946 22 12c0-2.226-0.728-4.284-1.96-5.946-0.246-0.333-0.716-0.403-1.048-0.157-0.333 0.247-0.403 0.716-0.157 1.05C19.881 8.358 20.5 10.106 20.5 12c0 1.531-0.404 2.966-1.112 4.206z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||||
|
<path android:pathData="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06l5.724 5.725H5.25C3.455 9.005 2 10.46 2 12.255v3.492c0 1.795 1.455 3.25 3.25 3.25h3.012c0.444 0 0.872 0.17 1.196 0.473l4.937 4.626c0.799 0.748 2.105 0.182 2.105-0.912v-5.623l8.22 8.22c0.292 0.292 0.767 0.292 1.06 0 0.293-0.293 0.293-0.768 0-1.061L3.28 2.22zM15 16.06v6.547l-4.516-4.231c-0.602-0.565-1.397-0.879-2.222-0.879H5.25c-0.966 0-1.75-0.783-1.75-1.75v-3.492c0-0.966 0.784-1.75 1.75-1.75h3.011c0.35 0 0.693-0.056 1.02-0.164L15 16.061zm-4.378-8.62l1.061 1.061L15 5.392v6.427l1.5 1.5V4.814c0-1.094-1.307-1.66-2.105-0.912L10.622 7.44zm9.55 9.55l1.137 1.137C21.912 16.88 22.25 15.478 22.25 14c0-2.136-0.706-4.11-1.897-5.697-0.249-0.332-0.719-0.399-1.05-0.15-0.332 0.249-0.399 0.719-0.15 1.05C20.156 10.54 20.75 12.199 20.75 14c0 1.058-0.205 2.067-0.578 2.99zm2.803 2.803l1.095 1.096c1.224-2.008 1.93-4.366 1.93-6.89 0-3.35-1.245-6.414-3.298-8.747-0.274-0.31-0.747-0.341-1.058-0.068-0.311 0.274-0.342 0.748-0.068 1.059C23.396 8.313 24.5 11.027 24.5 14c0 2.107-0.554 4.084-1.525 5.793z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -12,10 +12,17 @@
|
|||||||
|
|
||||||
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MastodonApp"
|
android:name=".MastodonApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:label="@string/sk_app_name"
|
android:label="@string/mo_app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -34,7 +41,7 @@
|
|||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<data android:scheme="megalodon-android-auth" android:host="callback"/>
|
<data android:scheme="moshidon-android-auth" android:host="callback"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize">
|
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize">
|
||||||
|
|||||||
89
mastodon/src/main/assets/blocks.tsv
Normal file
89
mastodon/src/main/assets/blocks.tsv
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
|
||||||
|
# https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
|
||||||
|
# This list contains domains of toxic mastodon instances
|
||||||
|
# Last-Modified: 1672044500
|
||||||
|
|
||||||
|
# gab - a neonazi social network
|
||||||
|
gab.ai
|
||||||
|
gab.com
|
||||||
|
gab.protohype.net
|
||||||
|
|
||||||
|
# consequence-free speech
|
||||||
|
social.unzensiert.to
|
||||||
|
freeatlantis.com
|
||||||
|
|
||||||
|
# reactionary bigotry and hatespeech against magrinalized groups
|
||||||
|
poa.st
|
||||||
|
freespeechextremist.com
|
||||||
|
rdrama.cc
|
||||||
|
outpoa.st
|
||||||
|
anime.website
|
||||||
|
gameliberty.club
|
||||||
|
social.byoblu.com
|
||||||
|
yggdrasil.social
|
||||||
|
smuglo.li
|
||||||
|
dogeposting.social
|
||||||
|
unsafe.space
|
||||||
|
freezepeach.xyz
|
||||||
|
|
||||||
|
# + CSAM
|
||||||
|
rojogato.com
|
||||||
|
|
||||||
|
# antivaxxer shitposting & fearmongering
|
||||||
|
shadowsocial.org
|
||||||
|
|
||||||
|
# Kiwifarms
|
||||||
|
kiwifarms.net
|
||||||
|
kiwifarms.cc
|
||||||
|
kiwifarms.is
|
||||||
|
kiwifarms.pleroma.net
|
||||||
|
|
||||||
|
|
||||||
|
# https://mastodon.art/@Curator/109649354849593592
|
||||||
|
|
||||||
|
poa.st antisemitic racist homophobic
|
||||||
|
nicecrew.digital antisemitic
|
||||||
|
beefyboys.win antisemitic racist homophobic harassment
|
||||||
|
cawfee.club antisemitic racist homophobic
|
||||||
|
comfyboy.club antisemitic racist homophobic
|
||||||
|
freespeechextremist.com racist homophobic
|
||||||
|
cum.salon racist misogynist
|
||||||
|
bae.st racist
|
||||||
|
natehiggers.online racist
|
||||||
|
rapemeat.solutions misogynist
|
||||||
|
rapist.town misogynist
|
||||||
|
rapefeminists.network misogynist
|
||||||
|
kiwifarms.cc harassment
|
||||||
|
noagendasocial.com noagenda
|
||||||
|
posting.lolicon.rocks underage
|
||||||
|
urchan.org harassment homophobic racist
|
||||||
|
ryona.agency harassment
|
||||||
|
yggdrasil.social antisemitic homophobic racist
|
||||||
|
genderheretics.xyz transphobic
|
||||||
|
baraag.net underage
|
||||||
|
lolison.top underage
|
||||||
|
shota.house underage
|
||||||
|
shota.social underage
|
||||||
|
aethy.com underage
|
||||||
|
taullo.social underage
|
||||||
|
childpawn.shop underage
|
||||||
|
posting.lolicon.rocks underage
|
||||||
|
loli.best underage
|
||||||
|
gothloli.club underage
|
||||||
|
smuglo.li underage
|
||||||
|
youjo.love underage
|
||||||
|
pedo.school underage
|
||||||
|
lolison.network underage
|
||||||
|
freak.university underage
|
||||||
|
mirr0r.city underage
|
||||||
|
xhais.love underage
|
||||||
|
refusal.biz underage
|
||||||
|
refusal.llc underage
|
||||||
|
mirr0r.city underage
|
||||||
|
nnia.space underage
|
||||||
|
ignorelist.com malicious
|
||||||
|
repl.co malicious
|
||||||
|
|
||||||
|
# custom
|
||||||
|
|
||||||
|
pawoo.net csam
|
||||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 15 KiB |
@@ -12,7 +12,6 @@ import android.widget.Toast;
|
|||||||
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.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -36,13 +35,10 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
openComposeFragment(sessions.get(0).getID());
|
openComposeFragment(sessions.get(0).getID());
|
||||||
}else{
|
}else{
|
||||||
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
||||||
new M3AlertDialogBuilder(this)
|
UiUtils.pickAccount(this, null, R.string.choose_account, 0,
|
||||||
.setItems(sessions.stream().map(as->"@"+as.self.username+"@"+as.domain).toArray(String[]::new), (dialog, which)->{
|
session -> openComposeFragment(session.getID()),
|
||||||
openComposeFragment(sessions.get(which).getID());
|
b -> b.setOnCancelListener(d -> finish())
|
||||||
})
|
);
|
||||||
.setTitle(R.string.choose_account)
|
|
||||||
.setOnCancelListener(dialog -> finish())
|
|
||||||
.show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +49,10 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
Intent intent=getIntent();
|
Intent intent=getIntent();
|
||||||
StringBuilder builder=new StringBuilder();
|
StringBuilder builder=new StringBuilder();
|
||||||
String subject = "";
|
String subject = "";
|
||||||
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) builder.append(subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)).append("\n\n");
|
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
|
||||||
|
subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||||
|
if (!subject.isBlank()) builder.append(subject).append("\n\n");
|
||||||
|
}
|
||||||
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
|
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
|
||||||
String text=builder.toString();
|
String text=builder.toString();
|
||||||
List<Uri> mediaUris;
|
List<Uri> mediaUris;
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
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.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class GlobalUserPreferences{
|
public class GlobalUserPreferences{
|
||||||
public static boolean playGifs;
|
public static boolean playGifs;
|
||||||
@@ -20,26 +25,47 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean showReplies;
|
public static boolean showReplies;
|
||||||
public static boolean showBoosts;
|
public static boolean showBoosts;
|
||||||
public static boolean loadNewPosts;
|
public static boolean loadNewPosts;
|
||||||
public static boolean showFederatedTimeline;
|
public static boolean showNewPostsButton;
|
||||||
public static boolean showInteractionCounts;
|
public static boolean showInteractionCounts;
|
||||||
public static boolean alwaysExpandContentWarnings;
|
public static boolean alwaysExpandContentWarnings;
|
||||||
public static boolean disableMarquee;
|
public static boolean disableMarquee;
|
||||||
public static boolean disableSwipe;
|
public static boolean disableSwipe;
|
||||||
|
public static boolean disableDividers;
|
||||||
public static boolean voteButtonForSingleChoice;
|
public static boolean voteButtonForSingleChoice;
|
||||||
|
public static boolean uniformNotificationIcon;
|
||||||
public static boolean enableDeleteNotifications;
|
public static boolean enableDeleteNotifications;
|
||||||
public static boolean translateButtonOpenedOnly;
|
public static boolean relocatePublishButton;
|
||||||
|
public static boolean reduceMotion;
|
||||||
|
public static boolean keepOnlyLatestNotification;
|
||||||
|
public static boolean enableFabAutoHide;
|
||||||
|
public static boolean disableAltTextReminder;
|
||||||
|
public static boolean showAltIndicator;
|
||||||
|
public static boolean showNoAltIndicator;
|
||||||
|
public static boolean enablePreReleases;
|
||||||
|
public static boolean prefixRepliesWithRe;
|
||||||
|
public static boolean bottomEncoding;
|
||||||
|
public static boolean collapseLongPosts;
|
||||||
|
public static boolean spectatorMode;
|
||||||
public static String publishButtonText;
|
public static String publishButtonText;
|
||||||
public static ThemePreference theme;
|
public static ThemePreference theme;
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
|
|
||||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||||
|
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
||||||
public static Map<String, List<String>> recentLanguages;
|
public static Map<String, List<String>> recentLanguages;
|
||||||
|
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
||||||
|
public static Set<String> accountsWithLocalOnlySupport;
|
||||||
|
public static Set<String> accountsInGlitchMode;
|
||||||
|
|
||||||
private static SharedPreferences getPrefs(){
|
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||||
|
public static Map<String, Integer> recentEmojis;
|
||||||
|
|
||||||
|
private static SharedPreferences getPrefs(){
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T fromJson(String json, Type type, T orElse) {
|
private static <T> T fromJson(String json, Type type, T orElse) {
|
||||||
|
if (json == null) return orElse;
|
||||||
try { return gson.fromJson(json, type); }
|
try { return gson.fromJson(json, type); }
|
||||||
catch (JsonSyntaxException ignored) { return orElse; }
|
catch (JsonSyntaxException ignored) { return orElse; }
|
||||||
}
|
}
|
||||||
@@ -52,23 +78,45 @@ public class GlobalUserPreferences{
|
|||||||
showReplies=prefs.getBoolean("showReplies", true);
|
showReplies=prefs.getBoolean("showReplies", true);
|
||||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||||
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
||||||
showFederatedTimeline=prefs.getBoolean("showFederatedTimeline", !BuildConfig.BUILD_TYPE.equals("playRelease"));
|
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
|
||||||
|
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", true);
|
||||||
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
||||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
||||||
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
||||||
|
disableDividers=prefs.getBoolean("disableDividers", true);
|
||||||
|
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
|
||||||
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
||||||
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
||||||
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
|
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
||||||
|
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||||
|
enableFabAutoHide=prefs.getBoolean("enableFabAutoHide", true);
|
||||||
|
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
|
||||||
|
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
||||||
|
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
||||||
|
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
|
||||||
|
prefixRepliesWithRe=prefs.getBoolean("prefixRepliesWithRe", false);
|
||||||
|
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||||
|
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
||||||
|
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
||||||
publishButtonText=prefs.getString("publishButtonText", "");
|
publishButtonText=prefs.getString("publishButtonText", "");
|
||||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||||
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
|
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
|
||||||
|
recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
|
||||||
|
publishButtonText=prefs.getString("publishButtonText", "");
|
||||||
|
pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
|
||||||
|
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
|
||||||
|
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
|
||||||
|
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.MATERIAL3.name()));
|
||||||
|
}else{
|
||||||
|
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PURPLE.name()));
|
||||||
|
}
|
||||||
} catch (IllegalArgumentException|ClassCastException ignored) {
|
} catch (IllegalArgumentException|ClassCastException ignored) {
|
||||||
// invalid color name or color was previously saved as integer
|
// invalid color name or color was previously saved as integer
|
||||||
color=ColorPreference.PINK;
|
color=ColorPreference.PURPLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,18 +127,35 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("showReplies", showReplies)
|
.putBoolean("showReplies", showReplies)
|
||||||
.putBoolean("showBoosts", showBoosts)
|
.putBoolean("showBoosts", showBoosts)
|
||||||
.putBoolean("loadNewPosts", loadNewPosts)
|
.putBoolean("loadNewPosts", loadNewPosts)
|
||||||
.putBoolean("showFederatedTimeline", showFederatedTimeline)
|
.putBoolean("showNewPostsButton", showNewPostsButton)
|
||||||
.putBoolean("trueBlackTheme", trueBlackTheme)
|
.putBoolean("trueBlackTheme", trueBlackTheme)
|
||||||
.putBoolean("showInteractionCounts", showInteractionCounts)
|
.putBoolean("showInteractionCounts", showInteractionCounts)
|
||||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||||
.putBoolean("disableMarquee", disableMarquee)
|
.putBoolean("disableMarquee", disableMarquee)
|
||||||
.putBoolean("disableSwipe", disableSwipe)
|
.putBoolean("disableSwipe", disableSwipe)
|
||||||
|
.putBoolean("disableDividers", disableDividers)
|
||||||
|
.putBoolean("relocatePublishButton", relocatePublishButton)
|
||||||
|
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||||
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
|
.putBoolean("reduceMotion", reduceMotion)
|
||||||
|
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||||
|
.putBoolean("enableFabAutoHide", enableFabAutoHide)
|
||||||
|
.putBoolean("disableAltTextReminder", disableAltTextReminder)
|
||||||
|
.putBoolean("showAltIndicator", showAltIndicator)
|
||||||
|
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
||||||
|
.putBoolean("enablePreReleases", enablePreReleases)
|
||||||
|
.putBoolean("prefixRepliesWithRe", prefixRepliesWithRe)
|
||||||
|
.putBoolean("collapseLongPosts", collapseLongPosts)
|
||||||
|
.putBoolean("spectatorMode", spectatorMode)
|
||||||
.putString("publishButtonText", publishButtonText)
|
.putString("publishButtonText", publishButtonText)
|
||||||
|
.putBoolean("bottomEncoding", bottomEncoding)
|
||||||
.putInt("theme", theme.ordinal())
|
.putInt("theme", theme.ordinal())
|
||||||
.putString("color", color.name())
|
.putString("color", color.name())
|
||||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||||
|
.putString("pinnedTimelines", gson.toJson(pinnedTimelines))
|
||||||
|
.putString("recentEmojis", gson.toJson(recentEmojis))
|
||||||
|
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
|
||||||
|
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +167,8 @@ public class GlobalUserPreferences{
|
|||||||
BLUE,
|
BLUE,
|
||||||
BROWN,
|
BROWN,
|
||||||
RED,
|
RED,
|
||||||
YELLOW
|
YELLOW,
|
||||||
|
NORD
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ThemePreference{
|
public enum ThemePreference{
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.fragments.ProfileFragment;
|
|||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
@@ -39,12 +40,13 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
AccountSession session;
|
AccountSession session;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
Intent intent=getIntent();
|
Intent intent=getIntent();
|
||||||
if(intent.getBooleanExtra("fromNotification", false)){
|
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
||||||
|
boolean hasNotification = intent.hasExtra("notification");
|
||||||
|
if(fromNotification){
|
||||||
String accountID=intent.getStringExtra("accountID");
|
String accountID=intent.getStringExtra("accountID");
|
||||||
try{
|
try{
|
||||||
session=AccountSessionManager.getInstance().getAccount(accountID);
|
session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
if(!intent.hasExtra("notification"))
|
if(!hasNotification) args.putString("tab", "notifications");
|
||||||
args.putString("tab", "notifications");
|
|
||||||
}catch(IllegalStateException x){
|
}catch(IllegalStateException x){
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
}
|
}
|
||||||
@@ -54,13 +56,13 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
args.putString("account", session.getID());
|
args.putString("account", session.getID());
|
||||||
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
showFragmentClearingBackStack(fragment);
|
if(fromNotification && hasNotification){
|
||||||
if(intent.getBooleanExtra("fromNotification", false) && intent.hasExtra("notification")){
|
|
||||||
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
||||||
showFragmentForNotification(notification, session.getID());
|
showFragmentForNotification(notification, session.getID());
|
||||||
}else if(intent.getBooleanExtra("compose", false)){
|
} else if (intent.getBooleanExtra("compose", false)){
|
||||||
showCompose();
|
showCompose();
|
||||||
}else{
|
} else {
|
||||||
|
showFragmentClearingBackStack(fragment);
|
||||||
maybeRequestNotificationsPermission();
|
maybeRequestNotificationsPermission();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,4 +141,31 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* when opening app through a notification: if (thread) fragment "can go back", clear back stack
|
||||||
|
* and show home fragment. upstream's implementation doesn't require this as it opens home first
|
||||||
|
* and then immediately switches to the notification's ThreadFragment. this causes a black
|
||||||
|
* screen in megalodon, for some reason, so i'm working around this that way.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
Fragment currentFragment = getFragmentManager().findFragmentById(
|
||||||
|
(fragmentContainers.get(fragmentContainers.size() - 1)).getId()
|
||||||
|
);
|
||||||
|
Bundle currentArgs = currentFragment.getArguments();
|
||||||
|
if (this.fragmentContainers.size() == 1
|
||||||
|
&& currentArgs != null
|
||||||
|
&& currentArgs.getBoolean("_can_go_back", false)
|
||||||
|
&& currentArgs.containsKey("account")) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString("account", currentArgs.getString("account"));
|
||||||
|
args.putString("tab", "notifications");
|
||||||
|
Fragment fragment=new HomeFragment();
|
||||||
|
fragment.setArguments(args);
|
||||||
|
showFragmentClearingBackStack(fragment);
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import android.app.PendingIntent;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.Icon;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -39,6 +37,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
private static final String TAG="PushNotificationReceive";
|
private static final String TAG="PushNotificationReceive";
|
||||||
|
|
||||||
public static final int NOTIFICATION_ID=178;
|
public static final int NOTIFICATION_ID=178;
|
||||||
|
private static final int SUMMARY_ID = 791;
|
||||||
|
private static int notificationId = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent){
|
public void onReceive(Context context, Intent intent){
|
||||||
@@ -99,6 +99,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||||
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
Notification.Builder builder;
|
Notification.Builder builder;
|
||||||
|
Notification.Builder summaryNotification;
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
||||||
boolean hasGroup=false;
|
boolean hasGroup=false;
|
||||||
List<NotificationChannelGroup> channelGroups=nm.getNotificationChannelGroups();
|
List<NotificationChannelGroup> channelGroups=nm.getNotificationChannelGroups();
|
||||||
@@ -121,42 +122,61 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
nm.createNotificationChannels(channels);
|
nm.createNotificationChannels(channels);
|
||||||
}
|
}
|
||||||
builder=new Notification.Builder(context, accountID+"_"+pn.notificationType);
|
builder=new Notification.Builder(context, accountID+"_"+pn.notificationType);
|
||||||
|
// summaryNotification=new Notification.Builder(context, accountID);
|
||||||
}else{
|
}else{
|
||||||
builder=new Notification.Builder(context)
|
builder=new Notification.Builder(context)
|
||||||
.setPriority(Notification.PRIORITY_DEFAULT)
|
.setPriority(Notification.PRIORITY_DEFAULT)
|
||||||
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
|
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
|
||||||
|
summaryNotification=new Notification.Builder(context)
|
||||||
|
.setPriority(Notification.PRIORITY_DEFAULT)
|
||||||
|
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
|
||||||
}
|
}
|
||||||
Drawable avatar=ImageCache.getInstance(context).get(new UrlImageLoaderRequest(pn.icon, V.dp(50), V.dp(50)));
|
Drawable avatar=ImageCache.getInstance(context).get(new UrlImageLoaderRequest(pn.icon, V.dp(50), V.dp(50)));
|
||||||
Intent contentIntent=new Intent(context, MainActivity.class);
|
Intent contentIntent=new Intent(context, MainActivity.class);
|
||||||
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
contentIntent.putExtra("fromNotification", true);
|
contentIntent.putExtra("fromNotification", true);
|
||||||
contentIntent.putExtra("accountID", accountID);
|
contentIntent.putExtra("accountID", accountID);
|
||||||
|
contentIntent.putExtra("notificationID", notificationId);
|
||||||
if(notification!=null){
|
if(notification!=null){
|
||||||
contentIntent.putExtra("notification", Parcels.wrap(notification));
|
contentIntent.putExtra("notification", Parcels.wrap(notification));
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setContentTitle(pn.title)
|
builder.setContentTitle(pn.title)
|
||||||
.setContentText(pn.body)
|
.setContentText(pn.body)
|
||||||
|
.setContentTitle(pn.title)
|
||||||
.setStyle(new Notification.BigTextStyle().bigText(pn.body))
|
.setStyle(new Notification.BigTextStyle().bigText(pn.body))
|
||||||
.setContentIntent(PendingIntent.getActivity(context, accountID.hashCode() & 0xFFFF, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||||
|
.setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setColor(UiUtils.getThemeColor(context, R.attr.colorPrimary700));
|
.setColor(context.getColor(R.color.shortcut_icon_background))
|
||||||
switch (pn.notificationType) {
|
.setGroup(accountID);
|
||||||
case FAVORITE -> builder.setSmallIcon(R.drawable.ic_fluent_star_24_filled);
|
|
||||||
case REBLOG -> builder.setSmallIcon(R.drawable.ic_fluent_arrow_repeat_all_24_filled);
|
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
||||||
case FOLLOW -> builder.setSmallIcon(R.drawable.ic_fluent_person_add_24_filled);
|
builder.setSmallIcon(switch (pn.notificationType) {
|
||||||
case MENTION -> builder.setSmallIcon(R.drawable.ic_fluent_mention_24_filled);
|
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
|
||||||
case POLL -> builder.setSmallIcon(R.drawable.ic_fluent_poll_24_filled);
|
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
|
||||||
default -> builder.setSmallIcon(R.drawable.ic_ntf_logo);
|
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
|
||||||
|
case MENTION -> R.drawable.ic_fluent_mention_24_filled;
|
||||||
|
case POLL -> R.drawable.ic_fluent_poll_24_filled;
|
||||||
|
case STATUS -> R.drawable.ic_fluent_chat_24_filled;
|
||||||
|
case UPDATE -> R.drawable.ic_fluent_history_24_filled;
|
||||||
|
case REPORT -> R.drawable.ic_fluent_warning_24_filled;
|
||||||
|
case SIGN_UP -> R.drawable.ic_fluent_person_available_24_filled;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(avatar!=null){
|
if(avatar!=null){
|
||||||
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
||||||
}
|
}
|
||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().size()>1){
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().size()>1){
|
||||||
builder.setSubText(accountName);
|
builder.setSubText(accountName);
|
||||||
}
|
}
|
||||||
nm.notify(accountID, NOTIFICATION_ID, builder.build());
|
|
||||||
|
notificationId++;
|
||||||
|
nm.notify(accountID, GlobalUserPreferences.keepOnlyLatestNotification ? NOTIFICATION_ID : notificationId, builder.build());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,14 @@ import com.google.gson.JsonParser;
|
|||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -47,9 +50,26 @@ public class MastodonAPIController{
|
|||||||
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
||||||
|
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
|
private static List<String> badDomains = new ArrayList<>();
|
||||||
|
|
||||||
static{
|
static{
|
||||||
thread.start();
|
thread.start();
|
||||||
|
try {
|
||||||
|
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||||
|
MastodonApp.context.getAssets().open("blocks.tsv")
|
||||||
|
));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (line.isBlank() || line.startsWith("#")) continue;
|
||||||
|
String[] parts = line.replaceAll("\"", "").split("[\s,;]");
|
||||||
|
if (parts.length == 0) continue;
|
||||||
|
String domain = parts[0].toLowerCase().trim();
|
||||||
|
if (domain.isBlank()) continue;
|
||||||
|
badDomains.add(domain);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPIController(@Nullable AccountSession session){
|
public MastodonAPIController(@Nullable AccountSession session){
|
||||||
@@ -57,8 +77,11 @@ public class MastodonAPIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <T> void submitRequest(final MastodonAPIRequest<T> req){
|
public <T> void submitRequest(final MastodonAPIRequest<T> req){
|
||||||
|
final String host = req.getURL().getHost();
|
||||||
|
final boolean isBad = host == null || badDomains.stream().anyMatch(h -> h.equalsIgnoreCase(host) || host.toLowerCase().endsWith("." + h));
|
||||||
thread.postRunnable(()->{
|
thread.postRunnable(()->{
|
||||||
try{
|
try{
|
||||||
|
if (isBad) throw new IllegalArgumentException();
|
||||||
if(req.canceled)
|
if(req.canceled)
|
||||||
return;
|
return;
|
||||||
Request.Builder builder=new Request.Builder()
|
Request.Builder builder=new Request.Builder()
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ public class PushSubscriptionManager{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(PushSubscription result){
|
public void onSuccess(PushSubscription result){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
|
result.serverKey=result.serverKey.replace('/','_');
|
||||||
|
result.serverKey=result.serverKey.replace('+','-');
|
||||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
||||||
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -18,12 +19,18 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
|
|
||||||
public class StatusInteractionController{
|
public class StatusInteractionController{
|
||||||
private final String accountID;
|
private final String accountID;
|
||||||
|
private final boolean updateCounters;
|
||||||
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
||||||
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
||||||
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
||||||
|
|
||||||
public StatusInteractionController(String accountID){
|
public StatusInteractionController(String accountID, boolean updateCounters) {
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
|
this.updateCounters=updateCounters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusInteractionController(String accountID){
|
||||||
|
this(accountID, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFavorited(Status status, boolean favorited, Consumer<Status> cb){
|
public void setFavorited(Status status, boolean favorited, Consumer<Status> cb){
|
||||||
@@ -41,7 +48,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);
|
||||||
E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -50,16 +57,16 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.favourited=!favorited;
|
status.favourited=!favorited;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
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;
|
||||||
E.post(new StatusCountersUpdatedEvent(status));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReblogged(Status status, boolean reblogged, Consumer<Status> cb){
|
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
||||||
if(!Looper.getMainLooper().isCurrentThread())
|
if(!Looper.getMainLooper().isCurrentThread())
|
||||||
throw new IllegalStateException("Can only be called from main thread");
|
throw new IllegalStateException("Can only be called from main thread");
|
||||||
|
|
||||||
@@ -67,7 +74,7 @@ public class StatusInteractionController{
|
|||||||
if(current!=null){
|
if(current!=null){
|
||||||
current.cancel();
|
current.cancel();
|
||||||
}
|
}
|
||||||
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged)
|
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged, visibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status reblog){
|
public void onSuccess(Status reblog){
|
||||||
@@ -75,7 +82,7 @@ public class StatusInteractionController{
|
|||||||
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);
|
||||||
E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -84,13 +91,13 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.reblogged=!reblogged;
|
status.reblogged=!reblogged;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
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;
|
||||||
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){
|
||||||
@@ -111,7 +118,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);
|
||||||
E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -120,12 +127,12 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.bookmarked=!bookmarked;
|
status.bookmarked=!bookmarked;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
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;
|
||||||
E.post(new StatusCountersUpdatedEvent(status));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
|||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
|
||||||
public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{
|
public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{
|
||||||
|
public SetAccountFollowed(String id, boolean followed, boolean showReblogs){
|
||||||
|
this(id, followed, showReblogs, false);
|
||||||
|
}
|
||||||
|
|
||||||
public SetAccountFollowed(String id, boolean followed, boolean showReblogs, boolean notify){
|
public SetAccountFollowed(String id, boolean followed, boolean showReblogs, boolean notify){
|
||||||
super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class);
|
super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class);
|
||||||
if(followed)
|
if(followed)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.announcements;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
|
||||||
|
public class DismissAnnouncement extends MastodonAPIRequest<Object>{
|
||||||
|
public DismissAnnouncement(String id){
|
||||||
|
super(HttpMethod.POST, "/announcements/" + id + "/dismiss", Object.class);
|
||||||
|
setRequestBody(new Object());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.announcements;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Announcement;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetAnnouncements extends MastodonAPIRequest<List<Announcement>> {
|
||||||
|
public GetAnnouncements(boolean withDismissed) {
|
||||||
|
super(MastodonAPIRequest.HttpMethod.GET, "/announcements", new TypeToken<>(){});
|
||||||
|
addQueryParameter("with_dismissed", withDismissed ? "true" : "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.lists;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AddList extends MastodonAPIRequest<Object> {
|
||||||
|
public AddList(String listName){
|
||||||
|
super(HttpMethod.POST, "/lists", Object.class);
|
||||||
|
Request req = new Request();
|
||||||
|
req.title = listName;
|
||||||
|
setRequestBody(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Request{
|
||||||
|
public String title;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.lists;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
|
||||||
|
public class CreateList extends MastodonAPIRequest<ListTimeline> {
|
||||||
|
public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||||
|
super(HttpMethod.POST, "/lists", ListTimeline.class);
|
||||||
|
Request req = new Request();
|
||||||
|
req.title = title;
|
||||||
|
req.repliesPolicy = repliesPolicy;
|
||||||
|
setRequestBody(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Request {
|
||||||
|
public String title;
|
||||||
|
public ListTimeline.RepliesPolicy repliesPolicy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.lists;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
|
||||||
|
public class DeleteList extends MastodonAPIRequest<Object> {
|
||||||
|
public DeleteList(String id) {
|
||||||
|
super(HttpMethod.DELETE, "/lists/" + id, Object.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.lists;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class EditListName extends MastodonAPIRequest<Object> {
|
||||||
|
public EditListName(String newListName, String listId){
|
||||||
|
super(HttpMethod.PUT, "/lists/"+listId, Object.class);
|
||||||
|
Request req = new Request();
|
||||||
|
req.title = newListName;
|
||||||
|
setRequestBody(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Request{
|
||||||
|
public String title;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.lists;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
|
||||||
|
public class GetList extends MastodonAPIRequest<ListTimeline> {
|
||||||
|
public GetList(String id) {
|
||||||
|
super(HttpMethod.GET, "/lists/" + id, ListTimeline.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.lists;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RemoveList extends MastodonAPIRequest<Object> {
|
||||||
|
public RemoveList(String listId){
|
||||||
|
super(HttpMethod.DELETE, "/lists/"+listId, Object.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.lists;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
|
||||||
|
public class UpdateList extends MastodonAPIRequest<ListTimeline> {
|
||||||
|
public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||||
|
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
|
||||||
|
CreateList.Request req = new CreateList.Request();
|
||||||
|
req.title = title;
|
||||||
|
req.repliesPolicy = repliesPolicy;
|
||||||
|
setRequestBody(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ import java.util.EnumSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DismissNotification extends MastodonAPIRequest<Object>{
|
public class DismissNotification extends MastodonAPIRequest<Object>{
|
||||||
public DismissNotification(String id){
|
public DismissNotification(String id){
|
||||||
super(HttpMethod.POST, "/notifications/" + (id != null ? id + "/dismiss" : "clear"), Object.class);
|
super(HttpMethod.POST, "/notifications/" + (id != null ? id + "/dismiss" : "clear"), Object.class);
|
||||||
setRequestBody(new Object());
|
setRequestBody(new Object());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
Request r=new Request();
|
Request r=new Request();
|
||||||
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
||||||
r.data.alerts=alerts;
|
r.data.alerts=alerts;
|
||||||
r.data.policy=policy;
|
r.policy=policy;
|
||||||
r.subscription.keys.p256dh=encryptionKey;
|
r.subscription.keys.p256dh=encryptionKey;
|
||||||
r.subscription.keys.auth=authKey;
|
r.subscription.keys.auth=authKey;
|
||||||
setRequestBody(r);
|
setRequestBody(r);
|
||||||
@@ -18,6 +18,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
private static class Request{
|
private static class Request{
|
||||||
public Subscription subscription=new Subscription();
|
public Subscription subscription=new Subscription();
|
||||||
public Data data=new Data();
|
public Data data=new Data();
|
||||||
|
public PushSubscription.Policy policy;
|
||||||
|
|
||||||
private static class Keys{
|
private static class Keys{
|
||||||
public String p256dh;
|
public String p256dh;
|
||||||
@@ -31,7 +32,6 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
|
|
||||||
private static class Data{
|
private static class Data{
|
||||||
public PushSubscription.Alerts alerts;
|
public PushSubscription.Alerts alerts;
|
||||||
public PushSubscription.Policy policy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,36 @@ package org.joinmastodon.android.api.requests.notifications;
|
|||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
||||||
|
private final PushSubscription.Policy policy;
|
||||||
|
|
||||||
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
||||||
setRequestBody(new Request(alerts, policy));
|
setRequestBody(new Request(alerts, policy));
|
||||||
|
this.policy=policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateAndPostprocessResponse(PushSubscription respObj, Response httpResponse) throws IOException{
|
||||||
|
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||||
|
respObj.policy=policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Request{
|
private static class Request{
|
||||||
public Data data=new Data();
|
public Data data=new Data();
|
||||||
|
public PushSubscription.Policy policy;
|
||||||
|
|
||||||
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
this.data.alerts=alerts;
|
this.data.alerts=alerts;
|
||||||
this.data.policy=policy;
|
this.policy=policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Data{
|
private static class Data{
|
||||||
public PushSubscription.Alerts alerts;
|
public PushSubscription.Alerts alerts;
|
||||||
public PushSubscription.Policy policy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class Request{
|
private static class Request{
|
||||||
public String clientName="Megalodon";
|
public String clientName="Moshidon";
|
||||||
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
||||||
public String scopes=AccountSessionManager.SCOPE;
|
public String scopes=AccountSessionManager.SCOPE;
|
||||||
public String website="https://sk22.github.io/megalodon";
|
public String website="https://github.com/LucasGGamerM/moshidon";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
@@ -9,18 +10,36 @@ 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 */;
|
||||||
|
private static final float draftFactor = 31536000000f /* one year */ / 253370764799999f /* end of 9998 */;
|
||||||
|
|
||||||
|
public static Instant getDraftInstant() {
|
||||||
|
// returns an instant between 9999-01-01 00:00:00 and 9999-12-31 23:59:59
|
||||||
|
// 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){
|
||||||
super(HttpMethod.POST, "/statuses", Status.class);
|
super(HttpMethod.POST, "/statuses", Status.class);
|
||||||
setRequestBody(req);
|
setRequestBody(req);
|
||||||
addHeader("Idempotency-Key", uuid);
|
addHeader("Idempotency-Key", uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Scheduled extends MastodonAPIRequest<ScheduledStatus>{
|
||||||
|
public Scheduled(CreateStatus.Request req, String uuid){
|
||||||
|
super(HttpMethod.POST, "/statuses", ScheduledStatus.class);
|
||||||
|
setRequestBody(req);
|
||||||
|
addHeader("Idempotency-Key", uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class Request{
|
public static class Request{
|
||||||
public String status;
|
public String status;
|
||||||
public List<String> mediaIds;
|
public List<String> mediaIds;
|
||||||
public Poll poll;
|
public Poll poll;
|
||||||
public String inReplyToId;
|
public String inReplyToId;
|
||||||
public boolean sensitive;
|
public boolean sensitive;
|
||||||
|
public boolean localOnly;
|
||||||
public String spoilerText;
|
public String spoilerText;
|
||||||
public StatusPrivacy visibility;
|
public StatusPrivacy visibility;
|
||||||
public Instant scheduledAt;
|
public Instant scheduledAt;
|
||||||
|
|||||||
@@ -7,4 +7,10 @@ public class DeleteStatus extends MastodonAPIRequest<Status>{
|
|||||||
public DeleteStatus(String id){
|
public DeleteStatus(String id){
|
||||||
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
|
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Scheduled extends MastodonAPIRequest<Object> {
|
||||||
|
public Scheduled(String id) {
|
||||||
|
super(HttpMethod.DELETE, "/scheduled_statuses/"+id, Object.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
|
|
||||||
|
public class GetScheduledStatuses extends HeaderPaginationRequest<ScheduledStatus>{
|
||||||
|
public GetScheduledStatuses(String maxID, int limit){
|
||||||
|
super(HttpMethod.GET, "/scheduled_statuses", new TypeToken<>(){});
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", limit+"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,17 @@ package org.joinmastodon.android.api.requests.statuses;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
public class SetStatusReblogged extends MastodonAPIRequest<Status>{
|
public class SetStatusReblogged extends MastodonAPIRequest<Status>{
|
||||||
public SetStatusReblogged(String id, boolean reblogged){
|
public SetStatusReblogged(String id, boolean reblogged, StatusPrivacy visibility){
|
||||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class);
|
super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class);
|
||||||
setRequestBody(new Object());
|
Request req = new Request();
|
||||||
|
req.visibility = visibility;
|
||||||
|
setRequestBody(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Request {
|
||||||
|
public StatusPrivacy visibility;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.tags;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetFollowedHashtags extends HeaderPaginationRequest<Hashtag> {
|
||||||
|
public GetFollowedHashtags() {
|
||||||
|
this(null, null, -1, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetFollowedHashtags(String maxID, String minID, int limit, String sinceID){
|
||||||
|
super(HttpMethod.GET, "/followed_tags", new TypeToken<>(){});
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(minID!=null)
|
||||||
|
addQueryParameter("min_id", minID);
|
||||||
|
if(sinceID!=null)
|
||||||
|
addQueryParameter("since_id", sinceID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", ""+limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,9 +8,11 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
||||||
public GetTrendingStatuses(int limit){
|
public GetTrendingStatuses(int offset, int limit){
|
||||||
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", ""+limit);
|
addQueryParameter("limit", ""+limit);
|
||||||
|
if(offset>0)
|
||||||
|
addQueryParameter("offset", ""+offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class AccountSession{
|
|||||||
public Preferences preferences;
|
public Preferences preferences;
|
||||||
public AccountActivationInfo activationInfo;
|
public AccountActivationInfo activationInfo;
|
||||||
private transient MastodonAPIController apiController;
|
private transient MastodonAPIController apiController;
|
||||||
private transient StatusInteractionController statusInteractionController;
|
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
||||||
private transient CacheController cacheController;
|
private transient CacheController cacheController;
|
||||||
private transient PushSubscriptionManager pushSubscriptionManager;
|
private transient PushSubscriptionManager pushSubscriptionManager;
|
||||||
|
|
||||||
@@ -52,6 +52,10 @@ public class AccountSession{
|
|||||||
return domain+"_"+self.id;
|
return domain+"_"+self.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFullUsername() {
|
||||||
|
return "@"+self.username+"@"+domain;
|
||||||
|
}
|
||||||
|
|
||||||
public MastodonAPIController getApiController(){
|
public MastodonAPIController getApiController(){
|
||||||
if(apiController==null)
|
if(apiController==null)
|
||||||
apiController=new MastodonAPIController(this);
|
apiController=new MastodonAPIController(this);
|
||||||
@@ -64,6 +68,12 @@ public class AccountSession{
|
|||||||
return statusInteractionController;
|
return statusInteractionController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StatusInteractionController getRemoteStatusInteractionController(){
|
||||||
|
if(remoteStatusInteractionController==null)
|
||||||
|
remoteStatusInteractionController=new StatusInteractionController(getID(), false);
|
||||||
|
return remoteStatusInteractionController;
|
||||||
|
}
|
||||||
|
|
||||||
public CacheController getCacheController(){
|
public CacheController getCacheController(){
|
||||||
if(cacheController==null)
|
if(cacheController==null)
|
||||||
cacheController=new CacheController(getID());
|
cacheController=new CacheController(getID());
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
@@ -63,7 +61,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
public class AccountSessionManager{
|
public class AccountSessionManager{
|
||||||
private static final String TAG="AccountSessionManager";
|
private static final String TAG="AccountSessionManager";
|
||||||
public static final String SCOPE="read write follow push";
|
public static final String SCOPE="read write follow push";
|
||||||
public static final String REDIRECT_URI="megalodon-android-auth://callback";
|
public static final String REDIRECT_URI="moshidon-android-auth://callback";
|
||||||
|
|
||||||
private static final AccountSessionManager instance=new AccountSessionManager();
|
private static final AccountSessionManager instance=new AccountSessionManager();
|
||||||
|
|
||||||
@@ -104,12 +102,11 @@ 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){
|
||||||
instances.put(instance.uri, instance);
|
instances.put(instance.uri, instance);
|
||||||
updateInstanceInfoV2(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);
|
||||||
lastActiveAccountID=session.getID();
|
lastActiveAccountID=session.getID();
|
||||||
writeAccountsFile();
|
writeAccountsFile();
|
||||||
updateInstanceEmojis(instance, instance.uri);
|
updateMoreInstanceInfo(instance, instance.uri);
|
||||||
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||||
}
|
}
|
||||||
@@ -214,7 +211,7 @@ public class AccountSessionManager{
|
|||||||
.path("/oauth/authorize")
|
.path("/oauth/authorize")
|
||||||
.appendQueryParameter("response_type", "code")
|
.appendQueryParameter("response_type", "code")
|
||||||
.appendQueryParameter("client_id", result.clientId)
|
.appendQueryParameter("client_id", result.clientId)
|
||||||
.appendQueryParameter("redirect_uri", "megalodon-android-auth://callback")
|
.appendQueryParameter("redirect_uri", "moshidon-android-auth://callback")
|
||||||
.appendQueryParameter("scope", SCOPE)
|
.appendQueryParameter("scope", SCOPE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -328,10 +325,7 @@ public class AccountSessionManager{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Instance instance){
|
public void onSuccess(Instance instance){
|
||||||
instances.put(domain, instance);
|
instances.put(domain, instance);
|
||||||
updateInstanceEmojis(instance, domain);
|
updateMoreInstanceInfo(instance, domain);
|
||||||
try {
|
|
||||||
updateInstanceInfoV2(instance);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -342,16 +336,18 @@ public class AccountSessionManager{
|
|||||||
.execNoAuth(domain);
|
.execNoAuth(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateInstanceInfoV2(Instance instance) {
|
public void updateMoreInstanceInfo(Instance instance, String domain) {
|
||||||
new GetInstance.V2().setCallback(new Callback<>() {
|
new GetInstance.V2().setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Instance.V2 v2) {
|
public void onSuccess(Instance.V2 v2) {
|
||||||
if (instance != null) instance.v2 = v2;
|
if (instance != null) instance.v2 = v2;
|
||||||
writeAccountsFile();
|
updateInstanceEmojis(instance, domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse errorResponse) {}
|
public void onError(ErrorResponse errorResponse) {
|
||||||
|
updateInstanceEmojis(instance, domain);
|
||||||
|
}
|
||||||
}).execNoAuth(instance.uri);
|
}).execNoAuth(instance.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class HashtagUpdatedEvent {
|
||||||
|
public final String name;
|
||||||
|
public final boolean following;
|
||||||
|
|
||||||
|
public HashtagUpdatedEvent(String name, boolean following) {
|
||||||
|
this.name = name;
|
||||||
|
this.following = following;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
public class ListDeletedEvent {
|
||||||
|
public final String id;
|
||||||
|
|
||||||
|
public ListDeletedEvent(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
|
||||||
|
public class ListUpdatedCreatedEvent {
|
||||||
|
public final String id;
|
||||||
|
public final String title;
|
||||||
|
public final ListTimeline.RepliesPolicy repliesPolicy;
|
||||||
|
|
||||||
|
public ListUpdatedCreatedEvent(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||||
|
this.id = id;
|
||||||
|
this.title = title;
|
||||||
|
this.repliesPolicy = repliesPolicy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
|
|
||||||
|
public class ScheduledStatusCreatedEvent {
|
||||||
|
public final ScheduledStatus scheduledStatus;
|
||||||
|
public final String accountID;
|
||||||
|
|
||||||
|
public ScheduledStatusCreatedEvent(ScheduledStatus scheduledStatus, String accountID){
|
||||||
|
this.scheduledStatus = scheduledStatus;
|
||||||
|
this.accountID=accountID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
|
|
||||||
|
public class ScheduledStatusDeletedEvent{
|
||||||
|
public final String id;
|
||||||
|
public final String accountID;
|
||||||
|
|
||||||
|
public ScheduledStatusDeletedEvent(String id, String accountID){
|
||||||
|
this.id=id;
|
||||||
|
this.accountID=accountID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,10 @@ package org.joinmastodon.android.fragments;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.animation.TranslateAnimation;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
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;
|
||||||
@@ -11,12 +15,15 @@ import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
|||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
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.Filter;
|
||||||
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.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
@@ -56,8 +63,8 @@ public class AccountTimelineFragment 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)
|
if(getActivity()==null) return;
|
||||||
return;
|
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.ACCOUNT)).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -67,6 +74,7 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
fab = ((ProfileFragment) getParentFragment()).getFab();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
||||||
|
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Announcement;
|
||||||
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.PaginatedList;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
|
public class AnnouncementsFragment extends BaseStatusListFragment<Announcement> {
|
||||||
|
private Instance instance;
|
||||||
|
private AccountSession session;
|
||||||
|
private List<String> unreadIDs = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity){
|
||||||
|
super.onAttach(activity);
|
||||||
|
setTitle(R.string.sk_announcements);
|
||||||
|
session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<StatusDisplayItem> buildDisplayItems(Announcement a) {
|
||||||
|
if(TextUtils.isEmpty(a.content)) return List.of();
|
||||||
|
Account instanceUser = new Account();
|
||||||
|
instanceUser.id = instanceUser.acct = instanceUser.username = session.domain;
|
||||||
|
instanceUser.displayName = instance.title;
|
||||||
|
instanceUser.url = "https://"+session.domain+"/about";
|
||||||
|
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
|
||||||
|
instanceUser.emojis = List.of();
|
||||||
|
Status fakeStatus = a.toStatus();
|
||||||
|
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
|
||||||
|
textItem.textSelectable = true;
|
||||||
|
return List.of(
|
||||||
|
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
|
||||||
|
textItem
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMarkAsRead(String id) {
|
||||||
|
if (unreadIDs == null) return;
|
||||||
|
unreadIDs.remove(id);
|
||||||
|
if (unreadIDs.isEmpty()) setResult(true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addAccountToKnown(Announcement s) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(String id) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
currentRequest=new GetAnnouncements(true)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Announcement> result){
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
List<Announcement> unread = result.stream().filter(a -> !a.read).collect(toList());
|
||||||
|
List<Announcement> read = result.stream().filter(a -> a.read).collect(toList());
|
||||||
|
onDataLoaded(unread, true);
|
||||||
|
onDataLoaded(read, false);
|
||||||
|
if (unread.isEmpty()) setResult(true, null);
|
||||||
|
else unreadIDs = unread.stream().map(a -> a.id).collect(toList());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,13 @@ import android.text.Layout;
|
|||||||
import android.text.StaticLayout;
|
import android.text.StaticLayout;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
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;
|
||||||
|
import android.view.animation.TranslateAnimation;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
@@ -30,6 +34,7 @@ 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.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||||
import org.joinmastodon.android.ui.TileGridLayoutManager;
|
import org.joinmastodon.android.ui.TileGridLayoutManager;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
@@ -40,6 +45,7 @@ import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
|||||||
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
||||||
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.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.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
@@ -56,6 +62,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
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.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
@@ -71,12 +79,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected DisplayItemsAdapter adapter;
|
protected DisplayItemsAdapter adapter;
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
protected PhotoViewer currentPhotoViewer;
|
protected PhotoViewer currentPhotoViewer;
|
||||||
|
protected ImageButton fab;
|
||||||
|
protected int scrollDiff = 0;
|
||||||
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected Rect tmpRect=new Rect();
|
protected Rect tmpRect=new Rect();
|
||||||
|
|
||||||
|
private final int THRESHHOLD = 800;
|
||||||
|
|
||||||
public BaseStatusListFragment(){
|
public BaseStatusListFragment(){
|
||||||
super(20);
|
super(20);
|
||||||
|
if (withComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean withComposeButton() {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,6 +107,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
return adapter=new DisplayItemsAdapter();
|
return adapter=new DisplayItemsAdapter();
|
||||||
@@ -273,13 +292,53 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
fab=view.findViewById(R.id.fab);
|
||||||
|
|
||||||
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(currentPhotoViewer!=null)
|
if(currentPhotoViewer!=null)
|
||||||
currentPhotoViewer.offsetView(-dx, -dy);
|
currentPhotoViewer.offsetView(-dx, -dy);
|
||||||
}
|
|
||||||
});
|
if (fab!=null && GlobalUserPreferences.enableFabAutoHide) {
|
||||||
|
// This piece of code should make it so that the fab is always visible if the status list scroll view is at the item at the top
|
||||||
|
if(list.getChildLayoutPosition(list.getChildAt(0)) < 1){
|
||||||
|
scrollDiff=THRESHHOLD+1;
|
||||||
|
}else{
|
||||||
|
if(dy > 0){
|
||||||
|
scrollDiff=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
fab.setVisibility(View.INVISIBLE);
|
||||||
|
scrollDiff = 0;
|
||||||
|
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
||||||
|
if (scrollDiff > THRESHHOLD) {
|
||||||
|
fab.setVisibility(View.VISIBLE);
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2,
|
||||||
|
0);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
scrollDiff = 0;
|
||||||
|
} else {
|
||||||
|
scrollDiff += Math.abs(dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}});
|
||||||
list.addItemDecoration(new StatusListItemDecoration());
|
list.addItemDecoration(new StatusListItemDecoration());
|
||||||
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
||||||
private Rect tmpRect=new Rect();
|
private Rect tmpRect=new Rect();
|
||||||
@@ -313,6 +372,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
list.setItemAnimator(new BetterItemAnimator());
|
list.setItemAnimator(new BetterItemAnimator());
|
||||||
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
|
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
|
|
||||||
|
if (withComposeButton()) {
|
||||||
|
fab = view.findViewById(R.id.fab);
|
||||||
|
fab.setVisibility(View.VISIBLE);
|
||||||
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
fab.setOnLongClickListener(this::onFabLongClick);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -478,7 +544,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
Status status=holder.getItem().status;
|
Status status=holder.getItem().status;
|
||||||
status.spoilerRevealed=!status.spoilerRevealed;
|
status.spoilerRevealed=!status.spoilerRevealed;
|
||||||
if(!TextUtils.isEmpty(status.spoilerText)){
|
if(!TextUtils.isEmpty(status.spoilerText)){
|
||||||
TextStatusDisplayItem.Holder text=findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class);
|
TextStatusDisplayItem.Holder text = findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class);
|
||||||
if(text!=null){
|
if(text!=null){
|
||||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
|
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
|
||||||
}
|
}
|
||||||
@@ -487,6 +553,23 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
updateImagesSpoilerState(status, holder.getItemID());
|
updateImagesSpoilerState(status, holder.getItemID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
|
||||||
|
if (holder.getItem().status.textExpandable != expandable && list != null) {
|
||||||
|
holder.getItem().status.textExpandable = expandable;
|
||||||
|
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
||||||
|
if (header != null) header.rebind();
|
||||||
|
holder.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onToggleExpanded(Status status, String itemID) {
|
||||||
|
status.textExpanded = !status.textExpanded;
|
||||||
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
|
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||||
|
if (text != null) text.rebind();
|
||||||
|
if (header != null) header.rebind();
|
||||||
|
}
|
||||||
|
|
||||||
protected void updateImagesSpoilerState(Status status, String itemID){
|
protected void updateImagesSpoilerState(Status status, String itemID){
|
||||||
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
||||||
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
|
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
|
||||||
@@ -504,6 +587,15 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
|
|
||||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
||||||
|
|
||||||
|
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
||||||
|
int startPos = warning.getAbsoluteAdapterPosition();
|
||||||
|
displayItems.remove(startPos);
|
||||||
|
displayItems.addAll(startPos, warning.filteredItems);
|
||||||
|
adapter.notifyItemRangeInserted(startPos, warning.filteredItems.size() - 1);
|
||||||
|
if (startPos == 0) scrollToTop();
|
||||||
|
warning.getItem().status.filterRevealed = true;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAccountID(){
|
public String getAccountID(){
|
||||||
return accountID;
|
return accountID;
|
||||||
}
|
}
|
||||||
@@ -619,6 +711,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
currentPhotoViewer.onPause();
|
currentPhotoViewer.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onFabClick(View v){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean onFabLongClick(View v) {
|
||||||
|
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
||||||
|
}
|
||||||
|
|
||||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
public DisplayItemsAdapter(){
|
public DisplayItemsAdapter(){
|
||||||
@@ -675,7 +777,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
private int currentMediaHiddenLayoutsWidth=0;
|
private int currentMediaHiddenLayoutsWidth=0;
|
||||||
|
|
||||||
{
|
{
|
||||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted));
|
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.disableDividers ? R.attr.colorWindowBackground : R.attr.colorPollVoted));
|
||||||
dividerPaint.setStyle(Paint.Style.STROKE);
|
dividerPaint.setStyle(Paint.Style.STROKE);
|
||||||
dividerPaint.setStrokeWidth(V.dp(1));
|
dividerPaint.setStrokeWidth(V.dp(1));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,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(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,352 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static android.view.Menu.NONE;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.ui.utils.UiUtils.makeBackItem;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.SubMenu;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
||||||
|
private String accountID;
|
||||||
|
private TimelinesAdapter adapter;
|
||||||
|
private final ItemTouchHelper itemTouchHelper;
|
||||||
|
private Menu optionsMenu;
|
||||||
|
private boolean updated;
|
||||||
|
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>();
|
||||||
|
private final List<ListTimeline> listTimelines = new ArrayList<>();
|
||||||
|
private final List<Hashtag> hashtags = new ArrayList<>();
|
||||||
|
|
||||||
|
public EditTimelinesFragment() {
|
||||||
|
super(10);
|
||||||
|
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ;
|
||||||
|
itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
setTitle(R.string.sk_timelines);
|
||||||
|
accountID = getArguments().getString("account");
|
||||||
|
|
||||||
|
new GetLists().setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<ListTimeline> result) {
|
||||||
|
listTimelines.addAll(result);
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
|
||||||
|
new GetFollowedHashtags().setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(HeaderPaginationList<Hashtag> result) {
|
||||||
|
hashtags.addAll(result);
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
|
super.onShown();
|
||||||
|
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
itemTouchHelper.attachToRecyclerView(list);
|
||||||
|
refreshLayout.setEnabled(false);
|
||||||
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
this.optionsMenu = menu;
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.menu_back) {
|
||||||
|
updateOptionsMenu();
|
||||||
|
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
TimelineDefinition tl = timelineByMenuItem.get(item);
|
||||||
|
if (tl != null) {
|
||||||
|
data.add(tl.copy());
|
||||||
|
adapter.notifyItemInserted(data.size());
|
||||||
|
saveTimelines();
|
||||||
|
updateOptionsMenu();
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
|
||||||
|
if (data.contains(tl)) return;
|
||||||
|
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, tl.getTitle(getContext()));
|
||||||
|
item.setIcon(tl.getIcon().iconRes);
|
||||||
|
timelineByMenuItem.put(item, tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOptionsMenu() {
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
optionsMenu.clear();
|
||||||
|
timelineByMenuItem.clear();
|
||||||
|
|
||||||
|
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().setIcon(R.drawable.ic_fluent_add_24_regular);
|
||||||
|
|
||||||
|
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline);
|
||||||
|
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
|
||||||
|
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list);
|
||||||
|
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
|
||||||
|
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag);
|
||||||
|
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
||||||
|
|
||||||
|
makeBackItem(timelinesMenu);
|
||||||
|
makeBackItem(listsMenu);
|
||||||
|
makeBackItem(hashtagsMenu);
|
||||||
|
|
||||||
|
TimelineDefinition.ALL_TIMELINES.forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
||||||
|
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
||||||
|
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
||||||
|
|
||||||
|
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0);
|
||||||
|
listsMenu.getItem().setVisible(listsMenu.size() > 0);
|
||||||
|
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0);
|
||||||
|
|
||||||
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveTimelines() {
|
||||||
|
updated = true;
|
||||||
|
GlobalUserPreferences.pinnedTimelines.put(accountID, data.size() > 0 ? data : List.of(TimelineDefinition.HOME_TIMELINE));
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeTimeline(int position) {
|
||||||
|
data.remove(position);
|
||||||
|
adapter.notifyItemRemoved(position);
|
||||||
|
saveTimelines();
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES), false);
|
||||||
|
updateOptionsMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() {
|
||||||
|
return adapter = new TimelinesAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scrollToTop() {
|
||||||
|
smoothScrollRecyclerViewToTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (updated) UiUtils.restartApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new TimelineViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) {
|
||||||
|
holder.bind(data.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
|
||||||
|
private final TextView title;
|
||||||
|
private final ImageView dragger;
|
||||||
|
|
||||||
|
public TimelineViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_text, list);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
dragger=findViewById(R.id.dragger_thingy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
Context ctx = getContext();
|
||||||
|
LinearLayout view = (LinearLayout) getActivity().getLayoutInflater()
|
||||||
|
.inflate(R.layout.edit_timeline, (ViewGroup) itemView, false);
|
||||||
|
|
||||||
|
TextInputFrameLayout inputLayout = view.findViewById(R.id.input);
|
||||||
|
EditText editText = inputLayout.getEditText();
|
||||||
|
editText.setText(item.getCustomTitle());
|
||||||
|
editText.setHint(item.getDefaultTitle(ctx));
|
||||||
|
|
||||||
|
ImageButton btn = view.findViewById(R.id.button);
|
||||||
|
PopupMenu popup = new PopupMenu(ctx, btn);
|
||||||
|
TimelineDefinition.Icon currentIcon = item.getIcon();
|
||||||
|
btn.setImageResource(currentIcon.iconRes);
|
||||||
|
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
|
||||||
|
btn.setOnTouchListener(popup.getDragToOpenListener());
|
||||||
|
btn.setOnClickListener(l -> popup.show());
|
||||||
|
|
||||||
|
Menu menu = popup.getMenu();
|
||||||
|
TimelineDefinition.Icon defaultIcon = item.getDefaultIcon();
|
||||||
|
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
|
||||||
|
if (!currentIcon.equals(defaultIcon)) {
|
||||||
|
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
|
||||||
|
}
|
||||||
|
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
|
||||||
|
if (icon.hidden || icon.equals(item.getIcon())) continue;
|
||||||
|
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
|
||||||
|
}
|
||||||
|
UiUtils.enablePopupMenuIcons(ctx, popup);
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
|
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
|
||||||
|
btn.setImageResource(icon.iconRes);
|
||||||
|
btn.setContentDescription(ctx.getString(icon.nameRes));
|
||||||
|
item.setIcon(icon);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
new M3AlertDialogBuilder(ctx)
|
||||||
|
.setTitle(R.string.sk_edit_timeline)
|
||||||
|
.setView(view)
|
||||||
|
.setPositiveButton(R.string.save, (d, which) -> {
|
||||||
|
item.setTitle(editText.getText().toString().trim());
|
||||||
|
rebind();
|
||||||
|
saveTimelines();
|
||||||
|
})
|
||||||
|
.setNeutralButton(R.string.sk_remove, (d, which) ->
|
||||||
|
removeTimeline(getAbsoluteAdapterPosition()))
|
||||||
|
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||||
|
.show();
|
||||||
|
|
||||||
|
btn.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
|
||||||
|
public ItemTouchHelperCallback() {
|
||||||
|
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||||
|
int fromPosition = viewHolder.getAbsoluteAdapterPosition();
|
||||||
|
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
|
||||||
|
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
|
||||||
|
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
|
||||||
|
viewHolder.itemView.animate().alpha(0.65f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder);
|
||||||
|
viewHolder.itemView.animate().alpha(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||||
|
int position = viewHolder.getAbsoluteAdapterPosition();
|
||||||
|
removeTimeline(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,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(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
|||||||
.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(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop {
|
||||||
|
private String nextMaxID;
|
||||||
|
private String accountId;
|
||||||
|
|
||||||
|
public FollowedHashtagsFragment() {
|
||||||
|
super(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Bundle args=getArguments();
|
||||||
|
accountId=args.getString("account");
|
||||||
|
setTitle(R.string.sk_hashtags_you_follow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
|
super.onShown();
|
||||||
|
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
currentRequest=new GetFollowedHashtags(offset==0 ? null : nextMaxID, null, count, null)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
if(result.nextPageUri!=null)
|
||||||
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
|
else
|
||||||
|
nextMaxID=null;
|
||||||
|
onDataLoaded(result, nextMaxID!=null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter getAdapter() {
|
||||||
|
return new HashtagsAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scrollToTop() {
|
||||||
|
smoothScrollRecyclerViewToTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public HashtagViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new HashtagViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull HashtagViewHolder holder, int position) {
|
||||||
|
holder.bind(data.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HashtagViewHolder extends BindableViewHolder<Hashtag> implements UsableRecyclerView.Clickable{
|
||||||
|
private final TextView title;
|
||||||
|
|
||||||
|
public HashtagViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_text, list);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(Hashtag item) {
|
||||||
|
title.setText(item.name);
|
||||||
|
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_number_symbol_24_regular), null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
UiUtils.openHashtagTimeline(getActivity(), accountId, item.name, item.following);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
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;
|
||||||
@@ -10,14 +11,21 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
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.requests.tags.GetHashtag;
|
||||||
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
||||||
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.model.Filter;
|
||||||
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.ui.utils.UiUtils;
|
||||||
|
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;
|
||||||
@@ -25,14 +33,14 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HashtagTimelineFragment extends StatusListFragment{
|
public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||||
private String hashtag;
|
private String hashtag;
|
||||||
private boolean following;
|
private boolean following;
|
||||||
private ImageButton fab;
|
|
||||||
private MenuItem followButton;
|
private MenuItem followButton;
|
||||||
|
|
||||||
public HashtagTimelineFragment(){
|
@Override
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
protected boolean withComposeButton() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,7 +48,6 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
updateTitle(getArguments().getString("hashtag"));
|
updateTitle(getArguments().getString("hashtag"));
|
||||||
following=getArguments().getBoolean("following", false);
|
following=getArguments().getBoolean("following", false);
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,35 +60,20 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
this.following = newFollowing;
|
this.following = newFollowing;
|
||||||
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
|
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);
|
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
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.hashtag_timeline, menu);
|
inflater.inflate(R.menu.hashtag_timeline, menu);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
followButton = menu.findItem(R.id.follow_hashtag);
|
followButton = menu.findItem(R.id.follow_hashtag);
|
||||||
updateFollowingState(following);
|
updateFollowingState(following);
|
||||||
|
|
||||||
followButton.setOnMenuItemClickListener(i -> {
|
|
||||||
updateFollowingState(!following);
|
|
||||||
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Hashtag i) {
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Hashtag hashtag) {
|
public void onSuccess(Hashtag hashtag) {
|
||||||
|
if (getActivity() == null) return;
|
||||||
updateTitle(hashtag.name);
|
updateTitle(hashtag.name);
|
||||||
updateFollowingState(hashtag.following);
|
updateFollowingState(hashtag.following);
|
||||||
}
|
}
|
||||||
@@ -93,12 +85,45 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
}).exec(accountID);
|
}).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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TimelineDefinition makeTimelineDefinition() {
|
||||||
|
return TimelineDefinition.ofHashtag(hashtag);
|
||||||
|
}
|
||||||
|
|
||||||
@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)
|
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count)
|
||||||
.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;
|
||||||
|
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -113,13 +138,12 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
protected boolean onFabLongClick(View v) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
|
||||||
fab=view.findViewById(R.id.fab);
|
|
||||||
fab.setOnClickListener(this::onFabClick);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFabClick(View v){
|
@Override
|
||||||
|
protected 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", '#'+hashtag+' ');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.app.NotificationManager;
|
|||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.service.notification.StatusBarNotification;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -15,7 +16,6 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import org.joinmastodon.android.PushNotificationReceiver;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
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;
|
||||||
@@ -41,7 +41,11 @@ import me.grishka.appkit.views.FragmentRootLinearLayout;
|
|||||||
|
|
||||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
||||||
private FragmentRootLinearLayout content;
|
private FragmentRootLinearLayout content;
|
||||||
private HomeTimelineFragment homeTimelineFragment;
|
|
||||||
|
private HomeTabFragment homeTabFragment;
|
||||||
|
|
||||||
|
// private HomeTimelineFragment homeTimelineFragment;
|
||||||
|
|
||||||
private NotificationsFragment notificationsFragment;
|
private NotificationsFragment notificationsFragment;
|
||||||
private DiscoverFragment searchFragment;
|
private DiscoverFragment searchFragment;
|
||||||
private ProfileFragment profileFragment;
|
private ProfileFragment profileFragment;
|
||||||
@@ -57,7 +61,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
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.mo_app_name);
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
@@ -65,8 +69,13 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
homeTimelineFragment=new HomeTimelineFragment();
|
|
||||||
homeTimelineFragment.setArguments(args);
|
homeTabFragment=new HomeTabFragment();
|
||||||
|
homeTabFragment.setArguments(args);
|
||||||
|
|
||||||
|
// homeTimelineFragment=new HomeTimelineFragment();
|
||||||
|
// homeTimelineFragment.setArguments(args);
|
||||||
|
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
args.putBoolean("noAutoLoad", true);
|
args.putBoolean("noAutoLoad", true);
|
||||||
searchFragment=new DiscoverFragment();
|
searchFragment=new DiscoverFragment();
|
||||||
@@ -110,12 +119,19 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.fragment_wrap, homeTimelineFragment)
|
.add(R.id.fragment_wrap, homeTabFragment)
|
||||||
.add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
.add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||||
.add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
.add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||||
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
|
// getChildFragmentManager().beginTransaction()
|
||||||
|
// .add(R.id.fragment_wrap, homeTimelineFragment)
|
||||||
|
// .add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||||
|
// .add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||||
|
// .add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||||
|
// .commit();
|
||||||
|
|
||||||
String defaultTab=getArguments().getString("tab");
|
String defaultTab=getArguments().getString("tab");
|
||||||
if("notifications".equals(defaultTab)){
|
if("notifications".equals(defaultTab)){
|
||||||
tabBar.selectTab(R.id.tab_notifications);
|
tabBar.selectTab(R.id.tab_notifications);
|
||||||
@@ -136,21 +152,36 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
@Override
|
@Override
|
||||||
public void onViewStateRestored(Bundle savedInstanceState){
|
public void onViewStateRestored(Bundle savedInstanceState){
|
||||||
super.onViewStateRestored(savedInstanceState);
|
super.onViewStateRestored(savedInstanceState);
|
||||||
if(savedInstanceState==null || homeTimelineFragment!=null)
|
|
||||||
return;
|
if(savedInstanceState==null) return;
|
||||||
homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment");
|
|
||||||
|
// if(savedInstanceState==null || homeTimelineFragment!=null)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
|
||||||
|
|
||||||
|
// homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment");
|
||||||
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
||||||
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
||||||
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
||||||
currentTab=savedInstanceState.getInt("selectedTab");
|
currentTab=savedInstanceState.getInt("selectedTab");
|
||||||
Fragment current=fragmentForTab(currentTab);
|
Fragment current=fragmentForTab(currentTab);
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.hide(homeTimelineFragment)
|
.hide(homeTabFragment)
|
||||||
.hide(searchFragment)
|
.hide(searchFragment)
|
||||||
.hide(notificationsFragment)
|
.hide(notificationsFragment)
|
||||||
.hide(profileFragment)
|
.hide(profileFragment)
|
||||||
.show(current)
|
.show(current)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
|
// getChildFragmentManager().beginTransaction()
|
||||||
|
// .hide(homeTimelineFragment)
|
||||||
|
// .hide(searchFragment)
|
||||||
|
// .hide(notificationsFragment)
|
||||||
|
// .hide(profileFragment)
|
||||||
|
// .show(current)
|
||||||
|
// .commit();
|
||||||
maybeTriggerLoading(current);
|
maybeTriggerLoading(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +211,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
}
|
}
|
||||||
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
||||||
homeTimelineFragment.onApplyWindowInsets(topOnlyInsets);
|
|
||||||
|
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
|
|
||||||
|
// homeTimelineFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
|
|
||||||
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
@@ -188,7 +223,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
|
|
||||||
private Fragment fragmentForTab(@IdRes int tab){
|
private Fragment fragmentForTab(@IdRes int tab){
|
||||||
if(tab==R.id.tab_home){
|
if(tab==R.id.tab_home){
|
||||||
return homeTimelineFragment;
|
return homeTabFragment;
|
||||||
|
|
||||||
|
// if(tab==R.id.tab_home){
|
||||||
|
// return homeTimelineFragment;
|
||||||
}else if(tab==R.id.tab_search){
|
}else if(tab==R.id.tab_search){
|
||||||
return searchFragment;
|
return searchFragment;
|
||||||
}else if(tab==R.id.tab_notifications){
|
}else if(tab==R.id.tab_notifications){
|
||||||
@@ -202,6 +240,17 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
private void onTabSelected(@IdRes int tab){
|
private void onTabSelected(@IdRes int tab){
|
||||||
Fragment newFragment=fragmentForTab(tab);
|
Fragment newFragment=fragmentForTab(tab);
|
||||||
if(tab==currentTab){
|
if(tab==currentTab){
|
||||||
|
if(tab == R.id.tab_search){
|
||||||
|
if(newFragment instanceof ScrollableToTop scrollable)
|
||||||
|
scrollable.scrollToTop();
|
||||||
|
searchFragment.selectSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(newFragment instanceof ScrollableToTop scrollable)
|
||||||
|
scrollable.scrollToTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(tab==currentTab && tab == R.id.tab_search){
|
||||||
if(newFragment instanceof ScrollableToTop scrollable)
|
if(newFragment instanceof ScrollableToTop scrollable)
|
||||||
scrollable.scrollToTop();
|
scrollable.scrollToTop();
|
||||||
return;
|
return;
|
||||||
@@ -222,7 +271,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
((NotificationsFragment) newFragment).loadData();
|
((NotificationsFragment) newFragment).loadData();
|
||||||
// TODO make an interface?
|
// TODO make an interface?
|
||||||
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
||||||
nm.cancel(accountID, PushNotificationReceiver.NOTIFICATION_ID);
|
for (StatusBarNotification notification : nm.getActiveNotifications()) {
|
||||||
|
if (accountID.equals(notification.getTag())) {
|
||||||
|
nm.cancel(accountID, notification.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +288,12 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
new AccountSwitcherSheet(getActivity()).show();
|
new AccountSwitcherSheet(getActivity()).show();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if(tab==R.id.tab_search){
|
||||||
|
onTabSelected(R.id.tab_search);
|
||||||
|
tabBar.selectTab(R.id.tab_search);
|
||||||
|
searchFragment.selectSearch();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,17 +307,24 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
tabBar.selectTab(R.id.tab_home);
|
tabBar.selectTab(R.id.tab_home);
|
||||||
onTabSelected(R.id.tab_home);
|
onTabSelected(R.id.tab_home);
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
return homeTabFragment.onBackPressed();
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState){
|
public void onSaveInstanceState(Bundle outState){
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt("selectedTab", currentTab);
|
outState.putInt("selectedTab", currentTab);
|
||||||
getChildFragmentManager().putFragment(outState, "homeTimelineFragment", homeTimelineFragment);
|
|
||||||
getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
|
||||||
getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||||
getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||||
|
if (profileFragment.isAdded()) getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||||
|
|
||||||
|
// getChildFragmentManager().putFragment(outState, "homeTimelineFragment", homeTimelineFragment);
|
||||||
|
// getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||||
|
// getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||||
|
// getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,684 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.reduceMotion;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Fragment;
|
||||||
|
import android.app.FragmentTransaction;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.SubMenu;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewParent;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
|
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||||
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
|
import org.joinmastodon.android.model.Announcement;
|
||||||
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener {
|
||||||
|
private static final int ANNOUNCEMENTS_RESULT = 654;
|
||||||
|
|
||||||
|
private String accountID;
|
||||||
|
private MenuItem announcements, announcementsAction, settings, settingsAction;
|
||||||
|
// private ImageView toolbarLogo;
|
||||||
|
private Button toolbarShowNewPostsBtn;
|
||||||
|
private boolean newPostsBtnShown;
|
||||||
|
private AnimatorSet currentNewPostsAnim;
|
||||||
|
private ViewPager2 pager;
|
||||||
|
private View switcher;
|
||||||
|
private FrameLayout toolbarFrame;
|
||||||
|
private ImageView timelineIcon;
|
||||||
|
private ImageView collapsedChevron;
|
||||||
|
private TextView timelineTitle;
|
||||||
|
private PopupMenu switcherPopup;
|
||||||
|
private final Map<Integer, ListTimeline> listItems = new HashMap<>();
|
||||||
|
private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>();
|
||||||
|
private List<TimelineDefinition> timelineDefinitions;
|
||||||
|
private int count;
|
||||||
|
private Fragment[] fragments;
|
||||||
|
private FrameLayout[] tabViews;
|
||||||
|
private TimelineDefinition[] timelines;
|
||||||
|
private final Map<Integer, TimelineDefinition> timelinesByMenuItem = new HashMap<>();
|
||||||
|
private SubMenu hashtagsMenu, listsMenu;
|
||||||
|
private PopupMenu overflowPopup;
|
||||||
|
private View overflowActionView = null;
|
||||||
|
private boolean announcementsBadged, settingsBadged;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
E.register(this);
|
||||||
|
accountID = getArguments().getString("account");
|
||||||
|
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES);
|
||||||
|
assert timelineDefinitions != null;
|
||||||
|
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
|
||||||
|
count = timelineDefinitions.size();
|
||||||
|
fragments = new Fragment[count];
|
||||||
|
tabViews = new FrameLayout[count];
|
||||||
|
timelines = new TimelineDefinition[count];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||||
|
FrameLayout view = new FrameLayout(getContext());
|
||||||
|
pager = new ViewPager2(getContext());
|
||||||
|
toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false);
|
||||||
|
|
||||||
|
if (fragments[0] == null) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putBoolean("__is_tab", true);
|
||||||
|
args.putBoolean("onlyPosts", true);
|
||||||
|
|
||||||
|
for (int i = 0; i < timelineDefinitions.size(); i++) {
|
||||||
|
TimelineDefinition tl = timelineDefinitions.get(i);
|
||||||
|
fragments[i] = tl.getFragment();
|
||||||
|
timelines[i] = tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
fragments[i].setArguments(timelines[i].populateArguments(new Bundle(args)));
|
||||||
|
FrameLayout tabView = new FrameLayout(getActivity());
|
||||||
|
tabView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
tabView.setVisibility(View.GONE);
|
||||||
|
tabView.setId(i + 1);
|
||||||
|
transaction.add(i + 1, fragments[i]);
|
||||||
|
view.addView(tabView);
|
||||||
|
tabViews[i] = tabView;
|
||||||
|
}
|
||||||
|
transaction.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
view.addView(pager, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
|
||||||
|
overflowActionView = UiUtils.makeOverflowActionView(getContext());
|
||||||
|
overflowPopup = new PopupMenu(getContext(), overflowActionView);
|
||||||
|
overflowPopup.setOnMenuItemClickListener(this::onOptionsItemSelected);
|
||||||
|
overflowActionView.setOnClickListener(l -> overflowPopup.show());
|
||||||
|
overflowActionView.setOnTouchListener(overflowPopup.getDragToOpenListener());
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
timelineIcon = toolbarFrame.findViewById(R.id.timeline_icon);
|
||||||
|
timelineTitle = toolbarFrame.findViewById(R.id.timeline_title);
|
||||||
|
collapsedChevron = toolbarFrame.findViewById(R.id.collapsed_chevron);
|
||||||
|
switcher = toolbarFrame.findViewById(R.id.switcher_btn);
|
||||||
|
switcherPopup = new PopupMenu(getContext(), switcher);
|
||||||
|
switcherPopup.setOnMenuItemClickListener(this::onSwitcherItemSelected);
|
||||||
|
UiUtils.enablePopupMenuIcons(getContext(), switcherPopup);
|
||||||
|
switcher.setOnClickListener(v->switcherPopup.show());
|
||||||
|
switcher.setOnTouchListener(switcherPopup.getDragToOpenListener());
|
||||||
|
updateSwitcherMenu();
|
||||||
|
|
||||||
|
UiUtils.reduceSwipeSensitivity(pager);
|
||||||
|
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||||
|
pager.setAdapter(new HomePagerAdapter());
|
||||||
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position){
|
||||||
|
if (!reduceMotion) {
|
||||||
|
// setting this here because page transformer appears to fire too late so the
|
||||||
|
// animation can appear bumpy, especially when navigating to a further-away tab
|
||||||
|
switcher.setScaleY(0.85f);
|
||||||
|
switcher.setScaleX(0.85f);
|
||||||
|
switcher.setAlpha(0.65f);
|
||||||
|
}
|
||||||
|
updateSwitcherIcon(position);
|
||||||
|
if (!timelines[position].equals(TimelineDefinition.HOME_TIMELINE)) hideNewPostsButton();
|
||||||
|
if (fragments[position] instanceof BaseRecyclerFragment<?> page){
|
||||||
|
if(!page.loaded && !page.isDataLoading()) page.loadData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!reduceMotion) {
|
||||||
|
pager.setPageTransformer((v, pos) -> {
|
||||||
|
if (reduceMotion || tabViews[pager.getCurrentItem()] != v) return;
|
||||||
|
float scaleFactor = Math.max(0.85f, 1 - Math.abs(pos) * 0.06f);
|
||||||
|
switcher.setScaleY(scaleFactor);
|
||||||
|
switcher.setScaleX(scaleFactor);
|
||||||
|
switcher.setAlpha(Math.max(0.65f, 1 - Math.abs(pos)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateToolbarLogo();
|
||||||
|
|
||||||
|
ViewTreeObserver vto = getToolbar().getViewTreeObserver();
|
||||||
|
if (vto.isAlive()) {
|
||||||
|
vto.addOnGlobalLayoutListener(() -> {
|
||||||
|
Toolbar t = getToolbar();
|
||||||
|
if (t == null) return;
|
||||||
|
int toolbarWidth = t.getWidth();
|
||||||
|
if (toolbarWidth == 0) return;
|
||||||
|
|
||||||
|
int toolbarFrameWidth = toolbarFrame.getWidth();
|
||||||
|
int padding = toolbarWidth - toolbarFrameWidth;
|
||||||
|
FrameLayout parent = ((FrameLayout) toolbarShowNewPostsBtn.getParent());
|
||||||
|
if (padding == parent.getPaddingStart()) return;
|
||||||
|
|
||||||
|
// toolbar frame goes from screen edge to beginning of right-aligned option buttons.
|
||||||
|
// centering button by applying the same space on the left
|
||||||
|
parent.setPaddingRelative(padding, 0, 0, 0);
|
||||||
|
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth - padding * 2);
|
||||||
|
|
||||||
|
switcher.setPivotX(V.dp(28)); // padding + half of icon
|
||||||
|
switcher.setPivotY(switcher.getHeight() / 2f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
|
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
||||||
|
}
|
||||||
|
|
||||||
|
new GetLists().setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<ListTimeline> lists) {
|
||||||
|
updateList(lists, listItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
|
||||||
|
new GetFollowedHashtags().setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(HeaderPaginationList<Hashtag> hashtags) {
|
||||||
|
updateList(hashtags, hashtagsItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
|
||||||
|
new GetAnnouncements(false).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<Announcement> result) {
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
if (result.stream().anyMatch(a -> !a.read)) {
|
||||||
|
announcementsBadged = true;
|
||||||
|
announcements.setVisible(false);
|
||||||
|
announcementsAction.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addListsToOverflowMenu() {
|
||||||
|
Context ctx = getContext();
|
||||||
|
listsMenu.clear();
|
||||||
|
listsMenu.getItem().setVisible(listItems.size() > 0);
|
||||||
|
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(listsMenu));
|
||||||
|
listItems.forEach((id, list) -> {
|
||||||
|
MenuItem item = listsMenu.add(Menu.NONE, id, Menu.NONE, list.title);
|
||||||
|
item.setIcon(R.drawable.ic_fluent_people_24_regular);
|
||||||
|
UiUtils.insetPopupMenuIcon(ctx, item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHashtagsToOverflowMenu() {
|
||||||
|
Context ctx = getContext();
|
||||||
|
hashtagsMenu.clear();
|
||||||
|
hashtagsMenu.getItem().setVisible(hashtagsItems.size() > 0);
|
||||||
|
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(hashtagsMenu));
|
||||||
|
hashtagsItems.forEach((id, hashtag) -> {
|
||||||
|
MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name);
|
||||||
|
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
||||||
|
UiUtils.insetPopupMenuIcon(ctx, item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateToolbarLogo(){
|
||||||
|
Toolbar toolbar = getToolbar();
|
||||||
|
ViewParent parentView = toolbarFrame.getParent();
|
||||||
|
if (parentView == toolbar) return;
|
||||||
|
if (parentView instanceof Toolbar parentToolbar) parentToolbar.removeView(toolbarFrame);
|
||||||
|
toolbar.addView(toolbarFrame, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
toolbar.setOnClickListener(v->scrollToTop());
|
||||||
|
toolbar.setNavigationContentDescription(R.string.back);
|
||||||
|
toolbar.setContentInsetsAbsolute(0, toolbar.getContentInsetRight());
|
||||||
|
|
||||||
|
updateSwitcherIcon(pager.getCurrentItem());
|
||||||
|
|
||||||
|
toolbarShowNewPostsBtn=toolbarFrame.findViewById(R.id.show_new_posts_btn);
|
||||||
|
toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors());
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N) UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn);
|
||||||
|
toolbarShowNewPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
|
||||||
|
|
||||||
|
if(newPostsBtnShown){
|
||||||
|
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
||||||
|
collapsedChevron.setVisibility(View.VISIBLE);
|
||||||
|
collapsedChevron.setAlpha(1f);
|
||||||
|
timelineTitle.setVisibility(View.GONE);
|
||||||
|
timelineTitle.setAlpha(0f);
|
||||||
|
}else{
|
||||||
|
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
||||||
|
toolbarShowNewPostsBtn.setAlpha(0f);
|
||||||
|
collapsedChevron.setVisibility(View.GONE);
|
||||||
|
collapsedChevron.setAlpha(0f);
|
||||||
|
toolbarShowNewPostsBtn.setScaleX(.8f);
|
||||||
|
toolbarShowNewPostsBtn.setScaleY(.8f);
|
||||||
|
timelineTitle.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOverflowMenu() {
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
Menu m = overflowPopup.getMenu();
|
||||||
|
m.clear();
|
||||||
|
overflowPopup.inflate(R.menu.home_overflow);
|
||||||
|
announcements = m.findItem(R.id.announcements);
|
||||||
|
settings = m.findItem(R.id.settings);
|
||||||
|
hashtagsMenu = m.findItem(R.id.hashtags).getSubMenu();
|
||||||
|
listsMenu = m.findItem(R.id.lists).getSubMenu();
|
||||||
|
|
||||||
|
announcements.setVisible(!announcementsBadged);
|
||||||
|
announcementsAction.setVisible(announcementsBadged);
|
||||||
|
settings.setVisible(!settingsBadged);
|
||||||
|
settingsAction.setVisible(settingsBadged);
|
||||||
|
|
||||||
|
UiUtils.enablePopupMenuIcons(getContext(), overflowPopup);
|
||||||
|
|
||||||
|
addListsToOverflowMenu();
|
||||||
|
addHashtagsToOverflowMenu();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
m.setGroupDividerEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
|
inflater.inflate(R.menu.home, menu);
|
||||||
|
|
||||||
|
menu.findItem(R.id.overflow).setActionView(overflowActionView);
|
||||||
|
announcementsAction = menu.findItem(R.id.announcements_action);
|
||||||
|
settingsAction = menu.findItem(R.id.settings_action);
|
||||||
|
|
||||||
|
updateOverflowMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void updateList(List<T> addItems, Map<Integer, T> items) {
|
||||||
|
if (addItems.size() == 0 || getActivity() == null) return;
|
||||||
|
for (int i = 0; i < addItems.size(); i++) items.put(View.generateViewId(), addItems.get(i));
|
||||||
|
updateOverflowMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSwitcherMenu() {
|
||||||
|
Menu switcherMenu = switcherPopup.getMenu();
|
||||||
|
switcherMenu.clear();
|
||||||
|
timelinesByMenuItem.clear();
|
||||||
|
|
||||||
|
for (TimelineDefinition tl : timelines) {
|
||||||
|
int menuItemId = View.generateViewId();
|
||||||
|
timelinesByMenuItem.put(menuItemId, tl);
|
||||||
|
MenuItem item = switcherMenu.add(0, menuItemId, 0, tl.getTitle(getContext()));
|
||||||
|
item.setIcon(tl.getIcon().iconRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
UiUtils.enablePopupMenuIcons(getContext(), switcherPopup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onSwitcherItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
|
||||||
|
if (id == R.id.menu_back) {
|
||||||
|
switcher.post(() -> switcherPopup.show());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimelineDefinition tl = timelinesByMenuItem.get(id);
|
||||||
|
if (tl != null) {
|
||||||
|
for (int i = 0; i < timelines.length; i++) {
|
||||||
|
if (timelines[i] == tl) {
|
||||||
|
navigateTo(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private void navigateTo(int i) {
|
||||||
|
navigateTo(i, !reduceMotion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateTo(int i, boolean smooth) {
|
||||||
|
pager.setCurrentItem(i, smooth);
|
||||||
|
updateSwitcherIcon(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSwitcherIcon(int i) {
|
||||||
|
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
||||||
|
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
int id = item.getItemId();
|
||||||
|
ListTimeline list;
|
||||||
|
Hashtag hashtag;
|
||||||
|
|
||||||
|
if (item.getItemId() == R.id.menu_back) {
|
||||||
|
getToolbar().post(() -> overflowPopup.show());
|
||||||
|
return true;
|
||||||
|
} else if (id == R.id.settings || id == R.id.settings_action) {
|
||||||
|
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||||
|
} else if (id == R.id.announcements || id == R.id.announcements_action) {
|
||||||
|
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
||||||
|
} else if (id == R.id.edit_timelines) {
|
||||||
|
Nav.go(getActivity(), EditTimelinesFragment.class, args);
|
||||||
|
} else if ((list = listItems.get(id)) != null) {
|
||||||
|
args.putString("listID", list.id);
|
||||||
|
args.putString("listTitle", list.title);
|
||||||
|
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
||||||
|
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||||
|
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
||||||
|
args.putString("hashtag", hashtag.name);
|
||||||
|
args.putBoolean("following", hashtag.following);
|
||||||
|
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scrollToTop(){
|
||||||
|
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideNewPostsButton(){
|
||||||
|
if(!newPostsBtnShown)
|
||||||
|
return;
|
||||||
|
newPostsBtnShown=false;
|
||||||
|
if(currentNewPostsAnim!=null){
|
||||||
|
currentNewPostsAnim.cancel();
|
||||||
|
}
|
||||||
|
timelineTitle.setVisibility(View.VISIBLE);
|
||||||
|
AnimatorSet set=new AnimatorSet();
|
||||||
|
set.playTogether(
|
||||||
|
ObjectAnimator.ofFloat(timelineTitle, View.ALPHA, 1f),
|
||||||
|
ObjectAnimator.ofFloat(timelineTitle, View.SCALE_X, 1f),
|
||||||
|
ObjectAnimator.ofFloat(timelineTitle, View.SCALE_Y, 1f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 0f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, .8f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f),
|
||||||
|
ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 0f)
|
||||||
|
);
|
||||||
|
set.setDuration(reduceMotion ? 0 : 300);
|
||||||
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
||||||
|
collapsedChevron.setVisibility(View.GONE);
|
||||||
|
currentNewPostsAnim=null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentNewPostsAnim=set;
|
||||||
|
set.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showNewPostsButton(){
|
||||||
|
if(newPostsBtnShown || pager == null || !timelines[pager.getCurrentItem()].equals(TimelineDefinition.HOME_TIMELINE))
|
||||||
|
return;
|
||||||
|
newPostsBtnShown=true;
|
||||||
|
if(currentNewPostsAnim!=null){
|
||||||
|
currentNewPostsAnim.cancel();
|
||||||
|
}
|
||||||
|
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
||||||
|
collapsedChevron.setVisibility(View.VISIBLE);
|
||||||
|
AnimatorSet set=new AnimatorSet();
|
||||||
|
set.playTogether(
|
||||||
|
ObjectAnimator.ofFloat(timelineTitle, View.ALPHA, 0f),
|
||||||
|
ObjectAnimator.ofFloat(timelineTitle, View.SCALE_X, .8f),
|
||||||
|
ObjectAnimator.ofFloat(timelineTitle, View.SCALE_Y, .8f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 1f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, 1f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f),
|
||||||
|
ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 1f)
|
||||||
|
);
|
||||||
|
set.setDuration(reduceMotion ? 0 : 300);
|
||||||
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
timelineTitle.setVisibility(View.GONE);
|
||||||
|
currentNewPostsAnim=null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentNewPostsAnim=set;
|
||||||
|
set.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNewPostsBtnShown() {
|
||||||
|
return newPostsBtnShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNewPostsBtnClick(View view) {
|
||||||
|
if(newPostsBtnShown){
|
||||||
|
hideNewPostsButton();
|
||||||
|
scrollToTop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||||
|
if (reqCode == ANNOUNCEMENTS_RESULT && success) {
|
||||||
|
announcementsBadged = false;
|
||||||
|
announcements.setVisible(true);
|
||||||
|
announcementsAction.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
||||||
|
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING) {
|
||||||
|
settingsBadged = true;
|
||||||
|
settingsAction.setVisible(true);
|
||||||
|
settings.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
||||||
|
updateUpdateState(ev.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onBackPressed(){
|
||||||
|
if(pager.getCurrentItem() > 0){
|
||||||
|
navigateTo(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView(){
|
||||||
|
super.onDestroyView();
|
||||||
|
if (overflowPopup != null) {
|
||||||
|
overflowPopup.dismiss();
|
||||||
|
overflowPopup = null;
|
||||||
|
}
|
||||||
|
if (switcherPopup != null) {
|
||||||
|
switcherPopup.dismiss();
|
||||||
|
switcherPopup = null;
|
||||||
|
}
|
||||||
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
|
E.unregister(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShown() {
|
||||||
|
super.onShown();
|
||||||
|
Object pinnedTimelines = GlobalUserPreferences.pinnedTimelines.get(accountID);
|
||||||
|
if (pinnedTimelines != null && timelineDefinitions != pinnedTimelines) UiUtils.restartApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewStateRestored(Bundle savedInstanceState) {
|
||||||
|
super.onViewStateRestored(savedInstanceState);
|
||||||
|
if (savedInstanceState == null) return;
|
||||||
|
navigateTo(savedInstanceState.getInt("selectedTab"), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putInt("selectedTab", pager.getCurrentItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onHashtagUpdatedEvent(HashtagUpdatedEvent event) {
|
||||||
|
handleListEvent(hashtagsItems, h -> h.name.equalsIgnoreCase(event.name), event.following, () -> {
|
||||||
|
Hashtag hashtag = new Hashtag();
|
||||||
|
hashtag.name = event.name;
|
||||||
|
hashtag.following = true;
|
||||||
|
return hashtag;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onListDeletedEvent(ListDeletedEvent event) {
|
||||||
|
handleListEvent(listItems, l -> l.id.equals(event.id), false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
||||||
|
handleListEvent(listItems, l -> l.id.equals(event.id), true, () -> {
|
||||||
|
ListTimeline list = new ListTimeline();
|
||||||
|
list.id = event.id;
|
||||||
|
list.title = event.title;
|
||||||
|
list.repliesPolicy = event.repliesPolicy;
|
||||||
|
return list;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void handleListEvent(
|
||||||
|
Map<Integer, T> existingThings,
|
||||||
|
Predicate<T> matchExisting,
|
||||||
|
boolean shouldBeInList,
|
||||||
|
Supplier<T> makeNewThing
|
||||||
|
) {
|
||||||
|
Optional<Map.Entry<Integer, T>> existingThing = existingThings.entrySet().stream()
|
||||||
|
.filter(e -> matchExisting.test(e.getValue())).findFirst();
|
||||||
|
if (shouldBeInList) {
|
||||||
|
existingThings.put(existingThing.isPresent()
|
||||||
|
? existingThing.get().getKey() : View.generateViewId(), makeNewThing.get());
|
||||||
|
updateOverflowMenu();
|
||||||
|
} else if (existingThing.isPresent() && !shouldBeInList) {
|
||||||
|
existingThings.remove(existingThing.get().getKey());
|
||||||
|
updateOverflowMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Hashtag> getHashtags() {
|
||||||
|
return hashtagsItems.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
FrameLayout tabView = tabViews[viewType % getItemCount()];
|
||||||
|
ViewGroup tabParent = (ViewGroup) tabView.getParent();
|
||||||
|
if (tabParent != null) tabParent.removeView(tabView);
|
||||||
|
tabView.setVisibility(View.VISIBLE);
|
||||||
|
return new SimpleViewHolder(tabView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position){
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,82 +1,56 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
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.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
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.ui.utils.UiUtils;
|
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
|
|
||||||
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.Locale;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
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.CubicBezierInterpolator;
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HomeTimelineFragment extends StatusListFragment{
|
public class HomeTimelineFragment extends StatusListFragment {
|
||||||
private ImageButton fab;
|
private HomeTabFragment parent;
|
||||||
private ImageView toolbarLogo;
|
|
||||||
private Button toolbarShowNewPostsBtn;
|
|
||||||
private boolean newPostsBtnShown;
|
|
||||||
private AnimatorSet currentNewPostsAnim;
|
|
||||||
|
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
private String lastSavedMarkerID;
|
||||||
|
|
||||||
public HomeTimelineFragment(){
|
@Override
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
protected boolean withComposeButton() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
setHasOptionsMenu(true);
|
if (getParentFragment() instanceof HomeTabFragment home) parent = home;
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> filterPosts(List<Status> items) {
|
private List<Status> filterPosts(List<Status> items) {
|
||||||
|
// This is the function I must use to solve the filters thing for real
|
||||||
return items.stream().filter(i ->
|
return items.stream().filter(i ->
|
||||||
(GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
|
(GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
|
||||||
(GlobalUserPreferences.showBoosts || i.reblog == null)
|
(GlobalUserPreferences.showBoosts || i.reblog == null)
|
||||||
@@ -90,8 +64,7 @@ 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)
|
if (getActivity() == null) return;
|
||||||
return;
|
|
||||||
List<Status> filteredItems = filterPosts(result.items);
|
List<Status> filteredItems = filterPosts(result.items);
|
||||||
onDataLoaded(filteredItems, !result.items.isEmpty());
|
onDataLoaded(filteredItems, !result.items.isEmpty());
|
||||||
maxID=result.maxID;
|
maxID=result.maxID;
|
||||||
@@ -104,41 +77,15 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
fab=view.findViewById(R.id.fab);
|
|
||||||
fab.setOnClickListener(this::onFabClick);
|
|
||||||
updateToolbarLogo();
|
|
||||||
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(newPostsBtnShown && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
||||||
hideNewPostsButton();
|
parent.hideNewPostsButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
|
||||||
E.register(this);
|
|
||||||
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
|
||||||
inflater.inflate(R.menu.home, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(Configuration newConfig){
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
updateToolbarLogo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -153,14 +100,31 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onStatusCreated(StatusCreatedEvent ev){
|
@Override
|
||||||
prependItems(Collections.singletonList(ev.status), true);
|
protected void onHidden(){
|
||||||
|
super.onHidden();
|
||||||
|
// if(!data.isEmpty()){
|
||||||
|
// String topPostID=displayItems.get(list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset()).parentID;
|
||||||
|
// if(!topPostID.equals(lastSavedMarkerID)){
|
||||||
|
// lastSavedMarkerID=topPostID;
|
||||||
|
// new SaveMarkers(topPostID, null)
|
||||||
|
// .setCallback(new Callback<>(){
|
||||||
|
// @Override
|
||||||
|
// public void onSuccess(SaveMarkers.Response result){
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public void onError(ErrorResponse error){
|
||||||
|
// lastSavedMarkerID=null;
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .exec(accountID);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFabClick(View v){
|
public void onStatusCreated(StatusCreatedEvent ev){
|
||||||
Bundle args=new Bundle();
|
prependItems(Collections.singletonList(ev.status), true);
|
||||||
args.putString("account", accountID);
|
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadNewPosts(){
|
private void loadNewPosts(){
|
||||||
@@ -187,13 +151,11 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
result.get(result.size()-1).hasGapAfter=true;
|
result.get(result.size()-1).hasGapAfter=true;
|
||||||
toAdd=result;
|
toAdd=result;
|
||||||
}
|
}
|
||||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
||||||
if(!filters.isEmpty()){
|
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
||||||
toAdd=toAdd.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
if(!toAdd.isEmpty()){
|
if(!toAdd.isEmpty()){
|
||||||
prependItems(toAdd, true);
|
prependItems(toAdd, true);
|
||||||
showNewPostsButton();
|
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,18 +226,14 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
targetList.clear();
|
targetList.clear();
|
||||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
||||||
outer:
|
|
||||||
for(Status s:result){
|
for(Status s:result){
|
||||||
if(idsBelowGap.contains(s.id))
|
if(idsBelowGap.contains(s.id))
|
||||||
break;
|
break;
|
||||||
for(Filter filter:filters){
|
if(filterPredicate.test(s)){
|
||||||
if(filter.matches(s)){
|
targetList.addAll(buildDisplayItems(s));
|
||||||
continue outer;
|
insertedPosts.add(s);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
targetList.addAll(buildDisplayItems(s));
|
|
||||||
insertedPosts.add(s);
|
|
||||||
}
|
}
|
||||||
if(targetList.isEmpty()){
|
if(targetList.isEmpty()){
|
||||||
// oops. We didn't add new posts, but at least we know there are none.
|
// oops. We didn't add new posts, but at least we know there are none.
|
||||||
@@ -313,131 +271,10 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
}
|
}
|
||||||
|
if (parent != null) parent.hideNewPostsButton();
|
||||||
super.onRefresh();
|
super.onRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateToolbarLogo(){
|
|
||||||
toolbarLogo=new ImageView(getActivity());
|
|
||||||
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
|
|
||||||
toolbarLogo.setImageResource(R.drawable.logo);
|
|
||||||
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
|
||||||
|
|
||||||
toolbarShowNewPostsBtn=new Button(getActivity());
|
|
||||||
toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium);
|
|
||||||
toolbarShowNewPostsBtn.setTextColor(0xffffffff);
|
|
||||||
toolbarShowNewPostsBtn.setStateListAnimator(null);
|
|
||||||
toolbarShowNewPostsBtn.setBackgroundResource(R.drawable.bg_button_new_posts);
|
|
||||||
toolbarShowNewPostsBtn.setText(R.string.see_new_posts);
|
|
||||||
toolbarShowNewPostsBtn.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_fluent_arrow_up_16_filled, 0, 0, 0);
|
|
||||||
toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors());
|
|
||||||
toolbarShowNewPostsBtn.setCompoundDrawablePadding(V.dp(8));
|
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn);
|
|
||||||
toolbarShowNewPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
|
|
||||||
|
|
||||||
if(newPostsBtnShown){
|
|
||||||
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
|
||||||
toolbarLogo.setVisibility(View.INVISIBLE);
|
|
||||||
toolbarLogo.setAlpha(0f);
|
|
||||||
}else{
|
|
||||||
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
|
||||||
toolbarShowNewPostsBtn.setAlpha(0f);
|
|
||||||
toolbarShowNewPostsBtn.setScaleX(.8f);
|
|
||||||
toolbarShowNewPostsBtn.setScaleY(.8f);
|
|
||||||
toolbarLogo.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
FrameLayout logoWrap=new FrameLayout(getActivity());
|
|
||||||
FrameLayout.LayoutParams logoParams=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
|
|
||||||
logoParams.setMargins(0, V.dp(2), 0, 0);
|
|
||||||
logoWrap.addView(toolbarLogo, logoParams);
|
|
||||||
logoWrap.addView(toolbarShowNewPostsBtn, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(32), Gravity.CENTER));
|
|
||||||
|
|
||||||
Toolbar toolbar=getToolbar();
|
|
||||||
toolbar.addView(logoWrap, new Toolbar.LayoutParams(Gravity.CENTER));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showNewPostsButton(){
|
|
||||||
if(newPostsBtnShown)
|
|
||||||
return;
|
|
||||||
newPostsBtnShown=true;
|
|
||||||
if(currentNewPostsAnim!=null){
|
|
||||||
currentNewPostsAnim.cancel();
|
|
||||||
}
|
|
||||||
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
|
||||||
AnimatorSet set=new AnimatorSet();
|
|
||||||
set.playTogether(
|
|
||||||
ObjectAnimator.ofFloat(toolbarLogo, View.ALPHA, 0f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 1f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, 1f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f)
|
|
||||||
);
|
|
||||||
set.setDuration(300);
|
|
||||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
set.addListener(new AnimatorListenerAdapter(){
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation){
|
|
||||||
toolbarLogo.setVisibility(View.INVISIBLE);
|
|
||||||
currentNewPostsAnim=null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
currentNewPostsAnim=set;
|
|
||||||
set.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideNewPostsButton(){
|
|
||||||
if(!newPostsBtnShown)
|
|
||||||
return;
|
|
||||||
newPostsBtnShown=false;
|
|
||||||
if(currentNewPostsAnim!=null){
|
|
||||||
currentNewPostsAnim.cancel();
|
|
||||||
}
|
|
||||||
toolbarLogo.setVisibility(View.VISIBLE);
|
|
||||||
AnimatorSet set=new AnimatorSet();
|
|
||||||
set.playTogether(
|
|
||||||
ObjectAnimator.ofFloat(toolbarLogo, View.ALPHA, 1f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 0f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, .8f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f)
|
|
||||||
);
|
|
||||||
set.setDuration(300);
|
|
||||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
set.addListener(new AnimatorListenerAdapter(){
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation){
|
|
||||||
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
|
||||||
currentNewPostsAnim=null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
currentNewPostsAnim=set;
|
|
||||||
set.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNewPostsBtnClick(View v){
|
|
||||||
if(newPostsBtnShown){
|
|
||||||
hideNewPostsButton();
|
|
||||||
scrollToTop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView(){
|
|
||||||
super.onDestroyView();
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
|
||||||
E.unregister(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
|
||||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
|
|
||||||
getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_24_badged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
|
||||||
updateUpdateState(ev.state);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public interface IsOnTop {
|
||||||
|
boolean isOnTop();
|
||||||
|
|
||||||
|
default boolean isRecyclerViewOnTop(@Nullable RecyclerView list) {
|
||||||
|
if (list == null) return true;
|
||||||
|
return !list.canScrollVertically(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +1,133 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.media.MediaRouter;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
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.ImageButton;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.GetList;
|
||||||
|
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.events.ListDeletedEvent;
|
||||||
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||||
|
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.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
|
||||||
public class ListTimelineFragment extends StatusListFragment {
|
public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||||
private String listID;
|
private String listID;
|
||||||
private String listTitle;
|
private String listTitle;
|
||||||
private ImageButton fab;
|
@Nullable
|
||||||
|
private ListTimeline.RepliesPolicy repliesPolicy;
|
||||||
|
|
||||||
public ListTimelineFragment() {
|
@Override
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
protected boolean withComposeButton() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
listID=getArguments().getString("listID");
|
Bundle args = getArguments();
|
||||||
listTitle=getArguments().getString("listTitle");
|
listID = args.getString("listID");
|
||||||
|
listTitle = args.getString("listTitle");
|
||||||
|
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
|
||||||
|
|
||||||
setTitle(listTitle);
|
setTitle(listTitle);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
new GetList(listID).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(ListTimeline listTimeline) {
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
// TODO: save updated info
|
||||||
|
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
|
||||||
|
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
|
||||||
|
repliesPolicy = listTimeline.repliesPolicy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.list, menu);
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
// TODO: implement edit, delete
|
UiUtils.enableOptionsMenuIcons(getContext(), menu, R.id.pin);
|
||||||
// inflater.inflate(R.menu.list, menu);
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (super.onOptionsItemSelected(item)) return true;
|
||||||
|
if (item.getItemId() == R.id.edit) {
|
||||||
|
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||||
|
editor.applyList(listTitle, repliesPolicy);
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_edit_list_title)
|
||||||
|
.setIcon(R.drawable.ic_fluent_people_28_regular)
|
||||||
|
.setView(editor)
|
||||||
|
.setPositiveButton(R.string.save, (d, which) -> {
|
||||||
|
String newTitle = editor.getTitle().trim();
|
||||||
|
setTitle(newTitle);
|
||||||
|
new UpdateList(listID, newTitle, editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(ListTimeline list) {
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
setTitle(list.title);
|
||||||
|
listTitle = list.title;
|
||||||
|
repliesPolicy = list.repliesPolicy;
|
||||||
|
E.post(new ListUpdatedCreatedEvent(listID, listTitle, repliesPolicy));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
setTitle(listTitle);
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||||
|
.show();
|
||||||
|
} else if (item.getItemId() == R.id.delete) {
|
||||||
|
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
|
||||||
|
E.post(new ListDeletedEvent(listID));
|
||||||
|
Nav.finish(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TimelineDefinition makeTimelineDefinition() {
|
||||||
|
return TimelineDefinition.ofList(listID, listTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -51,10 +136,12 @@ public class ListTimelineFragment 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;
|
||||||
|
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,13 +152,7 @@ public class ListTimelineFragment extends StatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
protected void onFabClick(View v){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
fab=view.findViewById(R.id.fab);
|
|
||||||
fab.setOnClickListener(this::onFabClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFabClick(View v){
|
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
|
|||||||
@@ -6,183 +6,254 @@ 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.Button;
|
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.CreateList;
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
||||||
|
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||||
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
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.Map;
|
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
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.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||||
private String accountId;
|
private String accountId;
|
||||||
private String profileAccountId;
|
private String profileAccountId;
|
||||||
private String profileDisplayUsername;
|
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||||
private HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
private final HashMap<String, Boolean> userInList = new HashMap<>();
|
||||||
private HashMap<String, Boolean> userInList = new HashMap<>();
|
private ListsAdapter adapter;
|
||||||
private int inProgress = 0;
|
|
||||||
|
|
||||||
public ListTimelinesFragment() {
|
public ListTimelinesFragment() {
|
||||||
super(10);
|
super(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Bundle args=getArguments();
|
Bundle args=getArguments();
|
||||||
accountId=args.getString("account");
|
accountId=args.getString("account");
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
E.register(this);
|
||||||
|
|
||||||
if(args.containsKey("profileAccount")){
|
if(args.containsKey("profileAccount")){
|
||||||
profileAccountId=args.getString("profileAccount");
|
profileAccountId=args.getString("profileAccount");
|
||||||
profileDisplayUsername=args.getString("profileDisplayUsername");
|
String profileDisplayUsername = args.getString("profileDisplayUsername");
|
||||||
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
||||||
// setHasOptionsMenu(true);
|
} else {
|
||||||
}
|
setTitle(R.string.sk_your_lists);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onShown(){
|
protected void onShown(){
|
||||||
super.onShown();
|
super.onShown();
|
||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
@Override
|
||||||
// public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
// Button saveButton=new Button(getActivity());
|
super.onViewCreated(view, savedInstanceState);
|
||||||
// saveButton.setText(R.string.save);
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||||
// saveButton.setOnClickListener(this::onSaveClick);
|
}
|
||||||
// LinearLayout wrap=new LinearLayout(getActivity());
|
|
||||||
// wrap.setOrientation(LinearLayout.HORIZONTAL);
|
|
||||||
// wrap.addView(saveButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
// wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
|
|
||||||
// wrap.setClipToPadding(false);
|
|
||||||
// MenuItem item=menu.add(R.string.save);
|
|
||||||
// item.setActionView(wrap);
|
|
||||||
// item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void saveListMembership(String listId, boolean isMember) {
|
@Override
|
||||||
userInList.put(listId, isMember);
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
inflater.inflate(R.menu.menu_list, menu);
|
||||||
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
|
}
|
||||||
req.setCallback(new SimpleCallback<>(this) {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Object o) {}
|
|
||||||
}).exec(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
userInListBefore.clear();
|
if (item.getItemId() == R.id.create) {
|
||||||
userInList.clear();
|
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||||
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
new M3AlertDialogBuilder(getActivity())
|
||||||
.setCallback(new SimpleCallback<>(this) {
|
.setTitle(R.string.sk_create_list_title)
|
||||||
@Override
|
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
||||||
public void onSuccess(List<ListTimeline> lists) {
|
.setView(editor)
|
||||||
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
.setPositiveButton(R.string.sk_create, (d, which) ->
|
||||||
userInList.putAll(userInListBefore);
|
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||||
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
@Override
|
||||||
if (profileAccountId == null) return;
|
public void onSuccess(ListTimeline list) {
|
||||||
|
data.add(0, list);
|
||||||
|
adapter.notifyItemRangeInserted(0, 1);
|
||||||
|
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
|
||||||
|
}
|
||||||
|
|
||||||
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
|
@Override
|
||||||
@Override
|
public void onError(ErrorResponse error) {
|
||||||
public void onSuccess(List<ListTimeline> allLists) {
|
error.showToast(getContext());
|
||||||
List<ListTimeline> newLists = new ArrayList<>();
|
}
|
||||||
for (ListTimeline l : allLists) {
|
}).exec(accountId)
|
||||||
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
)
|
||||||
if (!userInListBefore.containsKey(l.id)) {
|
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||||
userInListBefore.put(l.id, false);
|
.show();
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
userInList.putAll(userInListBefore);
|
}
|
||||||
onDataLoaded(newLists, false);
|
|
||||||
}
|
|
||||||
}).exec(accountId);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void saveListMembership(String listId, boolean isMember) {
|
||||||
protected RecyclerView.Adapter getAdapter() {
|
userInList.put(listId, isMember);
|
||||||
return new ListsAdapter();
|
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
||||||
}
|
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
|
||||||
|
req.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object o) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop() {
|
public void onError(ErrorResponse error) {
|
||||||
smoothScrollRecyclerViewToTop(list);
|
error.showToast(getContext());
|
||||||
}
|
}
|
||||||
|
}).exec(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
@Override
|
||||||
@NonNull
|
protected void doLoadData(int offset, int count){
|
||||||
@Override
|
userInListBefore.clear();
|
||||||
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
userInList.clear();
|
||||||
return new ListViewHolder();
|
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
||||||
}
|
.setCallback(new SimpleCallback<>(this) {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<ListTimeline> lists) {
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
||||||
|
userInList.putAll(userInListBefore);
|
||||||
|
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
||||||
|
if (profileAccountId == null) return;
|
||||||
|
|
||||||
@Override
|
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
|
||||||
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
@Override
|
||||||
holder.bind(data.get(position));
|
public void onSuccess(List<ListTimeline> allLists) {
|
||||||
}
|
if (getActivity() == null) return;
|
||||||
|
List<ListTimeline> newLists = new ArrayList<>();
|
||||||
|
for (ListTimeline l : allLists) {
|
||||||
|
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
||||||
|
if (!userInListBefore.containsKey(l.id)) {
|
||||||
|
userInListBefore.put(l.id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userInList.putAll(userInListBefore);
|
||||||
|
onDataLoaded(newLists, false);
|
||||||
|
}
|
||||||
|
}).exec(accountId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Subscribe
|
||||||
public int getItemCount() {
|
public void onListDeletedEvent(ListDeletedEvent event) {
|
||||||
return data.size();
|
for (int i = 0; i < data.size(); i++) {
|
||||||
}
|
ListTimeline item = data.get(i);
|
||||||
}
|
if (item.id.equals(event.id)) {
|
||||||
|
data.remove(i);
|
||||||
|
adapter.notifyItemRemoved(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
|
@Subscribe
|
||||||
private final TextView title;
|
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
||||||
private final CheckBox listToggle;
|
for (int i = 0; i < data.size(); i++) {
|
||||||
|
ListTimeline item = data.get(i);
|
||||||
|
if (item.id.equals(event.id)) {
|
||||||
|
item.title = event.title;
|
||||||
|
item.repliesPolicy = event.repliesPolicy;
|
||||||
|
adapter.notifyItemChanged(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ListViewHolder(){
|
@Override
|
||||||
super(getActivity(), R.layout.item_list_timeline, list);
|
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
|
||||||
title=findViewById(R.id.title);
|
return adapter = new ListsAdapter();
|
||||||
listToggle=findViewById(R.id.list_toggle);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(ListTimeline item) {
|
public void scrollToTop() {
|
||||||
title.setText(item.title);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
if (profileAccountId != null) {
|
}
|
||||||
Boolean checked = userInList.get(item.id);
|
|
||||||
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
|
||||||
listToggle.setOnClickListener(this::onClickToggle);
|
|
||||||
} else {
|
|
||||||
listToggle.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onClickToggle(View view) {
|
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
||||||
saveListMembership(item.id, listToggle.isChecked());
|
@NonNull
|
||||||
}
|
@Override
|
||||||
|
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new ListViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick() {
|
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
||||||
UiUtils.openListTimeline(getActivity(), accountId, item);
|
holder.bind(data.get(position));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
|
||||||
|
private final TextView title;
|
||||||
|
private final CheckBox listToggle;
|
||||||
|
|
||||||
|
public ListViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_text, list);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
listToggle=findViewById(R.id.list_toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(ListTimeline item) {
|
||||||
|
title.setText(item.title);
|
||||||
|
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
|
||||||
|
if (profileAccountId != null) {
|
||||||
|
Boolean checked = userInList.get(item.id);
|
||||||
|
listToggle.setVisibility(View.VISIBLE);
|
||||||
|
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
||||||
|
listToggle.setOnClickListener(this::onClickToggle);
|
||||||
|
} else {
|
||||||
|
listToggle.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClickToggle(View view) {
|
||||||
|
saveListMembership(item.id, listToggle.isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountId);
|
||||||
|
args.putString("listID", item.id);
|
||||||
|
args.putString("listTitle", item.title);
|
||||||
|
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
|
||||||
|
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(R.menu.notifications, menu);
|
inflater.inflate(R.menu.notifications, menu);
|
||||||
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
|
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
|
||||||
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -244,4 +245,4 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,7 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.text.TextUtils;
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
@@ -14,14 +13,20 @@ import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
|||||||
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.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
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.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.AccountStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||||
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.InsetStatusItemDecoration;
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -41,6 +46,12 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
private boolean onlyMentions;
|
private boolean onlyMentions;
|
||||||
private boolean onlyPosts;
|
private boolean onlyPosts;
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean withComposeButton() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -71,6 +82,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||||
|
Account reportTarget = n.report == null ? null : n.report.targetAccount == null ? null :
|
||||||
|
n.report.targetAccount;
|
||||||
String extraText=switch(n.type){
|
String extraText=switch(n.type){
|
||||||
case FOLLOW -> getString(R.string.user_followed_you);
|
case FOLLOW -> getString(R.string.user_followed_you);
|
||||||
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request);
|
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request);
|
||||||
@@ -78,10 +91,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
case REBLOG -> getString(R.string.notification_boosted);
|
case REBLOG -> getString(R.string.notification_boosted);
|
||||||
case FAVORITE -> getString(R.string.user_favorited);
|
case FAVORITE -> getString(R.string.user_favorited);
|
||||||
case POLL -> getString(R.string.poll_ended);
|
case POLL -> getString(R.string.poll_ended);
|
||||||
|
case UPDATE -> getString(R.string.sk_post_edited);
|
||||||
|
case SIGN_UP -> getString(R.string.sk_signed_up);
|
||||||
|
case REPORT -> getString(R.string.sk_reported);
|
||||||
};
|
};
|
||||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText, n) : null;
|
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null;
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n);
|
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS);
|
||||||
if(titleItem!=null){
|
if(titleItem!=null){
|
||||||
for(StatusDisplayItem item:items){
|
for(StatusDisplayItem item:items){
|
||||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
if(item instanceof ImageStatusDisplayItem imgItem){
|
||||||
@@ -93,8 +109,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
items.add(0, titleItem);
|
items.add(0, titleItem);
|
||||||
return items;
|
return items;
|
||||||
}else if(titleItem!=null){
|
}else if(titleItem!=null){
|
||||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, n.account, n);
|
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
|
||||||
return Arrays.asList(titleItem, card);
|
reportTarget != null ? reportTarget : n.account, n);
|
||||||
|
TextStatusDisplayItem text = n.report != null && !TextUtils.isEmpty(n.report.comment) ?
|
||||||
|
new TextStatusDisplayItem(n.id, n.report.comment, this,
|
||||||
|
Status.ofFake(n.id, n.report.comment, n.createdAt), true) :
|
||||||
|
null;
|
||||||
|
return text == null ? Arrays.asList(titleItem, card) : Arrays.asList(titleItem, text, card);
|
||||||
}else{
|
}else{
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -115,8 +136,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing, new SimpleCallback<>(this){
|
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing, new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||||
if(getActivity()==null)
|
if (getActivity() == null) return;
|
||||||
return;
|
|
||||||
if(refreshing)
|
if(refreshing)
|
||||||
relationships.clear();
|
relationships.clear();
|
||||||
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());
|
||||||
@@ -163,6 +183,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
||||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||||
|
}else if(n.report != null){
|
||||||
|
String domain = AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
|
UiUtils.launchWebBrowser(getActivity(), "https://"+domain+"/admin/reports/"+n.report.id);
|
||||||
}else{
|
}else{
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -175,6 +198,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
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 InsetStatusItemDecoration(this));
|
||||||
|
if (getParentFragment() instanceof NotificationsFragment) fab.setVisibility(View.GONE);
|
||||||
|
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Notification getNotificationByID(String id){
|
private Notification getNotificationByID(String id){
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class PinnableStatusListFragment extends StatusListFragment {
|
||||||
|
protected boolean pinnedUpdated;
|
||||||
|
protected List<TimelineDefinition> pinnedTimelines;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
updatePinButton(menu.findItem(R.id.pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isPinned() {
|
||||||
|
return pinnedTimelines.contains(makeTimelineDefinition());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updatePinButton(MenuItem pin) {
|
||||||
|
boolean pinned = isPinned();
|
||||||
|
pin.setIcon(pinned ?
|
||||||
|
R.drawable.ic_fluent_pin_24_filled :
|
||||||
|
R.drawable.ic_fluent_pin_24_regular);
|
||||||
|
pin.setTitle(pinned ? R.string.sk_unpin_timeline : R.string.sk_pin_timeline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract TimelineDefinition makeTimelineDefinition();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.pin) {
|
||||||
|
togglePin(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void togglePin(MenuItem pin) {
|
||||||
|
pinnedUpdated = true;
|
||||||
|
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||||
|
TimelineDefinition def = makeTimelineDefinition();
|
||||||
|
boolean pinned = isPinned();
|
||||||
|
if (pinned) pinnedTimelines.remove(def);
|
||||||
|
else pinnedTimelines.add(def);
|
||||||
|
Toast.makeText(getContext(), pinned ? R.string.sk_unpinned_timeline : R.string.sk_pinned_timeline, Toast.LENGTH_SHORT).show();
|
||||||
|
GlobalUserPreferences.pinnedTimelines.put(accountID, pinnedTimelines);
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
updatePinButton(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Bundle getResultArgs() {
|
||||||
|
return new Bundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
Bundle resultArgs = getResultArgs();
|
||||||
|
if (pinnedUpdated) {
|
||||||
|
resultArgs.putBoolean("pinnedUpdated", true);
|
||||||
|
setResult(true, resultArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
@@ -12,11 +13,23 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
|
||||||
import org.joinmastodon.android.model.AccountField;
|
import org.joinmastodon.android.model.AccountField;
|
||||||
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
@@ -26,11 +39,8 @@ import org.joinmastodon.android.ui.views.LinkedTextView;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import me.grishka.appkit.api.Callback;
|
||||||
import androidx.annotation.Nullable;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
|
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
@@ -46,6 +56,11 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
|||||||
private static final int MAX_FIELDS=4;
|
private static final int MAX_FIELDS=4;
|
||||||
|
|
||||||
public UsableRecyclerView list;
|
public UsableRecyclerView list;
|
||||||
|
public FrameLayout noteWrap;
|
||||||
|
public EditText noteEdit;
|
||||||
|
private String accountID;
|
||||||
|
private String profileAccountID;
|
||||||
|
private String note;
|
||||||
private List<AccountField> fields=Collections.emptyList();
|
private List<AccountField> fields=Collections.emptyList();
|
||||||
private AboutAdapter adapter;
|
private AboutAdapter adapter;
|
||||||
private Paint dividerPaint=new Paint();
|
private Paint dividerPaint=new Paint();
|
||||||
@@ -64,11 +79,49 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNote(String note, String accountID, String profileAccountID){
|
||||||
|
this.note=note;
|
||||||
|
this.accountID=accountID;
|
||||||
|
this.profileAccountID=profileAccountID;
|
||||||
|
// noteWrap.setVisibility(View.VISIBLE);
|
||||||
|
// noteEdit.setVisibility(View.VISIBLE);
|
||||||
|
// noteEdit.setText(note);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||||
list=new UsableRecyclerView(getActivity());
|
View view = inflater.inflate(R.layout.fragment_profile_about, null);
|
||||||
list.setId(R.id.list);
|
|
||||||
|
noteEdit = view.findViewById(R.id.note_edit);
|
||||||
|
noteWrap = view.findViewById(R.id.note_edit_wrap);
|
||||||
|
ImageButton noteEditConfirm = view.findViewById(R.id.note_edit_confirm);
|
||||||
|
|
||||||
|
|
||||||
|
noteEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
if (hasFocus) {
|
||||||
|
noteEditConfirm.setVisibility(View.VISIBLE);
|
||||||
|
noteEditConfirm.animate()
|
||||||
|
.alpha(1.0f)
|
||||||
|
.setDuration(700);
|
||||||
|
} else {
|
||||||
|
noteEditConfirm.animate()
|
||||||
|
.alpha(0.0f)
|
||||||
|
.setDuration(700);
|
||||||
|
noteEditConfirm.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
noteEditConfirm.setOnClickListener((v -> {
|
||||||
|
if (!noteEdit.getText().toString().trim().equals(note)) {
|
||||||
|
savePrivateNote();
|
||||||
|
}
|
||||||
|
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
|
||||||
|
noteEdit.clearFocus();
|
||||||
|
}));
|
||||||
|
|
||||||
|
list = view.findViewById(R.id.list);
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
list.setItemAnimator(new BetterItemAnimator());
|
||||||
list.setDrawSelectorOnTop(true);
|
list.setDrawSelectorOnTop(true);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
@@ -95,8 +148,20 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return list;
|
return view;
|
||||||
}
|
}
|
||||||
|
private void savePrivateNote(){
|
||||||
|
new SetPrivateNote(profileAccountID, noteEdit.getText().toString()).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Relationship result) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse result) {
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.mo_personal_note_update_failed), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void enterEditMode(List<AccountField> editableFields){
|
public void enterEditMode(List<AccountField> editableFields){
|
||||||
isInEditMode=true;
|
isInEditMode=true;
|
||||||
|
|||||||
@@ -1,50 +1,54 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import static android.content.Context.CLIPBOARD_SERVICE;
|
|
||||||
|
|
||||||
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.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
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.os.VibrationEffect;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ImageSpan;
|
import android.text.style.ImageSpan;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
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.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.view.animation.TranslateAnimation;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
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.requests.accounts.GetAccountByID;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||||
@@ -52,6 +56,7 @@ 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.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
||||||
@@ -61,6 +66,7 @@ import org.joinmastodon.android.model.Account;
|
|||||||
import org.joinmastodon.android.model.AccountField;
|
import org.joinmastodon.android.model.AccountField;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
||||||
import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable;
|
import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable;
|
||||||
@@ -69,8 +75,10 @@ import org.joinmastodon.android.ui.tabs.TabLayout;
|
|||||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.CoverImageView;
|
import org.joinmastodon.android.ui.views.CoverImageView;
|
||||||
|
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||||
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
|
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
|
||||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
@@ -84,6 +92,9 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
@@ -94,10 +105,17 @@ import me.grishka.appkit.api.SimpleCallback;
|
|||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.fragments.LoaderFragment;
|
import me.grishka.appkit.fragments.LoaderFragment;
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
|
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||||
|
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
||||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
|
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
|
||||||
private static final int AVATAR_RESULT=722;
|
private static final int AVATAR_RESULT=722;
|
||||||
@@ -105,24 +123,28 @@ 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, nameWrap;
|
||||||
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel, postsCount, postsLabel;
|
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel, postsCount, postsLabel;
|
||||||
private ProgressBarButton actionButton, notifyButton;
|
private ProgressBarButton actionButton, notifyButton;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
private NestedRecyclerScrollView scrollView;
|
private NestedRecyclerScrollView scrollView;
|
||||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
|
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
|
||||||
private ProfileAboutFragment aboutFragment;
|
// private ProfileAboutFragment aboutFragment;
|
||||||
private TabLayout tabbar;
|
private TabLayout tabbar;
|
||||||
private SwipeRefreshLayout refreshLayout;
|
private SwipeRefreshLayout refreshLayout;
|
||||||
private CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable();
|
private CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable();
|
||||||
private float titleTransY;
|
private float titleTransY;
|
||||||
private View postsBtn, followersBtn, followingBtn;
|
private View postsBtn, followersBtn, followingBtn, profileCounters;
|
||||||
private EditText nameEdit, bioEdit;
|
private EditText nameEdit, bioEdit;
|
||||||
private ProgressBar actionProgress, notifyProgress;
|
private ProgressBar actionProgress, notifyProgress;
|
||||||
private FrameLayout[] tabViews;
|
private FrameLayout[] tabViews;
|
||||||
private TabLayoutMediator tabLayoutMediator;
|
private TabLayoutMediator tabLayoutMediator;
|
||||||
private TextView followsYouView;
|
private TextView followsYouView;
|
||||||
|
private ViewGroup rolesView;
|
||||||
|
|
||||||
|
public FrameLayout noteWrap;
|
||||||
|
public EditText noteEdit;
|
||||||
|
private String note;
|
||||||
private Account account;
|
private Account account;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Relationship relationship;
|
private Relationship relationship;
|
||||||
@@ -134,10 +156,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private Uri editNewAvatar, editNewCover;
|
private Uri editNewAvatar, editNewCover;
|
||||||
private String profileAccountID;
|
private String profileAccountID;
|
||||||
private boolean refreshing;
|
private boolean refreshing;
|
||||||
private View fab;
|
private ImageButton fab;
|
||||||
private WindowInsets childInsets;
|
private WindowInsets childInsets;
|
||||||
private PhotoViewer currentPhotoViewer;
|
private PhotoViewer currentPhotoViewer;
|
||||||
private boolean editModeLoading;
|
private boolean editModeLoading;
|
||||||
|
protected int scrollDiff = 0;
|
||||||
|
|
||||||
|
private static final int MAX_FIELDS=4;
|
||||||
|
|
||||||
|
// from ProfileAboutFragment
|
||||||
|
public UsableRecyclerView list;
|
||||||
|
private List<AccountField> metadataListData=Collections.emptyList();
|
||||||
|
private MetadataAdapter adapter;
|
||||||
|
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
|
||||||
|
private RecyclerView.ViewHolder draggedViewHolder;
|
||||||
|
private ListImageLoaderWrapper imgLoader;
|
||||||
|
|
||||||
public ProfileFragment(){
|
public ProfileFragment(){
|
||||||
super(R.layout.loader_fragment_overlay_toolbar);
|
super(R.layout.loader_fragment_overlay_toolbar);
|
||||||
@@ -164,6 +197,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getPrefilledText() {
|
||||||
|
return account == null || AccountSessionManager.getInstance().isSelf(accountID, account)
|
||||||
|
? null : '@'+account.acct+' ';
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
@@ -178,14 +216,17 @@ 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);
|
||||||
|
nameWrap=content.findViewById(R.id.name_wrap);
|
||||||
username=content.findViewById(R.id.username);
|
username=content.findViewById(R.id.username);
|
||||||
bio=content.findViewById(R.id.bio);
|
bio=content.findViewById(R.id.bio);
|
||||||
|
profileCounters=content.findViewById(R.id.profile_counters);
|
||||||
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);
|
||||||
followersBtn=content.findViewById(R.id.followers_btn);
|
followersBtn=content.findViewById(R.id.followers_btn);
|
||||||
followingCount=content.findViewById(R.id.following_count);
|
followingCount=content.findViewById(R.id.following_count);
|
||||||
followingLabel=content.findViewById(R.id.following_label);
|
followingLabel=content.findViewById(R.id.following_label);
|
||||||
followingBtn=content.findViewById(R.id.following_btn);
|
followingBtn=content.findViewById(R.id.following_btn);
|
||||||
|
|
||||||
postsCount=content.findViewById(R.id.posts_count);
|
postsCount=content.findViewById(R.id.posts_count);
|
||||||
postsLabel=content.findViewById(R.id.posts_label);
|
postsLabel=content.findViewById(R.id.posts_label);
|
||||||
postsBtn=content.findViewById(R.id.posts_btn);
|
postsBtn=content.findViewById(R.id.posts_btn);
|
||||||
@@ -201,6 +242,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
notifyProgress=content.findViewById(R.id.notify_progress);
|
notifyProgress=content.findViewById(R.id.notify_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);
|
||||||
|
list=content.findViewById(R.id.metadata);
|
||||||
|
rolesView=content.findViewById(R.id.roles);
|
||||||
|
|
||||||
|
noteEdit = content.findViewById(R.id.note_edit);
|
||||||
|
noteWrap = content.findViewById(R.id.note_edit_wrap);
|
||||||
|
Button noteEditConfirm = content.findViewById(R.id.note_edit_confirm);
|
||||||
|
|
||||||
avatar.setOutlineProvider(new ViewOutlineProvider(){
|
avatar.setOutlineProvider(new ViewOutlineProvider(){
|
||||||
@Override
|
@Override
|
||||||
@@ -210,6 +257,49 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
});
|
});
|
||||||
avatar.setClipToOutline(true);
|
avatar.setClipToOutline(true);
|
||||||
|
|
||||||
|
noteEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
if (hasFocus) {
|
||||||
|
fab.setVisibility(View.INVISIBLE);
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
|
||||||
|
noteEditConfirm.setVisibility(View.VISIBLE);
|
||||||
|
noteEditConfirm.animate()
|
||||||
|
.alpha(1.0f)
|
||||||
|
.setDuration(700);
|
||||||
|
} else {
|
||||||
|
fab.setVisibility(View.VISIBLE);
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2,
|
||||||
|
0);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
|
||||||
|
noteEditConfirm.animate()
|
||||||
|
.alpha(0.0f)
|
||||||
|
.setDuration(700);
|
||||||
|
noteEditConfirm.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
noteEditConfirm.setOnClickListener((v -> {
|
||||||
|
if (!noteEdit.getText().toString().trim().equals(note)) {
|
||||||
|
savePrivateNote();
|
||||||
|
}
|
||||||
|
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
|
||||||
|
noteEdit.clearFocus();
|
||||||
|
}));
|
||||||
|
|
||||||
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){
|
||||||
@@ -220,7 +310,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tabViews=new FrameLayout[5];
|
tabViews=new FrameLayout[4];
|
||||||
for(int i=0;i<tabViews.length;i++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
FrameLayout tabView=new FrameLayout(getActivity());
|
FrameLayout tabView=new FrameLayout(getActivity());
|
||||||
tabView.setId(switch(i){
|
tabView.setId(switch(i){
|
||||||
@@ -236,7 +326,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
tabViews[i]=tabView;
|
tabViews[i]=tabView;
|
||||||
}
|
}
|
||||||
|
|
||||||
pager.setOffscreenPageLimit(5);
|
UiUtils.reduceSwipeSensitivity(pager);
|
||||||
|
pager.setOffscreenPageLimit(4);
|
||||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||||
pager.setAdapter(new ProfilePagerAdapter());
|
pager.setAdapter(new ProfilePagerAdapter());
|
||||||
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
||||||
@@ -275,6 +366,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
cover.setOnClickListener(this::onCoverClick);
|
cover.setOnClickListener(this::onCoverClick);
|
||||||
refreshLayout.setOnRefreshListener(this);
|
refreshLayout.setOnRefreshListener(this);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
fab.setOnLongClickListener(v->UiUtils.pickAccountForCompose(getActivity(), accountID, getPrefilledText()));
|
||||||
|
|
||||||
if(loaded){
|
if(loaded){
|
||||||
bindHeaderView();
|
bindHeaderView();
|
||||||
@@ -288,23 +380,51 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||||
|
|
||||||
username.setOnLongClickListener(v->{
|
username.setOnLongClickListener(v->{
|
||||||
String username=account.acct;
|
String usernameString=account.acct;
|
||||||
if(!username.contains("@")){
|
if(!usernameString.contains("@")){
|
||||||
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
usernameString+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
}
|
}
|
||||||
UiUtils.copyText(getActivity(), '@'+username);
|
UiUtils.copyText(username, '@'+usernameString);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// from ProfileAboutFragment
|
||||||
|
list.setItemAnimator(new BetterItemAnimator());
|
||||||
|
list.setDrawSelectorOnTop(true);
|
||||||
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
|
imgLoader=new ListImageLoaderWrapper(getActivity(), list, new RecyclerViewDelegate(list), null);
|
||||||
|
list.setAdapter(adapter=new MetadataAdapter());
|
||||||
|
list.setClipToPadding(false);
|
||||||
|
|
||||||
return sizeWrapper;
|
return sizeWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNote(String note){
|
||||||
|
this.note=note;
|
||||||
|
noteWrap.setVisibility(View.VISIBLE);
|
||||||
|
noteEdit.setVisibility(View.VISIBLE);
|
||||||
|
noteEdit.setText(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePrivateNote(){
|
||||||
|
new SetPrivateNote(profileAccountID, noteEdit.getText().toString()).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Relationship result) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse result) {
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.mo_personal_note_update_failed), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(){
|
protected void doLoadData(){
|
||||||
currentRequest=new GetAccountByID(profileAccountID)
|
currentRequest=new GetAccountByID(profileAccountID)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
|
if (getActivity() == null) return;
|
||||||
account=result;
|
account=result;
|
||||||
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
bindHeaderView();
|
bindHeaderView();
|
||||||
@@ -335,6 +455,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRefresh(){
|
public void onRefresh(){
|
||||||
|
if(isInEditMode){
|
||||||
|
refreshing=false;
|
||||||
|
refreshLayout.setRefreshing(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(refreshing)
|
if(refreshing)
|
||||||
return;
|
return;
|
||||||
refreshing=true;
|
refreshing=true;
|
||||||
@@ -350,8 +475,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
||||||
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
||||||
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
||||||
aboutFragment=new ProfileAboutFragment();
|
// aboutFragment=new ProfileAboutFragment();
|
||||||
aboutFragment.setFields(fields);
|
setFields(fields);
|
||||||
}
|
}
|
||||||
pager.getAdapter().notifyDataSetChanged();
|
pager.getAdapter().notifyDataSetChanged();
|
||||||
super.dataLoaded();
|
super.dataLoaded();
|
||||||
@@ -444,8 +569,22 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
name.setText(ssb);
|
name.setText(ssb);
|
||||||
setTitle(ssb);
|
setTitle(ssb);
|
||||||
|
|
||||||
|
if (account.roles != null && !account.roles.isEmpty()) {
|
||||||
|
rolesView.setVisibility(View.VISIBLE);
|
||||||
|
rolesView.removeAllViews();
|
||||||
|
name.setPadding(0, 0, V.dp(12), 0);
|
||||||
|
for (Account.Role role : account.roles) {
|
||||||
|
TextView roleText = new TextView(getActivity(), null, 0, R.style.role_label);
|
||||||
|
roleText.setText(role.name);
|
||||||
|
GradientDrawable bg = (GradientDrawable) roleText.getBackground().mutate();
|
||||||
|
bg.setStroke(V.dp(2), Color.parseColor(role.color));
|
||||||
|
rolesView.addView(roleText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
|
|
||||||
|
|
||||||
if(account.locked){
|
if(account.locked){
|
||||||
ssb=new SpannableStringBuilder("@");
|
ssb=new SpannableStringBuilder("@");
|
||||||
ssb.append(account.acct);
|
ssb.append(account.acct);
|
||||||
@@ -459,6 +598,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
lock.setTint(username.getCurrentTextColor());
|
lock.setTint(username.getCurrentTextColor());
|
||||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BASELINE), 0);
|
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BASELINE), 0);
|
||||||
username.setText(ssb);
|
username.setText(ssb);
|
||||||
|
}else if(account.bot){
|
||||||
|
ssb=new SpannableStringBuilder("@");
|
||||||
|
ssb.append(account.acct);
|
||||||
|
if(isSelf){
|
||||||
|
ssb.append('@');
|
||||||
|
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
|
||||||
|
}
|
||||||
|
ssb.append(" ");
|
||||||
|
Drawable botIcon=username.getResources().getDrawable(R.drawable.ic_bot, getActivity().getTheme()).mutate();
|
||||||
|
botIcon.setBounds(0, 0, botIcon.getIntrinsicWidth(), botIcon.getIntrinsicHeight());
|
||||||
|
botIcon.setTint(username.getCurrentTextColor());
|
||||||
|
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(botIcon, ImageSpan.ALIGN_BASELINE), 0);
|
||||||
|
username.setText(ssb);
|
||||||
}else{
|
}else{
|
||||||
// noinspection SetTextI18n
|
// noinspection SetTextI18n
|
||||||
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
||||||
@@ -470,6 +622,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
bio.setVisibility(View.VISIBLE);
|
bio.setVisibility(View.VISIBLE);
|
||||||
bio.setText(parsedBio);
|
bio.setText(parsedBio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
||||||
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
||||||
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
||||||
@@ -511,9 +665,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
fields.add(field);
|
fields.add(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(aboutFragment!=null){
|
setFields(fields);
|
||||||
aboutFragment.setFields(fields);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateToolbar(){
|
private void updateToolbar(){
|
||||||
@@ -549,21 +701,32 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(relationship==null && !isOwnProfile)
|
if(relationship==null && !isOwnProfile)
|
||||||
return;
|
return;
|
||||||
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
||||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
if(isOwnProfile){
|
||||||
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.scheduled, R.id.bookmarks, R.id.favorites);
|
||||||
|
}else{
|
||||||
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags, R.id.favorites, R.id.scheduled);
|
||||||
|
}
|
||||||
|
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
||||||
if(isOwnProfile)
|
if(isOwnProfile)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
MenuItem mute = menu.findItem(R.id.mute);
|
||||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
||||||
|
|
||||||
|
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_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.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
||||||
if(relationship.following) {
|
if(relationship.following) {
|
||||||
menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
|
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
|
||||||
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getDisplayUsername()));
|
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
||||||
manageUserLists.setVisible(true);
|
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);
|
||||||
|
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||||
}else {
|
}else {
|
||||||
menu.findItem(R.id.hide_boosts).setVisible(false);
|
menu.findItem(R.id.hide_boosts).setVisible(false);
|
||||||
manageUserLists.setVisible(false);
|
|
||||||
}
|
}
|
||||||
if(!account.isLocal())
|
if(!account.isLocal())
|
||||||
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||||
@@ -583,6 +746,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
confirmToggleMuted();
|
confirmToggleMuted();
|
||||||
}else if(id==R.id.block){
|
}else if(id==R.id.block){
|
||||||
confirmToggleBlocked();
|
confirmToggleBlocked();
|
||||||
|
}else if(id==R.id.soft_block){
|
||||||
|
confirmSoftBlockUser();
|
||||||
}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);
|
||||||
@@ -621,9 +786,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}else if(id==R.id.manage_user_lists){
|
}else if(id==R.id.manage_user_lists){
|
||||||
final Bundle args=new Bundle();
|
final Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putString("profileAccount", profileAccountID);
|
if (!isOwnProfile) {
|
||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
args.putString("profileAccount", profileAccountID);
|
||||||
|
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||||
|
}
|
||||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||||
|
}else if(id==R.id.followed_hashtags){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), FollowedHashtagsFragment.class, args);
|
||||||
|
}else if(id==R.id.scheduled){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), ScheduledStatusListFragment.class, args);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -653,6 +828,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateRelationship(){
|
private void updateRelationship(){
|
||||||
|
if (getActivity() == null) return;
|
||||||
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);
|
||||||
@@ -662,7 +838,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
notifyProgress.setIndeterminateTintList(notifyButton.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);
|
||||||
if (getActivity() != null) notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
if (!isOwnProfile) {
|
||||||
|
setNote(relationship.note);
|
||||||
|
// aboutFragment.setNote(relationship.note, accountID, profileAccountID);
|
||||||
|
}
|
||||||
|
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageButton getFab() {
|
||||||
|
return fab;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||||
@@ -685,8 +869,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
coverGradient.setTopOffset(scrollY);
|
coverGradient.setTopOffset(scrollY);
|
||||||
cover.invalidate();
|
cover.invalidate();
|
||||||
titleTransY=getToolbar().getHeight();
|
titleTransY=getToolbar().getHeight();
|
||||||
if(scrollY>name.getTop()-topBarsH){
|
if(scrollY>nameWrap.getTop()-topBarsH){
|
||||||
titleTransY=Math.max(0f, titleTransY-(scrollY-(name.getTop()-topBarsH)));
|
titleTransY=Math.max(0f, titleTransY-(scrollY-(nameWrap.getTop()-topBarsH)));
|
||||||
}
|
}
|
||||||
if(toolbarTitleView!=null){
|
if(toolbarTitleView!=null){
|
||||||
toolbarTitleView.setTranslationY(titleTransY);
|
toolbarTitleView.setTranslationY(titleTransY);
|
||||||
@@ -695,6 +879,36 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(currentPhotoViewer!=null){
|
if(currentPhotoViewer!=null){
|
||||||
currentPhotoViewer.offsetView(0, oldScrollY-scrollY);
|
currentPhotoViewer.offsetView(0, oldScrollY-scrollY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int dy = scrollY - oldScrollY;
|
||||||
|
|
||||||
|
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
fab.setVisibility(View.INVISIBLE);
|
||||||
|
scrollDiff = 0;
|
||||||
|
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
||||||
|
if (scrollDiff > 400) {
|
||||||
|
fab.setVisibility(View.VISIBLE);
|
||||||
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2,
|
||||||
|
0);
|
||||||
|
animate.setDuration(300);
|
||||||
|
animate.setFillAfter(true);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
scrollDiff = 0;
|
||||||
|
} else {
|
||||||
|
scrollDiff += Math.abs(dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Fragment getFragmentForPage(int page){
|
private Fragment getFragmentForPage(int page){
|
||||||
@@ -703,7 +917,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
case 1 -> postsWithRepliesFragment;
|
case 1 -> postsWithRepliesFragment;
|
||||||
case 2 -> pinnedPostsFragment;
|
case 2 -> pinnedPostsFragment;
|
||||||
case 3 -> mediaFragment;
|
case 3 -> mediaFragment;
|
||||||
case 4 -> aboutFragment;
|
// case 4 -> aboutFragment;
|
||||||
default -> throw new IllegalStateException();
|
default -> throw new IllegalStateException();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -745,8 +959,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
editModeLoading=false;
|
editModeLoading=false;
|
||||||
if(getActivity()==null)
|
if (getActivity() == null) return;
|
||||||
return;
|
|
||||||
enterEditMode(result);
|
enterEditMode(result);
|
||||||
setActionProgressVisible(false);
|
setActionProgressVisible(false);
|
||||||
}
|
}
|
||||||
@@ -754,8 +967,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
editModeLoading=false;
|
editModeLoading=false;
|
||||||
if(getActivity()==null)
|
if (getActivity() == null) return;
|
||||||
return;
|
|
||||||
error.showToast(getActivity());
|
error.showToast(getActivity());
|
||||||
setActionProgressVisible(false);
|
setActionProgressVisible(false);
|
||||||
}
|
}
|
||||||
@@ -770,16 +982,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
pager.setUserInputEnabled(false);
|
pager.setUserInputEnabled(false);
|
||||||
actionButton.setText(R.string.done);
|
actionButton.setText(R.string.done);
|
||||||
pager.setCurrentItem(4);
|
|
||||||
ArrayList<Animator> animators=new ArrayList<>();
|
ArrayList<Animator> animators=new ArrayList<>();
|
||||||
for(int i=0;i<tabViews.length-1;i++){
|
|
||||||
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, .3f));
|
|
||||||
tabbar.getTabAt(i).view.setEnabled(false);
|
|
||||||
}
|
|
||||||
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate();
|
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate();
|
||||||
avatar.setForeground(overlay);
|
avatar.setForeground(overlay);
|
||||||
animators.add(ObjectAnimator.ofInt(overlay, "alpha", 0, 255));
|
animators.add(ObjectAnimator.ofInt(overlay, "alpha", 0, 255));
|
||||||
|
|
||||||
|
nameWrap.setVisibility(View.GONE);
|
||||||
nameEdit.setVisibility(View.VISIBLE);
|
nameEdit.setVisibility(View.VISIBLE);
|
||||||
nameEdit.setText(account.displayName);
|
nameEdit.setText(account.displayName);
|
||||||
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
|
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
|
||||||
@@ -791,10 +999,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
bioEdit.setText(account.source.note);
|
bioEdit.setText(account.source.note);
|
||||||
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f, 1f));
|
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f, 1f));
|
||||||
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 0f));
|
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 0f));
|
||||||
|
profileCounters.setVisibility(View.GONE);
|
||||||
animators.add(ObjectAnimator.ofFloat(postsBtn, View.ALPHA, .3f));
|
pager.setVisibility(View.GONE);
|
||||||
animators.add(ObjectAnimator.ofFloat(followersBtn, View.ALPHA, .3f));
|
tabbar.setVisibility(View.GONE);
|
||||||
animators.add(ObjectAnimator.ofFloat(followingBtn, View.ALPHA, .3f));
|
|
||||||
|
|
||||||
AnimatorSet set=new AnimatorSet();
|
AnimatorSet set=new AnimatorSet();
|
||||||
set.playTogether(animators);
|
set.playTogether(animators);
|
||||||
@@ -802,7 +1009,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
set.start();
|
set.start();
|
||||||
|
|
||||||
aboutFragment.enterEditMode(account.source.fields);
|
// aboutFragment.enterEditMode(account.source.fields);
|
||||||
|
|
||||||
|
V.setVisibilityAnimated(fab, View.GONE);
|
||||||
|
metadataListData=account.source.fields;
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
dragHelper.attachToRecyclerView(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exitEditMode(){
|
private void exitEditMode(){
|
||||||
@@ -813,16 +1025,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
ArrayList<Animator> animators=new ArrayList<>();
|
ArrayList<Animator> animators=new ArrayList<>();
|
||||||
actionButton.setText(R.string.edit_profile);
|
actionButton.setText(R.string.edit_profile);
|
||||||
for(int i=0;i<tabViews.length-1;i++){
|
|
||||||
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, 1f));
|
|
||||||
}
|
|
||||||
animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0));
|
animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0));
|
||||||
animators.add(ObjectAnimator.ofFloat(nameEdit, View.ALPHA, 0f));
|
animators.add(ObjectAnimator.ofFloat(nameEdit, View.ALPHA, 0f));
|
||||||
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f));
|
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f));
|
||||||
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 1f));
|
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 1f));
|
||||||
animators.add(ObjectAnimator.ofFloat(postsBtn, View.ALPHA, 1f));
|
profileCounters.setVisibility(View.VISIBLE);
|
||||||
animators.add(ObjectAnimator.ofFloat(followersBtn, View.ALPHA, 1f));
|
pager.setVisibility(View.VISIBLE);
|
||||||
animators.add(ObjectAnimator.ofFloat(followingBtn, View.ALPHA, 1f));
|
tabbar.setVisibility(View.VISIBLE);
|
||||||
|
V.setVisibilityAnimated(nameWrap, View.VISIBLE);
|
||||||
|
|
||||||
AnimatorSet set=new AnimatorSet();
|
AnimatorSet set=new AnimatorSet();
|
||||||
set.playTogether(animators);
|
set.playTogether(animators);
|
||||||
@@ -831,20 +1041,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
set.addListener(new AnimatorListenerAdapter(){
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation){
|
public void onAnimationEnd(Animator animation){
|
||||||
for(int i=0;i<tabViews.length-1;i++){
|
|
||||||
tabbar.getTabAt(i).view.setEnabled(true);
|
|
||||||
}
|
|
||||||
pager.setUserInputEnabled(true);
|
pager.setUserInputEnabled(true);
|
||||||
nameEdit.setVisibility(View.GONE);
|
nameEdit.setVisibility(View.GONE);
|
||||||
bioEdit.setVisibility(View.GONE);
|
bioEdit.setVisibility(View.GONE);
|
||||||
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
|
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
|
||||||
lp.addRule(RelativeLayout.BELOW, R.id.name);
|
lp.addRule(RelativeLayout.BELOW, R.id.name_wrap);
|
||||||
username.getParent().requestLayout();
|
username.getParent().requestLayout();
|
||||||
avatar.setForeground(null);
|
avatar.setForeground(null);
|
||||||
|
scrollToTop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
set.start();
|
set.start();
|
||||||
|
|
||||||
|
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
||||||
|
imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
|
||||||
|
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||||
bindHeaderView();
|
bindHeaderView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,12 +1063,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(!isInEditMode)
|
if(!isInEditMode)
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
setActionProgressVisible(true);
|
setActionProgressVisible(true);
|
||||||
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), editNewAvatar, editNewCover, aboutFragment.getFields())
|
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), editNewAvatar, editNewCover, metadataListData)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
account=result;
|
account=result;
|
||||||
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
||||||
|
if (getActivity() == null) return;
|
||||||
exitEditMode();
|
exitEditMode();
|
||||||
setActionProgressVisible(false);
|
setActionProgressVisible(false);
|
||||||
}
|
}
|
||||||
@@ -879,6 +1091,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
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();
|
||||||
@@ -886,6 +1102,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onBackPressed(){
|
public boolean onBackPressed(){
|
||||||
|
if(noteEdit.hasFocus()) {
|
||||||
|
savePrivateNote();
|
||||||
|
}
|
||||||
if(isInEditMode){
|
if(isInEditMode){
|
||||||
exitEditMode();
|
exitEditMode();
|
||||||
return true;
|
return true;
|
||||||
@@ -935,9 +1154,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
if(!AccountSessionManager.getInstance().isSelf(accountID, account)){
|
if(getPrefilledText() != null) args.putString("prefilledText", getPrefilledText());
|
||||||
args.putString("prefilledText", '@'+account.acct+' ');
|
|
||||||
}
|
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1020,4 +1237,244 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from ProfileAboutFragment
|
||||||
|
public void setFields(ArrayList<AccountField> fields){
|
||||||
|
metadataListData=fields;
|
||||||
|
if (isInEditMode) {
|
||||||
|
isInEditMode=false;
|
||||||
|
dragHelper.attachToRecyclerView(null);
|
||||||
|
}
|
||||||
|
if (adapter != null) adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter {
|
||||||
|
public MetadataAdapter(){
|
||||||
|
super(imgLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return switch(viewType){
|
||||||
|
case 0 -> new AboutViewHolder();
|
||||||
|
case 1 -> new EditableAboutViewHolder();
|
||||||
|
case 2 -> new AddRowViewHolder();
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(BaseViewHolder holder, int position){
|
||||||
|
if(position<metadataListData.size()){
|
||||||
|
holder.bind(metadataListData.get(position));
|
||||||
|
}else{
|
||||||
|
holder.bind(null);
|
||||||
|
}
|
||||||
|
super.onBindViewHolder(holder, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
if(isInEditMode){
|
||||||
|
int size=metadataListData.size();
|
||||||
|
if(size<MAX_FIELDS)
|
||||||
|
size++;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
return metadataListData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position){
|
||||||
|
if(isInEditMode){
|
||||||
|
return position==metadataListData.size() ? 2 : 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getImageCountForItem(int position){
|
||||||
|
return isInEditMode || metadataListData.get(position).emojiRequests==null
|
||||||
|
? 0 : metadataListData.get(position).emojiRequests.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||||
|
return metadataListData.get(position).emojiRequests.get(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class BaseViewHolder extends BindableViewHolder<AccountField> {
|
||||||
|
public BaseViewHolder(int layout){
|
||||||
|
super(getActivity(), layout, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder {
|
||||||
|
private TextView title;
|
||||||
|
private LinkedTextView value;
|
||||||
|
|
||||||
|
public AboutViewHolder(){
|
||||||
|
super(R.layout.item_profile_about);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
value=findViewById(R.id.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(AccountField item){
|
||||||
|
title.setText(item.parsedName);
|
||||||
|
value.setText(item.parsedValue);
|
||||||
|
if(item.verifiedAt!=null){
|
||||||
|
int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63;
|
||||||
|
value.setTextColor(textColor);
|
||||||
|
value.setLinkTextColor(textColor);
|
||||||
|
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_24_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
|
||||||
|
public void setImage(int index, Drawable image){
|
||||||
|
CustomEmojiSpan span=index>=item.nameEmojis.length ? item.valueEmojis[index-item.nameEmojis.length] : item.nameEmojis[index];
|
||||||
|
span.setDrawable(image);
|
||||||
|
title.invalidate();
|
||||||
|
value.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearImage(int index){
|
||||||
|
setImage(index, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EditableAboutViewHolder extends BaseViewHolder {
|
||||||
|
private EditText title;
|
||||||
|
private boolean titleHasFocus, valueHasFocus;
|
||||||
|
private EditText value;
|
||||||
|
|
||||||
|
public EditableAboutViewHolder(){
|
||||||
|
super(R.layout.item_profile_about_editable);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
value=findViewById(R.id.value);
|
||||||
|
findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
|
||||||
|
dragHelper.startDrag(this);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
title.addTextChangedListener(new SimpleTextWatcher(e->item.name=e.toString()));
|
||||||
|
title.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onFocusChange(View v, boolean hasFocus) {
|
||||||
|
titleHasFocus = hasFocus;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
value.addTextChangedListener(new SimpleTextWatcher(e->item.value=e.toString()));
|
||||||
|
value.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onFocusChange(View v, boolean hasFocus) {
|
||||||
|
valueHasFocus = hasFocus;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
findViewById(R.id.remove_row_btn).setOnClickListener(this::onRemoveRowClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(AccountField item){
|
||||||
|
title.setText(item.name);
|
||||||
|
value.setText(item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onRemoveRowClick(View v){
|
||||||
|
if(titleHasFocus || valueHasFocus){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos=getAbsoluteAdapterPosition();
|
||||||
|
metadataListData.remove(pos);
|
||||||
|
adapter.notifyItemRemoved(pos);
|
||||||
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
|
BaseViewHolder vh=(BaseViewHolder) list.getChildViewHolder(list.getChildAt(i));
|
||||||
|
vh.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AddRowViewHolder extends BaseViewHolder implements UsableRecyclerView.Clickable{
|
||||||
|
public AddRowViewHolder(){
|
||||||
|
super(R.layout.item_profile_about_add_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(){
|
||||||
|
metadataListData.add(new AccountField());
|
||||||
|
if(metadataListData.size()==MAX_FIELDS){ // replace this row with new row
|
||||||
|
adapter.notifyItemChanged(metadataListData.size()-1);
|
||||||
|
}else{
|
||||||
|
adapter.notifyItemInserted(metadataListData.size()-1);
|
||||||
|
rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(AccountField item) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReorderCallback extends ItemTouchHelper.SimpleCallback{
|
||||||
|
public ReorderCallback(){
|
||||||
|
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target){
|
||||||
|
if(target instanceof AddRowViewHolder)
|
||||||
|
return false;
|
||||||
|
int fromPosition=viewHolder.getAbsoluteAdapterPosition();
|
||||||
|
int toPosition=target.getAbsoluteAdapterPosition();
|
||||||
|
if (fromPosition<toPosition) {
|
||||||
|
for (int i=fromPosition;i<toPosition;i++) {
|
||||||
|
Collections.swap(metadataListData, i, i+1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i=fromPosition;i>toPosition;i--) {
|
||||||
|
Collections.swap(metadataListData, i, i-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.notifyItemMoved(fromPosition, toPosition);
|
||||||
|
((BindableViewHolder)viewHolder).rebind();
|
||||||
|
((BindableViewHolder)target).rebind();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
||||||
|
super.onSelectedChanged(viewHolder, actionState);
|
||||||
|
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
|
||||||
|
viewHolder.itemView.setTag(R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
||||||
|
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||||
|
draggedViewHolder=viewHolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
|
||||||
|
super.clearView(recyclerView, viewHolder);
|
||||||
|
viewHolder.itemView.animate().translationZ(0).setDuration(100).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||||
|
draggedViewHolder=null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLongPressDragEnabled(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses;
|
||||||
|
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
||||||
|
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
|
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
||||||
|
private String nextMaxID;
|
||||||
|
private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean withComposeButton() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
E.register(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy(){
|
||||||
|
super.onDestroy();
|
||||||
|
E.unregister(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity){
|
||||||
|
super.onAttach(activity);
|
||||||
|
setTitle(R.string.sk_unsent_posts);
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFabClick(View v) {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
||||||
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onFabLongClick(View v) {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
||||||
|
return UiUtils.pickAccountForCompose(getActivity(), accountID, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
if (getArguments().getBoolean("hide_fab", false)) fab.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||||
|
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, Filter.FilterContext.HOME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addAccountToKnown(ScheduledStatus s) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(String id) {
|
||||||
|
final Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
ScheduledStatus scheduledStatus = getStatusByID(id);
|
||||||
|
Status status = scheduledStatus.toStatus();
|
||||||
|
args.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
|
||||||
|
args.putParcelable("editStatus", Parcels.wrap(status));
|
||||||
|
args.putString("sourceText", status.text);
|
||||||
|
args.putString("sourceSpoiler", status.spoilerText);
|
||||||
|
args.putBoolean("redraftStatus", true);
|
||||||
|
setResult(true, null);
|
||||||
|
|
||||||
|
// closing this scheduled status list if another status list is opened from compose fragment
|
||||||
|
Nav.goForResult(getActivity(), ComposeFragment.class, args, SCHEDULED_STATUS_LIST_OPENED, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFragmentResult(int reqCode, boolean success, Bundle result) {
|
||||||
|
if (reqCode == SCHEDULED_STATUS_LIST_OPENED && success && getActivity() != null) {
|
||||||
|
Nav.finish(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(HeaderPaginationList<ScheduledStatus> result){
|
||||||
|
if(result.nextPageUri!=null)
|
||||||
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
|
else
|
||||||
|
nextMaxID=null;
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
onDataLoaded(result, nextMaxID!=null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from StatusListFragment.java
|
||||||
|
@Subscribe
|
||||||
|
public void onScheduledStatusDeleted(ScheduledStatusDeletedEvent ev){
|
||||||
|
if(!ev.accountID.equals(accountID)) return;
|
||||||
|
ScheduledStatus status=getStatusByID(ev.id);
|
||||||
|
if(status==null) return;
|
||||||
|
removeStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from StatusListFragment.java
|
||||||
|
@Subscribe
|
||||||
|
public void onScheduledStatusCreated(ScheduledStatusCreatedEvent ev){
|
||||||
|
if(!ev.accountID.equals(accountID)) return;
|
||||||
|
prependItems(Collections.singletonList(ev.scheduledStatus), true);
|
||||||
|
scrollToTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from StatusListFragment.java
|
||||||
|
protected void removeStatus(ScheduledStatus status){
|
||||||
|
data.remove(status);
|
||||||
|
preloadedData.remove(status);
|
||||||
|
int index=-1;
|
||||||
|
for(int i=0;i<displayItems.size();i++){
|
||||||
|
if(status.id.equals(displayItems.get(i).parentID)){
|
||||||
|
index=i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(index==-1)
|
||||||
|
return;
|
||||||
|
int lastIndex;
|
||||||
|
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
||||||
|
if(!displayItems.get(lastIndex).parentID.equals(status.id))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
displayItems.subList(index, lastIndex).clear();
|
||||||
|
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from StatusListFragment.java
|
||||||
|
protected ScheduledStatus getStatusByID(String id){
|
||||||
|
for(ScheduledStatus s:data){
|
||||||
|
if(s.id.equals(id)){
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(ScheduledStatus s:preloadedData){
|
||||||
|
if(s.id.equals(id)){
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.LruCache;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -43,16 +45,20 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||||
|
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||||
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.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
import org.joinmastodon.android.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -62,6 +68,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
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.imageloader.ImageCache;
|
import me.grishka.appkit.imageloader.ImageCache;
|
||||||
@@ -74,13 +81,15 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
private ArrayList<Item> items=new ArrayList<>();
|
private ArrayList<Item> items=new ArrayList<>();
|
||||||
private ThemeItem themeItem;
|
private ThemeItem themeItem;
|
||||||
private NotificationPolicyItem notificationPolicyItem;
|
private NotificationPolicyItem notificationPolicyItem;
|
||||||
|
private SwitchItem showNewPostsButtonItem, glitchModeItem;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private boolean needUpdateNotificationSettings;
|
private boolean needUpdateNotificationSettings;
|
||||||
private boolean needAppRestart;
|
private boolean needAppRestart;
|
||||||
private PushSubscription pushSubscription;
|
private PushSubscription pushSubscription;
|
||||||
|
|
||||||
private ImageView themeTransitionWindowView;
|
private ImageView themeTransitionWindowView;
|
||||||
private TextItem checkForUpdateItem;
|
private TextItem checkForUpdateItem, clearImageCacheItem;
|
||||||
|
private ImageCache imageCache;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -88,9 +97,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
setTitle(R.string.settings);
|
setTitle(R.string.settings);
|
||||||
|
imageCache = ImageCache.getInstance(getActivity());
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||||
|
String instanceName = UiUtils.getInstanceName(accountID);
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||||
@@ -107,6 +118,17 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.disableMarquee=i.checked;
|
GlobalUserPreferences.disableMarquee=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
|
||||||
|
GlobalUserPreferences.uniformNotificationIcon=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
|
||||||
|
GlobalUserPreferences.reduceMotion=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
needAppRestart=true;
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
|
items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
|
||||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||||
popupMenu.inflate(R.menu.color_palettes);
|
popupMenu.inflate(R.menu.color_palettes);
|
||||||
@@ -123,42 +145,44 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
case BROWN -> R.string.sk_color_palette_brown;
|
case BROWN -> R.string.sk_color_palette_brown;
|
||||||
case RED -> R.string.sk_color_palette_red;
|
case RED -> R.string.sk_color_palette_red;
|
||||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
case YELLOW -> R.string.sk_color_palette_yellow;
|
||||||
|
case NORD -> R.string.mo_color_palette_nord;
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b->{
|
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
|
||||||
updatePublishText(b);
|
updatePublishText(b);
|
||||||
|
if (GlobalUserPreferences.relocatePublishButton) {
|
||||||
b.setOnClickListener(l->{
|
b.setOnClickListener(l -> {
|
||||||
FrameLayout inputWrap = new FrameLayout(getContext());
|
Toast.makeText(getActivity(), R.string.mo_disable_relocate_publish_button_to_enable_customization,
|
||||||
EditText input = new EditText(getContext());
|
Toast.LENGTH_LONG).show();
|
||||||
input.setHint(R.string.publish);
|
});
|
||||||
input.setText(GlobalUserPreferences.publishButtonText.trim());
|
} else {
|
||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
b.setOnClickListener(l -> {
|
||||||
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
|
FrameLayout inputWrap = new FrameLayout(getContext());
|
||||||
input.setLayoutParams(params);
|
EditText input = new EditText(getContext());
|
||||||
inputWrap.addView(input);
|
input.setHint(R.string.publish);
|
||||||
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap)
|
input.setText(GlobalUserPreferences.publishButtonText.trim());
|
||||||
.setPositiveButton(R.string.save, (d, which) -> {
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
GlobalUserPreferences.publishButtonText = input.getText().toString().trim();
|
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
|
||||||
GlobalUserPreferences.save();
|
input.setLayoutParams(params);
|
||||||
updatePublishText(b);
|
inputWrap.addView(input);
|
||||||
})
|
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap)
|
||||||
.setNeutralButton(R.string.clear, (d, which) -> {
|
.setPositiveButton(R.string.save, (d, which) -> {
|
||||||
GlobalUserPreferences.publishButtonText = "";
|
GlobalUserPreferences.publishButtonText = input.getText().toString().trim();
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
updatePublishText(b);
|
updatePublishText(b);
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
.setNeutralButton(R.string.clear, (d, which) -> {
|
||||||
.show();
|
GlobalUserPreferences.publishButtonText = "";
|
||||||
});
|
GlobalUserPreferences.save();
|
||||||
|
updatePublishText(b);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, (d, which) -> {
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
});}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_behavior));
|
items.add(new HeaderItem(R.string.settings_behavior));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_federated_timeline, R.drawable.ic_fluent_earth_24_regular, GlobalUserPreferences.showFederatedTimeline, i->{
|
|
||||||
GlobalUserPreferences.showFederatedTimeline=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
needAppRestart=true;
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||||
GlobalUserPreferences.playGifs=i.checked;
|
GlobalUserPreferences.playGifs=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
@@ -180,18 +204,36 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
needAppRestart=true;
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_delete_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
||||||
GlobalUserPreferences.enableDeleteNotifications=i.checked;
|
GlobalUserPreferences.enableDeleteNotifications=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
needAppRestart=true;
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_hide_translate_in_timeline, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
// items.add(new SwitchItem(R.string.sk_settings_show_differentiated_notification_icons, R.drawable.ic_ntf_logo, GlobalUserPreferences.showUniformPushNoticationIcons, this::onNotificationStyleChanged));
|
||||||
GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
items.add(new SwitchItem(R.string.mo_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{
|
||||||
|
GlobalUserPreferences.disableDividers=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
needAppRestart=true;
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.mo_hide_compose_button_while_scrolling_setting, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.enableFabAutoHide, i->{
|
||||||
|
GlobalUserPreferences.enableFabAutoHide =i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
needAppRestart=true;
|
||||||
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.mo_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
|
||||||
|
GlobalUserPreferences.relocatePublishButton=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
|
||||||
|
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
|
||||||
|
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.home_timeline));
|
items.add(new HeaderItem(R.string.sk_timelines));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||||
GlobalUserPreferences.showReplies=i.checked;
|
GlobalUserPreferences.showReplies=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
@@ -200,19 +242,58 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.showBoosts=i.checked;
|
GlobalUserPreferences.showBoosts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
||||||
GlobalUserPreferences.loadNewPosts=i.checked;
|
GlobalUserPreferences.loadNewPosts=i.checked;
|
||||||
|
showNewPostsButtonItem.enabled = i.checked;
|
||||||
|
if (!i.checked) {
|
||||||
|
GlobalUserPreferences.showNewPostsButton = false;
|
||||||
|
showNewPostsButtonItem.checked = false;
|
||||||
|
}
|
||||||
|
if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsButtonItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
items.add(showNewPostsButtonItem = new SwitchItem(R.string.sk_settings_show_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{
|
||||||
|
GlobalUserPreferences.showNewPostsButton=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_show_alt_indicator, R.drawable.ic_fluent_scan_text_24_regular, GlobalUserPreferences.showAltIndicator, i->{
|
||||||
|
GlobalUserPreferences.showAltIndicator=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
needAppRestart=true;
|
||||||
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_show_no_alt_indicator, R.drawable.ic_fluent_important_24_regular, GlobalUserPreferences.showNoAltIndicator, i->{
|
||||||
|
GlobalUserPreferences.showNoAltIndicator=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_filled, GlobalUserPreferences.collapseLongPosts, i->{
|
||||||
|
GlobalUserPreferences.collapseLongPosts=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_spectator_mode, R.drawable.ic_fluent_eye_24_regular, GlobalUserPreferences.spectatorMode, i->{
|
||||||
|
GlobalUserPreferences.spectatorMode=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
needAppRestart=true;
|
||||||
|
}));
|
||||||
|
// items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
||||||
|
// GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
||||||
|
// GlobalUserPreferences.save();
|
||||||
|
// needAppRestart=true;
|
||||||
|
// }));
|
||||||
|
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_notifications));
|
items.add(new HeaderItem(R.string.settings_notifications));
|
||||||
items.add(notificationPolicyItem=new NotificationPolicyItem());
|
items.add(notificationPolicyItem=new NotificationPolicyItem());
|
||||||
PushSubscription pushSubscription=getPushSubscription();
|
PushSubscription pushSubscription=getPushSubscription();
|
||||||
items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i->onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked)));
|
boolean switchEnabled=pushSubscription.policy!=PushSubscription.Policy.NONE;
|
||||||
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i->onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked)));
|
|
||||||
items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i->onNotificationsChanged(PushNotification.Type.REBLOG, i.checked)));
|
items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i->onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked), switchEnabled));
|
||||||
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked)));
|
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i->onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked), switchEnabled));
|
||||||
items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_alert_24_regular, pushSubscription.alerts.status, i->onNotificationsChanged(PushNotification.Type.STATUS, i.checked)));
|
items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i->onNotificationsChanged(PushNotification.Type.REBLOG, i.checked), switchEnabled));
|
||||||
|
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked), switchEnabled));
|
||||||
|
items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_chat_24_regular, pushSubscription.alerts.status, i->onNotificationsChanged(PushNotification.Type.STATUS, i.checked), switchEnabled));
|
||||||
|
items.add(new SwitchItem(R.string.sk_notify_update, R.drawable.ic_fluent_history_24_regular, pushSubscription.alerts.update, i->onNotificationsChanged(PushNotification.Type.UPDATE, i.checked), switchEnabled));
|
||||||
|
items.add(new SwitchItem(R.string.sk_notify_poll_results, R.drawable.ic_fluent_poll_24_regular, pushSubscription.alerts.poll, i->onNotificationsChanged(PushNotification.Type.POLL, i.checked), switchEnabled));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_account));
|
items.add(new HeaderItem(R.string.settings_account));
|
||||||
items.add(new TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
|
||||||
@@ -220,32 +301,93 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
items.add(new TextItem(R.string.sk_settings_filters, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/filters"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.sk_settings_filters, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/filters"), R.drawable.ic_fluent_open_24_regular));
|
||||||
items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
|
||||||
|
|
||||||
String instanceName = instance != null && !instance.title.isBlank() ? instance.title : session.domain;
|
|
||||||
items.add(new HeaderItem(instanceName));
|
items.add(new HeaderItem(instanceName));
|
||||||
items.add(new TextItem(R.string.sk_settings_rules, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.sk_settings_rules, ()->{
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putParcelable("instance", Parcels.wrap(instance));
|
||||||
|
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||||
|
}, R.drawable.ic_fluent_task_list_ltr_24_regular));
|
||||||
|
items.add(new TextItem(R.string.sk_settings_about_instance , ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_info_24_regular));
|
||||||
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
||||||
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
||||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
|
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
|
||||||
|
if (!TextUtils.isEmpty(instance.version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, instance.version)));
|
||||||
|
|
||||||
|
items.add(new HeaderItem(R.string.sk_instance_features));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
|
||||||
|
glitchModeItem.enabled = i.checked;
|
||||||
|
if (i.checked) {
|
||||||
|
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
||||||
|
if (instance.pleroma == null) GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||||
|
} else {
|
||||||
|
GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
|
||||||
|
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
|
||||||
|
}
|
||||||
|
glitchModeItem.checked = GlobalUserPreferences.accountsInGlitchMode.contains(accountID);
|
||||||
|
if (list.findViewHolderForAdapterPosition(items.indexOf(glitchModeItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SmallTextItem(getString(R.string.sk_settings_local_only_explanation)));
|
||||||
|
items.add(glitchModeItem = new SwitchItem(R.string.sk_settings_glitch_instance, 0, GlobalUserPreferences.accountsInGlitchMode.contains(accountID), i->{
|
||||||
|
if (i.checked) {
|
||||||
|
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||||
|
} else {
|
||||||
|
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
|
||||||
|
}
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
glitchModeItem.enabled = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||||
|
items.add(new SmallTextItem(getString(R.string.sk_settings_glitch_mode_explanation)));
|
||||||
|
|
||||||
|
|
||||||
boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled;
|
boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled;
|
||||||
items.add(new SmallTextItem(getString(translationAvailable ?
|
items.add(new SmallTextItem(getString(translationAvailable ?
|
||||||
R.string.sk_settings_translation_availability_note_available :
|
R.string.sk_settings_translation_availability_note_available :
|
||||||
R.string.sk_settings_translation_availability_note_unavailable, instanceName)));
|
R.string.sk_settings_translation_availability_note_unavailable, instance.title)));
|
||||||
|
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.sk_settings_about));
|
items.add(new HeaderItem(R.string.sk_settings_about));
|
||||||
items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon"), R.drawable.ic_fluent_open_24_regular));
|
|
||||||
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://ko-fi.com/xsk22"), R.drawable.ic_fluent_heart_24_regular));
|
|
||||||
if (GithubSelfUpdater.needSelfUpdating()) {
|
if (GithubSelfUpdater.needSelfUpdating()) {
|
||||||
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||||
items.add(checkForUpdateItem);
|
items.add(checkForUpdateItem);
|
||||||
|
items.add(new SwitchItem(R.string.sk_updater_enable_pre_releases, 0, GlobalUserPreferences.enablePreReleases, i->{
|
||||||
|
GlobalUserPreferences.enablePreReleases=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
|
||||||
|
items.add(new TextItem(R.string.mo_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
|
||||||
|
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
|
||||||
|
|
||||||
|
LruCache<?, ?> cache = imageCache == null ? null : imageCache.getLruCache();
|
||||||
|
clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), cache != null ? cache.size() : 0, true), this::clearImageCache, 0);
|
||||||
|
items.add(clearImageCacheItem);
|
||||||
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
|
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
|
||||||
GlobalUserPreferences.recentLanguages.remove(accountID);
|
GlobalUserPreferences.recentLanguages.remove(accountID);
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
})));
|
})));
|
||||||
|
|
||||||
items.add(new FooterItem(getString(R.string.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
items.add(new TextItem(R.string.mo_clear_recent_emoji, ()-> {
|
||||||
|
GlobalUserPreferences.recentEmojis.clear();
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
// items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||||
|
|
||||||
|
if(BuildConfig.DEBUG){
|
||||||
|
items.add(new RedHeaderItem("Debug options"));
|
||||||
|
items.add(new TextItem("Test e-mail confirmation flow", ()->{
|
||||||
|
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
sess.activated=false;
|
||||||
|
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putBoolean("debug", true);
|
||||||
|
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(new FooterItem(getString(R.string.mo_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePublishText(Button btn) {
|
private void updatePublishText(Button btn) {
|
||||||
@@ -298,11 +440,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
||||||
}
|
}
|
||||||
if(needAppRestart){
|
if(needAppRestart) UiUtils.restartApp();
|
||||||
Intent intent = Intent.makeRestartActivityTask(MastodonApp.context.getPackageManager().getLaunchIntentForPackage(MastodonApp.context.getPackageName()).getComponent());
|
|
||||||
MastodonApp.context.startActivity(intent);
|
|
||||||
Runtime.getRuntime().exit(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -337,6 +475,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
else if (id == R.id.brown_color) pref = ColorPreference.BROWN;
|
else if (id == R.id.brown_color) pref = ColorPreference.BROWN;
|
||||||
else if (id == R.id.red_color) pref = ColorPreference.RED;
|
else if (id == R.id.red_color) pref = ColorPreference.RED;
|
||||||
else if (id == R.id.yellow_color) pref = ColorPreference.YELLOW;
|
else if (id == R.id.yellow_color) pref = ColorPreference.YELLOW;
|
||||||
|
else if (id == R.id.nord_color) pref = ColorPreference.NORD;
|
||||||
|
|
||||||
if (pref == null) return false;
|
if (pref == null) return false;
|
||||||
|
|
||||||
@@ -346,6 +485,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void onTrueBlackThemeChanged(SwitchItem item){
|
private void onTrueBlackThemeChanged(SwitchItem item){
|
||||||
GlobalUserPreferences.trueBlackTheme=item.checked;
|
GlobalUserPreferences.trueBlackTheme=item.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
@@ -404,12 +544,20 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
case FAVORITE -> subscription.alerts.favourite=enabled;
|
case FAVORITE -> subscription.alerts.favourite=enabled;
|
||||||
case FOLLOW -> subscription.alerts.follow=enabled;
|
case FOLLOW -> subscription.alerts.follow=enabled;
|
||||||
case REBLOG -> subscription.alerts.reblog=enabled;
|
case REBLOG -> subscription.alerts.reblog=enabled;
|
||||||
case MENTION -> subscription.alerts.mention=subscription.alerts.poll=enabled;
|
case MENTION -> subscription.alerts.mention=enabled;
|
||||||
|
case POLL -> subscription.alerts.poll=enabled;
|
||||||
case STATUS -> subscription.alerts.status=enabled;
|
case STATUS -> subscription.alerts.status=enabled;
|
||||||
|
case UPDATE -> subscription.alerts.update=enabled;
|
||||||
}
|
}
|
||||||
needUpdateNotificationSettings=true;
|
needUpdateNotificationSettings=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onNotificationStyleChanged(SwitchItem item){
|
||||||
|
GlobalUserPreferences.uniformNotificationIcon=item.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
|
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
|
||||||
PushSubscription subscription=getPushSubscription();
|
PushSubscription subscription=getPushSubscription();
|
||||||
PushSubscription.Policy prevPolicy=subscription.policy;
|
PushSubscription.Policy prevPolicy=subscription.policy;
|
||||||
@@ -424,9 +572,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
list.getAdapter().notifyItemChanged(index);
|
list.getAdapter().notifyItemChanged(index);
|
||||||
}
|
}
|
||||||
if((prevPolicy==PushSubscription.Policy.NONE)!=(policy==PushSubscription.Policy.NONE)){
|
if((prevPolicy==PushSubscription.Policy.NONE)!=(policy==PushSubscription.Policy.NONE)){
|
||||||
|
boolean newState=policy!=PushSubscription.Policy.NONE;
|
||||||
|
for(PushNotification.Type value : PushNotification.Type.values()){
|
||||||
|
onNotificationsChanged(value, newState);
|
||||||
|
}
|
||||||
index++;
|
index++;
|
||||||
while(items.get(index) instanceof SwitchItem si){
|
while(items.get(index) instanceof SwitchItem si){
|
||||||
si.enabled=si.checked=policy!=PushSubscription.Policy.NONE;
|
si.enabled=si.checked=newState;
|
||||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index);
|
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index);
|
||||||
if(holder!=null)
|
if(holder!=null)
|
||||||
((BindableViewHolder<?>)holder).rebind();
|
((BindableViewHolder<?>)holder).rebind();
|
||||||
@@ -466,6 +618,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onLoggedOut(){
|
private void onLoggedOut(){
|
||||||
|
if (getActivity() == null) return;
|
||||||
AccountSessionManager.getInstance().removeAccount(accountID);
|
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
Intent intent=new Intent(getActivity(), MainActivity.class);
|
Intent intent=new Intent(getActivity(), MainActivity.class);
|
||||||
@@ -475,9 +628,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
private void clearImageCache(){
|
private void clearImageCache(){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
Activity activity=getActivity();
|
Activity activity=getActivity();
|
||||||
ImageCache.getInstance(getActivity()).clear();
|
imageCache.clear();
|
||||||
Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
|
Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
|
||||||
});
|
});
|
||||||
|
if (list.findViewHolderForAdapterPosition(items.indexOf(clearImageCacheItem)) instanceof TextViewHolder tvh) {
|
||||||
|
clearImageCacheItem.secondaryText = UiUtils.formatFileSize(getContext(), 0, true);
|
||||||
|
tvh.rebind();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
@@ -515,7 +672,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
this.text=getString(text);
|
this.text=getString(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HeaderItem(String text) {
|
public HeaderItem(String text){
|
||||||
this.text=text;
|
this.text=text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,7 +696,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
this.onChanged=onChanged;
|
this.onChanged=onChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SwitchItem(@StringRes int text, int icon, boolean checked, Consumer<SwitchItem> onChanged, boolean enabled){
|
public SwitchItem(@StringRes int text, @DrawableRes int icon, boolean checked, Consumer<SwitchItem> onChanged, boolean enabled){
|
||||||
this.text=getString(text);
|
this.text=getString(text);
|
||||||
this.icon=icon;
|
this.icon=icon;
|
||||||
this.checked=checked;
|
this.checked=checked;
|
||||||
@@ -570,6 +727,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ColorPicker extends Item{
|
||||||
|
@Override
|
||||||
|
public int getViewType(){
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class ThemeItem extends Item{
|
private static class ThemeItem extends Item{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -601,27 +765,34 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
|
|
||||||
private class TextItem extends Item{
|
private class TextItem extends Item{
|
||||||
private String text;
|
private String text;
|
||||||
|
private String secondaryText;
|
||||||
private Runnable onClick;
|
private Runnable onClick;
|
||||||
private boolean loading;
|
private boolean loading;
|
||||||
private int icon;
|
private int icon;
|
||||||
|
|
||||||
public TextItem(@StringRes int text, Runnable onClick) {
|
public TextItem(@StringRes int text, Runnable onClick) {
|
||||||
this(text, onClick, false, 0);
|
this(text, null, onClick, false, 0);
|
||||||
}
|
|
||||||
|
|
||||||
public TextItem(@StringRes int text, Runnable onClick, boolean loading) {
|
|
||||||
this(text, onClick, loading, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
|
public TextItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
|
||||||
this(text, onClick, false, icon);
|
this(text, null, onClick, false, icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextItem(@StringRes int text, Runnable onClick, boolean loading, @DrawableRes int icon){
|
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, @DrawableRes int icon) {
|
||||||
|
this(text, secondaryText, onClick, false, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, boolean loading, @DrawableRes int icon){
|
||||||
this.text=getString(text);
|
this.text=getString(text);
|
||||||
this.onClick=onClick;
|
this.onClick=onClick;
|
||||||
this.loading=loading;
|
this.loading=loading;
|
||||||
this.icon=icon;
|
this.icon=icon;
|
||||||
|
this.secondaryText = secondaryText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextItem(String text, Runnable onClick){
|
||||||
|
this.text=text;
|
||||||
|
this.onClick=onClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -636,6 +807,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
super(text);
|
super(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RedHeaderItem(String text){
|
||||||
|
super(text);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
return 5;
|
return 5;
|
||||||
@@ -729,7 +904,12 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onBind(SwitchItem item){
|
public void onBind(SwitchItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
icon.setImageResource(item.icon);
|
if (item.icon == 0) {
|
||||||
|
icon.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
icon.setVisibility(View.VISIBLE);
|
||||||
|
icon.setImageResource(item.icon);
|
||||||
|
}
|
||||||
checkbox.setChecked(item.checked && item.enabled);
|
checkbox.setChecked(item.checked && item.enabled);
|
||||||
checkbox.setEnabled(item.enabled);
|
checkbox.setEnabled(item.enabled);
|
||||||
}
|
}
|
||||||
@@ -807,6 +987,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
|
private class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
|
||||||
private final Button button;
|
private final Button button;
|
||||||
private final ImageView icon;
|
private final ImageView icon;
|
||||||
@@ -872,22 +1053,27 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
||||||
private final TextView text;
|
private final TextView text, secondaryText;
|
||||||
private final ProgressBar progress;
|
private final ProgressBar progress;
|
||||||
private final ImageView icon;
|
private final ImageView icon;
|
||||||
|
|
||||||
public TextViewHolder(){
|
public TextViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_text, list);
|
super(getActivity(), R.layout.item_settings_text, list);
|
||||||
text = itemView.findViewById(R.id.text);
|
text = itemView.findViewById(R.id.text);
|
||||||
|
secondaryText = itemView.findViewById(R.id.secondary_text);
|
||||||
progress = itemView.findViewById(R.id.progress);
|
progress = itemView.findViewById(R.id.progress);
|
||||||
icon = itemView.findViewById(R.id.icon);
|
icon = itemView.findViewById(R.id.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(TextItem item){
|
public void onBind(TextItem item){
|
||||||
|
icon.setVisibility(item.icon != 0 ? View.VISIBLE : View.GONE);
|
||||||
|
secondaryText.setVisibility(item.secondaryText != null ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
progress.animate().alpha(item.loading ? 1 : 0);
|
progress.animate().alpha(item.loading ? 1 : 0);
|
||||||
if (item.icon != 0) icon.setImageDrawable(getActivity().getTheme().getDrawable(item.icon));
|
icon.setImageResource(item.icon);
|
||||||
|
secondaryText.setText(item.secondaryText);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -898,19 +1084,19 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
|
|
||||||
private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> {
|
private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> {
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
;
|
|
||||||
|
|
||||||
public SmallTextViewHolder(){
|
public SmallTextViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_text, list);
|
super(getActivity(), R.layout.item_settings_text, list);
|
||||||
text = itemView.findViewById(R.id.text);
|
text = itemView.findViewById(R.id.text);
|
||||||
|
text.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary));
|
||||||
|
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||||
|
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||||
|
text.setPaddingRelative(text.getPaddingStart(), 0, text.getPaddingEnd(), text.getPaddingBottom());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(SmallTextItem item){
|
public void onBind(SmallTextItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
text.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary));
|
|
||||||
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
|
||||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,7 +1115,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
|
|
||||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||||
|
|
||||||
private final TextView text;
|
private final TextView text, changelog;
|
||||||
private final Button button;
|
private final Button button;
|
||||||
private final ImageButton cancelBtn;
|
private final ImageButton cancelBtn;
|
||||||
private final ProgressBar progress;
|
private final ProgressBar progress;
|
||||||
@@ -940,6 +1126,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
public UpdateViewHolder(){
|
public UpdateViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_update, list);
|
super(getActivity(), R.layout.item_settings_update, list);
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
|
changelog=findViewById(R.id.changelog);
|
||||||
button=findViewById(R.id.button);
|
button=findViewById(R.id.button);
|
||||||
cancelBtn=findViewById(R.id.cancel_btn);
|
cancelBtn=findViewById(R.id.cancel_btn);
|
||||||
progress=findViewById(R.id.progress);
|
progress=findViewById(R.id.progress);
|
||||||
@@ -964,10 +1151,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
||||||
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
||||||
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
||||||
text.setText(getString(R.string.sk_update_available, info.version));
|
text.setText(getString(R.string.mo_update_available, info.version));
|
||||||
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
||||||
}else{
|
}else{
|
||||||
text.setText(getString(R.string.sk_update_ready, info.version));
|
text.setText(getString(R.string.mo_update_ready, info.version));
|
||||||
button.setText(R.string.install_update);
|
button.setText(R.string.install_update);
|
||||||
}
|
}
|
||||||
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
||||||
@@ -983,6 +1170,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
progress.setVisibility(View.GONE);
|
progress.setVisibility(View.GONE);
|
||||||
progress.removeCallbacks(progressUpdater);
|
progress.removeCallbacks(progressUpdater);
|
||||||
}
|
}
|
||||||
|
changelog.setText(info.changelog);
|
||||||
|
// changelog.setText(getString(R.string.sk_changelog, info.changelog));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateProgress(){
|
private void updateProgress(){
|
||||||
|
|||||||
@@ -173,16 +173,7 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
TextView title=new TextView(getActivity());
|
TextView title=new TextView(getActivity());
|
||||||
title.setTextAppearance(R.style.m3_headline_medium);
|
title.setTextAppearance(R.style.m3_headline_medium);
|
||||||
title.setText(switch(page){
|
title.setText(switch(page){
|
||||||
case 0 -> {
|
case 0 -> getString(R.string.welcome_page1_title);
|
||||||
String src=getString(R.string.welcome_page1_title);
|
|
||||||
SpannableString ss=new SpannableString(src);
|
|
||||||
int start=src.indexOf("{logo}");
|
|
||||||
if(start!=-1){
|
|
||||||
LogoSpan span=new LogoSpan(getResources().getDrawable(R.drawable.splash_logo, getActivity().getTheme()));
|
|
||||||
ss.setSpan(span, start, start+6, 0);
|
|
||||||
}
|
|
||||||
yield ss;
|
|
||||||
}
|
|
||||||
case 1 -> getString(R.string.welcome_page2_title);
|
case 1 -> getString(R.string.welcome_page2_title);
|
||||||
case 2 -> getString(R.string.welcome_page3_title);
|
case 2 -> getString(R.string.welcome_page3_title);
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
@@ -204,26 +195,4 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LogoSpan extends ReplacementSpan{
|
|
||||||
private final Drawable drawable;
|
|
||||||
|
|
||||||
private LogoSpan(Drawable drawable){
|
|
||||||
this.drawable=drawable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
|
|
||||||
return drawable.getIntrinsicWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){
|
|
||||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
|
||||||
canvas.save();
|
|
||||||
canvas.translate(x, y-V.dp(20));
|
|
||||||
drawable.draw(canvas);
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
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.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
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;
|
||||||
@@ -46,6 +47,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
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);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -54,7 +56,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, true, false, null);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, Filter.FilterContext.HOME);
|
||||||
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()));
|
||||||
@@ -139,7 +141,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
action=getString(R.string.edit_multiple_changed);
|
action=getString(R.string.edit_multiple_changed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null));
|
String sep = getString(R.string.sk_separator);
|
||||||
|
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
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.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;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
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;
|
||||||
@@ -29,7 +32,9 @@ 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){
|
||||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true, null);
|
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
|
||||||
|
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
|
||||||
|
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, Filter.FilterContext.HOME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -55,6 +60,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
Status status=getContentStatusByID(id);
|
Status status=getContentStatusByID(id);
|
||||||
if(status==null)
|
if(status==null)
|
||||||
return;
|
return;
|
||||||
|
status.filterRevealed = true;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelable("status", Parcels.wrap(status));
|
args.putParcelable("status", Parcels.wrap(status));
|
||||||
@@ -171,6 +177,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig){
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo();
|
||||||
|
}
|
||||||
|
|
||||||
public class EventListener{
|
public class EventListener{
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
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.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
@@ -17,6 +16,7 @@ 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.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.StatusFilterPredicate;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
|
|||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class ThreadFragment extends StatusListFragment{
|
public class ThreadFragment extends StatusListFragment{
|
||||||
private Status mainStatus;
|
protected Status mainStatus;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -61,8 +61,7 @@ public class ThreadFragment extends StatusListFragment{
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(StatusContext result){
|
public void onSuccess(StatusContext result){
|
||||||
if(getActivity()==null)
|
if (getActivity() == null) return;
|
||||||
return;
|
|
||||||
if(refreshing){
|
if(refreshing){
|
||||||
data.clear();
|
data.clear();
|
||||||
displayItems.clear();
|
displayItems.clear();
|
||||||
@@ -92,16 +91,10 @@ public class ThreadFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> filterStatuses(List<Status> statuses){
|
private List<Status> filterStatuses(List<Status> statuses){
|
||||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.THREAD)).collect(Collectors.toList());
|
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
|
||||||
if(filters.isEmpty())
|
return statuses.stream()
|
||||||
return statuses;
|
.filter(statusFilterPredicate)
|
||||||
return statuses.stream().filter(status->{
|
.collect(Collectors.toList());
|
||||||
for(Filter filter:filters){
|
|
||||||
if(filter.matches(status))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -132,4 +125,14 @@ public class ThreadFragment extends StatusListFragment{
|
|||||||
public boolean isItemEnabled(String id){
|
public boolean isItemEnabled(String id){
|
||||||
return !id.equals(mainStatus.id);
|
return !id.equals(mainStatus.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsLightStatusBar(){
|
||||||
|
return !UiUtils.isDarkTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsLightNavigationBar(){
|
||||||
|
return !UiUtils.isDarkTheme();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
for(Relationship rel:result){
|
for(Relationship rel:result){
|
||||||
relationships.put(rel.id, rel);
|
relationships.put(rel.id, rel);
|
||||||
}
|
}
|
||||||
|
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++){
|
||||||
@@ -128,7 +129,8 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
// list.setPadding(0, V.dp(16), 0, V.dp(16));
|
// list.setPadding(0, V.dp(16), 0, V.dp(16));
|
||||||
list.setClipToPadding(false);
|
list.setClipToPadding(false);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 72, 16));
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1,
|
||||||
|
Math.round(16f + 56f * getResources().getConfiguration().fontScale), 16));
|
||||||
updateToolbar();
|
updateToolbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,6 +227,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
contextMenu=new PopupMenu(getActivity(), menuAnchor);
|
contextMenu=new PopupMenu(getActivity(), menuAnchor);
|
||||||
contextMenu.inflate(R.menu.profile);
|
contextMenu.inflate(R.menu.profile);
|
||||||
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
|
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
|
||||||
|
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -283,29 +286,32 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
Menu menu=contextMenu.getMenu();
|
Menu menu=contextMenu.getMenu();
|
||||||
Account account=item.account;
|
Account account=item.account;
|
||||||
|
|
||||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
||||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
|
||||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
MenuItem mute = menu.findItem(R.id.mute);
|
||||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getDisplayUsername())).setVisible(relationship.following);
|
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||||
|
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
||||||
|
|
||||||
|
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
||||||
|
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
||||||
|
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername())).setVisible(relationship.following);
|
||||||
|
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
||||||
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
||||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
||||||
if(relationship.following){
|
if(relationship.following){
|
||||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
|
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.setVisible(true);
|
hideBoosts.setVisible(true);
|
||||||
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getDisplayUsername()));
|
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
||||||
|
|
||||||
|
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||||
manageUserLists.setVisible(true);
|
manageUserLists.setVisible(true);
|
||||||
}else{
|
}else{
|
||||||
hideBoosts.setVisible(false);
|
hideBoosts.setVisible(false);
|
||||||
manageUserLists.setVisible(true);
|
manageUserLists.setVisible(true);
|
||||||
}
|
}
|
||||||
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
menu.findItem(R.id.block_domain).setVisible(false);
|
||||||
if(!account.isLocal()){
|
|
||||||
blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
|
||||||
blockDomain.setVisible(true);
|
|
||||||
}else{
|
|
||||||
blockDomain.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
menuAnchor.setTranslationX(x);
|
menuAnchor.setTranslationX(x);
|
||||||
menuAnchor.setTranslationY(y);
|
menuAnchor.setTranslationY(y);
|
||||||
@@ -346,6 +352,8 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
||||||
}else if(id==R.id.block){
|
}else if(id==R.id.block){
|
||||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
||||||
|
}else if(id==R.id.soft_block){
|
||||||
|
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);
|
||||||
@@ -364,6 +372,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Relationship result){
|
public void onSuccess(Relationship result){
|
||||||
relationships.put(AccountViewHolder.this.item.account.id, result);
|
relationships.put(AccountViewHolder.this.item.account.id, result);
|
||||||
|
if (getActivity() == null) return;
|
||||||
bindRelationship();
|
bindRelationship();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public abstract class PaginatedAccountListFragment extends BaseAccountListFragme
|
|||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
nextMaxID=null;
|
nextMaxID=null;
|
||||||
|
if (getActivity() == null) return;
|
||||||
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
|
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,6 +15,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.fragments.IsOnTop;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -48,7 +49,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop{
|
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
private GetAccountRelationships relationshipsRequest;
|
private GetAccountRelationships relationshipsRequest;
|
||||||
@@ -73,6 +74,7 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
|||||||
.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;
|
||||||
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();
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,7 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
|||||||
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(list==null)
|
if(list==null)
|
||||||
return;
|
return;
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
@@ -137,6 +140,11 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnTop() {
|
||||||
|
return isRecyclerViewOnTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
public AccountsAdapter(){
|
public AccountsAdapter(){
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
private String accountID;
|
private String accountID;
|
||||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||||
|
|
||||||
private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
|
// private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -81,20 +81,30 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
tabLayout=view.findViewById(R.id.tabbar);
|
tabLayout=view.findViewById(R.id.tabbar);
|
||||||
pager=view.findViewById(R.id.pager);
|
pager=view.findViewById(R.id.pager);
|
||||||
|
|
||||||
tabViews=new FrameLayout[noFederated ? 6 : 7];
|
// tabViews=new FrameLayout[noFederated ? 5 : 6];
|
||||||
|
tabViews=new FrameLayout[4];
|
||||||
for(int i=0;i<tabViews.length;i++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
FrameLayout tabView=new FrameLayout(getActivity());
|
FrameLayout tabView=new FrameLayout(getActivity());
|
||||||
int switchIndex = noFederated && i > 0 ? i + 1 : i;
|
|
||||||
tabView.setId(switch(switchIndex){
|
/// int switchIndex = noFederated && i > 0 ? i + 1 : i;
|
||||||
case 0 -> R.id.discover_local_timeline;
|
// tabView.setId(switch(switchIndex){
|
||||||
case 1 -> R.id.discover_federated_timeline;
|
// case 0 -> R.id.discover_local_timeline;
|
||||||
case 2 -> R.id.discover_hashtags;
|
// case 1 -> R.id.discover_federated_timeline;
|
||||||
case 3 -> R.id.discover_posts;
|
// case 2 -> R.id.discover_hashtags;
|
||||||
case 4 -> R.id.discover_news;
|
// case 3 -> R.id.discover_posts;
|
||||||
case 5 -> R.id.discover_users;
|
// case 4 -> R.id.discover_news;
|
||||||
case 6 -> R.id.discover_lists;
|
// case 5 -> R.id.discover_users;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+switchIndex);
|
// default -> throw new IllegalStateException("Unexpected value: "+switchIndex);
|
||||||
|
// });
|
||||||
|
|
||||||
|
tabView.setId(switch(i){
|
||||||
|
case 0 -> R.id.discover_hashtags;
|
||||||
|
case 1 -> R.id.discover_posts;
|
||||||
|
case 2 -> R.id.discover_news;
|
||||||
|
case 3 -> R.id.discover_users;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||||
});
|
});
|
||||||
|
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment
|
view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment
|
||||||
tabViews[i]=tabView;
|
tabViews[i]=tabView;
|
||||||
@@ -103,6 +113,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
tabLayout.setTabTextSize(V.dp(16));
|
tabLayout.setTabTextSize(V.dp(16));
|
||||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||||
|
|
||||||
|
UiUtils.reduceSwipeSensitivity(pager);
|
||||||
pager.setOffscreenPageLimit(4);
|
pager.setOffscreenPageLimit(4);
|
||||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||||
pager.setAdapter(new DiscoverPagerAdapter());
|
pager.setAdapter(new DiscoverPagerAdapter());
|
||||||
@@ -119,7 +130,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(localTimelineFragment==null){
|
if(localTimelineFragment==null || hashtagsFragment==null){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putBoolean("__is_tab", true);
|
args.putBoolean("__is_tab", true);
|
||||||
@@ -139,38 +150,56 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
localTimelineFragment=new LocalTimelineFragment();
|
localTimelineFragment=new LocalTimelineFragment();
|
||||||
localTimelineFragment.setArguments(args);
|
localTimelineFragment.setArguments(args);
|
||||||
|
|
||||||
listTimelinesFragment=new ListTimelinesFragment();
|
// listTimelinesFragment=new ListTimelinesFragment();
|
||||||
listTimelinesFragment.setArguments(args);
|
// listTimelinesFragment.setArguments(args);
|
||||||
|
//
|
||||||
|
// FragmentTransaction transaction = getChildFragmentManager().beginTransaction()
|
||||||
|
// .add(R.id.discover_posts, postsFragment)
|
||||||
|
// .add(R.id.discover_local_timeline, localTimelineFragment)
|
||||||
|
// .add(R.id.discover_hashtags, hashtagsFragment)
|
||||||
|
// .add(R.id.discover_news, newsFragment)
|
||||||
|
// .add(R.id.discover_users, accountsFragment)
|
||||||
|
// .add(R.id.discover_lists, listTimelinesFragment);
|
||||||
|
//
|
||||||
|
// if (!noFederated) {
|
||||||
|
// federatedTimelineFragment=new FederatedTimelineFragment();
|
||||||
|
// federatedTimelineFragment.setArguments(args);
|
||||||
|
// transaction.add(R.id.discover_federated_timeline, federatedTimelineFragment);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// transaction.commit();
|
||||||
|
|
||||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.discover_posts, postsFragment)
|
.add(R.id.discover_posts, postsFragment)
|
||||||
.add(R.id.discover_local_timeline, localTimelineFragment)
|
|
||||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||||
.add(R.id.discover_news, newsFragment)
|
.add(R.id.discover_news, newsFragment)
|
||||||
.add(R.id.discover_users, accountsFragment)
|
.add(R.id.discover_users, accountsFragment)
|
||||||
.add(R.id.discover_lists, listTimelinesFragment);
|
.commit();
|
||||||
|
|
||||||
if (!noFederated) {
|
|
||||||
federatedTimelineFragment=new FederatedTimelineFragment();
|
|
||||||
federatedTimelineFragment.setArguments(args);
|
|
||||||
transaction.add(R.id.discover_federated_timeline, federatedTimelineFragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||||
@Override
|
@Override
|
||||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||||
if (noFederated && position > 0) position++;
|
|
||||||
|
// if (noFederated && position > 0) position++;
|
||||||
|
|
||||||
|
// tab.setText(switch(position){
|
||||||
|
// case 0 -> R.string.local_timeline;
|
||||||
|
// case 1 -> R.string.sk_federated_timeline;
|
||||||
|
// case 2 -> R.string.sk_list_timelines;
|
||||||
|
// case 3 -> R.string.hashtags;
|
||||||
|
// case 4 -> R.string.posts;
|
||||||
|
// case 5 -> R.string.news;
|
||||||
|
// case 6 -> R.string.for_you;
|
||||||
|
//
|
||||||
|
// default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
|
// });
|
||||||
|
|
||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.local_timeline;
|
case 0 -> R.string.hashtags;
|
||||||
case 1 -> R.string.sk_federated_timeline;
|
case 1 -> R.string.posts;
|
||||||
case 2 -> R.string.hashtags;
|
case 2 -> R.string.news;
|
||||||
case 3 -> R.string.posts;
|
case 3 -> R.string.for_you;
|
||||||
case 4 -> R.string.news;
|
|
||||||
case 5 -> R.string.for_you;
|
|
||||||
case 6 -> R.string.sk_list_timelines;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
tab.view.textView.setAllCaps(true);
|
||||||
@@ -256,8 +285,11 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadData(){
|
public void loadData(){
|
||||||
if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
|
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
|
||||||
localTimelineFragment.loadData();
|
hashtagsFragment.loadData();
|
||||||
|
|
||||||
|
// if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
|
||||||
|
// localTimelineFragment.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
||||||
@@ -292,15 +324,24 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Fragment getFragmentForPage(int page){
|
private Fragment getFragmentForPage(int page){
|
||||||
if (noFederated && page > 0) page++;
|
// if (noFederated && page > 0) page++;
|
||||||
|
|
||||||
|
// return switch(page){
|
||||||
|
// case 0 -> localTimelineFragment;
|
||||||
|
// case 1 -> federatedTimelineFragment;
|
||||||
|
// case 2 -> hashtagsFragment;
|
||||||
|
// case 3 -> postsFragment;
|
||||||
|
// case 4 -> newsFragment;
|
||||||
|
// case 5 -> accountsFragment;
|
||||||
|
// case 6 -> listTimelinesFragment;
|
||||||
|
// default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
|
// };
|
||||||
|
|
||||||
return switch(page){
|
return switch(page){
|
||||||
case 0 -> localTimelineFragment;
|
case 0 -> hashtagsFragment;
|
||||||
case 1 -> federatedTimelineFragment;
|
case 1 -> postsFragment;
|
||||||
case 2 -> hashtagsFragment;
|
case 2 -> newsFragment;
|
||||||
case 3 -> postsFragment;
|
case 3 -> accountsFragment;
|
||||||
case 4 -> newsFragment;
|
|
||||||
case 5 -> accountsFragment;
|
|
||||||
case 6 -> listTimelinesFragment;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -357,4 +398,10 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void selectSearch(){
|
||||||
|
searchEdit.requestFocus();
|
||||||
|
onSearchEditFocusChanged(searchEdit, true);
|
||||||
|
getActivity().getSystemService(InputMethodManager.class).showSoftInput(searchEdit, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||||
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
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.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
@@ -34,7 +35,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop{
|
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||||
@@ -58,6 +59,7 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
|||||||
imageRequests=result.stream()
|
imageRequests=result.stream()
|
||||||
.map(card->TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(150), V.dp(150)))
|
.map(card->TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(150), V.dp(150)))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
if (getActivity() == null) return;
|
||||||
onDataLoaded(result, false);
|
onDataLoaded(result, false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -81,6 +83,11 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnTop() {
|
||||||
|
return isRecyclerViewOnTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
public LinksAdapter(){
|
public LinksAdapter(){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
|
|||||||
@@ -4,24 +4,30 @@ import android.os.Bundle;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||||
|
import org.joinmastodon.android.fragments.IsOnTop;
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
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;
|
||||||
|
|
||||||
public class DiscoverPostsFragment extends StatusListFragment{
|
public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop {
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetTrendingStatuses(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, false);
|
if (getActivity() == null) return;
|
||||||
|
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||||
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
@@ -31,4 +37,9 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnTop() {
|
||||||
|
return isRecyclerViewOnTop(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments.discover;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
@@ -15,10 +16,16 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class FederatedTimelineFragment extends StatusListFragment{
|
public class FederatedTimelineFragment extends StatusListFragment {
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean withComposeButton() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@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)
|
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count)
|
||||||
@@ -27,7 +34,9 @@ public class FederatedTimelineFragment extends StatusListFragment{
|
|||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(!result.isEmpty())
|
if(!result.isEmpty())
|
||||||
maxID=result.get(result.size()-1).id;
|
maxID=result.get(result.size()-1).id;
|
||||||
onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
|
if (getActivity() == null) return;
|
||||||
|
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||||
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -36,6 +45,6 @@ public class FederatedTimelineFragment extends StatusListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
// bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,16 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class LocalTimelineFragment extends StatusListFragment{
|
public class LocalTimelineFragment extends StatusListFragment {
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean withComposeButton() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@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)
|
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
|
||||||
@@ -27,7 +33,9 @@ public class LocalTimelineFragment extends StatusListFragment{
|
|||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(!result.isEmpty())
|
if(!result.isEmpty())
|
||||||
maxID=result.get(result.size()-1).id;
|
maxID=result.get(result.size()-1).id;
|
||||||
onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
|
if (getActivity() == null) return;
|
||||||
|
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||||
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
|||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.model.SearchResults;
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
@@ -62,6 +63,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
loadData();
|
loadData();
|
||||||
|
resetEmptyText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,12 +72,16 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
imm=activity.getSystemService(InputMethodManager.class);
|
imm=activity.getSystemService(InputMethodManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resetEmptyText() {
|
||||||
|
setEmptyText(R.string.sk_recent_searches_placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
|
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
|
||||||
return switch(s.type){
|
return switch(s.type){
|
||||||
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
||||||
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
||||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null);
|
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null, Filter.FilterContext.PUBLIC);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +125,8 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
resetEmptyText();
|
||||||
if(isInRecentMode()){
|
if(isInRecentMode()){
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
||||||
if(getActivity()==null)
|
if(getActivity()==null)
|
||||||
@@ -128,11 +136,13 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
onDataLoaded(sr, false);
|
onDataLoaded(sr, false);
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
|
setEmptyText(R.string.sk_searching);
|
||||||
progressVisibilityListener.onProgressVisibilityChanged(true);
|
progressVisibilityListener.onProgressVisibilityChanged(true);
|
||||||
currentRequest=new GetSearchResults(currentQuery, null, true)
|
currentRequest=new GetSearchResults(currentQuery, null, true)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
|
setEmptyText(R.string.sk_no_results);
|
||||||
ArrayList<SearchResult> results=new ArrayList<>();
|
ArrayList<SearchResult> results=new ArrayList<>();
|
||||||
if(result.accounts!=null){
|
if(result.accounts!=null){
|
||||||
for(Account acc:result.accounts)
|
for(Account acc:result.accounts)
|
||||||
@@ -148,11 +158,13 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
}
|
}
|
||||||
prevDisplayItems=new ArrayList<>(displayItems);
|
prevDisplayItems=new ArrayList<>(displayItems);
|
||||||
unfilteredResults=results;
|
unfilteredResults=results;
|
||||||
|
if (getActivity() == null) return;
|
||||||
onDataLoaded(filterSearchResults(results), false);
|
onDataLoaded(filterSearchResults(results), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
|
resetEmptyText();
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
Activity a=getActivity();
|
Activity a=getActivity();
|
||||||
if(a==null)
|
if(a==null)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
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.IsOnTop;
|
||||||
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.DividerItemDecoration;
|
||||||
@@ -23,7 +24,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|||||||
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 TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop{
|
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Hashtag> result){
|
public void onSuccess(List<Hashtag> result){
|
||||||
|
if (getActivity() == null) return;
|
||||||
onDataLoaded(result, false);
|
onDataLoaded(result, false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -66,6 +68,11 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnTop() {
|
||||||
|
return isRecyclerViewOnTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -96,9 +96,9 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// @Override
|
||||||
protected void onUpdateToolbar(){
|
protected void onUpdateToolbar(){
|
||||||
super.onUpdateToolbar();
|
// super.onUpdateToolbar();
|
||||||
getToolbar().setBackground(null);
|
getToolbar().setBackground(null);
|
||||||
getToolbar().setElevation(0);
|
getToolbar().setElevation(0);
|
||||||
}
|
}
|
||||||
@@ -193,30 +193,24 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
mgr.removeAccount(accountID);
|
mgr.removeAccount(accountID);
|
||||||
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
|
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
|
||||||
String newID=mgr.getLastActiveAccountID();
|
String newID=mgr.getLastActiveAccountID();
|
||||||
Bundle args=new Bundle();
|
accountID=newID;
|
||||||
args.putString("account", newID);
|
if((session.self.avatar!=null || session.self.displayName!=null) && !getArguments().getBoolean("debug")){
|
||||||
if(session.self.avatar!=null || session.self.displayName!=null){
|
new UpdateAccountCredentials(session.self.displayName, "", (File)null, null, Collections.emptyList())
|
||||||
File avaFile=session.self.avatar!=null ? new File(session.self.avatar) : null;
|
|
||||||
new UpdateAccountCredentials(session.self.displayName, "", avaFile, null, Collections.emptyList())
|
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
if(avaFile!=null)
|
|
||||||
avaFile.delete();
|
|
||||||
mgr.updateAccountInfo(newID, result);
|
mgr.updateAccountInfo(newID, result);
|
||||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
proceed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
if(avaFile!=null)
|
proceed();
|
||||||
avaFile.delete();
|
|
||||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(newID);
|
.exec(newID);
|
||||||
}else{
|
}else{
|
||||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
proceed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,4 +243,11 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
resendBtn.removeCallbacks(resendTimer);
|
resendBtn.removeCallbacks(resendTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void proceed(){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
// Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||||
|
Nav.goClearingStack(getActivity(), OnboardingProfileSetupFragment.class, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,8 +139,9 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
|||||||
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
||||||
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
|
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
|
||||||
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
|
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
|
||||||
((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
|
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
|
||||||
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
((TextView) headerView.findViewById(R.id.username)).setText(R.string.mo_app_username);
|
||||||
|
((TextView) headerView.findViewById(R.id.name)).setText(R.string.mo_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);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -19,6 +20,7 @@ import org.joinmastodon.android.model.Instance;
|
|||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
@@ -42,6 +44,7 @@ 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;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.Callback;
|
import okhttp3.Callback;
|
||||||
@@ -58,6 +61,9 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
private ArrayList<Item> items=new ArrayList<>();
|
private ArrayList<Item> items=new ArrayList<>();
|
||||||
private Call currentRequest;
|
private Call currentRequest;
|
||||||
private ItemsAdapter itemsAdapter;
|
private ItemsAdapter itemsAdapter;
|
||||||
|
private ElevationOnScrollListener onScrollListener;
|
||||||
|
|
||||||
|
private static final int SIGNUP_REQUEST=722;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -72,7 +78,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||||
|
|
||||||
items.add(new Item("Mastodon for Android Privacy Policy", "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
|
items.add(new Item("Mastodon for Android Privacy Policy", getString(R.string.privacy_policy_explanation), "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
|
||||||
loadServerPrivacyPolicy();
|
loadServerPrivacyPolicy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,18 +99,24 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
||||||
TextView text=headerView.findViewById(R.id.text);
|
TextView text=headerView.findViewById(R.id.text);
|
||||||
text.setText(R.string.privacy_policy_subtitle);
|
text.setText(getString(R.string.privacy_policy_subtitle, instance.uri));
|
||||||
|
|
||||||
adapter=new MergeRecyclerAdapter();
|
adapter=new MergeRecyclerAdapter();
|
||||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||||
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
|
||||||
|
|
||||||
btn=view.findViewById(R.id.btn_next);
|
btn=view.findViewById(R.id.btn_next);
|
||||||
btn.setOnClickListener(v->onButtonClick());
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
|
||||||
|
Button backBtn=view.findViewById(R.id.btn_back);
|
||||||
|
backBtn.setText(getString(R.string.server_policy_disagree, instance.uri));
|
||||||
|
backBtn.setOnClickListener(v->{
|
||||||
|
setResult(false, null);
|
||||||
|
Nav.finish(this);
|
||||||
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,19 +125,32 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
|
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onUpdateToolbar(){
|
protected void onUpdateToolbar(){
|
||||||
super.onUpdateToolbar();
|
super.onUpdateToolbar();
|
||||||
getToolbar().setBackground(null);
|
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||||
getToolbar().setElevation(0);
|
getToolbar().setElevation(0);
|
||||||
|
if(onScrollListener!=null){
|
||||||
|
onScrollListener.setViews(buttonBar, getToolbar());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onButtonClick(){
|
protected void onButtonClick(){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putParcelable("instance", Parcels.wrap(instance));
|
args.putParcelable("instance", Parcels.wrap(instance));
|
||||||
Nav.go(getActivity(), SignupFragment.class, args);
|
Nav.goForResult(getActivity(), SignupFragment.class, args, SIGNUP_REQUEST, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||||
|
super.onFragmentResult(reqCode, success, result);
|
||||||
|
if(reqCode==SIGNUP_REQUEST && !success){
|
||||||
|
setResult(false, null);
|
||||||
|
Nav.finish(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -158,7 +183,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
if(!response.isSuccessful())
|
if(!response.isSuccessful())
|
||||||
return;
|
return;
|
||||||
Document doc=Jsoup.parse(Objects.requireNonNull(body).byteStream(), Objects.requireNonNull(body.contentType()).charset(StandardCharsets.UTF_8).name(), req.url().toString());
|
Document doc=Jsoup.parse(Objects.requireNonNull(body).byteStream(), Objects.requireNonNull(body.contentType()).charset(StandardCharsets.UTF_8).name(), req.url().toString());
|
||||||
final Item item=new Item(doc.title(), instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico");
|
final Item item=new Item(doc.title(), null, instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico");
|
||||||
Activity activity=getActivity();
|
Activity activity=getActivity();
|
||||||
if(activity!=null){
|
if(activity!=null){
|
||||||
activity.runOnUiThread(()->{
|
activity.runOnUiThread(()->{
|
||||||
@@ -192,16 +217,23 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
|
|
||||||
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
||||||
private final TextView title;
|
private final TextView title;
|
||||||
|
private final TextView subtitle;
|
||||||
|
|
||||||
public ItemViewHolder(){
|
public ItemViewHolder(){
|
||||||
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
subtitle=findViewById(R.id.subtitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(Item item){
|
public void onBind(Item item){
|
||||||
title.setText(item.title);
|
title.setText(item.title);
|
||||||
|
if(TextUtils.isEmpty(item.subtitle)){
|
||||||
|
subtitle.setVisibility(View.GONE);
|
||||||
|
}else{
|
||||||
|
subtitle.setVisibility(View.VISIBLE);
|
||||||
|
subtitle.setText(item.subtitle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -211,10 +243,11 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class Item{
|
private static class Item{
|
||||||
public String title, domain, url, faviconUrl;
|
public String title, subtitle, domain, url, faviconUrl;
|
||||||
|
|
||||||
public Item(String title, String domain, String url, String faviconUrl){
|
public Item(String title, String subtitle, String domain, String url, String faviconUrl){
|
||||||
this.title=title;
|
this.title=title;
|
||||||
|
this.subtitle=subtitle;
|
||||||
this.domain=domain;
|
this.domain=domain;
|
||||||
this.url=url;
|
this.url=url;
|
||||||
this.faviconUrl=faviconUrl;
|
this.faviconUrl=faviconUrl;
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
||||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||||
return true;
|
return true;
|
||||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
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(currentSearchQuery));
|
||||||
@@ -106,7 +106,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void onSearchChangedDebounced(){
|
protected void onSearchChangedDebounced(){
|
||||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||||
updateFilteredList();
|
updateFilteredList();
|
||||||
loadInstanceInfo(currentSearchQuery, false);
|
loadInstanceInfo(currentSearchQuery, false);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user