Compare commits
1437 Commits
bugfix/#34
...
1.1.4+fork
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
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 | ||
|
|
a996a24b7f | ||
|
|
44dcc9fe2b | ||
|
|
f197c8201d | ||
|
|
2fc5669203 | ||
|
|
7cdddf06bc | ||
|
|
557d535e5a | ||
|
|
60517b00f3 | ||
|
|
faf5e8e82b | ||
|
|
7264982761 | ||
|
|
fedf74258f | ||
|
|
def4960be6 | ||
|
|
525cc69c70 | ||
|
|
7ed1b164b5 | ||
|
|
0a9c31fb09 | ||
|
|
5ed6f97846 | ||
|
|
7dc195606c | ||
|
|
b2377a3353 | ||
|
|
0fdae0c775 | ||
|
|
113bbd960f | ||
|
|
a48e09e77b | ||
|
|
3c3f759d9a | ||
|
|
5f0986d03b | ||
|
|
579794d7e0 | ||
|
|
4956543eac | ||
|
|
87043f19bc | ||
|
|
375f8ceb27 | ||
|
|
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 | ||
|
|
f0a704b93b | ||
|
|
cfc528df9a | ||
|
|
9231ea1446 | ||
|
|
f2f9138435 | ||
|
|
624700497b | ||
|
|
5794a64da6 | ||
|
|
7e6e9d3dcd | ||
|
|
07634edfa3 | ||
|
|
4a03fbfbf0 | ||
|
|
000cdb08ec | ||
|
|
e0521b3c95 | ||
|
|
9547be89e1 | ||
|
|
40d44269fc | ||
|
|
3bf0903453 | ||
|
|
c3abf8c05c | ||
|
|
26297fbb5b | ||
|
|
cd342d1034 | ||
|
|
029650ef2d | ||
|
|
ac24f636df | ||
|
|
1688168bc1 | ||
|
|
6220ce6780 | ||
|
|
6107698a76 | ||
|
|
46b842afc4 | ||
|
|
a3c3fec9b4 | ||
|
|
f3a9b19104 | ||
|
|
3f773a52cc | ||
|
|
48664bb580 | ||
|
|
d9ed6f600b | ||
|
|
8a6d86727c | ||
|
|
d9abf82918 | ||
|
|
094cd67728 | ||
|
|
3cd9020ee0 | ||
|
|
6d22a4d014 | ||
|
|
9d800106cc | ||
|
|
68bb23e3b4 | ||
|
|
68397bd487 | ||
|
|
3104ddb4b6 | ||
|
|
ccad5d40ec | ||
|
|
5b7b022d9f | ||
|
|
387139b5d3 | ||
|
|
0ae10d5fbe | ||
|
|
d53397d8f8 | ||
|
|
b1006d2a14 | ||
|
|
c5cf318cdd | ||
|
|
671338c16a | ||
|
|
bbaa70e396 | ||
|
|
536d6cf63e | ||
|
|
b564a297ab | ||
|
|
6d34ae1a50 | ||
|
|
9bddd6b274 | ||
|
|
0d2457f39e | ||
|
|
420505328c | ||
|
|
0e60d71006 | ||
|
|
0f1456819b | ||
|
|
3af89a6175 | ||
|
|
2fac871493 | ||
|
|
0166133b61 | ||
|
|
a9e0911796 | ||
|
|
213f341257 | ||
|
|
01cc2a2c67 | ||
|
|
6c7a040c02 | ||
|
|
4e40944b26 | ||
|
|
af859438b4 | ||
|
|
7ef80e87b8 | ||
|
|
f8e873cd78 | ||
|
|
5cc97f7cf1 | ||
|
|
44c56331fd | ||
|
|
1272c3962e | ||
|
|
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 | ||
|
|
29b4f7c91a | ||
|
|
0a9ee57233 | ||
|
|
94b69a9c1c | ||
|
|
f0209dd1cc | ||
|
|
366e432c18 | ||
|
|
0075c0e779 | ||
|
|
d99dfd4185 | ||
|
|
0309b3ad25 | ||
|
|
7a85532b73 | ||
|
|
7299d947f7 | ||
|
|
adec7b28f1 | ||
|
|
c2563da056 | ||
|
|
64b9e53916 | ||
|
|
86ab6f7b3d | ||
|
|
ed02733524 | ||
|
|
a99af63f34 | ||
|
|
1d900a66fe | ||
|
|
f29acc217d | ||
|
|
f60164f5c5 | ||
|
|
578cf1f00d | ||
|
|
bca3bc6b4a | ||
|
|
352c813544 | ||
|
|
7d876bddc7 | ||
|
|
43d334259b | ||
|
|
750579b1c2 | ||
|
|
d69221d85b | ||
|
|
a1c80e92cd | ||
|
|
ce1a0d66f1 | ||
|
|
f28057d620 | ||
|
|
8f6dc7e6d2 | ||
|
|
b25a237c20 | ||
|
|
ccb0b59e17 | ||
|
|
14658a2d70 | ||
|
|
5f26878c06 | ||
|
|
df78ae59fe | ||
|
|
47924dfb61 | ||
|
|
3eb442e0f6 | ||
|
|
b309fc09f6 | ||
|
|
a00fe0485b | ||
|
|
a60f65857b | ||
|
|
fe584b58b0 | ||
|
|
91ad2bf66b | ||
|
|
01988a9435 | ||
|
|
51bc8e1b2c | ||
|
|
b28c684bd3 | ||
|
|
42b2cde1e2 | ||
|
|
199a7b816c | ||
|
|
2b8fc6764e | ||
|
|
37f93cda4d | ||
|
|
4114f5b3c2 | ||
|
|
14d45eb759 | ||
|
|
0dfa9d2c2c | ||
|
|
6fce18ffe8 | ||
|
|
956c56c494 | ||
|
|
75e7c6a9eb | ||
|
|
a7fb66d269 | ||
|
|
5f48802357 | ||
|
|
a27488c8da | ||
|
|
b6dbd7512c | ||
|
|
e41386082c | ||
|
|
ae385c139b | ||
|
|
dd2f213a4f | ||
|
|
35f92a6e91 | ||
|
|
ab5648a5d6 | ||
|
|
8a9b59c66a | ||
|
|
a6e0c877b5 | ||
|
|
de5a623a00 | ||
|
|
b0f9ce081f | ||
|
|
e17b6e83a4 | ||
|
|
96f4322f74 | ||
|
|
780f59d666 | ||
|
|
8c5db2eef5 | ||
|
|
5ab004b87e | ||
|
|
1b001bdd4f | ||
|
|
a5fa44213d | ||
|
|
68e9d9d91c | ||
|
|
a14783a275 | ||
|
|
3ce58d2edf | ||
|
|
523efac4fb | ||
|
|
d352aca9cc | ||
|
|
ae6afab01b | ||
|
|
efea405b83 | ||
|
|
ad9262cf0f | ||
|
|
636c268e46 | ||
|
|
06a61f0374 | ||
|
|
4ba7763de5 | ||
|
|
b486542e7b | ||
|
|
f53ce6cbf0 | ||
|
|
b10ff655f1 | ||
|
|
bbc27dbeea | ||
|
|
dadb929afd | ||
|
|
c0f397cdd5 | ||
|
|
b597cf6e18 | ||
|
|
e3bbeb2022 | ||
|
|
cad9fc8977 | ||
|
|
2f191c1f71 | ||
|
|
dc0debce1a | ||
|
|
d96b6f0100 | ||
|
|
16f1901976 | ||
|
|
61d9e4d531 | ||
|
|
aec4479e19 | ||
|
|
ec7235c03a | ||
|
|
f3db153ea9 | ||
|
|
e1c258478f | ||
|
|
1f2e691eb1 | ||
|
|
be9a3a9975 | ||
|
|
8d17ac6f28 | ||
|
|
d9df150cf8 | ||
|
|
667a4aab1a | ||
|
|
862ffd8172 | ||
|
|
eb1dd954ee | ||
|
|
6f2e5a63d7 | ||
|
|
5bf78c5cd2 | ||
|
|
277361a562 | ||
|
|
6697d3d0d5 | ||
|
|
7202aae712 | ||
|
|
6d2c6748f7 | ||
|
|
6c98c9ccf2 | ||
|
|
afc25ec8b3 | ||
|
|
6c9553ba65 | ||
|
|
0fd5ea4689 | ||
|
|
8441208058 | ||
|
|
aa34298771 | ||
|
|
ccacd88f80 | ||
|
|
1f20b21fc8 | ||
|
|
ae7152aca7 | ||
|
|
ba36347f03 | ||
|
|
c0c5e83f31 | ||
|
|
9c05ff2f7c | ||
|
|
0ada05bf3a | ||
|
|
c77b5dfac2 | ||
|
|
673ea40238 | ||
|
|
f7ced7f253 | ||
|
|
6152ec9d0d | ||
|
|
7ed8bb259d | ||
|
|
06882d5bea | ||
|
|
f460456502 | ||
|
|
6ef9f2ff15 | ||
|
|
062af9937f | ||
|
|
452ee8e1a5 | ||
|
|
88c62427aa | ||
|
|
09458c5ecb | ||
|
|
4171a5d210 | ||
|
|
1362a03877 | ||
|
|
34ae099b89 | ||
|
|
679bd4588f | ||
|
|
7d4c69bc82 | ||
|
|
1749fcacb1 | ||
|
|
683f87cc19 | ||
|
|
2ef19be3c7 | ||
|
|
fb5289372d | ||
|
|
f8d9d00dac | ||
|
|
a7c707f62e | ||
|
|
336ebb71cf | ||
|
|
a5f98f5c50 | ||
|
|
9d5d4b7957 | ||
|
|
3626da7362 | ||
|
|
400e340859 | ||
|
|
31cad1efbe | ||
|
|
e5da24a44d | ||
|
|
d63e5af8d0 | ||
|
|
abdce64b99 | ||
|
|
d5696684fa | ||
|
|
d168794d4e | ||
|
|
52c5057e85 | ||
|
|
21167f64c9 | ||
|
|
26343ce10b | ||
|
|
238d930c48 | ||
|
|
87ade4a020 | ||
|
|
db9bb58b3c | ||
|
|
99cbc8f071 | ||
|
|
d2a4ae8f59 | ||
|
|
9a47530ab8 | ||
|
|
ed1d9165e1 | ||
|
|
ef421dd5dd | ||
|
|
acbf27f025 | ||
|
|
f54e2375be | ||
|
|
1a66db065f | ||
|
|
c51b2bb2e7 | ||
|
|
4de3da09b3 | ||
|
|
e0febda372 | ||
|
|
516f97e679 | ||
|
|
069d141451 | ||
|
|
166401ea18 | ||
|
|
55e5c03b5f | ||
|
|
c950b6e6c1 | ||
|
|
2dc884b1bb | ||
|
|
c49660950d | ||
|
|
9162908173 | ||
|
|
2789169dd7 | ||
|
|
0728b00381 | ||
|
|
34b82337b1 | ||
|
|
f25d4e4d44 | ||
|
|
ac3176c0d8 | ||
|
|
021fc9e5a0 | ||
|
|
a48c11332c | ||
|
|
93bccc02bf | ||
|
|
7a594be3f2 | ||
|
|
c9f4df3d4e | ||
|
|
9078667d51 | ||
|
|
7569e1aef6 | ||
|
|
723983dadf | ||
|
|
f87e020abd | ||
|
|
fb5729d5cc | ||
|
|
2ff6c53d6d | ||
|
|
cfc6895711 | ||
|
|
1c27fc68ee | ||
|
|
df0d578573 | ||
|
|
2fa3c69af1 | ||
|
|
095bf92fed | ||
|
|
debe017f12 | ||
|
|
f956e12167 | ||
|
|
2c50c38d82 | ||
|
|
b4980101ad | ||
|
|
8395fca60f | ||
|
|
b22e7d277f | ||
|
|
c0e67593ee | ||
|
|
5dc4235724 | ||
|
|
f77caeefae | ||
|
|
c1ef23bbe8 | ||
|
|
e7e80bcf7d | ||
|
|
c27f5aaf30 | ||
|
|
d52728f22e | ||
|
|
3c7c962320 | ||
|
|
abf570d177 | ||
|
|
46422cd62d | ||
|
|
f1ffa2629e | ||
|
|
2074f3c33b | ||
|
|
7c51803674 | ||
|
|
6d80c62f30 | ||
|
|
64907a7e1c | ||
|
|
17922ca1d5 | ||
|
|
01ac219854 | ||
|
|
9bbf8c4618 | ||
|
|
978beaec77 | ||
|
|
0950e2eb7f | ||
|
|
116328adb9 | ||
|
|
32a2c66c34 | ||
|
|
661f545e35 | ||
|
|
d1e0cd3c20 | ||
|
|
db16dde073 | ||
|
|
b3fe44bc08 | ||
|
|
e5fab4a555 | ||
|
|
abe28179ec | ||
|
|
60d4e4d396 | ||
|
|
435e73d718 | ||
|
|
8714b24388 | ||
|
|
495db142d7 | ||
|
|
ac5d11159f | ||
|
|
d1479f142b | ||
|
|
ee6ec631e8 | ||
|
|
dee21222a7 | ||
|
|
cd8123ca34 | ||
|
|
ceb08ea78d | ||
|
|
cac5b554e2 | ||
|
|
c4adbc8e45 | ||
|
|
bb01077c3b | ||
|
|
b370fcda6d | ||
|
|
d364ebbb2f | ||
|
|
5b28468efd | ||
|
|
6fd58c9682 | ||
|
|
b580743619 | ||
|
|
4a9cb9f2dc | ||
|
|
0dcdda75be | ||
|
|
145f55817f | ||
|
|
79025c2f36 | ||
|
|
2515a8d381 | ||
|
|
ede7ece25a | ||
|
|
2db39f8c66 | ||
|
|
5f0382456f | ||
|
|
63b1b58c4e | ||
|
|
e9fe4a82df |
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -8,25 +8,35 @@ assignees: ''
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
**To reproduce**
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Does this happen in the official app?**
|
||||
|
||||
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) or at least using the current Mastodon version from the Play Store)
|
||||
|
||||
> 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**
|
||||
|
||||
If applicable, add screenshots (and screen recordings, if possible) to help explain your problem.
|
||||
|
||||
**Version**
|
||||
|
||||
Megalodon version: [e.g. v1.1.4+fork.#]
|
||||
|
||||
**Additional context**
|
||||
- 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**
|
||||
|
||||
If you know your way around Android development tools, please consider attaching a crash log, if possible.
|
||||
|
||||
24
README.md
24
README.md
@@ -5,7 +5,7 @@
|
||||
> 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/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
||||
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
||||
|
||||
---
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
|
||||
### **Material you theme support on Android 12+ devices!**
|
||||
|
||||
### **Translate button**
|
||||
### **Show posts filtered with a warning!**
|
||||
|
||||
**Allows you to translate posts in instances with the translate feature!**
|
||||
**Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:**
|
||||
|
||||
**Screenshots**
|
||||
Before | After
|
||||
:-------------------------:|:-------------------------:
|
||||
 | 
|
||||
|
||||

|
||||

|
||||
|
||||
### **Color themes**
|
||||
|
||||
@@ -86,9 +86,16 @@ Variant with an integrated updater. If you download Moshidon from here (and not
|
||||
|
||||
### 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)
|
||||
([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))
|
||||
* [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))
|
||||
@@ -110,6 +117,7 @@ Variant with an integrated updater. If you download Moshidon from here (and not
|
||||
|
||||
### 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))
|
||||
* [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))
|
||||
@@ -140,4 +148,6 @@ This project is released under the [GPL-3 License](./LICENSE).
|
||||
|
||||
## Links
|
||||
|
||||
[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>
|
||||
|
||||
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
|
||||
android.useAndroidX=true
|
||||
# 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 |
@@ -9,8 +9,8 @@ android {
|
||||
applicationId "org.joinmastodon.android.moshinda"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 85
|
||||
versionName "1.1.4+fork.85.moshinda"
|
||||
versionCode 92
|
||||
versionName "1.1.4+fork.92.moshinda"
|
||||
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"
|
||||
}
|
||||
@@ -70,6 +70,7 @@ dependencies {
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
implementation 'de.psdev:async-otto:1.0.3'
|
||||
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'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
|
||||
@@ -14,12 +14,14 @@ import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
@@ -113,64 +115,70 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
|
||||
private void actuallyCheckForUpdates(){
|
||||
Request req=new Request.Builder()
|
||||
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases/latest")
|
||||
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases")
|
||||
.build();
|
||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||
try(Response resp=call.execute()){
|
||||
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
|
||||
String changelog=obj.get("body").getAsString();
|
||||
String tag=obj.get("tag_name").getAsString();
|
||||
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
||||
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();
|
||||
JsonArray arr=JsonParser.parseReader(resp.body().charStream()).getAsJsonArray();
|
||||
for (JsonElement jsonElement : arr) {
|
||||
JsonObject obj = jsonElement.getAsJsonObject();
|
||||
if (obj.get("prerelease").getAsBoolean() && !GlobalUserPreferences.enablePreReleases) continue;
|
||||
|
||||
UpdateInfo info=new UpdateInfo();
|
||||
info.size=size;
|
||||
info.version=version;
|
||||
info.changelog=changelog;
|
||||
this.info=info;
|
||||
String tag=obj.get("tag_name").getAsString();
|
||||
String changelog=obj.get("body").getAsString();
|
||||
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
||||
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()
|
||||
.putLong("apkSize", size)
|
||||
.putString("version", version)
|
||||
.putString("apkURL", url)
|
||||
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
||||
.putString("changelog", changelog)
|
||||
.remove("downloadID")
|
||||
.apply();
|
||||
UpdateInfo info=new UpdateInfo();
|
||||
info.size=size;
|
||||
info.version=version;
|
||||
info.changelog=changelog;
|
||||
this.info=info;
|
||||
|
||||
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){
|
||||
Log.w(TAG, "actuallyCheckForUpdates", x);
|
||||
}finally{
|
||||
|
||||
@@ -12,10 +12,17 @@
|
||||
|
||||
<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
|
||||
android:name=".MastodonApp"
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:label="@string/mo_app_name"
|
||||
android:supportsRtl="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
||||
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
|
||||
|
@@ -12,7 +12,6 @@ import android.widget.Toast;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -36,13 +35,10 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
openComposeFragment(sessions.get(0).getID());
|
||||
}else{
|
||||
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
||||
new M3AlertDialogBuilder(this)
|
||||
.setItems(sessions.stream().map(as->"@"+as.self.username+"@"+as.domain).toArray(String[]::new), (dialog, which)->{
|
||||
openComposeFragment(sessions.get(which).getID());
|
||||
})
|
||||
.setTitle(R.string.choose_account)
|
||||
.setOnCancelListener(dialog -> finish())
|
||||
.show();
|
||||
UiUtils.pickAccount(this, null, R.string.choose_account, 0,
|
||||
session -> openComposeFragment(session.getID()),
|
||||
b -> b.setOnCancelListener(d -> finish())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +49,10 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
Intent intent=getIntent();
|
||||
StringBuilder builder=new StringBuilder();
|
||||
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");
|
||||
String text=builder.toString();
|
||||
List<Uri> mediaUris;
|
||||
|
||||
@@ -9,10 +9,14 @@ import android.os.Build;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class GlobalUserPreferences{
|
||||
public static boolean playGifs;
|
||||
@@ -21,7 +25,7 @@ public class GlobalUserPreferences{
|
||||
public static boolean showReplies;
|
||||
public static boolean showBoosts;
|
||||
public static boolean loadNewPosts;
|
||||
public static boolean showFederatedTimeline;
|
||||
public static boolean showNewPostsButton;
|
||||
public static boolean showInteractionCounts;
|
||||
public static boolean alwaysExpandContentWarnings;
|
||||
public static boolean disableMarquee;
|
||||
@@ -32,21 +36,36 @@ public class GlobalUserPreferences{
|
||||
public static boolean enableDeleteNotifications;
|
||||
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 ThemePreference theme;
|
||||
public static ColorPreference color;
|
||||
|
||||
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<TimelineDefinition>> pinnedTimelines;
|
||||
public static Set<String> accountsWithLocalOnlySupport;
|
||||
public static Set<String> accountsInGlitchMode;
|
||||
|
||||
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||
public static Map<String, Integer> recentEmojis;
|
||||
|
||||
private static SharedPreferences getPrefs(){
|
||||
private static SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private static <T> T fromJson(String json, Type type, T orElse) {
|
||||
if (json == null) return orElse;
|
||||
try { return gson.fromJson(json, type); }
|
||||
catch (JsonSyntaxException ignored) { return orElse; }
|
||||
}
|
||||
@@ -59,8 +78,8 @@ public class GlobalUserPreferences{
|
||||
showReplies=prefs.getBoolean("showReplies", true);
|
||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
||||
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
|
||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", true);
|
||||
showFederatedTimeline=prefs.getBoolean("showFederatedTimeline", !BuildConfig.BUILD_TYPE.equals("playRelease"));
|
||||
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
||||
@@ -70,10 +89,24 @@ public class GlobalUserPreferences{
|
||||
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
||||
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", true);
|
||||
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", "");
|
||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||
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 {
|
||||
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
|
||||
@@ -94,7 +127,7 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("showReplies", showReplies)
|
||||
.putBoolean("showBoosts", showBoosts)
|
||||
.putBoolean("loadNewPosts", loadNewPosts)
|
||||
.putBoolean("showFederatedTimeline", showFederatedTimeline)
|
||||
.putBoolean("showNewPostsButton", showNewPostsButton)
|
||||
.putBoolean("trueBlackTheme", trueBlackTheme)
|
||||
.putBoolean("showInteractionCounts", showInteractionCounts)
|
||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||
@@ -105,11 +138,24 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||
.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)
|
||||
.putBoolean("bottomEncoding", bottomEncoding)
|
||||
.putInt("theme", theme.ordinal())
|
||||
.putString("color", color.name())
|
||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||
.putString("pinnedTimelines", gson.toJson(pinnedTimelines))
|
||||
.putString("recentEmojis", gson.toJson(recentEmojis))
|
||||
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
|
||||
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
|
||||
.apply();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomLoginFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
@@ -33,18 +34,19 @@ public class MainActivity extends FragmentStackActivity{
|
||||
|
||||
if(savedInstanceState==null){
|
||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||
showFragmentClearingBackStack(new CustomLoginFragment());
|
||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
||||
}else{
|
||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
||||
AccountSession session;
|
||||
Bundle args=new Bundle();
|
||||
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");
|
||||
try{
|
||||
session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
if(!intent.hasExtra("notification"))
|
||||
args.putString("tab", "notifications");
|
||||
if(!hasNotification) args.putString("tab", "notifications");
|
||||
}catch(IllegalStateException x){
|
||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
}
|
||||
@@ -54,13 +56,13 @@ public class MainActivity extends FragmentStackActivity{
|
||||
args.putString("account", session.getID());
|
||||
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
||||
fragment.setArguments(args);
|
||||
showFragmentClearingBackStack(fragment);
|
||||
if(intent.getBooleanExtra("fromNotification", false) && intent.hasExtra("notification")){
|
||||
if(fromNotification && hasNotification){
|
||||
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
||||
showFragmentForNotification(notification, session.getID());
|
||||
}else if(intent.getBooleanExtra("compose", false)){
|
||||
} else if (intent.getBooleanExtra("compose", false)){
|
||||
showCompose();
|
||||
}else{
|
||||
} else {
|
||||
showFragmentClearingBackStack(fragment);
|
||||
maybeRequestNotificationsPermission();
|
||||
}
|
||||
}
|
||||
@@ -139,4 +141,31 @@ public class MainActivity extends FragmentStackActivity{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
private static final String TAG="PushNotificationReceive";
|
||||
|
||||
public static final int NOTIFICATION_ID=178;
|
||||
private static final int SUMMARY_ID = 791;
|
||||
private static int notificationId = 0;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
@@ -97,6 +99,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||
Notification.Builder builder;
|
||||
Notification.Builder summaryNotification;
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
||||
boolean hasGroup=false;
|
||||
List<NotificationChannelGroup> channelGroups=nm.getNotificationChannelGroups();
|
||||
@@ -119,39 +122,50 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
nm.createNotificationChannels(channels);
|
||||
}
|
||||
builder=new Notification.Builder(context, accountID+"_"+pn.notificationType);
|
||||
// summaryNotification=new Notification.Builder(context, accountID);
|
||||
}else{
|
||||
builder=new Notification.Builder(context)
|
||||
.setPriority(Notification.PRIORITY_DEFAULT)
|
||||
.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)));
|
||||
Intent contentIntent=new Intent(context, MainActivity.class);
|
||||
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
contentIntent.putExtra("fromNotification", true);
|
||||
contentIntent.putExtra("accountID", accountID);
|
||||
contentIntent.putExtra("notificationID", notificationId);
|
||||
if(notification!=null){
|
||||
contentIntent.putExtra("notification", Parcels.wrap(notification));
|
||||
}
|
||||
|
||||
builder.setContentTitle(pn.title)
|
||||
.setContentText(pn.body)
|
||||
.setContentTitle(pn.title)
|
||||
.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())
|
||||
.setShowWhen(true)
|
||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||
.setAutoCancel(true)
|
||||
.setColor(context.getColor(R.color.shortcut_icon_background));
|
||||
if(!GlobalUserPreferences.uniformNotificationIcon){
|
||||
switch (pn.notificationType) {
|
||||
case FAVORITE -> builder.setSmallIcon(R.drawable.ic_fluent_star_24_filled);
|
||||
case REBLOG -> builder.setSmallIcon(R.drawable.ic_fluent_arrow_repeat_all_24_filled);
|
||||
case FOLLOW -> builder.setSmallIcon(R.drawable.ic_fluent_person_add_24_filled);
|
||||
case MENTION -> builder.setSmallIcon(R.drawable.ic_fluent_mention_24_filled);
|
||||
case POLL -> builder.setSmallIcon(R.drawable.ic_fluent_poll_24_filled);
|
||||
default -> builder.setSmallIcon(R.drawable.ic_ntf_logo);
|
||||
}
|
||||
}else{
|
||||
builder.setSmallIcon(R.drawable.ic_ntf_logo);
|
||||
.setColor(context.getColor(R.color.shortcut_icon_background))
|
||||
.setGroup(accountID);
|
||||
|
||||
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
||||
builder.setSmallIcon(switch (pn.notificationType) {
|
||||
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
|
||||
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
|
||||
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){
|
||||
@@ -160,6 +174,9 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().size()>1){
|
||||
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 org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
@@ -47,9 +50,26 @@ public class MastodonAPIController{
|
||||
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
||||
|
||||
private AccountSession session;
|
||||
private static List<String> badDomains = new ArrayList<>();
|
||||
|
||||
static{
|
||||
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){
|
||||
@@ -57,8 +77,11 @@ public class MastodonAPIController{
|
||||
}
|
||||
|
||||
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(()->{
|
||||
try{
|
||||
if (isBad) throw new IllegalArgumentException();
|
||||
if(req.canceled)
|
||||
return;
|
||||
Request.Builder builder=new Request.Builder()
|
||||
|
||||
@@ -162,6 +162,8 @@ public class PushSubscriptionManager{
|
||||
@Override
|
||||
public void onSuccess(PushSubscription result){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
result.serverKey=result.serverKey.replace('/','_');
|
||||
result.serverKey=result.serverKey.replace('+','-');
|
||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
||||
|
||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||
@@ -370,7 +372,7 @@ public class PushSubscriptionManager{
|
||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
if(session.pushSubscription==null || forceReRegister)
|
||||
session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription);
|
||||
else if(session.needUpdatePushSettings)
|
||||
else
|
||||
session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.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){
|
||||
super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class);
|
||||
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,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,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,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);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
||||
Request r=new Request();
|
||||
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
||||
r.data.alerts=alerts;
|
||||
r.data.policy=policy;
|
||||
r.policy=policy;
|
||||
r.subscription.keys.p256dh=encryptionKey;
|
||||
r.subscription.keys.auth=authKey;
|
||||
setRequestBody(r);
|
||||
@@ -18,6 +18,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
||||
private static class Request{
|
||||
public Subscription subscription=new Subscription();
|
||||
public Data data=new Data();
|
||||
public PushSubscription.Policy policy;
|
||||
|
||||
private static class Keys{
|
||||
public String p256dh;
|
||||
@@ -31,7 +32,6 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
||||
|
||||
private static class Data{
|
||||
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.model.PushSubscription;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Response;
|
||||
|
||||
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
||||
private final PushSubscription.Policy policy;
|
||||
|
||||
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
||||
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{
|
||||
public Data data=new Data();
|
||||
public PushSubscription.Policy policy;
|
||||
|
||||
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||
this.data.alerts=alerts;
|
||||
this.data.policy=policy;
|
||||
this.policy=policy;
|
||||
}
|
||||
|
||||
private static class Data{
|
||||
public PushSubscription.Alerts alerts;
|
||||
public PushSubscription.Policy policy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||
public Poll poll;
|
||||
public String inReplyToId;
|
||||
public boolean sensitive;
|
||||
public boolean localOnly;
|
||||
public String spoilerText;
|
||||
public StatusPrivacy visibility;
|
||||
public Instant scheduledAt;
|
||||
|
||||
@@ -9,6 +9,10 @@ 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)
|
||||
|
||||
@@ -8,9 +8,11 @@ import org.joinmastodon.android.model.Status;
|
||||
import java.util.List;
|
||||
|
||||
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
public GetTrendingStatuses(int limit){
|
||||
public GetTrendingStatuses(int offset, int limit){
|
||||
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(offset>0)
|
||||
addQueryParameter("offset", ""+offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@ package org.joinmastodon.android.fragments;
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
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.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.StatusUnpinnedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
@@ -56,8 +63,8 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if(getActivity()==null) return;
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.ACCOUNT)).collect(Collectors.toList());
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
@@ -67,6 +74,7 @@ public class AccountTimelineFragment extends StatusListFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab = ((ProfileFragment) getParentFragment()).getFab();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
@@ -40,6 +42,7 @@ import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
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.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -56,6 +59,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
@@ -71,12 +76,19 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
protected DisplayItemsAdapter adapter;
|
||||
protected String accountID;
|
||||
protected PhotoViewer currentPhotoViewer;
|
||||
protected ImageButton fab;
|
||||
protected int scrollDiff = 0;
|
||||
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected Rect tmpRect=new Rect();
|
||||
|
||||
public BaseStatusListFragment(){
|
||||
super(20);
|
||||
if (withComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
}
|
||||
|
||||
protected boolean withComposeButton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,6 +102,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return adapter=new DisplayItemsAdapter();
|
||||
@@ -273,11 +287,46 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||
if(currentPhotoViewer!=null)
|
||||
currentPhotoViewer.offsetView(-dx, -dy);
|
||||
|
||||
if (fab!=null && GlobalUserPreferences.enableFabAutoHide) {
|
||||
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 > 800) {
|
||||
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());
|
||||
@@ -313,6 +362,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
|
||||
updateToolbar();
|
||||
|
||||
if (withComposeButton()) {
|
||||
fab = view.findViewById(R.id.fab);
|
||||
fab.setVisibility(View.VISIBLE);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
fab.setOnLongClickListener(this::onFabLongClick);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -478,7 +534,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
Status status=holder.getItem().status;
|
||||
status.spoilerRevealed=!status.spoilerRevealed;
|
||||
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){
|
||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
|
||||
}
|
||||
@@ -487,6 +543,23 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
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){
|
||||
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
||||
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
|
||||
@@ -504,6 +577,15 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
|
||||
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(){
|
||||
return accountID;
|
||||
}
|
||||
@@ -619,6 +701,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
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{
|
||||
|
||||
public DisplayItemsAdapter(){
|
||||
|
||||
@@ -25,6 +25,7 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Status> result){
|
||||
if (getActivity() == null) return;
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import static android.os.ext.SdkExtensions.getExtensionVersion;
|
||||
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages;
|
||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.DRAFTS_AFTER_INSTANT;
|
||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDraftInstant;
|
||||
import static org.joinmastodon.android.ui.utils.UiUtils.isPhotoPickerAvailable;
|
||||
import static org.joinmastodon.android.utils.MastodonLanguage.allLanguages;
|
||||
import static org.joinmastodon.android.utils.MastodonLanguage.defaultRecentLanguages;
|
||||
|
||||
@@ -29,6 +32,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
@@ -40,6 +44,7 @@ import android.text.TextWatcher;
|
||||
import android.text.format.DateFormat;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -64,6 +69,7 @@ import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.github.bottomSoftwareFoundation.bottom.Bottom;
|
||||
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
@@ -113,6 +119,7 @@ import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
||||
import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
|
||||
import org.joinmastodon.android.utils.MastodonLanguage;
|
||||
import org.joinmastodon.android.utils.StatusTextEncoder;
|
||||
import org.parceler.Parcel;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
@@ -149,19 +156,21 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private static final int IMAGE_DESCRIPTION_RESULT=363;
|
||||
private static final int SCHEDULED_STATUS_OPENED_RESULT=161;
|
||||
private static final int MAX_ATTACHMENTS=4;
|
||||
private static final String GLITCH_LOCAL_ONLY_SUFFIX = "👁";
|
||||
private static final Pattern GLITCH_LOCAL_ONLY_PATTERN = Pattern.compile("[\\s\\S]*" + GLITCH_LOCAL_ONLY_SUFFIX + "[\uFE00-\uFE0F]*");
|
||||
private static final String TAG="ComposeFragment";
|
||||
|
||||
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
// from https://github.com/mastodon/mastodon-ios/blob/main/Mastodon/Helper/MastodonRegex.swift
|
||||
private static final Pattern AUTO_COMPLETE_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+)|:([a-zA-Z0-9_]+))");
|
||||
private static final Pattern HIGHLIGHT_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))");
|
||||
public static final Pattern AUTO_COMPLETE_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+)|:([a-zA-Z0-9_]+))");
|
||||
public static final Pattern HIGHLIGHT_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))");
|
||||
|
||||
@SuppressLint("NewApi") // this class actually exists on 6.0
|
||||
private final BreakIterator breakIterator=BreakIterator.getCharacterInstance();
|
||||
|
||||
private SizeListenerLinearLayout contentView;
|
||||
private TextView selfName, selfUsername;
|
||||
private TextView selfName, selfUsername, selfExtraText, extraText;
|
||||
private ImageView selfAvatar;
|
||||
private Account self;
|
||||
private String instanceDomain;
|
||||
@@ -210,6 +219,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private View sendingOverlay;
|
||||
private WindowManager wm;
|
||||
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
|
||||
private boolean localOnly;
|
||||
private ComposeAutocompleteSpan currentAutocompleteSpan;
|
||||
private FrameLayout mainEditTextWrap;
|
||||
private ComposeAutocompleteViewController autocompleteViewController;
|
||||
@@ -224,7 +234,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private boolean ignoreSelectionChanges=false;
|
||||
private Runnable updateUploadEtaRunnable;
|
||||
|
||||
private String language;
|
||||
private String language, encoding;
|
||||
private MastodonLanguage.LanguageResolver languageResolver;
|
||||
|
||||
private int navigationBarColorBefore;
|
||||
@@ -244,9 +254,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain);
|
||||
languageResolver=new MastodonLanguage.LanguageResolver(instance);
|
||||
redraftStatus=getArguments().getBoolean("redraftStatus", false);
|
||||
if(getArguments().containsKey("editStatus")){
|
||||
if(getArguments().containsKey("editStatus"))
|
||||
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
|
||||
}
|
||||
if(getArguments().containsKey("replyTo"))
|
||||
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
|
||||
if(instance==null){
|
||||
Nav.finish(this);
|
||||
return;
|
||||
@@ -307,17 +318,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
draftsBtn=view.findViewById(R.id.drafts_btn);
|
||||
draftsBtn.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
charCounter=view.findViewById(R.id.char_counter);
|
||||
charCounter.setVisibility(View.VISIBLE);
|
||||
charCounter.setText(String.valueOf(charLimit));
|
||||
}
|
||||
|
||||
mainEditText=view.findViewById(R.id.toot_text);
|
||||
mainEditTextWrap=view.findViewById(R.id.toot_text_wrap);
|
||||
charCounter=view.findViewById(R.id.char_counter);
|
||||
charCounter.setText(String.valueOf(charLimit));
|
||||
scrollView=view.findViewById(R.id.scroll_view);
|
||||
|
||||
selfName=view.findViewById(R.id.self_name);
|
||||
selfUsername=view.findViewById(R.id.self_username);
|
||||
selfAvatar=view.findViewById(R.id.self_avatar);
|
||||
selfExtraText=view.findViewById(R.id.self_extra_text);
|
||||
HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis);
|
||||
selfUsername.setText('@'+self.username+'@'+instanceDomain);
|
||||
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
|
||||
@@ -343,10 +357,26 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
sensitiveItem=view.findViewById(R.id.sensitive_item);
|
||||
replyText=view.findViewById(R.id.reply_text);
|
||||
|
||||
mediaBtn.setOnClickListener(v->openFilePicker());
|
||||
if (isPhotoPickerAvailable()) {
|
||||
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
|
||||
attachPopup.inflate(R.menu.attach);
|
||||
attachPopup.setOnMenuItemClickListener(i -> {
|
||||
openFilePicker(i.getItemId() == R.id.media);
|
||||
return true;
|
||||
});
|
||||
UiUtils.enablePopupMenuIcons(getContext(), attachPopup);
|
||||
mediaBtn.setOnClickListener(v->attachPopup.show());
|
||||
mediaBtn.setOnTouchListener(attachPopup.getDragToOpenListener());
|
||||
} else {
|
||||
mediaBtn.setOnClickListener(v -> openFilePicker(false));
|
||||
}
|
||||
pollBtn.setOnClickListener(v->togglePoll());
|
||||
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
||||
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
||||
|
||||
localOnly = savedInstanceState != null ? savedInstanceState.getBoolean("localOnly") :
|
||||
editingStatus != null ? editingStatus.localOnly : replyTo != null && replyTo.localOnly;
|
||||
|
||||
buildVisibilityPopup(visibilityBtn);
|
||||
visibilityBtn.setOnClickListener(v->visibilityPopup.show());
|
||||
visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener());
|
||||
@@ -421,6 +451,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable());
|
||||
spoilerEdit.setBackground(spoilerBg);
|
||||
if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){
|
||||
hasSpoiler=true;
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
spoilerBtn.setSelected(true);
|
||||
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
|
||||
@@ -430,6 +461,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
spoilerBtn.setSelected(true);
|
||||
}
|
||||
|
||||
sensitive = savedInstanceState==null && editingStatus != null ? editingStatus.sensitive
|
||||
: savedInstanceState!=null && savedInstanceState.getBoolean("sensitive", false);
|
||||
if (sensitive) {
|
||||
sensitiveItem.setVisibility(View.VISIBLE);
|
||||
sensitiveIcon.setSelected(true);
|
||||
}
|
||||
|
||||
if(savedInstanceState!=null && savedInstanceState.containsKey("attachments")){
|
||||
ArrayList<Parcelable> serializedAttachments=savedInstanceState.getParcelableArrayList("attachments");
|
||||
for(Parcelable a:serializedAttachments){
|
||||
@@ -457,7 +495,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
case UNLISTED -> R.id.vis_unlisted;
|
||||
case PRIVATE -> R.id.vis_followers;
|
||||
case DIRECT -> R.id.vis_private;
|
||||
case LOCAL -> R.id.vis_local;
|
||||
}).setChecked(true);
|
||||
visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly);
|
||||
|
||||
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
|
||||
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
|
||||
@@ -484,6 +524,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
outState.putBoolean("pollAllowMultiple", pollAllowMultipleItem.isSelected());
|
||||
}
|
||||
outState.putBoolean("sensitive", sensitive);
|
||||
outState.putBoolean("localOnly", localOnly);
|
||||
outState.putBoolean("hasSpoiler", hasSpoiler);
|
||||
outState.putString("language", language);
|
||||
if(!attachments.isEmpty()){
|
||||
@@ -515,6 +556,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
mainEditText.setSelectionListener(this);
|
||||
mainEditText.addTextChangedListener(new TextWatcher(){
|
||||
private int lastChangeStart, lastChangeCount;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||
|
||||
@@ -524,6 +567,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
if(s.length()==0)
|
||||
return;
|
||||
lastChangeStart=start;
|
||||
lastChangeCount=count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
if(s.length()==0)
|
||||
return;
|
||||
int start=lastChangeStart;
|
||||
int count=lastChangeCount;
|
||||
// offset one char back to catch an already typed '@' or '#' or ':'
|
||||
int realStart=start;
|
||||
start=Math.max(0, start-1);
|
||||
@@ -569,10 +622,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
editable.removeSpan(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
updateCharCounter();
|
||||
}
|
||||
});
|
||||
@@ -588,16 +638,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
scrollView.post(() -> {
|
||||
int bottom = scrollView.getChildAt(0).getBottom();
|
||||
int delta = bottom - (scrollView.getScrollY() + scrollView.getHeight());
|
||||
int space = GlobalUserPreferences.reduceMotion ? 0 : Math.min(V.dp(120), delta);
|
||||
int space = GlobalUserPreferences.reduceMotion ? 0 : Math.min(V.dp(70), delta);
|
||||
scrollView.scrollBy(0, delta - space);
|
||||
if (!GlobalUserPreferences.reduceMotion) {
|
||||
scrollView.postDelayed(() -> scrollView.smoothScrollBy(0, space), 100);
|
||||
scrollView.postDelayed(() -> scrollView.smoothScrollBy(0, space), 130);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
View originalPost = view.findViewById(R.id.original_post);
|
||||
extraText = view.findViewById(R.id.extra_text);
|
||||
originalPost.setVisibility(View.VISIBLE);
|
||||
originalPost.setOnClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
@@ -621,6 +672,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(replyTo.account));
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
});
|
||||
|
||||
@@ -629,9 +681,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
view.findViewById(R.id.visibility).setVisibility(View.GONE);
|
||||
Drawable visibilityIcon = getActivity().getDrawable(switch(replyTo.visibility){
|
||||
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled;
|
||||
case DIRECT -> R.drawable.ic_fluent_mention_20_regular;
|
||||
case LOCAL -> R.drawable.ic_fluent_eye_20_regular;
|
||||
});
|
||||
ImageView moreBtn = view.findViewById(R.id.more);
|
||||
moreBtn.setImageDrawable(visibilityIcon);
|
||||
@@ -655,8 +708,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
case UNLISTED -> R.string.sk_visibility_unlisted;
|
||||
case PRIVATE -> R.string.visibility_followers_only;
|
||||
case DIRECT -> R.string.visibility_private;
|
||||
case LOCAL -> R.string.sk_local_only;
|
||||
};
|
||||
replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
|
||||
replyText.setOnClickListener(v->{
|
||||
scrollView.smoothScrollTo(0, 0);
|
||||
});
|
||||
replyText.setOnClickListener(v->{
|
||||
scrollView.smoothScrollTo(0, 0);
|
||||
});
|
||||
|
||||
|
||||
ArrayList<String> mentions=new ArrayList<>();
|
||||
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
||||
@@ -678,7 +739,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(!TextUtils.isEmpty(replyTo.spoilerText)){
|
||||
hasSpoiler=true;
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
spoilerEdit.setText(replyTo.spoilerText);
|
||||
if(GlobalUserPreferences.prefixRepliesWithRe && !replyTo.spoilerText.startsWith("re: ")){
|
||||
spoilerEdit.setText("re: " + replyTo.spoilerText);
|
||||
}else{
|
||||
spoilerEdit.setText(replyTo.spoilerText);
|
||||
}
|
||||
spoilerBtn.setSelected(true);
|
||||
}
|
||||
if (replyTo.language != null && !replyTo.language.isEmpty()) updateLanguage(replyTo.language);
|
||||
@@ -701,7 +766,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
DraftMediaAttachment da=new DraftMediaAttachment();
|
||||
da.serverAttachment=att;
|
||||
da.description=att.description;
|
||||
da.uri=Uri.parse(att.previewUrl);
|
||||
da.uri=att.previewUrl!=null ? Uri.parse(att.previewUrl) : null;
|
||||
da.state=AttachmentUploadState.DONE;
|
||||
attachmentsView.addView(createMediaAttachmentView(da));
|
||||
attachments.add(da);
|
||||
@@ -732,6 +797,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
updateSensitive();
|
||||
updateHeaders();
|
||||
|
||||
if(editingStatus!=null){
|
||||
updateCharCounter();
|
||||
@@ -754,6 +820,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
draftsBtn = wrap.findViewById(R.id.drafts_btn);
|
||||
draftsBtn.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
charCounter = wrap.findViewById(R.id.char_counter);
|
||||
charCounter.setVisibility(View.VISIBLE);
|
||||
charCounter.setText(String.valueOf(charLimit));
|
||||
}
|
||||
|
||||
// draftsBtn = wrap.findViewById(R.id.drafts_btn);
|
||||
@@ -782,6 +852,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
|
||||
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
|
||||
buildLanguageSelector(languageButton);
|
||||
|
||||
if (editingStatus != null && scheduledStatus == null) {
|
||||
// editing an already published post
|
||||
draftsBtn.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void navigateToUnsentPosts() {
|
||||
@@ -805,9 +880,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
private void updateLanguage(MastodonLanguage loc) {
|
||||
language = loc.getLanguage();
|
||||
languageButton.setText(loc.getLanguageName());
|
||||
languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, loc.getDefaultName()));
|
||||
updateLanguage(loc.getLanguage(), loc.getLanguageName(), loc.getDefaultName());
|
||||
}
|
||||
|
||||
private void updateLanguage(String languageTag, String languageName, String defaultName) {
|
||||
language = languageTag;
|
||||
languageButton.setText(languageName);
|
||||
languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, defaultName));
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@@ -817,14 +896,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
btn.setOnClickListener(v->languagePopup.show());
|
||||
|
||||
Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences;
|
||||
updateLanguage(prefs != null && prefs.postingDefaultLanguage != null && prefs.postingDefaultLanguage.length() > 0
|
||||
if (language != null) updateLanguage(language);
|
||||
else updateLanguage(prefs != null && prefs.postingDefaultLanguage != null && prefs.postingDefaultLanguage.length() > 0
|
||||
? languageResolver.from(prefs.postingDefaultLanguage)
|
||||
: languageResolver.getDefault());
|
||||
|
||||
Menu languageMenu = languagePopup.getMenu();
|
||||
for (String recentLanguage : Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages)) {
|
||||
MastodonLanguage l = languageResolver.from(recentLanguage);
|
||||
languageMenu.add(0, allLanguages.indexOf(l), Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
|
||||
if (recentLanguage.equals("bottom")) {
|
||||
addBottomLanguage(languageMenu);
|
||||
} else {
|
||||
MastodonLanguage l = languageResolver.from(recentLanguage);
|
||||
languageMenu.add(0, allLanguages.indexOf(l), Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
|
||||
}
|
||||
}
|
||||
|
||||
SubMenu allLanguagesMenu = languageMenu.addSubMenu(R.string.sk_available_languages);
|
||||
@@ -833,13 +917,33 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
allLanguagesMenu.add(0, i, Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
|
||||
}
|
||||
|
||||
if (GlobalUserPreferences.bottomEncoding) addBottomLanguage(allLanguagesMenu);
|
||||
|
||||
btn.setOnLongClickListener(v->{
|
||||
btn.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
if (!GlobalUserPreferences.bottomEncoding) addBottomLanguage(allLanguagesMenu);
|
||||
return false;
|
||||
});
|
||||
|
||||
languagePopup.setOnMenuItemClickListener(i->{
|
||||
if (i.hasSubMenu()) return false;
|
||||
updateLanguage(allLanguages.get(i.getItemId()));
|
||||
if (i.getItemId() == allLanguages.size()) {
|
||||
updateLanguage(language, "\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48", "bottom");
|
||||
encoding = "bottom";
|
||||
} else {
|
||||
updateLanguage(allLanguages.get(i.getItemId()));
|
||||
encoding = null;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void addBottomLanguage(Menu menu) {
|
||||
if (menu.findItem(allLanguages.size()) == null) {
|
||||
menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
return true;
|
||||
@@ -869,6 +973,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(hasSpoiler){
|
||||
charCount+=spoilerEdit.length();
|
||||
}
|
||||
if (localOnly && GlobalUserPreferences.accountsInGlitchMode.contains(accountID)) {
|
||||
charCount -= GLITCH_LOCAL_ONLY_SUFFIX.length();
|
||||
}
|
||||
charCounter.setText(String.valueOf(charLimit-charCount));
|
||||
trimmedCharCount=text.toString().trim().length();
|
||||
updatePublishButtonState();
|
||||
@@ -901,7 +1008,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
nonDoneAttachmentCount++;
|
||||
}
|
||||
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
||||
sendError.setVisibility(View.GONE);
|
||||
// sendError.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void onCustomEmojiClick(Emoji emoji){
|
||||
@@ -921,8 +1028,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
&& statusVisibility != StatusPrivacy.DIRECT
|
||||
&& !attachments.stream().allMatch(attachment -> attachment.description != null && !attachment.description.isBlank())) {
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_no_image_desc_title)
|
||||
.setMessage(R.string.sk_no_image_desc)
|
||||
.setTitle(R.string.mo_no_image_desc_title)
|
||||
.setMessage(R.string.mo_no_image_desc)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.publish, (dialog, i)-> publish())
|
||||
.show();
|
||||
@@ -969,15 +1076,53 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
private void publish(){
|
||||
publish(false);
|
||||
}
|
||||
|
||||
private void publish(boolean force){
|
||||
String text=mainEditText.getText().toString();
|
||||
CreateStatus.Request req=new CreateStatus.Request();
|
||||
if ("bottom".equals(encoding)) {
|
||||
text = new StatusTextEncoder(Bottom::encode).encode(text);
|
||||
req.spoilerText = "bottom-encoded emoji spam";
|
||||
}
|
||||
if (localOnly &&
|
||||
GlobalUserPreferences.accountsInGlitchMode.contains(accountID) &&
|
||||
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()) {
|
||||
text += " " + GLITCH_LOCAL_ONLY_SUFFIX;
|
||||
}
|
||||
req.status=text;
|
||||
req.visibility=statusVisibility;
|
||||
req.localOnly=localOnly;
|
||||
req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility;
|
||||
req.sensitive=sensitive;
|
||||
req.language=language;
|
||||
req.scheduledAt = scheduledAt;
|
||||
if(!attachments.isEmpty()){
|
||||
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
||||
Optional<DraftMediaAttachment> withoutAltText = attachments.stream().filter(a -> a.description == null || a.description.isBlank()).findFirst();
|
||||
boolean isDraft = scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
|
||||
if (!force && !GlobalUserPreferences.disableAltTextReminder && !isDraft && withoutAltText.isPresent()) {
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_alt_text_missing_title)
|
||||
.setMessage(R.string.sk_alt_text_missing)
|
||||
.setPositiveButton(R.string.add_alt_text, (d, w) -> editMediaDescription(withoutAltText.get()))
|
||||
.setNegativeButton(R.string.sk_publish_anyway, (d, w) -> publish(true))
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// ask whether to publish now when editing an existing draft
|
||||
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_save_draft)
|
||||
.setMessage(R.string.sk_save_draft_message)
|
||||
.setPositiveButton(R.string.save, (d, w) -> publish(true))
|
||||
.setNegativeButton(R.string.publish, (d, w) -> {
|
||||
updateScheduledAt(null);
|
||||
publish();
|
||||
})
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
|
||||
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
|
||||
@@ -1083,6 +1228,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
List<String> newRecentLanguages = new ArrayList<>(Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages));
|
||||
newRecentLanguages.remove(language);
|
||||
newRecentLanguages.add(0, language);
|
||||
if (encoding != null) {
|
||||
newRecentLanguages.remove(encoding);
|
||||
newRecentLanguages.add(0, encoding);
|
||||
}
|
||||
if ("bottom".equals(encoding) && !GlobalUserPreferences.bottomEncoding) {
|
||||
GlobalUserPreferences.bottomEncoding = true;
|
||||
GlobalUserPreferences.save();
|
||||
}
|
||||
recentLanguages.put(accountID, newRecentLanguages.stream().limit(4).collect(Collectors.toList()));
|
||||
GlobalUserPreferences.save();
|
||||
}
|
||||
@@ -1148,7 +1301,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
private void confirmDiscardDraftAndFinish(){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
boolean attachmentsPending = attachments.stream().anyMatch(att -> att.state != AttachmentUploadState.DONE);
|
||||
if (attachmentsPending) new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_unfinished_attachments)
|
||||
.setMessage(R.string.sk_unfinished_attachments_message)
|
||||
.setPositiveButton(R.string.edit, (d, w) -> {})
|
||||
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
|
||||
.show();
|
||||
else new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
|
||||
.setPositiveButton(R.string.save, (d, w) -> {
|
||||
updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt);
|
||||
@@ -1158,14 +1318,38 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
.show();
|
||||
}
|
||||
|
||||
private void openFilePicker(){
|
||||
Intent intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
if(instance.configuration!=null && instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments.supportedMimeTypes!=null && !instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()){
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, instance.configuration.mediaAttachments.supportedMimeTypes.toArray(new String[0]));
|
||||
|
||||
/**
|
||||
* Builds the correct intent for the device version to select media.
|
||||
*
|
||||
* <p>For Device version > T or R_SDK_v2, use the android platform photopicker via
|
||||
* {@link MediaStore#ACTION_PICK_IMAGES}
|
||||
*
|
||||
* <p>For earlier versions use the built in docs ui via {@link Intent#ACTION_GET_CONTENT}
|
||||
*/
|
||||
private void openFilePicker(boolean photoPicker){
|
||||
Intent intent;
|
||||
boolean usePhotoPicker=photoPicker && isPhotoPickerAvailable();
|
||||
if(usePhotoPicker){
|
||||
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MAX_ATTACHMENTS-getMediaAttachmentsCount());
|
||||
}else{
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
||||
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
}
|
||||
if(!usePhotoPicker && instance.configuration!=null &&
|
||||
instance.configuration.mediaAttachments!=null &&
|
||||
instance.configuration.mediaAttachments.supportedMimeTypes!=null &&
|
||||
!instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()){
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES,
|
||||
instance.configuration.mediaAttachments.supportedMimeTypes.toArray(
|
||||
new String[0]));
|
||||
}else{
|
||||
if(!usePhotoPicker){
|
||||
// If photo picker is being used these are the default mimetypes.
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
|
||||
}
|
||||
}
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||
startActivityForResult(intent, MEDIA_RESULT);
|
||||
@@ -1248,7 +1432,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
View thumb=getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
|
||||
ImageView img=thumb.findViewById(R.id.thumb);
|
||||
if(draft.serverAttachment!=null){
|
||||
ViewImageLoader.load(img, draft.serverAttachment.blurhashPlaceholder, new UrlImageLoaderRequest(draft.serverAttachment.previewUrl, V.dp(250), V.dp(250)));
|
||||
if(draft.serverAttachment.previewUrl!=null)
|
||||
ViewImageLoader.load(img, draft.serverAttachment.blurhashPlaceholder, new UrlImageLoaderRequest(draft.serverAttachment.previewUrl, V.dp(250), V.dp(250)));
|
||||
}else{
|
||||
if(draft.mimeType.startsWith("image/")){
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(draft.uri, V.dp(250), V.dp(250)));
|
||||
@@ -1582,18 +1767,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
menu.getMenu().add(0, 2, 0, getResources().getQuantityString(R.plurals.x_minutes, 30, 30));
|
||||
menu.getMenu().add(0, 3, 0, getResources().getQuantityString(R.plurals.x_hours, 1, 1));
|
||||
menu.getMenu().add(0, 4, 0, getResources().getQuantityString(R.plurals.x_hours, 6, 6));
|
||||
menu.getMenu().add(0, 5, 0, getResources().getQuantityString(R.plurals.x_days, 1, 1));
|
||||
menu.getMenu().add(0, 6, 0, getResources().getQuantityString(R.plurals.x_days, 3, 3));
|
||||
menu.getMenu().add(0, 7, 0, getResources().getQuantityString(R.plurals.x_days, 7, 7));
|
||||
menu.getMenu().add(0, 5, 0, getResources().getQuantityString(R.plurals.x_hours, 12, 12));
|
||||
menu.getMenu().add(0, 6, 0, getResources().getQuantityString(R.plurals.x_days, 1, 1));
|
||||
menu.getMenu().add(0, 7, 0, getResources().getQuantityString(R.plurals.x_days, 3, 3));
|
||||
menu.getMenu().add(0, 8, 0, getResources().getQuantityString(R.plurals.x_days, 7, 7));
|
||||
menu.setOnMenuItemClickListener(item->{
|
||||
pollDuration=switch(item.getItemId()){
|
||||
case 1 -> 5*60;
|
||||
case 2 -> 30*60;
|
||||
case 3 -> 3600;
|
||||
case 4 -> 6*3600;
|
||||
case 5 -> 24*3600;
|
||||
case 6 -> 3*24*3600;
|
||||
case 7 -> 7*24*3600;
|
||||
case 5 -> 12*3600;
|
||||
case 6 -> 24*3600;
|
||||
case 7 -> 3*24*3600;
|
||||
case 8 -> 7*24*3600;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+item.getItemId());
|
||||
};
|
||||
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr=item.getTitle().toString()));
|
||||
@@ -1702,12 +1889,33 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
return attachments.size();
|
||||
}
|
||||
|
||||
private void updateHeaders() {
|
||||
UiUtils.setExtraTextInfo(getContext(), selfExtraText, statusVisibility, localOnly);
|
||||
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, replyTo.visibility, replyTo.localOnly);
|
||||
}
|
||||
|
||||
private void buildVisibilityPopup(View v){
|
||||
visibilityPopup=new PopupMenu(getActivity(), v);
|
||||
visibilityPopup.inflate(R.menu.compose_visibility);
|
||||
Menu m=visibilityPopup.getMenu();
|
||||
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
|
||||
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||
if (instance.pleroma != null) {
|
||||
m.findItem(R.id.vis_local).setVisible(true);
|
||||
} else if (localOnly || prefsSaysSupported) {
|
||||
localOnlyItem.setVisible(true);
|
||||
localOnlyItem.setChecked(localOnly);
|
||||
Status status = editingStatus != null ? editingStatus : replyTo;
|
||||
if (!prefsSaysSupported) {
|
||||
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
||||
if (GLITCH_LOCAL_ONLY_PATTERN.matcher(status.getStrippedText()).matches()) {
|
||||
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||
}
|
||||
GlobalUserPreferences.save();
|
||||
}
|
||||
}
|
||||
UiUtils.enablePopupMenuIcons(getActivity(), visibilityPopup);
|
||||
m.setGroupCheckable(0, true, true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) m.setGroupDividerEnabled(true);
|
||||
visibilityPopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item){
|
||||
@@ -1720,41 +1928,44 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
statusVisibility=StatusPrivacy.PRIVATE;
|
||||
}else if(id==R.id.vis_private){
|
||||
statusVisibility=StatusPrivacy.DIRECT;
|
||||
}else if(id==R.id.vis_local){
|
||||
statusVisibility=StatusPrivacy.LOCAL;
|
||||
}
|
||||
if (id == R.id.local_only) {
|
||||
localOnly = !item.isChecked();
|
||||
item.setChecked(localOnly);
|
||||
} else {
|
||||
item.setChecked(true);
|
||||
}
|
||||
item.setChecked(true);
|
||||
updateVisibilityIcon();
|
||||
updateHeaders();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
||||
if(getArguments().containsKey("replyTo")){
|
||||
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
|
||||
statusVisibility = replyTo.visibility;
|
||||
}
|
||||
if(replyTo != null) statusVisibility = replyTo.visibility;
|
||||
|
||||
// A saved privacy setting from a previous compose session wins over the reply visibility
|
||||
if(savedInstanceState !=null){
|
||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||
}
|
||||
|
||||
Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences;
|
||||
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||
Preferences prefs = asm.getAccount(accountID).preferences;
|
||||
if (prefs != null) {
|
||||
// Only override the reply visibility if our preference is more private
|
||||
if (prefs.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
|
||||
statusVisibility = switch (prefs.postingDefaultVisibility) {
|
||||
case PUBLIC -> StatusPrivacy.PUBLIC;
|
||||
case UNLISTED -> StatusPrivacy.UNLISTED;
|
||||
case PRIVATE -> StatusPrivacy.PRIVATE;
|
||||
case DIRECT -> StatusPrivacy.DIRECT;
|
||||
};
|
||||
// (and we're not replying to ourselves, or not at all)
|
||||
if (prefs.postingDefaultVisibility.isLessVisibleThan(statusVisibility) &&
|
||||
(replyTo == null || !asm.isSelf(accountID, replyTo.account))) {
|
||||
statusVisibility = prefs.postingDefaultVisibility;
|
||||
}
|
||||
}
|
||||
|
||||
// A saved privacy setting from a previous compose session wins over all
|
||||
if(savedInstanceState !=null){
|
||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||
}
|
||||
// A saved privacy setting from a previous compose session wins over all
|
||||
if(savedInstanceState !=null){
|
||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1764,9 +1975,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
visibilityBtn.setImageResource(switch(statusVisibility){
|
||||
case PUBLIC -> R.drawable.ic_fluent_earth_24_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_people_community_24_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_24_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_lock_open_24_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_lock_closed_24_filled;
|
||||
case DIRECT -> R.drawable.ic_fluent_mention_24_regular;
|
||||
case LOCAL -> R.drawable.ic_fluent_eye_24_regular;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1888,6 +2100,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
});
|
||||
}
|
||||
private void editMediaDescription(DraftMediaAttachment att) {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("attachment", att.serverAttachment.id);
|
||||
args.putParcelable("uri", att.uri);
|
||||
args.putString("existingDescription", att.description);
|
||||
Nav.goForResult(getActivity(), ComposeImageDescriptionFragment.class, args, IMAGE_DESCRIPTION_RESULT, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle(){
|
||||
|
||||
@@ -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){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Status> result){
|
||||
if (getActivity() == null) return;
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
|
||||
@@ -80,6 +80,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Account> result){
|
||||
if (getActivity() == null) return;
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -11,6 +12,7 @@ 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;
|
||||
@@ -41,12 +43,19 @@ public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -10,15 +11,21 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.tags.GetHashtag;
|
||||
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
||||
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.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.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -26,14 +33,14 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HashtagTimelineFragment extends StatusListFragment{
|
||||
public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||
private String hashtag;
|
||||
private boolean following;
|
||||
private ImageButton fab;
|
||||
private MenuItem followButton;
|
||||
|
||||
public HashtagTimelineFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
@Override
|
||||
protected boolean withComposeButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -41,7 +48,6 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
||||
super.onAttach(activity);
|
||||
updateTitle(getArguments().getString("hashtag"));
|
||||
following=getArguments().getBoolean("following", false);
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@@ -54,35 +60,20 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
||||
this.following = newFollowing;
|
||||
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
|
||||
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||
E.post(new HashtagUpdatedEvent(hashtag, following));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.hashtag_timeline, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
followButton = menu.findItem(R.id.follow_hashtag);
|
||||
updateFollowingState(following);
|
||||
|
||||
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<>() {
|
||||
@Override
|
||||
public void onSuccess(Hashtag hashtag) {
|
||||
if (getActivity() == null) return;
|
||||
updateTitle(hashtag.name);
|
||||
updateFollowingState(hashtag.following);
|
||||
}
|
||||
@@ -94,12 +85,45 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
||||
}).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
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
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());
|
||||
}
|
||||
})
|
||||
@@ -114,14 +138,12 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '));
|
||||
protected boolean onFabLongClick(View v) {
|
||||
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
|
||||
}
|
||||
|
||||
private void onFabClick(View v){
|
||||
@Override
|
||||
protected void onFabClick(View v){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("prefilledText", '#'+hashtag+' ');
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.app.NotificationManager;
|
||||
import android.graphics.Outline;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -15,7 +16,6 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.joinmastodon.android.PushNotificationReceiver;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
@@ -41,7 +41,11 @@ import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
||||
private FragmentRootLinearLayout content;
|
||||
private HomeTimelineFragment homeTimelineFragment;
|
||||
|
||||
private HomeTabFragment homeTabFragment;
|
||||
|
||||
// private HomeTimelineFragment homeTimelineFragment;
|
||||
|
||||
private NotificationsFragment notificationsFragment;
|
||||
private DiscoverFragment searchFragment;
|
||||
private ProfileFragment profileFragment;
|
||||
@@ -57,7 +61,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
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)
|
||||
setRetainInstance(true);
|
||||
@@ -65,8 +69,13 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
if(savedInstanceState==null){
|
||||
Bundle args=new Bundle();
|
||||
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.putBoolean("noAutoLoad", true);
|
||||
searchFragment=new DiscoverFragment();
|
||||
@@ -110,12 +119,19 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
|
||||
if(savedInstanceState==null){
|
||||
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, notificationsFragment).hide(notificationsFragment)
|
||||
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||
.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");
|
||||
if("notifications".equals(defaultTab)){
|
||||
tabBar.selectTab(R.id.tab_notifications);
|
||||
@@ -136,21 +152,36 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
@Override
|
||||
public void onViewStateRestored(Bundle savedInstanceState){
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
if(savedInstanceState==null || homeTimelineFragment!=null)
|
||||
return;
|
||||
homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment");
|
||||
|
||||
if(savedInstanceState==null) return;
|
||||
|
||||
// if(savedInstanceState==null || homeTimelineFragment!=null)
|
||||
// return;
|
||||
|
||||
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
|
||||
|
||||
// homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment");
|
||||
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
||||
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
||||
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
||||
currentTab=savedInstanceState.getInt("selectedTab");
|
||||
Fragment current=fragmentForTab(currentTab);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.hide(homeTimelineFragment)
|
||||
.hide(homeTabFragment)
|
||||
.hide(searchFragment)
|
||||
.hide(notificationsFragment)
|
||||
.hide(profileFragment)
|
||||
.show(current)
|
||||
.commit();
|
||||
|
||||
// getChildFragmentManager().beginTransaction()
|
||||
// .hide(homeTimelineFragment)
|
||||
// .hide(searchFragment)
|
||||
// .hide(notificationsFragment)
|
||||
// .hide(profileFragment)
|
||||
// .show(current)
|
||||
// .commit();
|
||||
maybeTriggerLoading(current);
|
||||
}
|
||||
|
||||
@@ -180,7 +211,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
||||
homeTimelineFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
|
||||
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
|
||||
// homeTimelineFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
|
||||
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
@@ -188,7 +223,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
|
||||
private Fragment fragmentForTab(@IdRes int tab){
|
||||
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){
|
||||
return searchFragment;
|
||||
}else if(tab==R.id.tab_notifications){
|
||||
@@ -233,7 +271,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
((NotificationsFragment) newFragment).loadData();
|
||||
// TODO make an interface?
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,17 +307,24 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
tabBar.selectTab(R.id.tab_home);
|
||||
onTabSelected(R.id.tab_home);
|
||||
return true;
|
||||
} else {
|
||||
return homeTabFragment.onBackPressed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState){
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt("selectedTab", currentTab);
|
||||
getChildFragmentManager().putFragment(outState, "homeTimelineFragment", homeTimelineFragment);
|
||||
getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||
getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||
getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||
|
||||
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
|
||||
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||
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,45 +1,24 @@
|
||||
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.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
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.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.E;
|
||||
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.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
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 java.util.Collections;
|
||||
@@ -48,34 +27,30 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HomeTimelineFragment extends StatusListFragment{
|
||||
private ImageButton fab;
|
||||
private ImageView toolbarLogo;
|
||||
private Button toolbarShowNewPostsBtn;
|
||||
private boolean newPostsBtnShown;
|
||||
private AnimatorSet currentNewPostsAnim;
|
||||
|
||||
public class HomeTimelineFragment extends StatusListFragment {
|
||||
private HomeTabFragment parent;
|
||||
private String maxID;
|
||||
private String lastSavedMarkerID;
|
||||
|
||||
public HomeTimelineFragment(){
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
@Override
|
||||
protected boolean withComposeButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setHasOptionsMenu(true);
|
||||
if (getParentFragment() instanceof HomeTabFragment home) parent = home;
|
||||
loadData();
|
||||
}
|
||||
|
||||
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 ->
|
||||
(GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
|
||||
(GlobalUserPreferences.showBoosts || i.reblog == null)
|
||||
@@ -89,8 +64,7 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if (getActivity() == null) return;
|
||||
List<Status> filteredItems = filterPosts(result.items);
|
||||
onDataLoaded(filteredItems, !result.items.isEmpty());
|
||||
maxID=result.maxID;
|
||||
@@ -103,43 +77,15 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
fab.setOnLongClickListener(v->UiUtils.pickAccountForCompose(getActivity(), accountID));
|
||||
|
||||
updateToolbarLogo();
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||
if(newPostsBtnShown && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
||||
hideNewPostsButton();
|
||||
if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
||||
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
|
||||
@@ -154,14 +100,31 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
}
|
||||
|
||||
public void onStatusCreated(StatusCreatedEvent ev){
|
||||
prependItems(Collections.singletonList(ev.status), true);
|
||||
@Override
|
||||
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){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||
public void onStatusCreated(StatusCreatedEvent ev){
|
||||
prependItems(Collections.singletonList(ev.status), true);
|
||||
}
|
||||
|
||||
private void loadNewPosts(){
|
||||
@@ -188,13 +151,11 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
toAdd=result;
|
||||
}
|
||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||
if(!filters.isEmpty()){
|
||||
toAdd=toAdd.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList());
|
||||
}
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
||||
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
||||
if(!toAdd.isEmpty()){
|
||||
prependItems(toAdd, true);
|
||||
showNewPostsButton();
|
||||
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
||||
}
|
||||
}
|
||||
@@ -310,132 +271,10 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
currentRequest=null;
|
||||
dataLoading=false;
|
||||
}
|
||||
if (parent != null) parent.hideNewPostsButton();
|
||||
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)));
|
||||
// toolbarLogo =new TextView(getActivity());
|
||||
// toolbarLogo.setText(getString(R.string.app_name).toLowerCase(Locale.getDefault()));
|
||||
// toolbarLogo.setTextAppearance(R.style.app_title);
|
||||
|
||||
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());
|
||||
logoWrap.addView(toolbarLogo, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||
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
|
||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -4,45 +4,130 @@ import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
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.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.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.stream.Collectors;
|
||||
|
||||
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.utils.V;
|
||||
|
||||
|
||||
public class ListTimelineFragment extends StatusListFragment {
|
||||
public class ListTimelineFragment extends PinnableStatusListFragment {
|
||||
private String listID;
|
||||
private String listTitle;
|
||||
private ImageButton fab;
|
||||
@Nullable
|
||||
private ListTimeline.RepliesPolicy repliesPolicy;
|
||||
|
||||
public ListTimelineFragment() {
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
@Override
|
||||
protected boolean withComposeButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
listID=getArguments().getString("listID");
|
||||
listTitle=getArguments().getString("listTitle");
|
||||
Bundle args = getArguments();
|
||||
listID = args.getString("listID");
|
||||
listTitle = args.getString("listTitle");
|
||||
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
|
||||
|
||||
setTitle(listTitle);
|
||||
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
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.list, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
// TODO: implement edit, delete
|
||||
// inflater.inflate(R.menu.list, menu);
|
||||
UiUtils.enableOptionsMenuIcons(getContext(), menu, R.id.pin);
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -51,10 +136,12 @@ public class ListTimelineFragment extends StatusListFragment {
|
||||
.setCallback(new SimpleCallback<>(this) {
|
||||
@Override
|
||||
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());
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,14 +152,7 @@ public class ListTimelineFragment extends StatusListFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID));
|
||||
}
|
||||
|
||||
private void onFabClick(View v){
|
||||
protected void onFabClick(View v){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
@@ -9,173 +12,248 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
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.RemoveAccountsFromList;
|
||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||
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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
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.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||
private String accountId;
|
||||
private String profileAccountId;
|
||||
private String profileDisplayUsername;
|
||||
private HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||
private HashMap<String, Boolean> userInList = new HashMap<>();
|
||||
private int inProgress = 0;
|
||||
private String accountId;
|
||||
private String profileAccountId;
|
||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||
private final HashMap<String, Boolean> userInList = new HashMap<>();
|
||||
private ListsAdapter adapter;
|
||||
|
||||
public ListTimelinesFragment() {
|
||||
super(10);
|
||||
}
|
||||
public ListTimelinesFragment() {
|
||||
super(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle args=getArguments();
|
||||
accountId=args.getString("account");
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle args=getArguments();
|
||||
accountId=args.getString("account");
|
||||
setHasOptionsMenu(true);
|
||||
E.register(this);
|
||||
|
||||
if(args.containsKey("profileAccount")){
|
||||
profileAccountId=args.getString("profileAccount");
|
||||
profileDisplayUsername=args.getString("profileDisplayUsername");
|
||||
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
||||
// setHasOptionsMenu(true);
|
||||
}
|
||||
}
|
||||
if(args.containsKey("profileAccount")){
|
||||
profileAccountId=args.getString("profileAccount");
|
||||
String profileDisplayUsername = args.getString("profileDisplayUsername");
|
||||
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
||||
} else {
|
||||
setTitle(R.string.sk_your_lists);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
// Button saveButton=new Button(getActivity());
|
||||
// saveButton.setText(R.string.save);
|
||||
// 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);
|
||||
// }
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||
}
|
||||
|
||||
private void saveListMembership(String listId, boolean isMember) {
|
||||
userInList.put(listId, isMember);
|
||||
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
||||
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
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_list, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
userInListBefore.clear();
|
||||
userInList.clear();
|
||||
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
||||
.setCallback(new SimpleCallback<>(this) {
|
||||
@Override
|
||||
public void onSuccess(List<ListTimeline> lists) {
|
||||
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
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.create) {
|
||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_create_list_title)
|
||||
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
||||
.setView(editor)
|
||||
.setPositiveButton(R.string.sk_create, (d, which) ->
|
||||
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
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
|
||||
public void onSuccess(List<ListTimeline> allLists) {
|
||||
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
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountId)
|
||||
)
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter() {
|
||||
return new ListsAdapter();
|
||||
}
|
||||
private void saveListMembership(String listId, boolean isMember) {
|
||||
userInList.put(listId, isMember);
|
||||
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
|
||||
public void scrollToTop() {
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountId);
|
||||
}
|
||||
|
||||
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new ListViewHolder();
|
||||
}
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
userInListBefore.clear();
|
||||
userInList.clear();
|
||||
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
|
||||
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
||||
holder.bind(data.get(position));
|
||||
}
|
||||
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
|
||||
@Override
|
||||
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
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
@Subscribe
|
||||
public void onListDeletedEvent(ListDeletedEvent event) {
|
||||
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{
|
||||
private final TextView title;
|
||||
private final CheckBox listToggle;
|
||||
@Subscribe
|
||||
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
||||
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(){
|
||||
super(getActivity(), R.layout.item_text, list);
|
||||
title=findViewById(R.id.title);
|
||||
listToggle=findViewById(R.id.list_toggle);
|
||||
}
|
||||
@Override
|
||||
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
|
||||
return adapter = new ListsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ListTimeline item) {
|
||||
title.setText(item.title);
|
||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_community_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);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void scrollToTop() {
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
private void onClickToggle(View view) {
|
||||
saveListMembership(item.id, listToggle.isChecked());
|
||||
}
|
||||
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new ListViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
UiUtils.openListTimeline(getActivity(), accountId, item);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,4 +245,4 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
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.events.PollUpdatedEvent;
|
||||
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.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
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.ImageStatusDisplayItem;
|
||||
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.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -41,6 +46,12 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
private boolean onlyMentions;
|
||||
private boolean onlyPosts;
|
||||
private String maxID;
|
||||
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
|
||||
|
||||
@Override
|
||||
protected boolean withComposeButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -71,6 +82,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
|
||||
@Override
|
||||
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){
|
||||
case FOLLOW -> getString(R.string.user_followed_you);
|
||||
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 FAVORITE -> getString(R.string.user_favorited);
|
||||
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) : 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){
|
||||
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){
|
||||
for(StatusDisplayItem item:items){
|
||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
||||
@@ -93,8 +109,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
items.add(0, titleItem);
|
||||
return items;
|
||||
}else if(titleItem!=null){
|
||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, n.account, n);
|
||||
return Arrays.asList(titleItem, card);
|
||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
|
||||
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{
|
||||
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){
|
||||
@Override
|
||||
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if (getActivity() == null) return;
|
||||
if(refreshing)
|
||||
relationships.clear();
|
||||
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))
|
||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||
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{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -175,6 +198,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||
if (getParentFragment() instanceof NotificationsFragment) fab.setVisibility(View.GONE);
|
||||
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
@@ -12,11 +13,23 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
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.api.requests.accounts.SetPrivateNote;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||
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.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
@@ -46,6 +56,11 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
private static final int MAX_FIELDS=4;
|
||||
|
||||
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 AboutAdapter adapter;
|
||||
private Paint dividerPaint=new Paint();
|
||||
@@ -64,11 +79,49 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
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
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
list=new UsableRecyclerView(getActivity());
|
||||
list.setId(R.id.list);
|
||||
View view = inflater.inflate(R.layout.fragment_profile_about, null);
|
||||
|
||||
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.setDrawSelectorOnTop(true);
|
||||
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){
|
||||
isInEditMode=true;
|
||||
|
||||
@@ -8,9 +8,11 @@ import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -27,13 +29,17 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -48,6 +54,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
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.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
||||
@@ -57,6 +64,7 @@ import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
||||
import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable;
|
||||
@@ -65,8 +73,10 @@ import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||
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.views.CoverImageView;
|
||||
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.parceler.Parcels;
|
||||
@@ -79,6 +89,13 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -86,10 +103,17 @@ import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.fragments.LoaderFragment;
|
||||
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.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
|
||||
private static final int AVATAR_RESULT=722;
|
||||
@@ -97,24 +121,28 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
private ImageView avatar;
|
||||
private CoverImageView cover;
|
||||
private View avatarBorder;
|
||||
private View avatarBorder, nameWrap;
|
||||
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel, postsCount, postsLabel;
|
||||
private ProgressBarButton actionButton, notifyButton;
|
||||
private ViewPager2 pager;
|
||||
private NestedRecyclerScrollView scrollView;
|
||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
|
||||
private ProfileAboutFragment aboutFragment;
|
||||
// private ProfileAboutFragment aboutFragment;
|
||||
private TabLayout tabbar;
|
||||
private SwipeRefreshLayout refreshLayout;
|
||||
private CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable();
|
||||
private float titleTransY;
|
||||
private View postsBtn, followersBtn, followingBtn;
|
||||
private View postsBtn, followersBtn, followingBtn, profileCounters;
|
||||
private EditText nameEdit, bioEdit;
|
||||
private ProgressBar actionProgress, notifyProgress;
|
||||
private FrameLayout[] tabViews;
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
private TextView followsYouView;
|
||||
private ViewGroup rolesView;
|
||||
|
||||
public FrameLayout noteWrap;
|
||||
public EditText noteEdit;
|
||||
private String note;
|
||||
private Account account;
|
||||
private String accountID;
|
||||
private Relationship relationship;
|
||||
@@ -126,10 +154,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private Uri editNewAvatar, editNewCover;
|
||||
private String profileAccountID;
|
||||
private boolean refreshing;
|
||||
private View fab;
|
||||
private ImageButton fab;
|
||||
private WindowInsets childInsets;
|
||||
private PhotoViewer currentPhotoViewer;
|
||||
private boolean editModeLoading;
|
||||
private boolean isScrollingUp = false;
|
||||
|
||||
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(){
|
||||
super(R.layout.loader_fragment_overlay_toolbar);
|
||||
@@ -175,8 +214,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
cover=content.findViewById(R.id.cover);
|
||||
avatarBorder=content.findViewById(R.id.avatar_border);
|
||||
name=content.findViewById(R.id.name);
|
||||
nameWrap=content.findViewById(R.id.name_wrap);
|
||||
username=content.findViewById(R.id.username);
|
||||
bio=content.findViewById(R.id.bio);
|
||||
profileCounters=content.findViewById(R.id.profile_counters);
|
||||
followersCount=content.findViewById(R.id.followers_count);
|
||||
followersLabel=content.findViewById(R.id.followers_label);
|
||||
followersBtn=content.findViewById(R.id.followers_btn);
|
||||
@@ -199,6 +240,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
notifyProgress=content.findViewById(R.id.notify_progress);
|
||||
fab=content.findViewById(R.id.fab);
|
||||
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(){
|
||||
@Override
|
||||
@@ -208,6 +255,49 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
});
|
||||
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()){
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
@@ -218,7 +308,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
};
|
||||
|
||||
tabViews=new FrameLayout[5];
|
||||
tabViews=new FrameLayout[4];
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
@@ -234,7 +324,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
tabViews[i]=tabView;
|
||||
}
|
||||
|
||||
pager.setOffscreenPageLimit(5);
|
||||
UiUtils.reduceSwipeSensitivity(pager);
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||
pager.setAdapter(new ProfilePagerAdapter());
|
||||
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
||||
@@ -286,10 +377,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
|
||||
if (account != null && account.bot) {
|
||||
username.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_fluent_bot_24_filled, 0, 0, 0);
|
||||
}
|
||||
|
||||
username.setOnLongClickListener(v->{
|
||||
String usernameString=account.acct;
|
||||
if(!usernameString.contains("@")){
|
||||
@@ -299,15 +386,43 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
protected void doLoadData(){
|
||||
currentRequest=new GetAccountByID(profileAccountID)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
if (getActivity() == null) return;
|
||||
account=result;
|
||||
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||
bindHeaderView();
|
||||
@@ -353,8 +468,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
||||
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
||||
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
||||
aboutFragment=new ProfileAboutFragment();
|
||||
aboutFragment.setFields(fields);
|
||||
// aboutFragment=new ProfileAboutFragment();
|
||||
setFields(fields);
|
||||
}
|
||||
pager.getAdapter().notifyDataSetChanged();
|
||||
super.dataLoaded();
|
||||
@@ -447,8 +562,22 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
name.setText(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);
|
||||
|
||||
|
||||
if(account.locked){
|
||||
ssb=new SpannableStringBuilder("@");
|
||||
ssb.append(account.acct);
|
||||
@@ -462,6 +591,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
lock.setTint(username.getCurrentTextColor());
|
||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BASELINE), 0);
|
||||
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{
|
||||
// noinspection SetTextI18n
|
||||
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
||||
@@ -473,6 +615,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bio.setVisibility(View.VISIBLE);
|
||||
bio.setText(parsedBio);
|
||||
}
|
||||
|
||||
|
||||
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
||||
@@ -514,9 +658,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
fields.add(field);
|
||||
}
|
||||
|
||||
if(aboutFragment!=null){
|
||||
aboutFragment.setFields(fields);
|
||||
}
|
||||
setFields(fields);
|
||||
}
|
||||
|
||||
private void updateToolbar(){
|
||||
@@ -553,7 +695,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
return;
|
||||
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
||||
if(isOwnProfile){
|
||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags, R.id.favorites, R.id.scheduled, R.id.share);
|
||||
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);
|
||||
}
|
||||
@@ -568,17 +710,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
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()));
|
||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
||||
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
|
||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
||||
if(relationship.following) {
|
||||
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
|
||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
||||
hideBoosts.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);
|
||||
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||
manageUserLists.setVisible(true);
|
||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||
}else {
|
||||
menu.findItem(R.id.hide_boosts).setVisible(false);
|
||||
manageUserLists.setVisible(false);
|
||||
}
|
||||
if(!account.isLocal())
|
||||
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||
@@ -598,6 +739,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
confirmToggleMuted();
|
||||
}else if(id==R.id.block){
|
||||
confirmToggleBlocked();
|
||||
}else if(id==R.id.soft_block){
|
||||
confirmSoftBlockUser();
|
||||
}else if(id==R.id.report){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -636,8 +779,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}else if(id==R.id.manage_user_lists){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("profileAccount", profileAccountID);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
if (!isOwnProfile) {
|
||||
args.putString("profileAccount", profileAccountID);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
}
|
||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||
}else if(id==R.id.followed_hashtags){
|
||||
Bundle args=new Bundle();
|
||||
@@ -676,6 +821,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
|
||||
private void updateRelationship(){
|
||||
if (getActivity() == null) return;
|
||||
invalidateOptionsMenu();
|
||||
actionButton.setVisibility(View.VISIBLE);
|
||||
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
|
||||
@@ -685,7 +831,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
||||
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
||||
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){
|
||||
@@ -708,8 +862,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
coverGradient.setTopOffset(scrollY);
|
||||
cover.invalidate();
|
||||
titleTransY=getToolbar().getHeight();
|
||||
if(scrollY>name.getTop()-topBarsH){
|
||||
titleTransY=Math.max(0f, titleTransY-(scrollY-(name.getTop()-topBarsH)));
|
||||
if(scrollY>nameWrap.getTop()-topBarsH){
|
||||
titleTransY=Math.max(0f, titleTransY-(scrollY-(nameWrap.getTop()-topBarsH)));
|
||||
}
|
||||
if(toolbarTitleView!=null){
|
||||
toolbarTitleView.setTranslationY(titleTransY);
|
||||
@@ -726,7 +880,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
case 1 -> postsWithRepliesFragment;
|
||||
case 2 -> pinnedPostsFragment;
|
||||
case 3 -> mediaFragment;
|
||||
case 4 -> aboutFragment;
|
||||
// case 4 -> aboutFragment;
|
||||
default -> throw new IllegalStateException();
|
||||
};
|
||||
}
|
||||
@@ -768,8 +922,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
editModeLoading=false;
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if (getActivity() == null) return;
|
||||
enterEditMode(result);
|
||||
setActionProgressVisible(false);
|
||||
}
|
||||
@@ -777,8 +930,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
editModeLoading=false;
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if (getActivity() == null) return;
|
||||
error.showToast(getActivity());
|
||||
setActionProgressVisible(false);
|
||||
}
|
||||
@@ -793,16 +945,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
invalidateOptionsMenu();
|
||||
pager.setUserInputEnabled(false);
|
||||
actionButton.setText(R.string.done);
|
||||
pager.setCurrentItem(4);
|
||||
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();
|
||||
avatar.setForeground(overlay);
|
||||
animators.add(ObjectAnimator.ofInt(overlay, "alpha", 0, 255));
|
||||
|
||||
nameWrap.setVisibility(View.GONE);
|
||||
nameEdit.setVisibility(View.VISIBLE);
|
||||
nameEdit.setText(account.displayName);
|
||||
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
|
||||
@@ -814,10 +962,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bioEdit.setText(account.source.note);
|
||||
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f, 1f));
|
||||
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 0f));
|
||||
|
||||
animators.add(ObjectAnimator.ofFloat(postsBtn, View.ALPHA, .3f));
|
||||
animators.add(ObjectAnimator.ofFloat(followersBtn, View.ALPHA, .3f));
|
||||
animators.add(ObjectAnimator.ofFloat(followingBtn, View.ALPHA, .3f));
|
||||
profileCounters.setVisibility(View.GONE);
|
||||
pager.setVisibility(View.GONE);
|
||||
tabbar.setVisibility(View.GONE);
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(animators);
|
||||
@@ -825,7 +972,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
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(){
|
||||
@@ -836,16 +988,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
invalidateOptionsMenu();
|
||||
ArrayList<Animator> animators=new ArrayList<>();
|
||||
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.ofFloat(nameEdit, View.ALPHA, 0f));
|
||||
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f));
|
||||
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 1f));
|
||||
animators.add(ObjectAnimator.ofFloat(postsBtn, View.ALPHA, 1f));
|
||||
animators.add(ObjectAnimator.ofFloat(followersBtn, View.ALPHA, 1f));
|
||||
animators.add(ObjectAnimator.ofFloat(followingBtn, View.ALPHA, 1f));
|
||||
profileCounters.setVisibility(View.VISIBLE);
|
||||
pager.setVisibility(View.VISIBLE);
|
||||
tabbar.setVisibility(View.VISIBLE);
|
||||
V.setVisibilityAnimated(nameWrap, View.VISIBLE);
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(animators);
|
||||
@@ -854,20 +1004,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
for(int i=0;i<tabViews.length-1;i++){
|
||||
tabbar.getTabAt(i).view.setEnabled(true);
|
||||
}
|
||||
pager.setUserInputEnabled(true);
|
||||
nameEdit.setVisibility(View.GONE);
|
||||
bioEdit.setVisibility(View.GONE);
|
||||
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();
|
||||
avatar.setForeground(null);
|
||||
scrollToTop();
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
|
||||
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
||||
imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
|
||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||
bindHeaderView();
|
||||
}
|
||||
|
||||
@@ -875,12 +1026,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(!isInEditMode)
|
||||
throw new IllegalStateException();
|
||||
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<>(){
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
account=result;
|
||||
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
||||
if (getActivity() == null) return;
|
||||
exitEditMode();
|
||||
setActionProgressVisible(false);
|
||||
}
|
||||
@@ -902,6 +1054,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
||||
}
|
||||
|
||||
private void confirmSoftBlockUser(){
|
||||
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
|
||||
}
|
||||
|
||||
private void updateRelationship(Relationship r){
|
||||
relationship=r;
|
||||
updateRelationship();
|
||||
@@ -909,6 +1065,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(noteEdit.hasFocus()) {
|
||||
savePrivateNote();
|
||||
}
|
||||
if(isInEditMode){
|
||||
exitEditMode();
|
||||
return true;
|
||||
@@ -1041,4 +1200,227 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
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 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()));
|
||||
value.addTextChangedListener(new SimpleTextWatcher(e->item.value=e.toString()));
|
||||
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){
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ 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;
|
||||
@@ -28,11 +29,11 @@ import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
||||
private String nextMaxID;
|
||||
private ImageButton fab;
|
||||
private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
|
||||
|
||||
public ScheduledStatusListFragment() {
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
@Override
|
||||
protected boolean withComposeButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,20 +57,30 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
protected void onFabClick(View v) {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
||||
fab.setOnClickListener(v -> Nav.go(getActivity(), ComposeFragment.class, args));
|
||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, args));
|
||||
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);
|
||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, Filter.FilterContext.HOME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,6 +120,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
if (getActivity() == null) return;
|
||||
onDataLoaded(result, nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -9,6 +9,8 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.LruCache;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -43,11 +45,13 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
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.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
@@ -64,7 +68,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -78,13 +81,15 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
private ArrayList<Item> items=new ArrayList<>();
|
||||
private ThemeItem themeItem;
|
||||
private NotificationPolicyItem notificationPolicyItem;
|
||||
private SwitchItem showNewPostsButtonItem, glitchModeItem;
|
||||
private String accountID;
|
||||
private boolean needUpdateNotificationSettings;
|
||||
private boolean needAppRestart;
|
||||
private PushSubscription pushSubscription;
|
||||
|
||||
private ImageView themeTransitionWindowView;
|
||||
private TextItem checkForUpdateItem;
|
||||
private TextItem checkForUpdateItem, clearImageCacheItem;
|
||||
private ImageCache imageCache;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -92,6 +97,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
setRetainInstance(true);
|
||||
setTitle(R.string.settings);
|
||||
imageCache = ImageCache.getInstance(getActivity());
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||
@@ -112,6 +118,17 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
GlobalUserPreferences.disableMarquee=i.checked;
|
||||
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->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.color_palettes);
|
||||
@@ -128,57 +145,44 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
case BROWN -> R.string.sk_color_palette_brown;
|
||||
case RED -> R.string.sk_color_palette_red;
|
||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
||||
case NORD -> R.string.sk_color_palette_nord;
|
||||
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-> {
|
||||
updatePublishText(b);
|
||||
if (GlobalUserPreferences.relocatePublishButton) {
|
||||
b.setOnClickListener(l -> {
|
||||
Toast.makeText(getActivity(), R.string.sk_disable_relocate_publish_button_to_enable_customization,
|
||||
Toast.makeText(getActivity(), R.string.mo_disable_relocate_publish_button_to_enable_customization,
|
||||
Toast.LENGTH_LONG).show();
|
||||
});
|
||||
} else {
|
||||
b.setOnClickListener(l -> {
|
||||
FrameLayout inputWrap = new FrameLayout(getContext());
|
||||
EditText input = new EditText(getContext());
|
||||
input.setHint(R.string.publish);
|
||||
input.setText(GlobalUserPreferences.publishButtonText.trim());
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
|
||||
input.setLayoutParams(params);
|
||||
inputWrap.addView(input);
|
||||
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap)
|
||||
.setPositiveButton(R.string.save, (d, which) -> {
|
||||
GlobalUserPreferences.publishButtonText = input.getText().toString().trim();
|
||||
GlobalUserPreferences.save();
|
||||
updatePublishText(b);
|
||||
})
|
||||
.setNeutralButton(R.string.clear, (d, which) -> {
|
||||
GlobalUserPreferences.publishButtonText = "";
|
||||
GlobalUserPreferences.save();
|
||||
updatePublishText(b);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {
|
||||
})
|
||||
.show();
|
||||
});}
|
||||
}));
|
||||
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();
|
||||
b.setOnClickListener(l -> {
|
||||
FrameLayout inputWrap = new FrameLayout(getContext());
|
||||
EditText input = new EditText(getContext());
|
||||
input.setHint(R.string.publish);
|
||||
input.setText(GlobalUserPreferences.publishButtonText.trim());
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
|
||||
input.setLayoutParams(params);
|
||||
inputWrap.addView(input);
|
||||
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap)
|
||||
.setPositiveButton(R.string.save, (d, which) -> {
|
||||
GlobalUserPreferences.publishButtonText = input.getText().toString().trim();
|
||||
GlobalUserPreferences.save();
|
||||
updatePublishText(b);
|
||||
})
|
||||
.setNeutralButton(R.string.clear, (d, which) -> {
|
||||
GlobalUserPreferences.publishButtonText = "";
|
||||
GlobalUserPreferences.save();
|
||||
updatePublishText(b);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {
|
||||
})
|
||||
.show();
|
||||
});}
|
||||
}));
|
||||
|
||||
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->{
|
||||
GlobalUserPreferences.playGifs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -201,27 +205,30 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
needAppRestart=true;
|
||||
}));
|
||||
// items.add(new SwitchItem(R.string.sk_settings_show_differentiated_notification_icons, R.drawable.ic_ntf_logo, GlobalUserPreferences.showUniformPushNoticationIcons, this::onNotificationStyleChanged));
|
||||
items.add(new SwitchItem(R.string.sk_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{
|
||||
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();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
// items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_delete_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
||||
// GlobalUserPreferences.enableDeleteNotifications=i.checked;
|
||||
// GlobalUserPreferences.save();
|
||||
// needAppRestart=true;
|
||||
// }));
|
||||
items.add(new SwitchItem(R.string.sk_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
|
||||
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_hide_translate_in_timeline, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
||||
// GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
||||
// GlobalUserPreferences.save();
|
||||
// needAppRestart=true;
|
||||
// }));
|
||||
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->{
|
||||
GlobalUserPreferences.showReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -230,19 +237,58 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
GlobalUserPreferences.showBoosts=i.checked;
|
||||
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;
|
||||
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();
|
||||
}));
|
||||
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(notificationPolicyItem=new NotificationPolicyItem());
|
||||
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)));
|
||||
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_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked)));
|
||||
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)));
|
||||
boolean switchEnabled=pushSubscription.policy!=PushSubscription.Policy.NONE;
|
||||
|
||||
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_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.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 TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
|
||||
@@ -256,9 +302,39 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
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_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));
|
||||
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;
|
||||
items.add(new SmallTextItem(getString(translationAvailable ?
|
||||
R.string.sk_settings_translation_availability_note_available :
|
||||
@@ -266,25 +342,46 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
|
||||
|
||||
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()) {
|
||||
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||
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.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
|
||||
|
||||
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));
|
||||
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||
|
||||
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, ()->{
|
||||
GlobalUserPreferences.recentLanguages.remove(accountID);
|
||||
GlobalUserPreferences.save();
|
||||
})));
|
||||
items.add(new TextItem(R.string.sk_clear_recent_emoji, ()-> {
|
||||
|
||||
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.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||
}
|
||||
|
||||
@@ -338,11 +435,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
||||
}
|
||||
if(needAppRestart){
|
||||
Intent intent = Intent.makeRestartActivityTask(MastodonApp.context.getPackageManager().getLaunchIntentForPackage(MastodonApp.context.getPackageName()).getComponent());
|
||||
MastodonApp.context.startActivity(intent);
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
if(needAppRestart) UiUtils.restartApp();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -446,8 +539,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
case FAVORITE -> subscription.alerts.favourite=enabled;
|
||||
case FOLLOW -> subscription.alerts.follow=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 UPDATE -> subscription.alerts.update=enabled;
|
||||
}
|
||||
needUpdateNotificationSettings=true;
|
||||
}
|
||||
@@ -472,9 +567,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
list.getAdapter().notifyItemChanged(index);
|
||||
}
|
||||
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++;
|
||||
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);
|
||||
if(holder!=null)
|
||||
((BindableViewHolder<?>)holder).rebind();
|
||||
@@ -514,6 +613,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
|
||||
private void onLoggedOut(){
|
||||
if (getActivity() == null) return;
|
||||
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||
getActivity().finish();
|
||||
Intent intent=new Intent(getActivity(), MainActivity.class);
|
||||
@@ -523,9 +623,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
private void clearImageCache(){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
Activity activity=getActivity();
|
||||
ImageCache.getInstance(getActivity()).clear();
|
||||
imageCache.clear();
|
||||
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
|
||||
@@ -563,7 +667,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
this.text=getString(text);
|
||||
}
|
||||
|
||||
public HeaderItem(String text) {
|
||||
public HeaderItem(String text){
|
||||
this.text=text;
|
||||
}
|
||||
|
||||
@@ -587,7 +691,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
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.icon=icon;
|
||||
this.checked=checked;
|
||||
@@ -656,27 +760,34 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
|
||||
private class TextItem extends Item{
|
||||
private String text;
|
||||
private String secondaryText;
|
||||
private Runnable onClick;
|
||||
private boolean loading;
|
||||
private int icon;
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick) {
|
||||
this(text, onClick, false, 0);
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick, boolean loading) {
|
||||
this(text, onClick, loading, 0);
|
||||
this(text, null, onClick, false, 0);
|
||||
}
|
||||
|
||||
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.onClick=onClick;
|
||||
this.loading=loading;
|
||||
this.icon=icon;
|
||||
this.secondaryText = secondaryText;
|
||||
}
|
||||
|
||||
public TextItem(String text, Runnable onClick){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -691,6 +802,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
super(text);
|
||||
}
|
||||
|
||||
public RedHeaderItem(String text){
|
||||
super(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 5;
|
||||
@@ -784,7 +899,12 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
@Override
|
||||
public void onBind(SwitchItem item){
|
||||
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.setEnabled(item.enabled);
|
||||
}
|
||||
@@ -928,22 +1048,27 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
|
||||
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
||||
private final TextView text;
|
||||
private final TextView text, secondaryText;
|
||||
private final ProgressBar progress;
|
||||
private final ImageView icon;
|
||||
|
||||
public TextViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_text, list);
|
||||
text = itemView.findViewById(R.id.text);
|
||||
secondaryText = itemView.findViewById(R.id.secondary_text);
|
||||
progress = itemView.findViewById(R.id.progress);
|
||||
icon = itemView.findViewById(R.id.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
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
|
||||
@@ -954,21 +1079,19 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
|
||||
private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> {
|
||||
private final TextView text;
|
||||
;
|
||||
|
||||
public SmallTextViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_text, list);
|
||||
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
|
||||
public void onBind(SmallTextItem item){
|
||||
text.setText(item.text);
|
||||
TypedValue val = new TypedValue();
|
||||
getContext().getTheme().resolveAttribute(android.R.attr.textColorSecondary, val, true);
|
||||
text.setTextColor(getResources().getColor(val.resourceId, getContext().getTheme()));
|
||||
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1023,10 +1146,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
||||
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
||||
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)));
|
||||
}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);
|
||||
}
|
||||
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
||||
|
||||
@@ -173,16 +173,7 @@ public class SplashFragment extends AppKitFragment{
|
||||
TextView title=new TextView(getActivity());
|
||||
title.setTextAppearance(R.style.m3_headline_medium);
|
||||
title.setText(switch(page){
|
||||
case 0 -> {
|
||||
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 0 -> getString(R.string.welcome_page1_title);
|
||||
case 1 -> getString(R.string.welcome_page2_title);
|
||||
case 2 -> getString(R.string.welcome_page3_title);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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.api.requests.statuses.GetStatusEditHistory;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
@@ -46,6 +47,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
|
||||
if (getActivity() == null) return;
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
})
|
||||
@@ -54,7 +56,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
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);
|
||||
if(idx>=0){
|
||||
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);
|
||||
}
|
||||
}
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null, 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;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
@@ -29,7 +32,9 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
protected EventListener eventListener=new EventListener();
|
||||
|
||||
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
|
||||
@@ -55,6 +60,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
Status status=getContentStatusByID(id);
|
||||
if(status==null)
|
||||
return;
|
||||
status.filterRevealed = true;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(status));
|
||||
@@ -171,6 +177,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo();
|
||||
}
|
||||
|
||||
public class EventListener{
|
||||
|
||||
@Subscribe
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class ThreadFragment extends StatusListFragment{
|
||||
private Status mainStatus;
|
||||
protected Status mainStatus;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -61,8 +61,7 @@ public class ThreadFragment extends StatusListFragment{
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(StatusContext result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
if (getActivity() == null) return;
|
||||
if(refreshing){
|
||||
data.clear();
|
||||
displayItems.clear();
|
||||
@@ -126,4 +125,14 @@ public class ThreadFragment extends StatusListFragment{
|
||||
public boolean isItemEnabled(String 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){
|
||||
relationships.put(rel.id, rel);
|
||||
}
|
||||
if (getActivity() == null) return;
|
||||
if(list==null)
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
@@ -128,7 +129,8 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
// list.setPadding(0, V.dp(16), 0, V.dp(16));
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -225,6 +227,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
contextMenu=new PopupMenu(getActivity(), menuAnchor);
|
||||
contextMenu.inflate(R.menu.profile);
|
||||
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
|
||||
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -283,29 +286,32 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
Menu menu=contextMenu.getMenu();
|
||||
Account account=item.account;
|
||||
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
||||
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()));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getDisplayUsername())).setVisible(relationship.following);
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
||||
|
||||
MenuItem mute = menu.findItem(R.id.mute);
|
||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||
mute.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 manageUserLists=menu.findItem(R.id.manage_user_lists);
|
||||
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);
|
||||
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);
|
||||
}else{
|
||||
hideBoosts.setVisible(false);
|
||||
manageUserLists.setVisible(true);
|
||||
}
|
||||
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||
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);
|
||||
}
|
||||
menu.findItem(R.id.block_domain).setVisible(false);
|
||||
|
||||
menuAnchor.setTranslationX(x);
|
||||
menuAnchor.setTranslationY(y);
|
||||
@@ -346,6 +352,8 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
||||
}else if(id==R.id.block){
|
||||
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){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -364,6 +372,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
relationships.put(AccountViewHolder.this.item.account.id, result);
|
||||
if (getActivity() == null) return;
|
||||
bindRelationship();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public abstract class PaginatedAccountListFragment extends BaseAccountListFragme
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
if (getActivity() == null) return;
|
||||
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.api.requests.accounts.GetAccountRelationships;
|
||||
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.ScrollableToTop;
|
||||
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.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 Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
@@ -73,6 +74,7 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FollowSuggestion> result){
|
||||
if (getActivity() == null) return;
|
||||
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
|
||||
loadRelationships();
|
||||
}
|
||||
@@ -107,6 +109,7 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
||||
public void onSuccess(List<Relationship> result){
|
||||
relationshipsRequest=null;
|
||||
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
||||
if (getActivity() == null) return;
|
||||
if(list==null)
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
@@ -137,6 +140,11 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnTop() {
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public AccountsAdapter(){
|
||||
|
||||
@@ -62,7 +62,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private String accountID;
|
||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
|
||||
private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
|
||||
// private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -81,20 +81,30 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabLayout=view.findViewById(R.id.tabbar);
|
||||
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++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
int switchIndex = noFederated && i > 0 ? i + 1 : i;
|
||||
tabView.setId(switch(switchIndex){
|
||||
case 0 -> R.id.discover_local_timeline;
|
||||
case 1 -> R.id.discover_federated_timeline;
|
||||
case 2 -> R.id.discover_lists;
|
||||
case 3 -> R.id.discover_hashtags;
|
||||
case 4 -> R.id.discover_posts;
|
||||
case 5 -> R.id.discover_news;
|
||||
case 6 -> R.id.discover_users;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+switchIndex);
|
||||
|
||||
/// int switchIndex = noFederated && i > 0 ? i + 1 : i;
|
||||
// tabView.setId(switch(switchIndex){
|
||||
// case 0 -> R.id.discover_local_timeline;
|
||||
// case 1 -> R.id.discover_federated_timeline;
|
||||
// case 2 -> R.id.discover_hashtags;
|
||||
// case 3 -> R.id.discover_posts;
|
||||
// case 4 -> R.id.discover_news;
|
||||
// case 5 -> R.id.discover_users;
|
||||
// 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);
|
||||
view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment
|
||||
tabViews[i]=tabView;
|
||||
@@ -103,6 +113,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabLayout.setTabTextSize(V.dp(16));
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
|
||||
UiUtils.reduceSwipeSensitivity(pager);
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||
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();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("__is_tab", true);
|
||||
@@ -139,39 +150,56 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
localTimelineFragment=new LocalTimelineFragment();
|
||||
localTimelineFragment.setArguments(args);
|
||||
|
||||
listTimelinesFragment=new ListTimelinesFragment();
|
||||
listTimelinesFragment.setArguments(args);
|
||||
// listTimelinesFragment=new ListTimelinesFragment();
|
||||
// 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_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();
|
||||
.commit();
|
||||
}
|
||||
|
||||
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||
@Override
|
||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int 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;
|
||||
|
||||
// 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){
|
||||
case 0 -> R.string.hashtags;
|
||||
case 1 -> R.string.posts;
|
||||
case 2 -> R.string.news;
|
||||
case 3 -> R.string.for_you;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
tab.view.textView.setAllCaps(true);
|
||||
@@ -257,8 +285,11 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
|
||||
public void loadData(){
|
||||
if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
|
||||
localTimelineFragment.loadData();
|
||||
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
|
||||
hashtagsFragment.loadData();
|
||||
|
||||
// if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
|
||||
// localTimelineFragment.loadData();
|
||||
}
|
||||
|
||||
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
||||
@@ -293,15 +324,24 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
|
||||
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){
|
||||
case 0 -> localTimelineFragment;
|
||||
case 1 -> federatedTimelineFragment;
|
||||
case 2 -> hashtagsFragment;
|
||||
case 3 -> postsFragment;
|
||||
case 4 -> newsFragment;
|
||||
case 5 -> accountsFragment;
|
||||
case 6 -> listTimelinesFragment;
|
||||
case 0 -> hashtagsFragment;
|
||||
case 1 -> postsFragment;
|
||||
case 2 -> newsFragment;
|
||||
case 3 -> accountsFragment;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||
import org.joinmastodon.android.fragments.IsOnTop;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
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.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop{
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop {
|
||||
private String accountID;
|
||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||
@@ -58,6 +59,7 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
imageRequests=result.stream()
|
||||
.map(card->TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(150), V.dp(150)))
|
||||
.collect(Collectors.toList());
|
||||
if (getActivity() == null) return;
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
})
|
||||
@@ -81,6 +83,11 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnTop() {
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public LinksAdapter(){
|
||||
super(imgLoader);
|
||||
|
||||
@@ -4,24 +4,30 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||
import org.joinmastodon.android.fragments.IsOnTop;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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);
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetTrendingStatuses(count)
|
||||
currentRequest=new GetTrendingStatuses(offset, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
@@ -31,4 +37,9 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
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.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
@@ -15,10 +16,16 @@ import java.util.stream.Collectors;
|
||||
|
||||
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 String maxID;
|
||||
|
||||
@Override
|
||||
protected boolean withComposeButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int 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){
|
||||
if(!result.isEmpty())
|
||||
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);
|
||||
@@ -36,6 +45,6 @@ public class FederatedTimelineFragment extends StatusListFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle 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;
|
||||
|
||||
public class LocalTimelineFragment extends StatusListFragment{
|
||||
public class LocalTimelineFragment extends StatusListFragment {
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
|
||||
private String maxID;
|
||||
|
||||
@Override
|
||||
protected boolean withComposeButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int 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){
|
||||
if(!result.isEmpty())
|
||||
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);
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
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)
|
||||
setRetainInstance(true);
|
||||
loadData();
|
||||
resetEmptyText();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,12 +72,16 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
imm=activity.getSystemService(InputMethodManager.class);
|
||||
}
|
||||
|
||||
private void resetEmptyText() {
|
||||
setEmptyText(R.string.sk_recent_searches_placeholder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
|
||||
return switch(s.type){
|
||||
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
||||
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
|
||||
protected void doLoadData(int offset, int count){
|
||||
if (getActivity() == null) return;
|
||||
resetEmptyText();
|
||||
if(isInRecentMode()){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
||||
if(getActivity()==null)
|
||||
@@ -128,11 +136,13 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
onDataLoaded(sr, false);
|
||||
});
|
||||
}else{
|
||||
setEmptyText(R.string.sk_searching);
|
||||
progressVisibilityListener.onProgressVisibilityChanged(true);
|
||||
currentRequest=new GetSearchResults(currentQuery, null, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
setEmptyText(R.string.sk_no_results);
|
||||
ArrayList<SearchResult> results=new ArrayList<>();
|
||||
if(result.accounts!=null){
|
||||
for(Account acc:result.accounts)
|
||||
@@ -148,11 +158,13 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
}
|
||||
prevDisplayItems=new ArrayList<>(displayItems);
|
||||
unfilteredResults=results;
|
||||
if (getActivity() == null) return;
|
||||
onDataLoaded(filterSearchResults(results), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
resetEmptyText();
|
||||
currentRequest=null;
|
||||
Activity a=getActivity();
|
||||
if(a==null)
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
||||
import org.joinmastodon.android.fragments.IsOnTop;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
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.views.UsableRecyclerView;
|
||||
|
||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop{
|
||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop {
|
||||
private String accountID;
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
||||
|
||||
@@ -43,6 +44,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Hashtag> result){
|
||||
if (getActivity() == null) return;
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
})
|
||||
@@ -66,6 +68,11 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnTop() {
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -193,30 +193,24 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||
mgr.removeAccount(accountID);
|
||||
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
|
||||
String newID=mgr.getLastActiveAccountID();
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", newID);
|
||||
if(session.self.avatar!=null || session.self.displayName!=null){
|
||||
File avaFile=session.self.avatar!=null ? new File(session.self.avatar) : null;
|
||||
new UpdateAccountCredentials(session.self.displayName, "", avaFile, null, Collections.emptyList())
|
||||
accountID=newID;
|
||||
if((session.self.avatar!=null || session.self.displayName!=null) && !getArguments().getBoolean("debug")){
|
||||
new UpdateAccountCredentials(session.self.displayName, "", (File)null, null, Collections.emptyList())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
if(avaFile!=null)
|
||||
avaFile.delete();
|
||||
mgr.updateAccountInfo(newID, result);
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(avaFile!=null)
|
||||
avaFile.delete();
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
proceed();
|
||||
}
|
||||
})
|
||||
.exec(newID);
|
||||
}else{
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
proceed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,4 +243,11 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||
super.onDestroyView();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,10 @@ import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class CustomLoginFragment extends InstanceCatalogFragment {
|
||||
public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
||||
private View headerView;
|
||||
|
||||
public CustomLoginFragment() {
|
||||
public CustomWelcomeFragment() {
|
||||
super(R.layout.fragment_welcome_custom, 1);
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ public class CustomLoginFragment extends InstanceCatalogFragment {
|
||||
dataLoaded();
|
||||
}
|
||||
|
||||
// @Override
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
// super.onUpdateToolbar();
|
||||
super.onUpdateToolbar();
|
||||
|
||||
if (!canGoBack()) {
|
||||
ImageView toolbarLogo=new ImageView(getActivity());
|
||||
@@ -137,9 +137,11 @@ public class CustomLoginFragment extends InstanceCatalogFragment {
|
||||
|
||||
headerView.findViewById(R.id.more).setVisibility(View.GONE);
|
||||
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
||||
((TextView) headerView.findViewById(R.id.username)).setText("@moshidon");
|
||||
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
||||
((TextView) headerView.findViewById(R.id.timestamp)).setText(R.string.time_now);
|
||||
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
|
||||
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
|
||||
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
|
||||
((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));
|
||||
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
|
||||
|
||||
@@ -168,7 +170,7 @@ public class CustomLoginFragment extends InstanceCatalogFragment {
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder>{
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder> {
|
||||
public InstancesAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
@@ -204,11 +206,6 @@ public class CustomLoginFragment extends InstanceCatalogFragment {
|
||||
|
||||
public InstanceViewHolder(){
|
||||
super(getActivity(), R.layout.item_instance_custom, list);
|
||||
|
||||
// itemView.setPadding(V.dp(16), V.dp(16), V.dp(16), V.dp(16));
|
||||
// TypedValue value = new TypedValue();
|
||||
// getActivity().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, value, true);
|
||||
// itemView.setBackground(getActivity().getTheme().getDrawable(R.drawable.bg_search_field));
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
userCount=findViewById(R.id.user_count);
|
||||
@@ -5,6 +5,7 @@ import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
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.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
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.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
@@ -58,6 +61,9 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
private ArrayList<Item> items=new ArrayList<>();
|
||||
private Call currentRequest;
|
||||
private ItemsAdapter itemsAdapter;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
|
||||
private static final int SIGNUP_REQUEST=722;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -72,7 +78,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -93,18 +99,24 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
||||
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.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
||||
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.setOnClickListener(v->onButtonClick());
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -113,19 +125,32 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(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(){
|
||||
// super.onUpdateToolbar();
|
||||
getToolbar().setBackground(null);
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
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
|
||||
@@ -158,7 +183,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
if(!response.isSuccessful())
|
||||
return;
|
||||
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();
|
||||
if(activity!=null){
|
||||
activity.runOnUiThread(()->{
|
||||
@@ -192,16 +217,23 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
|
||||
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title;
|
||||
private final TextView subtitle;
|
||||
|
||||
public ItemViewHolder(){
|
||||
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
||||
title=findViewById(R.id.title);
|
||||
title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
subtitle=findViewById(R.id.subtitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Item item){
|
||||
title.setText(item.title);
|
||||
if(TextUtils.isEmpty(item.subtitle)){
|
||||
subtitle.setVisibility(View.GONE);
|
||||
}else{
|
||||
subtitle.setVisibility(View.VISIBLE);
|
||||
subtitle.setText(item.subtitle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -211,10 +243,11 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
}
|
||||
|
||||
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.subtitle=subtitle;
|
||||
this.domain=domain;
|
||||
this.url=url;
|
||||
this.faviconUrl=faviconUrl;
|
||||
|
||||
@@ -92,7 +92,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||
return true;
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||
updateFilteredList();
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
|
||||
@@ -106,7 +106,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
}
|
||||
|
||||
protected void onSearchChangedDebounced(){
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
|
||||
updateFilteredList();
|
||||
loadInstanceInfo(currentSearchQuery, false);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
@@ -20,7 +14,6 @@ import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.RadioButton;
|
||||
@@ -38,6 +31,7 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FilterChipView;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -56,11 +50,7 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
@@ -80,6 +70,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
private LinearLayout filtersWrap;
|
||||
private HorizontalScrollView filtersScroll;
|
||||
private ImageButton backBtn, clearSearchBtn;
|
||||
private View focusThing;
|
||||
|
||||
private FilterChipView categoryGeneral, categorySpecialInterests;
|
||||
private List<FilterChipView> regionalFilters;
|
||||
@@ -214,47 +205,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
setStatusBarColor(0);
|
||||
topBar=view.findViewById(R.id.top_bar);
|
||||
|
||||
LayerDrawable topBg=(LayerDrawable) topBar.getBackground().mutate();
|
||||
topBar.setBackground(topBg);
|
||||
Drawable topOverlay=topBg.findDrawableByLayerId(R.id.color_overlay);
|
||||
topOverlay.setAlpha(0);
|
||||
|
||||
LayerDrawable btmBg=(LayerDrawable) buttonBar.getBackground().mutate();
|
||||
buttonBar.setBackground(btmBg);
|
||||
Drawable btmOverlay=btmBg.findDrawableByLayerId(R.id.color_overlay);
|
||||
btmOverlay.setAlpha(0);
|
||||
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||
private boolean isAtTop=true;
|
||||
private Animator currentPanelsAnim;
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||
boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop());
|
||||
if(newAtTop!=isAtTop){
|
||||
isAtTop=newAtTop;
|
||||
if(currentPanelsAnim!=null)
|
||||
currentPanelsAnim.cancel();
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofInt(topOverlay, "alpha", isAtTop ? 0 : 20),
|
||||
ObjectAnimator.ofInt(btmOverlay, "alpha", isAtTop ? 0 : 20),
|
||||
ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)),
|
||||
ObjectAnimator.ofFloat(buttonBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3))
|
||||
);
|
||||
set.setDuration(150);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentPanelsAnim=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
currentPanelsAnim=set;
|
||||
}
|
||||
}
|
||||
});
|
||||
list.addOnScrollListener(new ElevationOnScrollListener(null, topBar, buttonBar));
|
||||
|
||||
searchEdit=view.findViewById(R.id.search_edit);
|
||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||
@@ -285,7 +236,13 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
|
||||
FilterChipView langFilter=new FilterChipView(getActivity());
|
||||
langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
|
||||
langFilter.setText(R.string.server_filter_any_language);
|
||||
if(currentLanguage==null){
|
||||
langFilter.setText(R.string.server_filter_any_language);
|
||||
}else{
|
||||
Locale locale=Locale.forLanguageTag(currentLanguage);
|
||||
langFilter.setText(locale.getDisplayLanguage(locale));
|
||||
langFilter.setSelected(true);
|
||||
}
|
||||
langFilterMenu=new PopupMenu(getContext(), langFilter);
|
||||
langFilter.setOnTouchListener(langFilterMenu.getDragToOpenListener());
|
||||
langFilter.setOnClickListener(v->langFilterMenu.show());
|
||||
@@ -301,8 +258,12 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
speedFilterMenu.getMenu().add(0, 2, 0, R.string.server_filter_manual_review);
|
||||
speedFilter.setOnTouchListener(speedFilterMenu.getDragToOpenListener());
|
||||
speedFilter.setOnClickListener(v->speedFilterMenu.show());
|
||||
speedFilter.setText(R.string.server_filter_instant_signup);
|
||||
speedFilter.setSelected(true);
|
||||
speedFilter.setText(switch(currentSignupSpeedFilter){
|
||||
case ANY -> R.string.server_filter_any_signup_speed;
|
||||
case INSTANT -> R.string.server_filter_instant_signup;
|
||||
case REVIEWED -> R.string.server_filter_manual_review;
|
||||
});
|
||||
speedFilter.setSelected(currentSignupSpeedFilter!=SignupSpeedFilter.ANY);
|
||||
filtersWrap.addView(speedFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
speedFilterMenu.setOnMenuItemClickListener(item->{
|
||||
@@ -328,11 +289,13 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
categoryGeneral.setText(R.string.category_general);
|
||||
categoryGeneral.setTag(CategoryChoice.GENERAL);
|
||||
categoryGeneral.setOnClickListener(this::onCategoryFilterClick);
|
||||
categoryGeneral.setSelected(categoryChoice==CategoryChoice.GENERAL);
|
||||
filtersWrap.addView(categoryGeneral, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
categorySpecialInterests=new FilterChipView(getActivity());
|
||||
categorySpecialInterests.setText(R.string.category_special_interests);
|
||||
categorySpecialInterests.setTag(CategoryChoice.SPECIAL);
|
||||
categorySpecialInterests.setOnClickListener(this::onCategoryFilterClick);
|
||||
categorySpecialInterests.setSelected(categoryChoice==CategoryChoice.SPECIAL);
|
||||
filtersWrap.addView(categorySpecialInterests, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
regionalFilters=Arrays.stream(CatalogInstance.Region.values()).map(r->{
|
||||
@@ -351,6 +314,11 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
filtersWrap.addView(fv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return fv;
|
||||
}).collect(Collectors.toList());
|
||||
focusThing=view.findViewById(R.id.focus_thing);
|
||||
focusThing.requestFocus();
|
||||
|
||||
view.findViewById(R.id.btn_random_instance).setOnClickListener(this::onPickRandomInstanceClick);
|
||||
nextButton.setEnabled(chosenInstance!=null);
|
||||
}
|
||||
|
||||
private void onRegionFilterClick(View v){
|
||||
@@ -381,22 +349,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNextClick(View v){
|
||||
if(chosenInstance==null){
|
||||
String lang=Locale.getDefault().getLanguage();
|
||||
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
return;
|
||||
}
|
||||
chosenInstance=instances.get(new Random().nextInt(instances.size()));
|
||||
}
|
||||
super.onNextClick(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void proceedWithAuthOrSignup(Instance instance){
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||
@@ -413,6 +365,19 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}
|
||||
|
||||
private void onPickRandomInstanceClick(View v){
|
||||
String lang=Locale.getDefault().getLanguage();
|
||||
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
return;
|
||||
}
|
||||
chosenInstance=instances.get(new Random().nextInt(instances.size()));
|
||||
onNextClick(v);
|
||||
}
|
||||
|
||||
// private String getEmojiForCategory(String category){
|
||||
// return switch(category){
|
||||
// case "all" -> "💬";
|
||||
@@ -550,7 +515,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0));
|
||||
}else{
|
||||
filtersScroll.setVisibility(View.VISIBLE);
|
||||
searchEdit.clearFocus();
|
||||
focusThing.requestFocus();
|
||||
searchEdit.setText("");
|
||||
lp.addRule(RelativeLayout.END_OF, R.id.btn_back);
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
||||
@@ -562,7 +527,16 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!searchQueryMode){
|
||||
// Prevent search view automatically getting focused when the user returns to this fragment
|
||||
focusThing.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder>{
|
||||
public InstancesAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
@@ -588,22 +562,11 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
public int getItemViewType(int position){
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return filteredData.get(position).thumbnailRequest!=null ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
return filteredData.get(position).thumbnailRequest;
|
||||
}
|
||||
}
|
||||
|
||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable, ImageLoaderViewHolder{
|
||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable{
|
||||
private final TextView title, description;
|
||||
private final RadioButton radioButton;
|
||||
private final ImageView thumbnail;
|
||||
private boolean enabled;
|
||||
|
||||
public InstanceViewHolder(){
|
||||
@@ -611,15 +574,12 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
radioButton=findViewById(R.id.radiobtn);
|
||||
thumbnail=findViewById(R.id.image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(CatalogInstance item){
|
||||
title.setText(item.normalizedDomain);
|
||||
radioButton.setChecked(chosenInstance==item);
|
||||
if(item.thumbnailRequest==null)
|
||||
thumbnail.setImageDrawable(null);
|
||||
Instance realInstance=instancesCache.get(item.normalizedDomain);
|
||||
float alpha;
|
||||
if(realInstance!=null && !realInstance.registrations){
|
||||
@@ -634,7 +594,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
title.setAlpha(alpha);
|
||||
description.setAlpha(alpha);
|
||||
radioButton.setAlpha(alpha);
|
||||
thumbnail.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -657,6 +616,9 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
adapter.notifyItemChanged(idx);
|
||||
}
|
||||
}
|
||||
if(!nextButton.isEnabled()){
|
||||
nextButton.setEnabled(true);
|
||||
}
|
||||
radioButton.setChecked(true);
|
||||
if(chosenInstance==null)
|
||||
nextButton.setEnabled(true);
|
||||
@@ -664,16 +626,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
loadInstanceInfo(chosenInstance.domain, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
thumbnail.setImageDrawable(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return enabled;
|
||||
@@ -695,4 +647,5 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
|
||||
return (this==GENERAL)==isGeneral;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -18,6 +17,7 @@ import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -29,6 +29,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceRulesFragment extends ToolbarFragment{
|
||||
@@ -37,6 +38,9 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
||||
private Button btn;
|
||||
private View buttonBar;
|
||||
private Instance instance;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
|
||||
private static final int RULES_REQUEST=376;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -66,33 +70,47 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
adapter.addAdapter(new ItemsAdapter());
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
// setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
// view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
setStatusBarColor(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(){
|
||||
// super.onUpdateToolbar();
|
||||
getToolbar().setBackground(null);
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), GoogleMadeMeAddThisFragment.class, args);
|
||||
Nav.goForResult(getActivity(), GoogleMadeMeAddThisFragment.class, args, RULES_REQUEST, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
||||
super.onFragmentResult(reqCode, success, result);
|
||||
if(reqCode==RULES_REQUEST && !success){
|
||||
Nav.finish(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
import org.joinmastodon.android.model.ParsedAccount;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
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.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<ParsedAccount>{
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
private View buttonBar;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
private int numRunningFollowRequests=0;
|
||||
|
||||
public OnboardingFollowSuggestionsFragment(){
|
||||
super(R.layout.fragment_onboarding_follow_suggestions, 40);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setTitle(R.string.popular_on_mastodon);
|
||||
accountID=getArguments().getString("account");
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
|
||||
view.findViewById(R.id.btn_next).setOnClickListener(UiUtils.rateLimitedClickListener(this::onFollowAllClick));
|
||||
view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetFollowSuggestions(40)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FollowSuggestion> result){
|
||||
onDataLoaded(result.stream().map(fs->new ParsedAccount(fs.account, accountID)).collect(Collectors.toList()), false);
|
||||
loadRelationships();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void loadRelationships(){
|
||||
relationships=Collections.emptyMap();
|
||||
relationshipsRequest=new GetAccountRelationships(data.stream().map(fs->fs.account.id).collect(Collectors.toList()));
|
||||
relationshipsRequest.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Relationship> result){
|
||||
relationshipsRequest=null;
|
||||
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
|
||||
if(list==null)
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof SuggestionViewHolder svh)
|
||||
svh.rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
relationshipsRequest=null;
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=27){
|
||||
int inset=insets.getSystemWindowInsetBottom();
|
||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||
}else{
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
return new SuggestionsAdapter();
|
||||
}
|
||||
|
||||
private void onFollowAllClick(View v){
|
||||
if(!loaded || relationships.isEmpty())
|
||||
return;
|
||||
if(data.isEmpty()){
|
||||
proceed();
|
||||
return;
|
||||
}
|
||||
ArrayList<String> accountIdsToFollow=new ArrayList<>();
|
||||
for(ParsedAccount acc:data){
|
||||
Relationship rel=relationships.get(acc.account.id);
|
||||
if(rel==null)
|
||||
continue;
|
||||
if(rel.canFollow())
|
||||
accountIdsToFollow.add(acc.account.id);
|
||||
}
|
||||
|
||||
final ProgressDialog progress=new ProgressDialog(getActivity());
|
||||
progress.setIndeterminate(false);
|
||||
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progress.setMax(accountIdsToFollow.size());
|
||||
progress.setCancelable(false);
|
||||
progress.setMessage(getString(R.string.sending_follows));
|
||||
progress.show();
|
||||
|
||||
for(int i=0;i<Math.min(accountIdsToFollow.size(), 5);i++){ // Send up to 5 requests in parallel
|
||||
followNextAccount(accountIdsToFollow, progress);
|
||||
}
|
||||
}
|
||||
|
||||
private void followNextAccount(ArrayList<String> accountIdsToFollow, ProgressDialog progress){
|
||||
if(accountIdsToFollow.isEmpty()){
|
||||
if(numRunningFollowRequests==0){
|
||||
progress.dismiss();
|
||||
proceed();
|
||||
}
|
||||
return;
|
||||
}
|
||||
numRunningFollowRequests++;
|
||||
String id=accountIdsToFollow.remove(0);
|
||||
new SetAccountFollowed(id, true, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
numRunningFollowRequests--;
|
||||
progress.setProgress(progress.getMax()-accountIdsToFollow.size()-numRunningFollowRequests);
|
||||
followNextAccount(accountIdsToFollow, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
numRunningFollowRequests--;
|
||||
progress.setProgress(progress.getMax()-accountIdsToFollow.size()-numRunningFollowRequests);
|
||||
followNextAccount(accountIdsToFollow, progress);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void proceed(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), HomeFragment.class, args);
|
||||
getActivity().getWindow().getDecorView().postDelayed(()->Nav.finish(this), 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGoBack(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolbarNavigationClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
}
|
||||
|
||||
private class SuggestionsAdapter extends UsableRecyclerView.Adapter<SuggestionViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public SuggestionsAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SuggestionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new SuggestionViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(SuggestionViewHolder holder, int position){
|
||||
holder.bind(data.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return data.get(position).emojiHelper.getImageCount()+1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
ParsedAccount account=data.get(position);
|
||||
if(image==0)
|
||||
return account.avatarRequest;
|
||||
return account.emojiHelper.getImageRequest(image-1);
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionViewHolder extends BindableViewHolder<ParsedAccount> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
|
||||
private final TextView name, username, bio;
|
||||
private final ImageView avatar;
|
||||
private final ProgressBarButton actionButton;
|
||||
private final ProgressBar actionProgress;
|
||||
private final View actionWrap;
|
||||
|
||||
private Relationship relationship;
|
||||
|
||||
public SuggestionViewHolder(){
|
||||
super(getActivity(), R.layout.item_user_row_m3, list);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
bio=findViewById(R.id.bio);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
actionButton=findViewById(R.id.action_btn);
|
||||
actionProgress=findViewById(R.id.action_progress);
|
||||
actionWrap=findViewById(R.id.action_btn_wrap);
|
||||
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
|
||||
avatar.setClipToOutline(true);
|
||||
actionButton.setOnClickListener(UiUtils.rateLimitedClickListener(this::onActionButtonClick));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ParsedAccount item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText(item.account.getDisplayUsername());
|
||||
if(TextUtils.isEmpty(item.parsedBio)){
|
||||
bio.setVisibility(View.GONE);
|
||||
}else{
|
||||
bio.setVisibility(View.VISIBLE);
|
||||
bio.setText(item.parsedBio);
|
||||
}
|
||||
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
}else{
|
||||
actionWrap.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
if(index==0){
|
||||
avatar.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-1, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
}
|
||||
if(image instanceof Animatable a && !a.isRunning())
|
||||
a.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
}
|
||||
|
||||
private void onActionButtonClick(View v){
|
||||
itemView.setHasTransientState(true);
|
||||
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
|
||||
itemView.setHasTransientState(false);
|
||||
relationships.put(item.account.id, rel);
|
||||
rebind();
|
||||
});
|
||||
}
|
||||
|
||||
private void setActionProgressVisible(boolean visible){
|
||||
actionButton.setTextVisible(!visible);
|
||||
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
actionButton.setClickable(!visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class OnboardingProfileSetupFragment extends ToolbarFragment implements ReorderableLinearLayout.OnDragListener{
|
||||
private Button btn;
|
||||
private View buttonBar;
|
||||
private String accountID;
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
private ScrollView scroller;
|
||||
private EditText nameEdit, bioEdit;
|
||||
private ImageView avaImage, coverImage;
|
||||
private Button addRow;
|
||||
private ReorderableLinearLayout profileFieldsLayout;
|
||||
private Uri avatarUri, coverUri;
|
||||
|
||||
private static final int AVATAR_RESULT=348;
|
||||
private static final int COVER_RESULT=183;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.profile_setup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_profile_setup, container, false);
|
||||
|
||||
scroller=view.findViewById(R.id.scroller);
|
||||
nameEdit=view.findViewById(R.id.display_name);
|
||||
bioEdit=view.findViewById(R.id.bio);
|
||||
avaImage=view.findViewById(R.id.avatar);
|
||||
coverImage=view.findViewById(R.id.header);
|
||||
addRow=view.findViewById(R.id.add_row);
|
||||
profileFieldsLayout=view.findViewById(R.id.profile_fields);
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
|
||||
avaImage.setOutlineProvider(OutlineProviders.roundedRect(24));
|
||||
avaImage.setClipToOutline(true);
|
||||
|
||||
Account account=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||
if(savedInstanceState==null){
|
||||
nameEdit.setText(account.displayName);
|
||||
makeFieldsRow();
|
||||
}else{
|
||||
ArrayList<String> fieldTitles=savedInstanceState.getStringArrayList("fieldTitles");
|
||||
ArrayList<String> fieldValues=savedInstanceState.getStringArrayList("fieldValues");
|
||||
for(int i=0;i<fieldTitles.size();i++){
|
||||
View row=makeFieldsRow();
|
||||
EditText title=row.findViewById(R.id.title);
|
||||
EditText content=row.findViewById(R.id.content);
|
||||
title.setText(fieldTitles.get(i));
|
||||
content.setText(fieldValues.get(i));
|
||||
}
|
||||
if(fieldTitles.size()==4)
|
||||
addRow.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
addRow.setOnClickListener(v->{
|
||||
makeFieldsRow();
|
||||
if(profileFieldsLayout.getChildCount()==4){
|
||||
addRow.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
profileFieldsLayout.setDragListener(this);
|
||||
avaImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), AVATAR_RESULT));
|
||||
coverImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), COVER_RESULT));
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
scroller.setOnScrollChangeListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
ArrayList<AccountField> fields=new ArrayList<>();
|
||||
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
|
||||
View row=profileFieldsLayout.getChildAt(i);
|
||||
EditText title=row.findViewById(R.id.title);
|
||||
EditText content=row.findViewById(R.id.content);
|
||||
AccountField fld=new AccountField();
|
||||
fld.name=title.getText().toString();
|
||||
fld.value=content.getText().toString();
|
||||
fields.add(fld);
|
||||
}
|
||||
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), avatarUri, coverUri, fields)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
AccountSessionManager.getInstance().updateAccountInfo(accountID, result);
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
|
||||
getActivity().getWindow().getDecorView().postDelayed(()->Nav.finish(OnboardingProfileSetupFragment.this), 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.saving, true)
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=27){
|
||||
int inset=insets.getSystemWindowInsetBottom();
|
||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||
}else{
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
}
|
||||
|
||||
private View makeFieldsRow(){
|
||||
View view=LayoutInflater.from(getActivity()).inflate(R.layout.onboarding_profile_field, profileFieldsLayout, false);
|
||||
profileFieldsLayout.addView(view);
|
||||
view.findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
|
||||
profileFieldsLayout.startDragging(view);
|
||||
return true;
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwapItems(int oldIndex, int newIndex){}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState){
|
||||
super.onSaveInstanceState(outState);
|
||||
ArrayList<String> fieldTitles=new ArrayList<>(), fieldValues=new ArrayList<>();
|
||||
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
|
||||
View row=profileFieldsLayout.getChildAt(i);
|
||||
EditText title=row.findViewById(R.id.title);
|
||||
EditText content=row.findViewById(R.id.content);
|
||||
fieldTitles.add(title.getText().toString());
|
||||
fieldValues.add(content.getText().toString());
|
||||
}
|
||||
outState.putStringArrayList("fieldTitles", fieldTitles);
|
||||
outState.putStringArrayList("fieldValues", fieldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(resultCode!=Activity.RESULT_OK)
|
||||
return;
|
||||
ImageView img;
|
||||
Uri uri=data.getData();
|
||||
int size;
|
||||
if(requestCode==AVATAR_RESULT){
|
||||
img=avaImage;
|
||||
avatarUri=uri;
|
||||
size=V.dp(100);
|
||||
}else{
|
||||
img=coverImage;
|
||||
coverUri=uri;
|
||||
size=V.dp(1000);
|
||||
}
|
||||
img.setForeground(null);
|
||||
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(uri, size, size));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGoBack(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolbarNavigationClick(){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -16,11 +20,9 @@ import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||
@@ -31,18 +33,22 @@ import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Application;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
import org.joinmastodon.android.ui.text.LinkSpan;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
||||
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.nodes.TextNode;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -51,12 +57,10 @@ import me.grishka.appkit.api.APIRequest;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class SignupFragment extends ToolbarFragment{
|
||||
private static final int AVATAR_RESULT=198;
|
||||
private static final String TAG="SignupFragment";
|
||||
|
||||
private Instance instance;
|
||||
@@ -73,6 +77,7 @@ public class SignupFragment extends ToolbarFragment{
|
||||
private boolean submitAfterGettingToken;
|
||||
private ProgressDialog progressDialog;
|
||||
private HashSet<EditText> errorFields=new HashSet<>();
|
||||
private ElevationOnScrollListener onScrollListener;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -145,19 +150,22 @@ public class SignupFragment extends ToolbarFragment{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.findViewById(R.id.scroller).setOnScrollChangeListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
}
|
||||
|
||||
// @Override
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
// super.onUpdateToolbar();
|
||||
getToolbar().setBackground(null);
|
||||
super.onUpdateToolbar();
|
||||
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||
getToolbar().setElevation(0);
|
||||
if(onScrollListener!=null){
|
||||
onScrollListener.setViews(buttonBar, getToolbar());
|
||||
}
|
||||
}
|
||||
|
||||
private void onButtonClick(){
|
||||
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
|
||||
passwordConfirm.setError(getString(R.string.signup_passwords_dont_match));
|
||||
passwordConfirmWrap.setErrorState();
|
||||
passwordConfirmWrap.setErrorState(getString(R.string.signup_passwords_dont_match));
|
||||
return;
|
||||
}
|
||||
showProgressDialog();
|
||||
@@ -212,8 +220,22 @@ public class SignupFragment extends ToolbarFragment{
|
||||
anyFieldsSkipped=true;
|
||||
continue;
|
||||
}
|
||||
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
||||
getFieldWrapByName(fieldName).setErrorState();
|
||||
List<MastodonDetailedErrorResponse.FieldError> errors=Objects.requireNonNull(fieldErrors.get(fieldName));
|
||||
if(errors.size()==1){
|
||||
getFieldWrapByName(fieldName).setErrorState(getErrorDescription(errors.get(0), fieldName));
|
||||
}else{
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
boolean firstErr=true;
|
||||
for(MastodonDetailedErrorResponse.FieldError err:errors){
|
||||
if(firstErr){
|
||||
firstErr=false;
|
||||
}else{
|
||||
ssb.append('\n');
|
||||
}
|
||||
ssb.append(getErrorDescription(err, fieldName));
|
||||
}
|
||||
getFieldWrapByName(fieldName).setErrorState(getErrorDescription(errors.get(0), fieldName));
|
||||
}
|
||||
errorFields.add(field);
|
||||
if(first){
|
||||
first=false;
|
||||
@@ -231,6 +253,40 @@ public class SignupFragment extends ToolbarFragment{
|
||||
.exec(instance.uri, apiToken);
|
||||
}
|
||||
|
||||
private CharSequence getErrorDescription(MastodonDetailedErrorResponse.FieldError error, String fieldName){
|
||||
return switch(fieldName){
|
||||
case "email" -> switch(error.error){
|
||||
case "ERR_BLOCKED" -> {
|
||||
String emailAddr=email.getText().toString();
|
||||
String s=getResources().getString(R.string.signup_email_domain_blocked, TextUtils.htmlEncode(instance.uri), TextUtils.htmlEncode(emailAddr.substring(emailAddr.lastIndexOf('@')+1)));
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder();
|
||||
Jsoup.parseBodyFragment(s).body().traverse(new NodeVisitor(){
|
||||
private int spanStart;
|
||||
@Override
|
||||
public void head(Node node, int depth){
|
||||
if(node instanceof TextNode tn){
|
||||
ssb.append(tn.text());
|
||||
}else if(node instanceof Element){
|
||||
spanStart=ssb.length();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tail(Node node, int depth){
|
||||
if(node instanceof Element){
|
||||
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
});
|
||||
yield ssb;
|
||||
}
|
||||
default -> error.description;
|
||||
};
|
||||
default -> error.description;
|
||||
};
|
||||
}
|
||||
|
||||
private EditText getFieldByName(String name){
|
||||
return switch(name){
|
||||
case "email" -> email;
|
||||
@@ -323,6 +379,11 @@ public class SignupFragment extends ToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private void onGoBackLinkClick(LinkSpan span){
|
||||
setResult(false, null);
|
||||
Nav.finish(this);
|
||||
}
|
||||
|
||||
private class ErrorClearingListener implements TextWatcher{
|
||||
public final EditText editText;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
||||
@@ -89,6 +90,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if (getActivity() == null) return;
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
@@ -237,7 +239,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
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);
|
||||
for(StatusDisplayItem item:items){
|
||||
if(item instanceof ImageStatusDisplayItem isdi){
|
||||
isdi.horizontalInset=V.dp(40+32);
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
@@ -28,15 +29,17 @@ import java.util.ArrayList;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||
private String accountID;
|
||||
private Account reportAccount;
|
||||
private Button btn;
|
||||
private View buttonBar;
|
||||
private View buttonBar, forwardReportItem;
|
||||
private TextView forwardReportText;
|
||||
private Switch forwardReportSwitch;
|
||||
private EditText commentEdit;
|
||||
private boolean forwardReport;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -77,7 +80,17 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(this::onButtonClick);
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
commentEdit=view.findViewById(R.id.text);
|
||||
|
||||
forwardReportSwitch = view.findViewById(R.id.forward_report_switch);
|
||||
forwardReportItem = view.findViewById(R.id.forward_report);
|
||||
forwardReportText = view.findViewById(R.id.forward_report_text);
|
||||
String domain = reportAccount.getDomain();
|
||||
if (domain == null) {
|
||||
forwardReportItem.setVisibility(View.GONE);
|
||||
} else {
|
||||
forwardReportItem.setOnClickListener(this::onForwardReportClick);
|
||||
forwardReportText.setText(getActivity().getString(R.string.sk_forward_report_to, domain));
|
||||
forwardReportSwitch.setChecked(forwardReport = true);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -102,7 +115,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||
ReportReason reason=ReportReason.valueOf(getArguments().getString("reason"));
|
||||
ArrayList<String> statusIDs=getArguments().getStringArrayList("statusIDs");
|
||||
ArrayList<String> ruleIDs=getArguments().getStringArrayList("ruleIDs");
|
||||
new SendReport(reportAccount.id, reason, statusIDs, ruleIDs, v.getId()==R.id.btn_back ? null : commentEdit.getText().toString(), true)
|
||||
new SendReport(reportAccount.id, reason, statusIDs, ruleIDs, v.getId()==R.id.btn_back ? null : commentEdit.getText().toString(), forwardReport)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
@@ -123,6 +136,11 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void onForwardReportClick(View v) {
|
||||
forwardReport = !forwardReport;
|
||||
forwardReportSwitch.setChecked(forwardReport);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onFinishReportFragments(FinishReportFragmentsEvent ev){
|
||||
if(ev.reportAccountID.equals(reportAccount.id))
|
||||
|
||||
@@ -133,6 +133,14 @@ public class Account extends BaseModel{
|
||||
*/
|
||||
public Instant muteExpiresAt;
|
||||
|
||||
public List<Role> roles;
|
||||
|
||||
@Parcel
|
||||
public static class Role {
|
||||
public String name;
|
||||
/** #rrggbb */
|
||||
public String color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Parcel
|
||||
public class Announcement extends BaseModel implements DisplayItemsParent {
|
||||
@RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
public String content;
|
||||
public Instant startsAt;
|
||||
public Instant endsAt;
|
||||
public boolean published;
|
||||
public boolean allDay;
|
||||
public Instant publishedAt;
|
||||
public Instant updatedAt;
|
||||
public boolean read;
|
||||
public List<Emoji> emojis;
|
||||
public List<Mention> mentions;
|
||||
public List<Hashtag> tags;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Announcement{" +
|
||||
"id='" + id + '\'' +
|
||||
", content='" + content + '\'' +
|
||||
", startsAt=" + startsAt +
|
||||
", endsAt=" + endsAt +
|
||||
", published=" + published +
|
||||
", allDay=" + allDay +
|
||||
", publishedAt=" + publishedAt +
|
||||
", updatedAt=" + updatedAt +
|
||||
", read=" + read +
|
||||
", emojis=" + emojis +
|
||||
", mentions=" + mentions +
|
||||
", tags=" + tags +
|
||||
'}';
|
||||
}
|
||||
|
||||
public Status toStatus() {
|
||||
Status s = Status.ofFake(id, content, publishedAt);
|
||||
s.createdAt = startsAt != null ? startsAt : publishedAt;
|
||||
if (updatedAt != null) s.editedAt = updatedAt;
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,11 @@ import org.parceler.Parcel;
|
||||
import org.parceler.ParcelConstructor;
|
||||
import org.parceler.ParcelProperty;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Parcel
|
||||
public class Attachment extends BaseModel{
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
public Type type;
|
||||
@@ -45,26 +47,26 @@ public class Attachment extends BaseModel{
|
||||
|
||||
public int getWidth(){
|
||||
if(meta==null)
|
||||
return 0;
|
||||
return 1920;
|
||||
if(meta.width>0)
|
||||
return meta.width;
|
||||
if(meta.original!=null && meta.original.width>0)
|
||||
return meta.original.width;
|
||||
if(meta.small!=null && meta.small.width>0)
|
||||
return meta.small.width;
|
||||
return 0;
|
||||
return 1920;
|
||||
}
|
||||
|
||||
public int getHeight(){
|
||||
if(meta==null)
|
||||
return 0;
|
||||
return 1080;
|
||||
if(meta.height>0)
|
||||
return meta.height;
|
||||
if(meta.original!=null && meta.original.height>0)
|
||||
return meta.original.height;
|
||||
if(meta.small!=null && meta.small.height>0)
|
||||
return meta.small.height;
|
||||
return 0;
|
||||
return 1080;
|
||||
}
|
||||
|
||||
public double getDuration(){
|
||||
@@ -85,6 +87,12 @@ public class Attachment extends BaseModel{
|
||||
if(placeholder!=null)
|
||||
blurhashPlaceholder=new BlurHashDrawable(placeholder, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
if (id == null) {
|
||||
// akkoma servers doesn't provide IDs for attachments,
|
||||
// but IDs are needed by the AudioPlayerService
|
||||
id = "" + this.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,8 @@ public class Filter extends BaseModel{
|
||||
@RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
public String title;
|
||||
@RequiredField
|
||||
public String phrase;
|
||||
public transient EnumSet<FilterContext> context=EnumSet.noneOf(FilterContext.class);
|
||||
public Instant expiresAt;
|
||||
@@ -50,6 +52,7 @@ public class Filter extends BaseModel{
|
||||
else
|
||||
pattern=Pattern.compile(Pattern.quote(phrase), Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
if (title == null) title = phrase;
|
||||
return pattern.matcher(text).find();
|
||||
}
|
||||
|
||||
@@ -61,6 +64,7 @@ public class Filter extends BaseModel{
|
||||
public String toString(){
|
||||
return "Filter{"+
|
||||
"id='"+id+'\''+
|
||||
", title='"+title+'\''+
|
||||
", phrase='"+phrase+'\''+
|
||||
", context="+context+
|
||||
", expiresAt="+expiresAt+
|
||||
@@ -77,7 +81,9 @@ public class Filter extends BaseModel{
|
||||
@SerializedName("public")
|
||||
PUBLIC,
|
||||
@SerializedName("thread")
|
||||
THREAD
|
||||
THREAD,
|
||||
@SerializedName("account")
|
||||
ACCOUNT
|
||||
}
|
||||
|
||||
public enum FilterAction{
|
||||
|
||||
@@ -27,7 +27,7 @@ public class Instance extends BaseModel{
|
||||
/**
|
||||
* Admin-defined description of the Mastodon site.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String description;
|
||||
/**
|
||||
* A shorter description defined by the admin.
|
||||
@@ -37,7 +37,7 @@ public class Instance extends BaseModel{
|
||||
/**
|
||||
* An email that may be contacted for any inquiries.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String email;
|
||||
/**
|
||||
* The version of Mastodon installed on the instance.
|
||||
@@ -45,7 +45,7 @@ public class Instance extends BaseModel{
|
||||
@RequiredField
|
||||
public String version;
|
||||
/**
|
||||
* Primary langauges of the website and its staff.
|
||||
* Primary languages of the website and its staff.
|
||||
*/
|
||||
// @RequiredField
|
||||
public List<String> languages;
|
||||
@@ -84,6 +84,8 @@ public class Instance extends BaseModel{
|
||||
|
||||
public V2 v2;
|
||||
|
||||
public Pleroma pleroma;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
@@ -193,4 +195,9 @@ public class Instance extends BaseModel{
|
||||
public boolean enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class Pleroma extends BaseModel {
|
||||
// metadata etc
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
@@ -11,9 +13,9 @@ public class ListTimeline extends BaseModel {
|
||||
public String id;
|
||||
@RequiredField
|
||||
public String title;
|
||||
@RequiredField
|
||||
public RepliesPolicy repliesPolicy;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "List{" +
|
||||
|
||||
@@ -18,8 +18,8 @@ public class Notification extends BaseModel implements DisplayItemsParent{
|
||||
public Instant createdAt;
|
||||
@RequiredField
|
||||
public Account account;
|
||||
|
||||
public Status status;
|
||||
public Report report;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
@@ -48,6 +48,19 @@ public class Notification extends BaseModel implements DisplayItemsParent{
|
||||
@SerializedName("poll")
|
||||
POLL,
|
||||
@SerializedName("status")
|
||||
STATUS
|
||||
STATUS,
|
||||
@SerializedName("update")
|
||||
UPDATE,
|
||||
@SerializedName("admin.sign_up")
|
||||
SIGN_UP,
|
||||
@SerializedName("admin.report")
|
||||
REPORT
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class Report {
|
||||
public String id;
|
||||
public String comment;
|
||||
public Account targetAccount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ParsedAccount{
|
||||
public Account account;
|
||||
public CharSequence parsedName, parsedBio;
|
||||
public CustomEmojiHelper emojiHelper;
|
||||
public ImageLoaderRequest avatarRequest;
|
||||
|
||||
public ParsedAccount(Account account, String accountID){
|
||||
this.account=account;
|
||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
|
||||
emojiHelper=new CustomEmojiHelper();
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
|
||||
ssb.append(parsedBio);
|
||||
emojiHelper.setText(ssb);
|
||||
|
||||
avatarRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(40), V.dp(40));
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,13 @@ public class PushNotification extends BaseModel{
|
||||
@SerializedName("poll")
|
||||
POLL(R.string.notification_type_poll),
|
||||
@SerializedName("status")
|
||||
STATUS(R.string.sk_notification_type_status);
|
||||
STATUS(R.string.sk_notification_type_status),
|
||||
@SerializedName("update")
|
||||
UPDATE(R.string.sk_notification_type_update),
|
||||
@SerializedName("admin.sign_up")
|
||||
SIGN_UP(R.string.sk_sign_ups),
|
||||
@SerializedName("admin.report")
|
||||
REPORT(R.string.sk_new_reports);
|
||||
|
||||
@StringRes
|
||||
public final int localizedName;
|
||||
|
||||
@@ -23,6 +23,7 @@ public class PushSubscription extends BaseModel implements Cloneable{
|
||||
", endpoint='"+endpoint+'\''+
|
||||
", alerts="+alerts+
|
||||
", serverKey='"+serverKey+'\''+
|
||||
", policy="+policy+
|
||||
'}';
|
||||
}
|
||||
|
||||
@@ -44,10 +45,19 @@ public class PushSubscription extends BaseModel implements Cloneable{
|
||||
public boolean mention;
|
||||
public boolean poll;
|
||||
public boolean status;
|
||||
public boolean update;
|
||||
|
||||
// set to true here because i didn't add any items for those to the settings
|
||||
// (so i don't have to determine whether the user is an admin to show the items or not, and
|
||||
// admins can still disable those through the android notifications settings)
|
||||
@SerializedName("admin.sign_up")
|
||||
public boolean adminSignUp = true;
|
||||
@SerializedName("admin.report")
|
||||
public boolean adminReport = true;
|
||||
|
||||
public static Alerts ofAll(){
|
||||
Alerts alerts=new Alerts();
|
||||
alerts.follow=alerts.favourite=alerts.reblog=alerts.mention=alerts.poll=alerts.status=true;
|
||||
alerts.follow=alerts.favourite=alerts.reblog=alerts.mention=alerts.poll=alerts.status=alerts.update=true;
|
||||
return alerts;
|
||||
}
|
||||
|
||||
@@ -60,6 +70,9 @@ public class PushSubscription extends BaseModel implements Cloneable{
|
||||
", mention="+mention+
|
||||
", poll="+poll+
|
||||
", status="+status+
|
||||
", update="+update+
|
||||
", adminSignUp="+adminSignUp+
|
||||
", adminReport="+adminReport+
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ public class Relationship extends BaseModel{
|
||||
public boolean blockedBy;
|
||||
public String note;
|
||||
|
||||
public boolean canFollow(){
|
||||
return !(following || blocking || blockedBy || domainBlocking);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Relationship{"+
|
||||
|
||||
@@ -62,17 +62,13 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
||||
}
|
||||
|
||||
public Status toStatus() {
|
||||
Status s = new Status();
|
||||
s.id = id;
|
||||
Status s = Status.ofFake(id, params.text, scheduledAt);
|
||||
s.mediaAttachments = mediaAttachments;
|
||||
s.createdAt = scheduledAt;
|
||||
s.content = s.text = params.text;
|
||||
s.inReplyToId = params.inReplyToId > 0 ? "" + params.inReplyToId : null;
|
||||
s.spoilerText = params.spoilerText;
|
||||
s.visibility = params.visibility;
|
||||
s.language = params.language;
|
||||
s.mentions = List.of();
|
||||
s.tags = List.of();
|
||||
s.emojis = List.of();
|
||||
s.sensitive = params.sensitive;
|
||||
if (params.poll != null) s.poll = params.poll.toPoll();
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
public Card card;
|
||||
public String language;
|
||||
public String text;
|
||||
public boolean localOnly;
|
||||
|
||||
public boolean favourited;
|
||||
public boolean reblogged;
|
||||
@@ -57,7 +58,9 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
public boolean bookmarked;
|
||||
public boolean pinned;
|
||||
|
||||
public transient boolean filterRevealed;
|
||||
public transient boolean spoilerRevealed;
|
||||
public transient boolean textExpanded, textExpandable;
|
||||
public transient boolean hasGapAfter;
|
||||
private transient String strippedText;
|
||||
|
||||
@@ -83,6 +86,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
reblog.postprocess();
|
||||
|
||||
spoilerRevealed=GlobalUserPreferences.alwaysExpandContentWarnings || !sensitive;
|
||||
if (visibility.equals(StatusPrivacy.LOCAL)) localOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,4 +148,19 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
strippedText=HtmlParser.strip(content);
|
||||
return strippedText;
|
||||
}
|
||||
|
||||
public static Status ofFake(String id, String text, Instant createdAt) {
|
||||
Status s = new Status();
|
||||
s.id = id;
|
||||
s.mediaAttachments = List.of();
|
||||
s.createdAt = createdAt;
|
||||
s.content = s.text = text;
|
||||
s.spoilerText = "";
|
||||
s.visibility = StatusPrivacy.PUBLIC;
|
||||
s.mentions = List.of();
|
||||
s.tags = List.of();
|
||||
s.emojis = List.of();
|
||||
s.filtered = List.of();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ public enum StatusPrivacy{
|
||||
@SerializedName("private")
|
||||
PRIVATE(2),
|
||||
@SerializedName("direct")
|
||||
DIRECT(3);
|
||||
DIRECT(3),
|
||||
@SerializedName("local")
|
||||
LOCAL(4); // akkoma
|
||||
|
||||
private int privacy;
|
||||
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||
import org.joinmastodon.android.fragments.HomeTimelineFragment;
|
||||
import org.joinmastodon.android.fragments.ListTimelineFragment;
|
||||
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||
import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment;
|
||||
import org.joinmastodon.android.fragments.discover.LocalTimelineFragment;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class TimelineDefinition {
|
||||
private TimelineType type;
|
||||
private String title;
|
||||
private @Nullable Icon icon;
|
||||
|
||||
private @Nullable String listId;
|
||||
private @Nullable String listTitle;
|
||||
|
||||
private @Nullable String hashtagName;
|
||||
|
||||
public static TimelineDefinition ofList(String listId, String listTitle) {
|
||||
TimelineDefinition def = new TimelineDefinition(TimelineType.LIST);
|
||||
def.listId = listId;
|
||||
def.listTitle = listTitle;
|
||||
return def;
|
||||
}
|
||||
|
||||
public static TimelineDefinition ofList(ListTimeline list) {
|
||||
return ofList(list.id, list.title);
|
||||
}
|
||||
|
||||
public static TimelineDefinition ofHashtag(String hashtag) {
|
||||
TimelineDefinition def = new TimelineDefinition(TimelineType.HASHTAG);
|
||||
def.hashtagName = hashtag;
|
||||
return def;
|
||||
}
|
||||
|
||||
public static TimelineDefinition ofHashtag(Hashtag hashtag) {
|
||||
return ofHashtag(hashtag.name);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public TimelineDefinition() {}
|
||||
|
||||
public TimelineDefinition(TimelineType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getTitle(Context ctx) {
|
||||
return title != null ? title : getDefaultTitle(ctx);
|
||||
}
|
||||
|
||||
public String getCustomTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title == null || title.isBlank() ? null : title;
|
||||
}
|
||||
|
||||
public String getDefaultTitle(Context ctx) {
|
||||
return switch (type) {
|
||||
case HOME -> ctx.getString(R.string.sk_timeline_home);
|
||||
case LOCAL -> ctx.getString(R.string.sk_timeline_local);
|
||||
case FEDERATED -> ctx.getString(R.string.sk_timeline_federated);
|
||||
case POST_NOTIFICATIONS -> ctx.getString(R.string.sk_timeline_posts);
|
||||
case LIST -> listTitle;
|
||||
case HASHTAG -> hashtagName;
|
||||
};
|
||||
}
|
||||
|
||||
public Icon getDefaultIcon() {
|
||||
return switch (type) {
|
||||
case HOME -> Icon.HOME;
|
||||
case LOCAL -> Icon.LOCAL;
|
||||
case FEDERATED -> Icon.FEDERATED;
|
||||
case POST_NOTIFICATIONS -> Icon.POST_NOTIFICATIONS;
|
||||
case LIST -> Icon.LIST;
|
||||
case HASHTAG -> Icon.HASHTAG;
|
||||
};
|
||||
}
|
||||
|
||||
public Fragment getFragment() {
|
||||
return switch (type) {
|
||||
case HOME -> new HomeTimelineFragment();
|
||||
case LOCAL -> new LocalTimelineFragment();
|
||||
case FEDERATED -> new FederatedTimelineFragment();
|
||||
case LIST -> new ListTimelineFragment();
|
||||
case HASHTAG -> new HashtagTimelineFragment();
|
||||
case POST_NOTIFICATIONS -> new NotificationsListFragment();
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Icon getIcon() {
|
||||
return icon == null ? getDefaultIcon() : icon;
|
||||
}
|
||||
|
||||
public void setIcon(@Nullable Icon icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public TimelineType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
TimelineDefinition that = (TimelineDefinition) o;
|
||||
if (type != that.type) return false;
|
||||
if (type == TimelineType.LIST) return Objects.equals(listId, that.listId);
|
||||
if (type == TimelineType.HASHTAG) return Objects.equals(hashtagName.toLowerCase(), that.hashtagName.toLowerCase());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = type.ordinal();
|
||||
result = 31 * result + (listId != null ? listId.hashCode() : 0);
|
||||
result = 31 * result + (hashtagName.toLowerCase() != null ? hashtagName.toLowerCase().hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public TimelineDefinition copy() {
|
||||
TimelineDefinition def = new TimelineDefinition(type);
|
||||
def.title = title;
|
||||
def.listId = listId;
|
||||
def.listTitle = listTitle;
|
||||
def.hashtagName = hashtagName;
|
||||
def.icon = icon == null ? null : Icon.values()[icon.ordinal()];
|
||||
return def;
|
||||
}
|
||||
|
||||
public Bundle populateArguments(Bundle args) {
|
||||
if (type == TimelineType.LIST) {
|
||||
args.putString("listTitle", title);
|
||||
args.putString("listID", listId);
|
||||
} else if (type == TimelineType.HASHTAG) {
|
||||
args.putString("hashtag", hashtagName);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG }
|
||||
|
||||
public enum Icon {
|
||||
HEART(R.drawable.ic_fluent_heart_24_regular, R.string.sk_icon_heart),
|
||||
STAR(R.drawable.ic_fluent_star_24_regular, R.string.sk_icon_star),
|
||||
PEOPLE(R.drawable.ic_fluent_people_24_regular, R.string.sk_icon_people),
|
||||
CITY(R.drawable.ic_fluent_city_24_regular, R.string.sk_icon_city),
|
||||
IMAGE(R.drawable.ic_fluent_image_24_regular, R.string.sk_icon_image),
|
||||
NEWS(R.drawable.ic_fluent_news_24_regular, R.string.sk_icon_news),
|
||||
COLOR_PALETTE(R.drawable.ic_fluent_color_24_regular, R.string.sk_icon_color_palette),
|
||||
CAT(R.drawable.ic_fluent_animal_cat_24_regular, R.string.sk_icon_cat),
|
||||
DOG(R.drawable.ic_fluent_animal_dog_24_regular, R.string.sk_icon_dog),
|
||||
RABBIT(R.drawable.ic_fluent_animal_rabbit_24_regular, R.string.sk_icon_rabbit),
|
||||
TURTLE(R.drawable.ic_fluent_animal_turtle_24_regular, R.string.sk_icon_turtle),
|
||||
ACADEMIC_CAP(R.drawable.ic_fluent_hat_graduation_24_regular, R.string.sk_icon_academic_cap),
|
||||
BOT(R.drawable.ic_fluent_bot_24_regular, R.string.sk_icon_bot),
|
||||
IMPORTANT(R.drawable.ic_fluent_important_24_regular, R.string.sk_icon_important),
|
||||
PIN(R.drawable.ic_fluent_pin_24_regular, R.string.sk_icon_pin),
|
||||
SHIELD(R.drawable.ic_fluent_shield_24_regular, R.string.sk_icon_shield),
|
||||
CHAT(R.drawable.ic_fluent_chat_multiple_24_regular, R.string.sk_icon_chat),
|
||||
TAG(R.drawable.ic_fluent_tag_24_regular, R.string.sk_icon_tag),
|
||||
TRAIN(R.drawable.ic_fluent_vehicle_subway_24_regular, R.string.sk_icon_train),
|
||||
BICYCLE(R.drawable.ic_fluent_vehicle_bicycle_24_regular, R.string.sk_icon_bicycle),
|
||||
MAP(R.drawable.ic_fluent_map_24_regular, R.string.sk_icon_map),
|
||||
BACKPACK(R.drawable.ic_fluent_backpack_24_regular, R.string.sk_icon_backpack),
|
||||
BRIEFCASE(R.drawable.ic_fluent_briefcase_24_regular, R.string.sk_icon_briefcase),
|
||||
BOOK(R.drawable.ic_fluent_book_open_24_regular, R.string.sk_icon_book),
|
||||
LANGUAGE(R.drawable.ic_fluent_local_language_24_regular, R.string.sk_icon_language),
|
||||
WEATHER(R.drawable.ic_fluent_weather_rain_showers_day_24_regular, R.string.sk_icon_weather),
|
||||
APERTURE(R.drawable.ic_fluent_scan_24_regular, R.string.sk_icon_aperture),
|
||||
MUSIC(R.drawable.ic_fluent_music_note_2_24_regular, R.string.sk_icon_music),
|
||||
LOCATION(R.drawable.ic_fluent_location_24_regular, R.string.sk_icon_location),
|
||||
GLOBE(R.drawable.ic_fluent_globe_24_regular, R.string.sk_icon_globe),
|
||||
MEGAPHONE(R.drawable.ic_fluent_megaphone_loud_24_regular, R.string.sk_icon_megaphone),
|
||||
MICROPHONE(R.drawable.ic_fluent_mic_24_regular, R.string.sk_icon_microphone),
|
||||
MICROSCOPE(R.drawable.ic_fluent_microscope_24_regular, R.string.sk_icon_microscope),
|
||||
STETHOSCOPE(R.drawable.ic_fluent_stethoscope_24_regular, R.string.sk_icon_stethoscope),
|
||||
KEYBOARD(R.drawable.ic_fluent_midi_24_regular, R.string.sk_icon_keyboard),
|
||||
COFFEE(R.drawable.ic_fluent_drink_coffee_24_regular, R.string.sk_icon_coffee),
|
||||
CLAPPER_BOARD(R.drawable.ic_fluent_movies_and_tv_24_regular, R.string.sk_icon_clapper_board),
|
||||
LAUGH(R.drawable.ic_fluent_emoji_laugh_24_regular, R.string.sk_icon_laugh),
|
||||
BALLOON(R.drawable.ic_fluent_balloon_24_regular, R.string.sk_icon_balloon),
|
||||
PI(R.drawable.ic_fluent_pi_24_regular, R.string.sk_icon_pi),
|
||||
MATH_FORMULA(R.drawable.ic_fluent_math_formula_24_regular, R.string.sk_icon_math_formula),
|
||||
GAMES(R.drawable.ic_fluent_games_24_regular, R.string.sk_icon_games),
|
||||
CODE(R.drawable.ic_fluent_code_24_regular, R.string.sk_icon_code),
|
||||
BUG(R.drawable.ic_fluent_bug_24_regular, R.string.sk_icon_bug),
|
||||
LIGHT_BULB(R.drawable.ic_fluent_lightbulb_24_regular, R.string.sk_icon_light_bulb),
|
||||
FIRE(R.drawable.ic_fluent_fire_24_regular, R.string.sk_icon_fire),
|
||||
LEAVES(R.drawable.ic_fluent_leaf_three_24_regular, R.string.sk_icon_leaves),
|
||||
SPORT(R.drawable.ic_fluent_sport_24_regular, R.string.sk_icon_sport),
|
||||
HEALTH(R.drawable.ic_fluent_heart_pulse_24_regular, R.string.sk_icon_health),
|
||||
PIZZA(R.drawable.ic_fluent_food_pizza_24_regular, R.string.sk_icon_pizza),
|
||||
GAVEL(R.drawable.ic_fluent_gavel_24_regular, R.string.sk_icon_gavel),
|
||||
GAUGE(R.drawable.ic_fluent_gauge_24_regular, R.string.sk_icon_gauge),
|
||||
HEADPHONES(R.drawable.ic_fluent_headphones_sound_wave_24_regular, R.string.sk_icon_headphones),
|
||||
HUMAN(R.drawable.ic_fluent_accessibility_24_regular, R.string.sk_icon_human),
|
||||
|
||||
HOME(R.drawable.ic_fluent_home_24_regular, R.string.sk_timeline_home, true),
|
||||
LOCAL(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
|
||||
FEDERATED(R.drawable.ic_fluent_earth_24_regular, R.string.sk_timeline_federated, true),
|
||||
POST_NOTIFICATIONS(R.drawable.ic_fluent_chat_24_regular, R.string.sk_timeline_posts, true),
|
||||
LIST(R.drawable.ic_fluent_people_24_regular, R.string.sk_list, true),
|
||||
HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true);
|
||||
|
||||
public final int iconRes, nameRes;
|
||||
public final boolean hidden;
|
||||
|
||||
Icon(@DrawableRes int iconRes, @StringRes int nameRes) {
|
||||
this(iconRes, nameRes, false);
|
||||
}
|
||||
|
||||
Icon(@DrawableRes int iconRes, @StringRes int nameRes, boolean hidden) {
|
||||
this.iconRes = iconRes;
|
||||
this.nameRes = nameRes;
|
||||
this.hidden = hidden;
|
||||
}
|
||||
}
|
||||
|
||||
public static final TimelineDefinition HOME_TIMELINE = new TimelineDefinition(TimelineType.HOME);
|
||||
public static final TimelineDefinition LOCAL_TIMELINE = new TimelineDefinition(TimelineType.LOCAL);
|
||||
public static final TimelineDefinition FEDERATED_TIMELINE = new TimelineDefinition(TimelineType.FEDERATED);
|
||||
public static final TimelineDefinition POSTS_TIMELINE = new TimelineDefinition(TimelineType.POST_NOTIFICATIONS);
|
||||
|
||||
public static final List<TimelineDefinition> DEFAULT_TIMELINES = BuildConfig.BUILD_TYPE.equals("playRelease")
|
||||
? List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy())
|
||||
: List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy(), FEDERATED_TIMELINE.copy());
|
||||
public static final List<TimelineDefinition> ALL_TIMELINES = List.of(
|
||||
HOME_TIMELINE.copy(),
|
||||
LOCAL_TIMELINE.copy(),
|
||||
FEDERATED_TIMELINE.copy(),
|
||||
POSTS_TIMELINE.copy()
|
||||
);
|
||||
}
|
||||
@@ -23,7 +23,8 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomLoginFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.List;
|
||||
@@ -77,7 +78,7 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
|
||||
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
|
||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, ()->{
|
||||
Nav.go(activity, CustomLoginFragment.class, null);
|
||||
Nav.go(activity, CustomWelcomeFragment.class, null);
|
||||
dismiss();
|
||||
}));
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
||||
Optional<Emoji> element = allAvailableEmojis.stream().filter(e -> e.shortcode.equals(emojiCode)).findFirst();
|
||||
element.ifPresent(recentEmojiList::add);
|
||||
}
|
||||
emojis.add(0, new EmojiCategory(activity.getString(R.string.sk_emoji_recent), recentEmojiList));
|
||||
emojis.add(0, new EmojiCategory(activity.getString(R.string.mo_emoji_recent), recentEmojiList));
|
||||
}
|
||||
|
||||
for(EmojiCategory category:emojis)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -9,6 +10,7 @@ import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.AudioPlayerService;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
@@ -103,6 +105,7 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
|
||||
}else{
|
||||
seekBar.setEnabled(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void onPlayPauseClick(View v){
|
||||
@@ -126,6 +129,10 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
|
||||
lastKnownPositionTime=SystemClock.uptimeMillis();
|
||||
this.playing=playing;
|
||||
playPauseBtn.setImageResource(playing ? R.drawable.ic_fluent_pause_circle_24_filled : R.drawable.ic_fluent_play_circle_24_filled);
|
||||
playPauseBtn.setContentDescription(MastodonApp.context.getResources().getString(playing ? R.string.pause : R.string.play));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
playPauseBtn.setTooltipText(playPauseBtn.getContentDescription());
|
||||
}
|
||||
if(!playing){
|
||||
lastRemainingSeconds=-1;
|
||||
time.setText(formatDuration((int) item.attachment.getDuration()));
|
||||
|
||||
@@ -80,7 +80,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
editHistory.setVisibility(View.GONE);
|
||||
}
|
||||
String timeStr=TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault()));
|
||||
|
||||
|
||||
if (item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)) {
|
||||
time.setText(item.parentFragment.getString(R.string.timestamp_via_app, timeStr, ""));
|
||||
applicationName.setText(item.status.application.name);
|
||||
@@ -96,9 +96,10 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
visibility.setImageResource(switch (s.visibility) {
|
||||
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
|
||||
case DIRECT -> R.drawable.ic_fluent_mention_24_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled;
|
||||
case DIRECT -> R.drawable.ic_fluent_mention_20_regular;
|
||||
case LOCAL -> R.drawable.ic_fluent_eye_20_regular;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -133,4 +134,4 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,8 +151,9 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
boost.setSelected(item.status.reblogged);
|
||||
favorite.setSelected(item.status.favourited);
|
||||
bookmark.setSelected(item.status.bookmarked);
|
||||
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED
|
||||
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
|
||||
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||
|
||||
}
|
||||
|
||||
private void bindButton(TextView btn, long count){
|
||||
@@ -253,8 +254,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
Drawable checkMark = ctx.getDrawable(R.drawable.ic_fluent_checkmark_circle_20_regular);
|
||||
Drawable publicDrawable = ctx.getDrawable(R.drawable.ic_fluent_earth_24_regular);
|
||||
Drawable unlistedDrawable = ctx.getDrawable(R.drawable.ic_fluent_people_community_24_regular);
|
||||
Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_people_checkmark_24_regular);
|
||||
Drawable unlistedDrawable = ctx.getDrawable(R.drawable.ic_fluent_lock_open_24_regular);
|
||||
Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_lock_closed_24_regular);
|
||||
|
||||
StatusPrivacy defaultVisibility = session.preferences != null ? session.preferences.postingDefaultVisibility : null;
|
||||
// e.g. post visibility is unlisted, but default is public
|
||||
|
||||
@@ -9,7 +9,9 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
@@ -21,25 +23,31 @@ import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.announcements.DismissAnnouncement;
|
||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -49,9 +57,11 @@ import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.APIRequest;
|
||||
@@ -75,6 +85,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
private String extraText;
|
||||
private Notification notification;
|
||||
private ScheduledStatus scheduledStatus;
|
||||
private Announcement announcement;
|
||||
private Consumer<String> consumeReadAnnouncement;
|
||||
|
||||
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, String extraText, Notification notification, ScheduledStatus scheduledStatus){
|
||||
super(parentID, parentFragment);
|
||||
@@ -103,6 +115,13 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
this.extraText=extraText;
|
||||
}
|
||||
|
||||
public static HeaderStatusDisplayItem fromAnnouncement(Announcement a, Status fakeStatus, Account instanceUser, BaseStatusListFragment parentFragment, String accountID, Consumer<String> consumeReadID) {
|
||||
HeaderStatusDisplayItem item = new HeaderStatusDisplayItem(a.id, instanceUser, a.startsAt, parentFragment, accountID, fakeStatus, null, null, null);
|
||||
item.announcement = a;
|
||||
item.consumeReadAnnouncement = consumeReadID;
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(){
|
||||
return Type.HEADER;
|
||||
@@ -122,8 +141,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final TextView name, username, timestamp, extraText;
|
||||
private final ImageView avatar, more, visibility, deleteNotification;
|
||||
private final TextView name, username, timestamp, extraText, separator;
|
||||
private final View collapseBtn;
|
||||
private final ImageView avatar, more, visibility, deleteNotification, unreadIndicator, collapseBtnIcon,botIcon;
|
||||
private final PopupMenu optionsMenu;
|
||||
private Relationship relationship;
|
||||
private APIRequest<?> currentRelationshipRequest;
|
||||
@@ -139,11 +159,16 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
super(activity, R.layout.display_item_header, parent);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
separator=findViewById(R.id.separator);
|
||||
timestamp=findViewById(R.id.timestamp);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
more=findViewById(R.id.more);
|
||||
visibility=findViewById(R.id.visibility);
|
||||
deleteNotification=findViewById(R.id.delete_notification);
|
||||
unreadIndicator=findViewById(R.id.unread_indicator);
|
||||
collapseBtn=findViewById(R.id.collapse_btn);
|
||||
collapseBtnIcon=findViewById(R.id.collapse_btn_icon);
|
||||
botIcon=findViewById(R.id.bot_icon);
|
||||
extraText=findViewById(R.id.extra_text);
|
||||
avatar.setOnClickListener(this::onAvaClick);
|
||||
avatar.setOutlineProvider(roundCornersOutline);
|
||||
@@ -155,9 +180,12 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
fragment.removeNotification(item.notification);
|
||||
}
|
||||
}));
|
||||
collapseBtn.setOnClickListener(l -> item.parentFragment.onToggleExpanded(item.status, getItemID()));
|
||||
|
||||
optionsMenu=new PopupMenu(activity, more);
|
||||
|
||||
optionsMenu.inflate(R.menu.post);
|
||||
|
||||
optionsMenu.setOnMenuItemClickListener(menuItem->{
|
||||
Account account=item.user;
|
||||
int id=menuItem.getItemId();
|
||||
@@ -166,7 +194,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putParcelable("editStatus", Parcels.wrap(item.status));
|
||||
if (id==R.id.delete_and_redraft) {
|
||||
boolean redraft = id==R.id.delete_and_redraft;
|
||||
if (redraft) {
|
||||
args.putBoolean("redraftStatus", true);
|
||||
if (item.parentFragment instanceof ThreadFragment thread && !thread.isItemEnabled(item.status.id)) {
|
||||
// ("enabled" = clickable; opened status is not clickable)
|
||||
@@ -174,7 +203,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
args.putBoolean("navigateToStatus", true);
|
||||
}
|
||||
}
|
||||
if(TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
|
||||
if(!redraft && TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
|
||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||
}else if(item.scheduledStatus!=null){
|
||||
args.putString("sourceText", item.status.text);
|
||||
@@ -189,7 +218,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onSuccess(GetStatusSourceText.Response result){
|
||||
args.putString("sourceText", result.text);
|
||||
args.putString("sourceSpoiler", result.spoilerText);
|
||||
if (id==R.id.delete_and_redraft) {
|
||||
if (redraft) {
|
||||
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{
|
||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||
}, true);
|
||||
@@ -247,12 +276,36 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
|
||||
}else if(id==R.id.bookmark){
|
||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
|
||||
}else if(id==R.id.manage_user_lists){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", item.parentFragment.getAccountID());
|
||||
args.putString("profileAccount", account.id);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
Nav.go(item.parentFragment.getActivity(), ListTimelinesFragment.class, args);
|
||||
}
|
||||
|
||||
if(!item.status.filterRevealed){
|
||||
this.itemView.setVisibility(View.GONE);
|
||||
ViewGroup.LayoutParams params = this.itemView.getLayoutParams();
|
||||
params.height = 0;
|
||||
params.width = 0;
|
||||
this.itemView.setLayoutParams(params);
|
||||
// item.parentFragment.notifyItemsChanged(this.getAbsoluteAdapterPosition());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
UiUtils.enablePopupMenuIcons(activity, optionsMenu);
|
||||
|
||||
}
|
||||
|
||||
// public void setFilteredShown(){
|
||||
// this.itemView.setVisibility(View.VISIBLE);
|
||||
// params = this.itemView.getLayoutParams();
|
||||
// params.height = 0;
|
||||
// params.width = 0;
|
||||
// this.itemView.setLayoutParams(params);
|
||||
// }
|
||||
|
||||
private void populateAccountsMenu(Menu menu) {
|
||||
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
||||
sessions.stream().filter(s -> !s.getID().equals(item.accountID)).forEach(s -> {
|
||||
@@ -268,8 +321,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(HeaderStatusDisplayItem item){
|
||||
name.setText(item.parsedName);
|
||||
username.setText('@'+item.user.acct);
|
||||
|
||||
username.setCompoundDrawablesWithIntrinsicBounds(item.user.bot ? R.drawable.ic_fluent_bot_24_filled : 0, 0, 0, 0);
|
||||
botIcon.setVisibility(item.user.bot ? View.VISIBLE : View.GONE);
|
||||
botIcon.setColorFilter(username.getCurrentTextColor());
|
||||
separator.setVisibility(View.VISIBLE);
|
||||
|
||||
if (item.scheduledStatus!=null)
|
||||
if (item.scheduledStatus.scheduledAt.isAfter(CreateStatus.DRAFTS_AFTER_INSTANT)) {
|
||||
@@ -278,10 +332,14 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
|
||||
timestamp.setText(item.scheduledStatus.scheduledAt.atZone(ZoneId.systemDefault()).format(formatter));
|
||||
}
|
||||
else if(item.status==null || item.status.editedAt==null)
|
||||
else if ((!item.inset || item.status==null || item.status.editedAt==null) && item.createdAt != null)
|
||||
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
|
||||
else
|
||||
else if (item.status != null && item.status.editedAt != null)
|
||||
timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt)));
|
||||
else {
|
||||
separator.setVisibility(View.GONE);
|
||||
timestamp.setText("");
|
||||
}
|
||||
visibility.setVisibility(item.hasVisibilityToggle && !item.inset ? View.VISIBLE : View.GONE);
|
||||
deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification!=null && !item.inset ? View.VISIBLE : View.GONE);
|
||||
if(item.hasVisibilityToggle){
|
||||
@@ -293,18 +351,71 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
|
||||
if(TextUtils.isEmpty(item.extraText)){
|
||||
extraText.setVisibility(View.GONE);
|
||||
if (item.status != null) {
|
||||
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), extraText, item.status.visibility, item.status.localOnly);
|
||||
}
|
||||
}else{
|
||||
extraText.setVisibility(View.VISIBLE);
|
||||
extraText.setText(item.extraText);
|
||||
}
|
||||
more.setVisibility(item.inset ? View.GONE : View.VISIBLE);
|
||||
more.setVisibility(item.inset || (item.notification != null && item.notification.report != null)
|
||||
? View.GONE : View.VISIBLE);
|
||||
avatar.setClickable(!item.inset);
|
||||
avatar.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct));
|
||||
if(currentRelationshipRequest!=null){
|
||||
currentRelationshipRequest.cancel();
|
||||
}
|
||||
relationship=null;
|
||||
|
||||
String desc;
|
||||
if (item.announcement != null) {
|
||||
if (unreadIndicator.getVisibility() == View.GONE) {
|
||||
more.setAlpha(0f);
|
||||
unreadIndicator.setAlpha(0f);
|
||||
unreadIndicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
float alpha = item.announcement.read ? 0 : 1;
|
||||
more.setImageResource(R.drawable.ic_fluent_checkmark_20_filled);
|
||||
desc = item.parentFragment.getString(R.string.sk_mark_as_read);
|
||||
more.animate().alpha(alpha);
|
||||
unreadIndicator.animate().alpha(alpha);
|
||||
more.setEnabled(!item.announcement.read);
|
||||
more.setOnClickListener(v -> {
|
||||
if (item.announcement.read) return;
|
||||
new DismissAnnouncement(item.announcement.id).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Object o) {
|
||||
item.consumeReadAnnouncement.accept(item.announcement.id);
|
||||
item.announcement.read = true;
|
||||
if (item.parentFragment.getActivity() == null) return;
|
||||
rebind();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(item.parentFragment.getActivity());
|
||||
}
|
||||
}).exec(item.accountID);
|
||||
});
|
||||
} else {
|
||||
more.setImageResource(R.drawable.ic_fluent_more_vertical_20_filled);
|
||||
desc = item.parentFragment.getString(R.string.more_options);
|
||||
more.setOnClickListener(this::onMoreClick);
|
||||
}
|
||||
|
||||
more.setContentDescription(desc);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) more.setTooltipText(desc);
|
||||
|
||||
if (item.status == null || !item.status.textExpandable) {
|
||||
collapseBtn.setVisibility(View.GONE);
|
||||
} else {
|
||||
String collapseText = item.parentFragment.getString(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
||||
collapseBtn.setVisibility(item.status.textExpandable ? View.VISIBLE : View.GONE);
|
||||
collapseBtn.setContentDescription(collapseText);
|
||||
if (GlobalUserPreferences.reduceMotion) collapseBtnIcon.setScaleY(item.status.textExpanded ? -1 : 1);
|
||||
else collapseBtnIcon.animate().scaleY(item.status.textExpanded ? -1 : 1).start();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) collapseBtn.setTooltipText(collapseText);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -325,6 +436,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onAvaClick(View v){
|
||||
if (item.announcement != null) {
|
||||
UiUtils.openURL(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.user.url);
|
||||
return;
|
||||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.user));
|
||||
@@ -356,6 +471,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void updateOptionsMenu(){
|
||||
if (item.parentFragment.getActivity() == null) return;
|
||||
if (item.announcement != null) return;
|
||||
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
|
||||
Menu menu=optionsMenu.getMenu();
|
||||
|
||||
@@ -385,6 +502,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
MenuItem block=menu.findItem(R.id.block);
|
||||
MenuItem report=menu.findItem(R.id.report);
|
||||
MenuItem follow=menu.findItem(R.id.follow);
|
||||
MenuItem manageUserLists = menu.findItem(R.id.manage_user_lists);
|
||||
MenuItem bookmark=menu.findItem(R.id.bookmark);
|
||||
bookmark.setVisible(false);
|
||||
/* disabled in megalodon: add/remove bookmark is already available through status footer
|
||||
@@ -401,6 +519,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
report.setVisible(false);
|
||||
follow.setVisible(false);
|
||||
blockDomain.setVisible(false);
|
||||
manageUserLists.setVisible(false);
|
||||
}else{
|
||||
mute.setVisible(true);
|
||||
block.setVisible(true);
|
||||
@@ -421,6 +540,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
boolean following = relationship!=null && relationship.following;
|
||||
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, account.getShortUsername()));
|
||||
follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular);
|
||||
manageUserLists.setVisible(relationship != null && relationship.following);
|
||||
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user