Compare commits
662 Commits
m3-merger
...
2.1.4+fork
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03cb0b3c47 | ||
|
|
53c454a59b | ||
|
|
d21ae9d94e | ||
|
|
453f082822 | ||
|
|
23843e9002 | ||
|
|
232d2e7e6d | ||
|
|
e9d73cbe32 | ||
|
|
4bb0597edf | ||
|
|
a00afd5d7f | ||
|
|
796bdf2893 | ||
|
|
5b7868b44e | ||
|
|
66e67231c8 | ||
|
|
58be2214a4 | ||
|
|
72a370aa84 | ||
|
|
9a41a2d6fb | ||
|
|
2629e0039c | ||
|
|
2cd98a6620 | ||
|
|
24048a677f | ||
|
|
283b56be5b | ||
|
|
debf8c52a1 | ||
|
|
7fdea9710f | ||
|
|
d2de62ee55 | ||
|
|
d706768112 | ||
|
|
1614213aae | ||
|
|
3045fe0357 | ||
|
|
5a35be0ebd | ||
|
|
22b1cd0d0b | ||
|
|
49f10bacb3 | ||
|
|
2926e5096e | ||
|
|
361f27d5df | ||
|
|
d068093c5a | ||
|
|
3bea1b7f3e | ||
|
|
5a34d9ffce | ||
|
|
240754b70a | ||
|
|
7973ad7042 | ||
|
|
8446dbe05d | ||
|
|
7b0313cc69 | ||
|
|
994ae0242c | ||
|
|
fd34be3c66 | ||
|
|
20785d53fe | ||
|
|
3cc1748231 | ||
|
|
079112b905 | ||
|
|
38aa55b371 | ||
|
|
e07c01018b | ||
|
|
08de4525eb | ||
|
|
062dca6a77 | ||
|
|
d9abb8dfa1 | ||
|
|
5bcbe19435 | ||
|
|
955b985d1a | ||
|
|
6d6ab789a3 | ||
|
|
ad8be2f7a3 | ||
|
|
64be55e406 | ||
|
|
6230f32135 | ||
|
|
30db82fc98 | ||
|
|
921589b9bb | ||
|
|
0b0de7a4e3 | ||
|
|
c790eaf9c0 | ||
|
|
2c7cef488b | ||
|
|
c26603e0ca | ||
|
|
e1e5c9b0bd | ||
|
|
96f0817c20 | ||
|
|
e0165a25f4 | ||
|
|
e465fb944a | ||
|
|
fb159b5a52 | ||
|
|
1a349f0bb8 | ||
|
|
034dfe551c | ||
|
|
1f713dc50e | ||
|
|
258df9546b | ||
|
|
2daabd6a7e | ||
|
|
1e96b76b33 | ||
|
|
6dd944341e | ||
|
|
5dca70c657 | ||
|
|
c6f2e8151f | ||
|
|
99135ebdd6 | ||
|
|
53348be732 | ||
|
|
023c1d1598 | ||
|
|
1d0c591cdb | ||
|
|
a03482fa53 | ||
|
|
bc5fd2530a | ||
|
|
0ca57c3d72 | ||
|
|
5d20697e07 | ||
|
|
2847a1fae0 | ||
|
|
4bd78f31f2 | ||
|
|
e6281f9bb5 | ||
|
|
2c83b5aa02 | ||
|
|
586d48c80e | ||
|
|
fff2c5a3f5 | ||
|
|
3411cf4e56 | ||
|
|
e126858e7a | ||
|
|
9e0b40d409 | ||
|
|
e362efb453 | ||
|
|
8095d3004a | ||
|
|
27f023863b | ||
|
|
af6fae4d86 | ||
|
|
8119049df1 | ||
|
|
b5c09a4398 | ||
|
|
14df6c4e05 | ||
|
|
5c34d38783 | ||
|
|
cfce3595f6 | ||
|
|
411b561a3a | ||
|
|
ff9c211f3c | ||
|
|
c8a650acd4 | ||
|
|
a8cca76392 | ||
|
|
5e2f653d30 | ||
|
|
418f87999c | ||
|
|
c330428c07 | ||
|
|
119537f45f | ||
|
|
9f09b437ab | ||
|
|
1141dd5875 | ||
|
|
a008a0ac9a | ||
|
|
5cf5267e49 | ||
|
|
df2246b71a | ||
|
|
7862fefe94 | ||
|
|
b59957bd90 | ||
|
|
e307f1cca2 | ||
|
|
de185e5a27 | ||
|
|
e1fbe2c96e | ||
|
|
d7babf1e4d | ||
|
|
1203de0015 | ||
|
|
e7148acefb | ||
|
|
88741b0db4 | ||
|
|
12f9a11f03 | ||
|
|
234972352d | ||
|
|
67448f9de5 | ||
|
|
9681256a06 | ||
|
|
7f44c2ca49 | ||
|
|
f355919660 | ||
|
|
928f24b460 | ||
|
|
40e54b165b | ||
|
|
57d6b33b58 | ||
|
|
6b02a2df68 | ||
|
|
6d56771aba | ||
|
|
56a83e66cd | ||
|
|
9f17f1600a | ||
|
|
1724d8a532 | ||
|
|
7b4be83781 | ||
|
|
52030b3b2d | ||
|
|
3b36434dff | ||
|
|
f73be653ce | ||
|
|
808c922637 | ||
|
|
ceef155e03 | ||
|
|
eb5a38d514 | ||
|
|
33d4e05e96 | ||
|
|
968668e38c | ||
|
|
097a18fa92 | ||
|
|
5a85420866 | ||
|
|
50cee02191 | ||
|
|
e7dbb9fdf7 | ||
|
|
e2b2361ad2 | ||
|
|
89fe1dc783 | ||
|
|
eaadd80eeb | ||
|
|
c8063e961e | ||
|
|
3057546c55 | ||
|
|
f90e799caa | ||
|
|
b4cdf35d36 | ||
|
|
cad0ad7a59 | ||
|
|
ca60003c39 | ||
|
|
0f030e0bac | ||
|
|
6d4f212a18 | ||
|
|
183b39bc24 | ||
|
|
27ad0c6fcf | ||
|
|
9944185424 | ||
|
|
72ca3d0204 | ||
|
|
53204a9998 | ||
|
|
c3624eddb3 | ||
|
|
e144d0ac6f | ||
|
|
ac8562aaa2 | ||
|
|
4f8c4c67d2 | ||
|
|
b5f661f1af | ||
|
|
0015f3f0bf | ||
|
|
c5d0fdd645 | ||
|
|
2d09ad44fb | ||
|
|
667fffd124 | ||
|
|
699233d8c7 | ||
|
|
56aabdc4a6 | ||
|
|
443e2c7a6f | ||
|
|
985b0f6e63 | ||
|
|
cc86edf276 | ||
|
|
4071b9342d | ||
|
|
c92ab11dcd | ||
|
|
f3a1e7cf58 | ||
|
|
ebcafbf90d | ||
|
|
b5f7135e38 | ||
|
|
8a3df3657b | ||
|
|
f71d1bc5d3 | ||
|
|
f293619189 | ||
|
|
68ec3499ea | ||
|
|
4b79d35854 | ||
|
|
54ec1d781a | ||
|
|
2b926ffa46 | ||
|
|
6bcdbaba34 | ||
|
|
a2beead3a5 | ||
|
|
e7a25e353d | ||
|
|
af04a01130 | ||
|
|
fe1cfa1d7b | ||
|
|
b248797bb0 | ||
|
|
f24eba08d3 | ||
|
|
0e89559a47 | ||
|
|
d7ab019107 | ||
|
|
858657799f | ||
|
|
d02a72e079 | ||
|
|
3be57d1b0b | ||
|
|
bed550e97c | ||
|
|
7e2619ea75 | ||
|
|
4b22f1d3a7 | ||
|
|
885b3e3c60 | ||
|
|
9dcc7e293f | ||
|
|
6a68cf5e41 | ||
|
|
1edcf847b5 | ||
|
|
f5aff4d262 | ||
|
|
29297be4a3 | ||
|
|
90b87529e0 | ||
|
|
39af05524d | ||
|
|
2c8246341a | ||
|
|
77e19b4d6f | ||
|
|
18857e6233 | ||
|
|
4aee67382b | ||
|
|
3656402277 | ||
|
|
e3fb2cd03c | ||
|
|
56e752c0b3 | ||
|
|
de7bc49f85 | ||
|
|
beeffdff91 | ||
|
|
90f84d628a | ||
|
|
b89e0b5c5a | ||
|
|
2dd983c803 | ||
|
|
bb895c88b2 | ||
|
|
e33d0e692c | ||
|
|
133c941dc8 | ||
|
|
62222cd1d9 | ||
|
|
5d358d79ff | ||
|
|
f724644d84 | ||
|
|
f71c4b1374 | ||
|
|
a921a925b0 | ||
|
|
0f08af8c03 | ||
|
|
d54a59dbee | ||
|
|
c6cd3995b0 | ||
|
|
54d06b7a23 | ||
|
|
1ec0505d52 | ||
|
|
be225f8bb4 | ||
|
|
f5732fd2ae | ||
|
|
17fc14de97 | ||
|
|
7c9d11fab0 | ||
|
|
24abf9d317 | ||
|
|
22bf8840d5 | ||
|
|
a1fbb733ce | ||
|
|
851fa70563 | ||
|
|
6cab15b4c6 | ||
|
|
aac89c354c | ||
|
|
a032f9af10 | ||
|
|
ec3670d562 | ||
|
|
96c3bd890a | ||
|
|
8a0852d1a4 | ||
|
|
77e2c6c09f | ||
|
|
af60c8ba4a | ||
|
|
70a2f1fc0b | ||
|
|
b7d06a47db | ||
|
|
f0a5799eea | ||
|
|
f0b2a25dce | ||
|
|
de1a3fb939 | ||
|
|
823344dfc0 | ||
|
|
a6e6143157 | ||
|
|
b5feab89b4 | ||
|
|
c2c31de433 | ||
|
|
775f799968 | ||
|
|
5cda112129 | ||
|
|
642aaec6da | ||
|
|
f69ae5e816 | ||
|
|
91c7fc64bd | ||
|
|
91211b7720 | ||
|
|
ff667d6aed | ||
|
|
5e98496ea6 | ||
|
|
972fe1d15b | ||
|
|
26eaa36faa | ||
|
|
c517f41595 | ||
|
|
56a6d7243f | ||
|
|
18e43dfc22 | ||
|
|
816f6370ef | ||
|
|
51ef732d50 | ||
|
|
31a7aa9d40 | ||
|
|
30866a5292 | ||
|
|
3e1403d18a | ||
|
|
ebc2b2e59d | ||
|
|
10c2ee9597 | ||
|
|
c9a796dbfe | ||
|
|
1ba185ea9c | ||
|
|
1a50c3ff5f | ||
|
|
35a85c3247 | ||
|
|
6a729fa97f | ||
|
|
923639a329 | ||
|
|
fd99879ffc | ||
|
|
a78be8bc1d | ||
|
|
abfb497577 | ||
|
|
a10b184508 | ||
|
|
f0ea6660e6 | ||
|
|
a829f25d56 | ||
|
|
deff3dd8e0 | ||
|
|
ed6aa0725e | ||
|
|
6fd9047a8e | ||
|
|
48d7635a56 | ||
|
|
9515edea4b | ||
|
|
dab596f527 | ||
|
|
0c18ab2319 | ||
|
|
652c5e1d16 | ||
|
|
e1d9cc4add | ||
|
|
d51cd2d497 | ||
|
|
39d1b69014 | ||
|
|
74a9e49190 | ||
|
|
b0763473c7 | ||
|
|
4e5bbe6f9b | ||
|
|
6c5fb5ea09 | ||
|
|
afe0c9e0db | ||
|
|
1f2213042f | ||
|
|
5edd2466f9 | ||
|
|
f3b3a1a577 | ||
|
|
068619b815 | ||
|
|
f121e94979 | ||
|
|
b5b52529d4 | ||
|
|
876bf73454 | ||
|
|
522dbf6e4a | ||
|
|
ae685095ba | ||
|
|
30d5fe2f12 | ||
|
|
2bf27c561c | ||
|
|
bbdc72323d | ||
|
|
6e335930f3 | ||
|
|
9b309939da | ||
|
|
faf2e5115d | ||
|
|
dc5d9412c8 | ||
|
|
fc0680d66f | ||
|
|
56c9a5433f | ||
|
|
60e473ee55 | ||
|
|
ae34ecd5c3 | ||
|
|
fd1caa8729 | ||
|
|
1182e5c60c | ||
|
|
d99d515dfa | ||
|
|
70a15e7d9c | ||
|
|
1691382369 | ||
|
|
b7da9c6d51 | ||
|
|
3426538dca | ||
|
|
63de2b200b | ||
|
|
ff1ee766dc | ||
|
|
f033411adf | ||
|
|
a738eaf8c0 | ||
|
|
5074aadd6e | ||
|
|
0854961470 | ||
|
|
227b077935 | ||
|
|
1e4358290a | ||
|
|
925169eb31 | ||
|
|
e1abeb9252 | ||
|
|
cbe0add211 | ||
|
|
299b524d62 | ||
|
|
31c094e696 | ||
|
|
a8038a2863 | ||
|
|
29933bb916 | ||
|
|
5ec0c078d8 | ||
|
|
e6287f1ff2 | ||
|
|
be9caf8905 | ||
|
|
f375142084 | ||
|
|
fd3668d520 | ||
|
|
d5e03e9d9e | ||
|
|
d62f094919 | ||
|
|
6d84f28600 | ||
|
|
209e603f2c | ||
|
|
1b4dc01c74 | ||
|
|
6aab8f6578 | ||
|
|
645af12c3f | ||
|
|
fadc42d72b | ||
|
|
fc831e7d42 | ||
|
|
1ba30a4d28 | ||
|
|
2998ee9145 | ||
|
|
971c4e5879 | ||
|
|
48c53ee88b | ||
|
|
acf1fa15da | ||
|
|
1c3b28f9d7 | ||
|
|
e274cf89c0 | ||
|
|
8929482466 | ||
|
|
b570c8d946 | ||
|
|
b396ee7987 | ||
|
|
90856a414a | ||
|
|
ea19925be6 | ||
|
|
03b3775843 | ||
|
|
38b39751ae | ||
|
|
54a4b0fe41 | ||
|
|
3bf591c944 | ||
|
|
584a6bbfa3 | ||
|
|
0f803cd4fa | ||
|
|
167a14b8db | ||
|
|
81cbc2d10c | ||
|
|
9bd8aff99b | ||
|
|
a770828165 | ||
|
|
ab457035ff | ||
|
|
f886e4c1d2 | ||
|
|
380e4ff77e | ||
|
|
58f0c07357 | ||
|
|
77dee59b9c | ||
|
|
464dc93d99 | ||
|
|
dcdfd3e5d3 | ||
|
|
646f83ff0a | ||
|
|
fdf0414698 | ||
|
|
cc699a3f5e | ||
|
|
12eaa8d5f1 | ||
|
|
70680e39c6 | ||
|
|
8bd8f90d58 | ||
|
|
54b53a266e | ||
|
|
66921e3b5a | ||
|
|
9d7af3964b | ||
|
|
ec73687e9b | ||
|
|
c8af800b88 | ||
|
|
e74ac5da56 | ||
|
|
efa003a9a5 | ||
|
|
de5165434d | ||
|
|
be648cc5ab | ||
|
|
90f1f464dc | ||
|
|
734aa52816 | ||
|
|
068d42175e | ||
|
|
2314871246 | ||
|
|
e4f13c900b | ||
|
|
4ddfa483d4 | ||
|
|
20f41ce7c9 | ||
|
|
c8df9e085e | ||
|
|
0238aa4375 | ||
|
|
79d7873790 | ||
|
|
996842489d | ||
|
|
23c2c2b5e7 | ||
|
|
9dd694ce2e | ||
|
|
f51f2a1197 | ||
|
|
3020cab243 | ||
|
|
be73c9e81c | ||
|
|
1c2183bf1a | ||
|
|
da54c93af1 | ||
|
|
777891ebe9 | ||
|
|
9836538562 | ||
|
|
4a443d4826 | ||
|
|
1c93d1fa67 | ||
|
|
0246059556 | ||
|
|
46382bd8b0 | ||
|
|
090d2658dd | ||
|
|
e47fa4f8db | ||
|
|
8338fff4e5 | ||
|
|
b2d09c2ab6 | ||
|
|
0aa4493e5c | ||
|
|
6e2a5437e8 | ||
|
|
ae61007d22 | ||
|
|
20f3c7c9db | ||
|
|
1789d90dc3 | ||
|
|
57306ff7fe | ||
|
|
bbdd1c4aa9 | ||
|
|
81d13537e3 | ||
|
|
b6c951c026 | ||
|
|
860cdb05e2 | ||
|
|
e239600af6 | ||
|
|
d7bef67550 | ||
|
|
ad86a00fa7 | ||
|
|
da13926269 | ||
|
|
9da0173215 | ||
|
|
cfeda06bf9 | ||
|
|
ca60ac6722 | ||
|
|
80f70bd530 | ||
|
|
3982fa4835 | ||
|
|
31e720690c | ||
|
|
560c75c669 | ||
|
|
fb441b21e0 | ||
|
|
e2f957f985 | ||
|
|
a4f97a134b | ||
|
|
18ae9efad3 | ||
|
|
8232047c55 | ||
|
|
fb3b9513f2 | ||
|
|
2ee4b03fe8 | ||
|
|
61b20eea05 | ||
|
|
2aba90f353 | ||
|
|
5065c7e7e2 | ||
|
|
a10e661b21 | ||
|
|
4975bde76f | ||
|
|
ebbd56e3bc | ||
|
|
454ec6b4c0 | ||
|
|
10a8b195b1 | ||
|
|
6f273df060 | ||
|
|
7cfade62d3 | ||
|
|
0e58b00086 | ||
|
|
9f18844148 | ||
|
|
d854da54fe | ||
|
|
33f4dd4716 | ||
|
|
93ca187f1c | ||
|
|
6fd59a19e8 | ||
|
|
531ce568c0 | ||
|
|
c49b25454e | ||
|
|
d39d5c2602 | ||
|
|
0a338ad607 | ||
|
|
0cb3e1863e | ||
|
|
209081f1f0 | ||
|
|
f0eb6573f4 | ||
|
|
e7f5dd3357 | ||
|
|
8101bb9ea1 | ||
|
|
54d48253d5 | ||
|
|
3373b2bb04 | ||
|
|
a7e23aa228 | ||
|
|
a2d0738a68 | ||
|
|
8cd55fc365 | ||
|
|
924d6a9dd9 | ||
|
|
816a307bc0 | ||
|
|
a947a6ea26 | ||
|
|
f14df2bb0f | ||
|
|
b5123ff865 | ||
|
|
ca4630e6e1 | ||
|
|
a8a1c7824d | ||
|
|
0725c2a741 | ||
|
|
c3f3a9be7e | ||
|
|
228fdc8ffe | ||
|
|
da2a69b4a1 | ||
|
|
822a6a3653 | ||
|
|
410f694863 | ||
|
|
57b2596d9f | ||
|
|
caebce897b | ||
|
|
677fb4e4da | ||
|
|
ce51fc3c0d | ||
|
|
0dcade767b | ||
|
|
f4f4cbe78b | ||
|
|
ebb6897582 | ||
|
|
9720a133ba | ||
|
|
29c36e90ab | ||
|
|
1b822676a8 | ||
|
|
2d24e50ff2 | ||
|
|
53369eb2d4 | ||
|
|
aeb0aa65a8 | ||
|
|
e9df125cde | ||
|
|
807010893a | ||
|
|
ea01b14ffb | ||
|
|
3fd9dc1dcd | ||
|
|
02a4a77885 | ||
|
|
651090a504 | ||
|
|
203254c9f4 | ||
|
|
74db111a4f | ||
|
|
16ef577a7a | ||
|
|
734b3bced6 | ||
|
|
e26c641dc7 | ||
|
|
9295cf4e9c | ||
|
|
dd9237e9ca | ||
|
|
ea81c1fad6 | ||
|
|
d334703c65 | ||
|
|
befa5a9f6d | ||
|
|
5f6f3c94c9 | ||
|
|
09ba42a974 | ||
|
|
87a9acd860 | ||
|
|
b1e43d6f97 | ||
|
|
ea92a61d13 | ||
|
|
7fda69a6aa | ||
|
|
cf8b9ac649 | ||
|
|
d871d2cad8 | ||
|
|
323018daa9 | ||
|
|
3c122b005d | ||
|
|
f9dc6105f4 | ||
|
|
d7fe3c80e6 | ||
|
|
e996deea0b | ||
|
|
67d89f5799 | ||
|
|
485d25f9f4 | ||
|
|
c4b7262e89 | ||
|
|
7191e5a96a | ||
|
|
0edaffee59 | ||
|
|
c859d35c93 | ||
|
|
17a59b5db4 | ||
|
|
580ae15af6 | ||
|
|
d76e823489 | ||
|
|
7c3b4128de | ||
|
|
da5929c092 | ||
|
|
77fca4d763 | ||
|
|
e66078e52e | ||
|
|
f84356f5d0 | ||
|
|
1fa5a8436b | ||
|
|
2a471ffa96 | ||
|
|
1ee5e1ab3a | ||
|
|
4d90cad034 | ||
|
|
45615b1fc5 | ||
|
|
9fd0e7fea4 | ||
|
|
696016bd8f | ||
|
|
cc46e09853 | ||
|
|
83e84836b5 | ||
|
|
14bb544344 | ||
|
|
ceec18ff5e | ||
|
|
d36ad43700 | ||
|
|
d93bcd9241 | ||
|
|
cfad3144f8 | ||
|
|
69272e891e | ||
|
|
576030f262 | ||
|
|
fb39f74ba5 | ||
|
|
272afca0fb | ||
|
|
9bfc73d6ee | ||
|
|
d0e3da67a4 | ||
|
|
1ecb9820c0 | ||
|
|
efb72eace9 | ||
|
|
84b15f4a49 | ||
|
|
2c793fd83b | ||
|
|
8810ac52f6 | ||
|
|
9442de7e5a | ||
|
|
4f40ecd112 | ||
|
|
5456f4fbea | ||
|
|
b294c998ae | ||
|
|
b7b7ef4faf | ||
|
|
6a736ef387 | ||
|
|
2a7d7d2e45 | ||
|
|
bdf822d6a5 | ||
|
|
a9cd67a64e | ||
|
|
a3ee20f06e | ||
|
|
2158a1abae | ||
|
|
52955b6c42 | ||
|
|
8524e57ecd | ||
|
|
639ecba380 | ||
|
|
ed6961016e | ||
|
|
5d1d6e4bd7 | ||
|
|
e87f47228f | ||
|
|
8e038b204c | ||
|
|
900b204bb0 | ||
|
|
17d679901a | ||
|
|
d8036779f8 | ||
|
|
3a35674ea2 | ||
|
|
3235cf1c4f | ||
|
|
3f6bda28b3 | ||
|
|
c0b4f4dd79 | ||
|
|
2a913e26e7 | ||
|
|
f60375cd5f | ||
|
|
5920270899 | ||
|
|
f36aee44c6 | ||
|
|
cd24526a9d | ||
|
|
a345ac1390 | ||
|
|
3d987b8e1d | ||
|
|
57043912e0 | ||
|
|
00aef5ea6b | ||
|
|
369b69668c | ||
|
|
65245f4560 | ||
|
|
4d4fdc97d4 | ||
|
|
c96577891c | ||
|
|
f48b2fc9cb | ||
|
|
2fca2580ed | ||
|
|
7adc1da361 | ||
|
|
6dc24dde43 | ||
|
|
4929e0e6ec | ||
|
|
16a8b8ed71 | ||
|
|
ad1412817e | ||
|
|
d9e6bb3bea | ||
|
|
8970404638 | ||
|
|
2a2241d7f9 | ||
|
|
db1a47e8eb | ||
|
|
3e57061cef | ||
|
|
cd200f8450 | ||
|
|
782013079f | ||
|
|
e5db8acd66 | ||
|
|
1a6a8019c8 | ||
|
|
e935eef29f | ||
|
|
381defda51 | ||
|
|
02ae80c204 | ||
|
|
82214b30e8 | ||
|
|
33a1f48602 | ||
|
|
aee845e5cc | ||
|
|
cd780f6006 | ||
|
|
d4741fefa0 | ||
|
|
7e1e8a2616 | ||
|
|
d73c05cdfc | ||
|
|
78323023cb | ||
|
|
2cf084c98f | ||
|
|
009dc226d5 | ||
|
|
94ab5835d8 | ||
|
|
ae5d2a7ed3 | ||
|
|
6ed7d24513 | ||
|
|
605ba441d3 |
2
.github/workflows/nightly-builds.yml
vendored
2
.github/workflows/nightly-builds.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
- name: Checkout Appkit Repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: grishka/appkit
|
||||
repository: LucasGGamerM/appkit
|
||||
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
|
||||
0
fix-metadata-markdown-lists.sh
Executable file → Normal file
0
fix-metadata-markdown-lists.sh
Executable file → Normal file
@@ -16,8 +16,8 @@ android {
|
||||
applicationId "org.joinmastodon.android.moshinda"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 101
|
||||
versionName "1.2.3+fork.101.moshinda"
|
||||
versionCode 102
|
||||
versionName "2.1.4+fork.102.moshinda"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
||||
}
|
||||
@@ -118,7 +118,7 @@ dependencies {
|
||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||
implementation 'me.grishka.litex:palette:1.0.0'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.9'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.14'
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
|
||||
@@ -19,7 +19,9 @@ public class StatusFilterPredicateTest {
|
||||
|
||||
private static final Status
|
||||
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
|
||||
warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now());
|
||||
warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now()),
|
||||
noAltText = Status.ofFake(null, "display me with a warning", Instant.now()),
|
||||
withAltText = Status.ofFake(null, "display me with a warning", Instant.now());
|
||||
|
||||
static {
|
||||
hideMeFilter.phrase = "hide me";
|
||||
@@ -29,6 +31,12 @@ public class StatusFilterPredicateTest {
|
||||
warnMeFilter.phrase = "warning";
|
||||
warnMeFilter.filterAction = WARN;
|
||||
warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
|
||||
|
||||
noAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable());
|
||||
withAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable());
|
||||
for (Attachment mediaAttachment : withAltText.mediaAttachments) {
|
||||
mediaAttachment.description = "Alt Text";
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,4 +86,16 @@ public class StatusFilterPredicateTest {
|
||||
assertTrue("should pass because matching filter is for hiding",
|
||||
new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAltTextFilterNoPass() {
|
||||
assertFalse("should not pass because of no alt text",
|
||||
new StatusFilterPredicate(allFilters, HOME).test(noAltText));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAltTextFilterPass() {
|
||||
assertTrue("should pass because of alt text",
|
||||
new StatusFilterPredicate(allFilters, HOME).test(withAltText));
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
import org.joinmastodon.android.ui.utils.ColorPalette;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
@@ -27,6 +26,8 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
public class GlobalUserPreferences{
|
||||
private static final String TAG="GlobalUserPreferences";
|
||||
|
||||
@@ -62,11 +63,13 @@ public class GlobalUserPreferences{
|
||||
public static boolean showNavigationLabels;
|
||||
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
|
||||
public static boolean overlayMedia;
|
||||
public static boolean showSuicideHelp;
|
||||
|
||||
// MOSHIDON
|
||||
public static boolean showDividers;
|
||||
public static boolean relocatePublishButton;
|
||||
public static boolean defaultToUnlistedReplies;
|
||||
public static boolean doubleTapToSearch;
|
||||
public static boolean doubleTapToSwipe;
|
||||
public static boolean confirmBeforeReblog;
|
||||
public static boolean hapticFeedback;
|
||||
@@ -74,6 +77,8 @@ public class GlobalUserPreferences{
|
||||
public static boolean swapBookmarkWithBoostAction;
|
||||
public static boolean loadRemoteAccountFollowers;
|
||||
public static boolean mentionRebloggerAutomatically;
|
||||
public static boolean showPostsWithoutAlt;
|
||||
public static boolean showMediaPreview;
|
||||
|
||||
public static SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||
@@ -133,6 +138,7 @@ public class GlobalUserPreferences{
|
||||
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
|
||||
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
|
||||
overlayMedia=prefs.getBoolean("overlayMedia", false);
|
||||
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
|
||||
|
||||
// MOSHIDON
|
||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
||||
@@ -140,6 +146,7 @@ public class GlobalUserPreferences{
|
||||
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
|
||||
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
||||
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
|
||||
doubleTapToSearch =prefs.getBoolean("doubleTapToSearch", true);
|
||||
doubleTapToSwipe =prefs.getBoolean("doubleTapToSwipe", true);
|
||||
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
|
||||
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
|
||||
@@ -147,6 +154,9 @@ public class GlobalUserPreferences{
|
||||
swapBookmarkWithBoostAction=prefs.getBoolean("swapBookmarkWithBoostAction", false);
|
||||
loadRemoteAccountFollowers=prefs.getBoolean("loadRemoteAccountFollowers", true);
|
||||
mentionRebloggerAutomatically=prefs.getBoolean("mentionRebloggerAutomatically", false);
|
||||
showPostsWithoutAlt=prefs.getBoolean("showPostsWithoutAlt", true);
|
||||
showMediaPreview=prefs.getBoolean("showMediaPreview", true);
|
||||
|
||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||
|
||||
|
||||
@@ -211,19 +221,25 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("displayPronounsInThreads", displayPronounsInThreads)
|
||||
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
|
||||
.putBoolean("overlayMedia", overlayMedia)
|
||||
.putBoolean("showSuicideHelp", showSuicideHelp)
|
||||
|
||||
// MOSHIDON
|
||||
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
|
||||
.putBoolean("doubleTapToSearch", doubleTapToSearch)
|
||||
.putBoolean("doubleTapToSwipe", doubleTapToSwipe)
|
||||
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
||||
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
|
||||
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
|
||||
.putBoolean("swapBookmarkWithBoostAction", swapBookmarkWithBoostAction)
|
||||
.putBoolean("loadRemoteAccountFollowers", loadRemoteAccountFollowers)
|
||||
.putBoolean("hapticFeedback", hapticFeedback)
|
||||
.putBoolean("mentionRebloggerAutomatically", mentionRebloggerAutomatically)
|
||||
.putBoolean("showDividers", showDividers)
|
||||
.putBoolean("relocatePublishButton", relocatePublishButton)
|
||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||
.putBoolean("showPostsWithoutAlt", showPostsWithoutAlt)
|
||||
.putBoolean("showMediaPreview", showMediaPreview)
|
||||
|
||||
.putInt("theme", theme.ordinal())
|
||||
|
||||
.apply();
|
||||
@@ -281,8 +297,8 @@ public class GlobalUserPreferences{
|
||||
|
||||
public enum ColorPreference{
|
||||
MATERIAL3,
|
||||
PINK,
|
||||
PURPLE,
|
||||
PINK,
|
||||
GREEN,
|
||||
BLUE,
|
||||
BROWN,
|
||||
|
||||
@@ -151,7 +151,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||
}
|
||||
|
||||
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
|
||||
new GetSearchResults(q, null, true)
|
||||
new GetSearchResults(q, null, true, null, 0, 0)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
|
||||
@@ -182,6 +182,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
|
||||
.map(type->{
|
||||
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.setLightColor(context.getColor(R.color.primary_700));
|
||||
channel.enableLights(true);
|
||||
channel.setGroup(accountID);
|
||||
return channel;
|
||||
})
|
||||
@@ -211,6 +213,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
.setShowWhen(true)
|
||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||
.setAutoCancel(true)
|
||||
.setLights(context.getColor(R.color.primary_700), 500, 1000)
|
||||
.setColor(context.getColor(R.color.shortcut_icon_background));
|
||||
|
||||
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
||||
@@ -336,11 +339,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
|
||||
CreateStatus.Request req=new CreateStatus.Request();
|
||||
req.status = initialText + input.toString();
|
||||
req.language = preferences.postingDefaultLanguage;
|
||||
req.visibility = preferences.postingDefaultVisibility;
|
||||
req.language = notification.status.language;
|
||||
req.visibility = (notification.status.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : notification.status.visibility);
|
||||
req.inReplyToId = notification.status.id;
|
||||
|
||||
if (!notification.status.spoilerText.isEmpty() &&
|
||||
if (notification.status.hasSpoiler() &&
|
||||
(GlobalUserPreferences.prefixReplies == ALWAYS
|
||||
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(notification.status.account.id)))
|
||||
&& !notification.status.spoilerText.startsWith("re: ")) {
|
||||
|
||||
@@ -27,9 +27,9 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
|
||||
public void onNewEndpoint(@NotNull Context context, @NotNull String endpoint, @NotNull String instance) {
|
||||
// Called when a new endpoint be used for sending push messages
|
||||
Log.d(TAG, "onNewEndpoint: New Endpoint " + endpoint + " for "+ instance);
|
||||
AccountSession account = AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||
if (account != null)
|
||||
account.getPushSubscriptionManager().registerAccountForPush(null);
|
||||
account.getPushSubscriptionManager().registerAccountForPush(null, endpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,7 +37,7 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
|
||||
// called when the registration is not possible, eg. no network
|
||||
Log.d(TAG, "onRegistrationFailed: " + instance);
|
||||
//re-register for gcm
|
||||
AccountSession account = AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||
if (account != null)
|
||||
account.getPushSubscriptionManager().registerAccountForPush(null);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
|
||||
// called when this application is unregistered from receiving push messages
|
||||
Log.d(TAG, "onUnregistered: " + instance);
|
||||
//re-register for gcm
|
||||
AccountSession account = AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||
if (account != null)
|
||||
account.getPushSubscriptionManager().registerAccountForPush(null);
|
||||
}
|
||||
@@ -55,7 +55,10 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
|
||||
@Override
|
||||
public void onMessage(@NotNull Context context, @NotNull byte[] message, @NotNull String instance) {
|
||||
// Called when a new message is received. The message contains the full POST body of the push message
|
||||
AccountSession account = AccountSessionManager.getInstance().getAccount(instance);
|
||||
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||
|
||||
if (account == null)
|
||||
return;
|
||||
|
||||
//this is stupid
|
||||
// Mastodon stores the info to decrypt the message in the HTTP headers, which are not accessible in UnifiedPush,
|
||||
|
||||
@@ -97,7 +97,7 @@ public class PushSubscriptionManager{
|
||||
deviceToken=getPrefs().getString("deviceToken", null);
|
||||
int tokenVersion=getPrefs().getInt("version", 0);
|
||||
if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){
|
||||
registerAllAccountsForPush(false);
|
||||
registerAllAccountsForPush(true); // TODO: revert this before release
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");
|
||||
@@ -125,17 +125,16 @@ public class PushSubscriptionManager{
|
||||
// this function is used for registering push notifications using FCM
|
||||
// to avoid NonFreeNet in F-Droid, this registration is disabled in it
|
||||
// see https://github.com/LucasGGamerM/moshidon/issues/206 for more context
|
||||
if(BuildConfig.BUILD_TYPE.equals("fdroidRelease"))
|
||||
if(BuildConfig.BUILD_TYPE.equals("fdroidRelease") || TextUtils.isEmpty(deviceToken)){
|
||||
Log.d(TAG, "Skipping registering for FCM push notifications");
|
||||
return;
|
||||
}
|
||||
|
||||
if(TextUtils.isEmpty(deviceToken))
|
||||
throw new IllegalStateException("No device push token available");
|
||||
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
||||
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/";
|
||||
registerAccountForPush(subscription, endpoint);
|
||||
}
|
||||
|
||||
public void registerAccountForPush(PushSubscription subscription, String endpoint){
|
||||
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
Log.d(TAG, "registerAccountForPush: started for "+accountID);
|
||||
String encodedPublicKey, encodedAuthKey, pushAccountID;
|
||||
@@ -164,7 +163,13 @@ public class PushSubscriptionManager{
|
||||
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
|
||||
return;
|
||||
}
|
||||
new RegisterForPushNotifications(endpoint,
|
||||
|
||||
//work-around for adding the randomAccountId
|
||||
String newEndpoint = endpoint;
|
||||
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
|
||||
newEndpoint += pushAccountID;
|
||||
|
||||
new RegisterForPushNotifications(newEndpoint,
|
||||
encodedPublicKey,
|
||||
encodedAuthKey,
|
||||
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -23,6 +24,7 @@ public class StatusInteractionController{
|
||||
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusMuted> runningMuteRequests=new HashMap<>();
|
||||
|
||||
public StatusInteractionController(String accountID, boolean updateCounters) {
|
||||
this.accountID=accountID;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
public class GetAccountBlocks extends HeaderPaginationRequest<Account>{
|
||||
public GetAccountBlocks(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/blocks", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
public class GetAccountMutes extends HeaderPaginationRequest<Account>{
|
||||
public GetAccountMutes(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/mutes/", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,19 @@ import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
|
||||
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){
|
||||
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable, Boolean indexable){
|
||||
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
|
||||
setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
|
||||
setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public Boolean locked, discoverable;
|
||||
public Boolean locked, discoverable, indexable;
|
||||
public RequestSource source;
|
||||
|
||||
public Request(Boolean locked, Boolean discoverable, RequestSource source){
|
||||
public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){
|
||||
this.locked=locked;
|
||||
this.discoverable=discoverable;
|
||||
this.indexable=indexable;
|
||||
this.source=source;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import org.joinmastodon.android.model.FilterContext;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
class FilterRequest{
|
||||
public String title;
|
||||
public EnumSet<FilterContext> context;
|
||||
|
||||
@@ -4,13 +4,19 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
|
||||
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
|
||||
public GetSearchResults(String query, Type type, boolean resolve){
|
||||
public GetSearchResults(String query, Type type, boolean resolve, String maxID, int offset, int count){
|
||||
super(HttpMethod.GET, "/search", SearchResults.class);
|
||||
addQueryParameter("q", query);
|
||||
if(type!=null)
|
||||
addQueryParameter("type", type.name().toLowerCase());
|
||||
if(resolve)
|
||||
addQueryParameter("resolve", "true");
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(offset>0)
|
||||
addQueryParameter("offset", String.valueOf(offset));
|
||||
if(count>0)
|
||||
addQueryParameter("limit", String.valueOf(count));
|
||||
}
|
||||
|
||||
public GetSearchResults limit(int limit){
|
||||
|
||||
@@ -11,13 +11,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||
public static final Instant DRAFTS_AFTER_INSTANT = Instant.ofEpochMilli(253370764799999L) /* end of 9998 */;
|
||||
private static final float draftFactor = 31536000000f /* one year */ / 253370764799999f /* end of 9998 */;
|
||||
public static long EPOCH_OF_THE_YEAR_FIVE_THOUSAND=95617584000000L;
|
||||
public static final Instant DRAFTS_AFTER_INSTANT=Instant.ofEpochMilli(EPOCH_OF_THE_YEAR_FIVE_THOUSAND - 1) /* end of 4999 */;
|
||||
|
||||
public static Instant getDraftInstant() {
|
||||
// returns an instant between 9999-01-01 00:00:00 and 9999-12-31 23:59:59
|
||||
// yes, this is a weird implementation for something that hardly matters
|
||||
return DRAFTS_AFTER_INSTANT.plusMillis(1 + (long) (System.currentTimeMillis() * draftFactor));
|
||||
return DRAFTS_AFTER_INSTANT.plusMillis(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public CreateStatus(CreateStatus.Request req, String uuid){
|
||||
@@ -36,6 +34,7 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||
|
||||
public static class Request{
|
||||
public String status;
|
||||
public List<MediaAttribute> mediaAttributes;
|
||||
public List<String> mediaIds;
|
||||
public Poll poll;
|
||||
public String inReplyToId;
|
||||
@@ -55,5 +54,17 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||
public boolean multiple;
|
||||
public boolean hideTotals;
|
||||
}
|
||||
|
||||
public static class MediaAttribute{
|
||||
public String id;
|
||||
public String description;
|
||||
public String focus;
|
||||
|
||||
public MediaAttribute(String id, String description, String focus){
|
||||
this.id=id;
|
||||
this.description=description;
|
||||
this.focus=focus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class SetStatusMuted extends MastodonAPIRequest<Status>{
|
||||
public SetStatusMuted(String id, boolean muted){
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(muted ? "mute" : "unmute"), Status.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.tags;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
public class GetTag extends MastodonAPIRequest<Hashtag>{
|
||||
public GetTag(String tag){
|
||||
super(HttpMethod.GET, "/tags/"+tag, Hashtag.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.tags;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
public class SetTagFollowed extends MastodonAPIRequest<Hashtag>{
|
||||
public SetTagFollowed(String tag, boolean followed){
|
||||
super(HttpMethod.POST, "/tags/"+tag+(followed ? "/follow" : "/unfollow"), Hashtag.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public class AccountLocalPreferences{
|
||||
public boolean keepOnlyLatestNotification;
|
||||
|
||||
public boolean emojiReactionsEnabled;
|
||||
public boolean showEmojiReactionsInLists;
|
||||
public ShowEmojiReactions showEmojiReactions;
|
||||
|
||||
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
|
||||
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
|
||||
@@ -73,7 +73,7 @@ public class AccountLocalPreferences{
|
||||
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
|
||||
showEmojiReactionsInLists=prefs.getBoolean("showEmojiReactionsInLists", false);
|
||||
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
||||
|
||||
// MOSHIDON
|
||||
recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
|
||||
@@ -109,10 +109,16 @@ public class AccountLocalPreferences{
|
||||
.putString("timelineReplyVisibility", timelineReplyVisibility)
|
||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
|
||||
.putBoolean("showEmojiReactionsInLists", showEmojiReactionsInLists)
|
||||
.putString("showEmojiReactions", showEmojiReactions.name())
|
||||
|
||||
// MOSHIDON
|
||||
.putString("recentEmojis", gson.toJson(recentEmojis))
|
||||
.apply();
|
||||
}
|
||||
|
||||
public enum ShowEmojiReactions{
|
||||
HIDE_EMPTY,
|
||||
ONLY_OPENED,
|
||||
ALWAYS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,9 @@ public class AccountSession{
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
Log.w(TAG, "Failed to load preferences for account "+getID()+": "+error);
|
||||
if (preferences==null)
|
||||
preferences=new Preferences();
|
||||
preferencesFromAccountSource(self);
|
||||
}
|
||||
})
|
||||
.exec(getID());
|
||||
@@ -216,7 +219,7 @@ public class AccountSession{
|
||||
|
||||
public void savePreferencesIfPending(){
|
||||
if(preferencesNeedSaving){
|
||||
new UpdateAccountCredentialsPreferences(preferences, null, null)
|
||||
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account result){
|
||||
@@ -310,4 +313,14 @@ public class AccountSession{
|
||||
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
||||
.build();
|
||||
}
|
||||
|
||||
public String getDefaultAvatarUrl() {
|
||||
return getInstance()
|
||||
.map(instance->"https://"+domain+(instance.isAkkoma() ? "/images/avi.png" : "/avatars/original/missing.png"))
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public void updateAccountInfo(){
|
||||
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ public class AccountSessionManager{
|
||||
}
|
||||
|
||||
|
||||
private void updateSessionLocalInfo(AccountSession session){
|
||||
public void updateSessionLocalInfo(AccountSession session){
|
||||
new GetOwnAccount()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import org.joinmastodon.android.model.EmojiReaction;
|
||||
import java.util.List;
|
||||
|
||||
public class EmojiReactionsUpdatedEvent{
|
||||
public final String id;
|
||||
public final List<EmojiReaction> reactions;
|
||||
public final boolean updateTextPadding;
|
||||
public RecyclerView.ViewHolder viewHolder;
|
||||
|
||||
public EmojiReactionsUpdatedEvent(String id, List<EmojiReaction> reactions, boolean updateTextPadding, RecyclerView.ViewHolder viewHolder){
|
||||
this.id=id;
|
||||
this.reactions=reactions;
|
||||
this.updateTextPadding=updateTextPadding;
|
||||
this.viewHolder=viewHolder;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,14 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.api.CacheController;
|
||||
import org.joinmastodon.android.model.EmojiReaction;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class StatusCountersUpdatedEvent{
|
||||
public String id;
|
||||
public long favorites, reblogs, replies;
|
||||
public boolean favorited, reblogged, bookmarked, pinned;
|
||||
public List<EmojiReaction> reactions;
|
||||
public Status status;
|
||||
public RecyclerView.ViewHolder viewHolder;
|
||||
|
||||
public StatusCountersUpdatedEvent(Status s){
|
||||
this(s, null);
|
||||
}
|
||||
|
||||
public StatusCountersUpdatedEvent(Status s, RecyclerView.ViewHolder vh){
|
||||
id=s.id;
|
||||
status=s;
|
||||
favorites=s.favouritesCount;
|
||||
@@ -31,7 +18,5 @@ public class StatusCountersUpdatedEvent{
|
||||
reblogged=s.reblogged;
|
||||
bookmarked=s.bookmarked;
|
||||
pinned=s.pinned;
|
||||
reactions=new ArrayList<>(s.reactions);
|
||||
viewHolder=vh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
public class StatusMuteChangedEvent{
|
||||
public String id;
|
||||
public boolean muted;
|
||||
public Status status;
|
||||
|
||||
public StatusMuteChangedEvent(Status s){
|
||||
id=s.id;
|
||||
muted=s.muted;
|
||||
status=s;
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.PreviewlessMediaGridStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
@@ -51,6 +52,7 @@ 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.MediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||
@@ -90,6 +92,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected Rect tmpRect=new Rect();
|
||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, PreviewlessMediaAttachmentViewController> previewlessAttachmentViewsPool=new TypedObjectPool<>(this::makeNewPreviewlessMediaAttachmentView);
|
||||
|
||||
protected boolean currentlyScrolling;
|
||||
|
||||
public BaseStatusListFragment(){
|
||||
@@ -280,6 +284,79 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void openPreviewlessMediaPhotoViewer(String parentID, Status _status, int attachmentIndex, PreviewlessMediaGridStatusDisplayItem.Holder gridHolder){
|
||||
final Status status=_status.getContentStatus();
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
|
||||
private PreviewlessMediaAttachmentViewController transitioningHolder;
|
||||
|
||||
@Override
|
||||
public void setPhotoViewVisibility(int index, boolean visible){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||
PreviewlessMediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||
if(holder!=null && list!=null){
|
||||
transitioningHolder=holder;
|
||||
View view=transitioningHolder.inner;
|
||||
int[] pos={0, 0};
|
||||
view.getLocationOnScreen(pos);
|
||||
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
|
||||
list.setClipChildren(false);
|
||||
gridHolder.setClipChildren(false);
|
||||
transitioningHolder.view.setElevation(1f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransitioningViewTransform(float translateX, float translateY, float scale){
|
||||
View view=transitioningHolder.inner;
|
||||
view.setTranslationX(translateX);
|
||||
view.setTranslationY(translateY);
|
||||
view.setScaleX(scale);
|
||||
view.setScaleY(scale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endPhotoViewTransition(){
|
||||
View view=transitioningHolder.inner;
|
||||
view.setTranslationX(0f);
|
||||
view.setTranslationY(0f);
|
||||
view.setScaleX(1f);
|
||||
view.setScaleY(1f);
|
||||
transitioningHolder.view.setElevation(0f);
|
||||
if(list!=null)
|
||||
list.setClipChildren(true);
|
||||
gridHolder.setClipChildren(true);
|
||||
transitioningHolder=null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Drawable getPhotoViewCurrentDrawable(int index){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void photoViewerDismissed(){
|
||||
currentPhotoViewer=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissions(String[] permissions){
|
||||
requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST);
|
||||
}
|
||||
|
||||
private PreviewlessMediaAttachmentViewController findPhotoViewHolder(int index){
|
||||
return gridHolder.getViewController(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View getFab() {
|
||||
if (getParentFragment() instanceof HasFab l) return l.getFab();
|
||||
@@ -622,7 +699,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
}
|
||||
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
|
||||
|
||||
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
||||
int startPos = warning.getAbsoluteAdapterPosition();
|
||||
@@ -768,10 +845,18 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
return new MediaAttachmentViewController(getActivity(), type);
|
||||
}
|
||||
|
||||
private PreviewlessMediaAttachmentViewController makeNewPreviewlessMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){
|
||||
return new PreviewlessMediaAttachmentViewController(getActivity(), type);
|
||||
}
|
||||
|
||||
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> getAttachmentViewsPool(){
|
||||
return attachmentViewsPool;
|
||||
}
|
||||
|
||||
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, PreviewlessMediaAttachmentViewController> getPreviewlessAttachmentViewsPool(){
|
||||
return previewlessAttachmentViewsPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent assistContent) {
|
||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||
@@ -848,7 +933,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
{
|
||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.showDividers ? R.attr.colorM3OutlineVariant : R.attr.colorM3Surface));
|
||||
dividerPaint.setStyle(Paint.Style.STROKE);
|
||||
dividerPaint.setStrokeWidth(V.dp(0.5f));
|
||||
dividerPaint.setStrokeWidth(V.dp(1f));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -465,7 +465,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
hasSpoiler=true;
|
||||
spoilerWrap.setVisibility(View.VISIBLE);
|
||||
spoilerBtn.setSelected(true);
|
||||
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
|
||||
}else if(editingStatus!=null && editingStatus.hasSpoiler()){
|
||||
hasSpoiler=true;
|
||||
spoilerWrap.setVisibility(View.VISIBLE);
|
||||
spoilerEdit.setText(getArguments().getString("sourceSpoiler", editingStatus.spoilerText));
|
||||
@@ -737,7 +737,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
view.findViewById(R.id.time).setVisibility(time==null ? View.GONE : View.VISIBLE);
|
||||
if(time!=null) ((TextView) view.findViewById(R.id.time)).setText(time);
|
||||
|
||||
if (status.spoilerText != null && !status.spoilerText.isBlank()) {
|
||||
if (status.hasSpoiler()) {
|
||||
TextView replyToSpoiler = view.findViewById(R.id.reply_to_spoiler);
|
||||
replyToSpoiler.setVisibility(View.VISIBLE);
|
||||
replyToSpoiler.setText(status.spoilerText);
|
||||
@@ -1171,6 +1171,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
req.scheduledAt=scheduledAt;
|
||||
if(!mediaViewController.isEmpty()){
|
||||
req.mediaIds=mediaViewController.getAttachmentIDs();
|
||||
if(editingStatus != null){
|
||||
req.mediaAttributes=mediaViewController.getAttachmentAttributes();
|
||||
}
|
||||
}
|
||||
// ask whether to publish now when editing an existing draft
|
||||
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
|
||||
@@ -1291,7 +1294,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
wm.removeView(sendingOverlay);
|
||||
sendingOverlay=null;
|
||||
V.setVisibilityAnimated(sendProgress, View.GONE);
|
||||
publishButton.setEnabled(true);
|
||||
|
||||
if(GlobalUserPreferences.relocatePublishButton) {
|
||||
publishButtonRelocated.setEnabled(true);
|
||||
} else {
|
||||
publishButton.setEnabled(true);
|
||||
}
|
||||
|
||||
if(error instanceof MastodonErrorResponse me){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.post_failed)
|
||||
@@ -1410,7 +1419,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
|
||||
if(usePhotoPicker){
|
||||
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
|
||||
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
|
||||
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
|
||||
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
|
||||
}else{
|
||||
intent=new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
@@ -1589,7 +1599,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
} else {
|
||||
draftsBtn.setImageDrawable(getContext().getDrawable(GlobalUserPreferences.relocatePublishButton ? R.drawable.ic_fluent_clock_24_regular : R.drawable.ic_fluent_clock_20_regular));
|
||||
if(GlobalUserPreferences.relocatePublishButton){
|
||||
publishButtonRelocated.setImageResource(R.drawable.ic_fluent_send_24_selector);
|
||||
publishButtonRelocated.setImageResource(R.drawable.ic_fluent_send_24_regular);
|
||||
}
|
||||
resetPublishButtonText();
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
|
||||
public interface DomainDisplay {
|
||||
|
||||
default String getDomain(){
|
||||
AccountSession session = AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
if (session != null)
|
||||
return session.domain;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -274,16 +274,21 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
||||
|
||||
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
|
||||
if (tags == null || tags.isEmpty()) return false;
|
||||
editText.setText(String.join(",", tags));
|
||||
editText.chipifyAllUnterminatedTokens();
|
||||
editText.setText(tags);
|
||||
editText.chipifyAllUnterminatedTokens();
|
||||
return true;
|
||||
}
|
||||
|
||||
private NachoTextView prepareChipTextView(NachoTextView nacho) {
|
||||
nacho.addChipTerminator(',', BEHAVIOR_CHIPIFY_ALL);
|
||||
nacho.addChipTerminator('\n', BEHAVIOR_CHIPIFY_ALL);
|
||||
nacho.addChipTerminator(' ', BEHAVIOR_CHIPIFY_ALL);
|
||||
nacho.addChipTerminator(';', BEHAVIOR_CHIPIFY_ALL);
|
||||
//I’ll Be Back
|
||||
nacho.setChipTerminators(
|
||||
Map.of(
|
||||
',', BEHAVIOR_CHIPIFY_ALL,
|
||||
'\n', BEHAVIOR_CHIPIFY_ALL,
|
||||
' ', BEHAVIOR_CHIPIFY_ALL,
|
||||
';', BEHAVIOR_CHIPIFY_ALL
|
||||
)
|
||||
);
|
||||
nacho.enableEditChipOnTouch(true, true);
|
||||
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
|
||||
return nacho;
|
||||
@@ -379,7 +384,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
||||
mainHashtag = name;
|
||||
name = null;
|
||||
}
|
||||
if (TextUtils.isEmpty(mainHashtag)) {
|
||||
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) {
|
||||
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
|
||||
onSave.accept(null);
|
||||
return;
|
||||
|
||||
@@ -45,8 +45,8 @@ public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id){
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, id, data.stream().filter(h -> Objects.equals(h.name, id)).findAny().map(h -> h.following).orElse(null));
|
||||
public void onItemClick(String hashtag){
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag, data.stream().filter(h -> Objects.equals(h.name, hashtag)).findAny().map(h -> h.following).orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,8 +17,10 @@ import android.widget.TextView;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
@@ -357,8 +359,9 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
||||
|
||||
public AccountWrapper(Account account){
|
||||
this.account=account;
|
||||
if(!TextUtils.isEmpty(account.avatar))
|
||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
||||
avaRequest=new UrlImageLoaderRequest(
|
||||
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(getAccountID()).getDefaultAvatarUrl() : account.avatar,
|
||||
V.dp(50), V.dp(50));
|
||||
if(!TextUtils.isEmpty(account.header))
|
||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
|
||||
@@ -13,18 +13,29 @@ import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.filters.CreateFilter;
|
||||
import org.joinmastodon.android.api.requests.filters.DeleteFilter;
|
||||
import org.joinmastodon.android.api.requests.filters.GetFilters;
|
||||
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.fragments.settings.EditFilterFragment;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.FilterAction;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.FilterKeyword;
|
||||
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 org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
@@ -41,6 +52,8 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||
private boolean following;
|
||||
private boolean localOnly;
|
||||
private MenuItem followButton;
|
||||
private MenuItem muteButton;
|
||||
private Optional<Filter> filter = Optional.empty();
|
||||
|
||||
@Override
|
||||
protected boolean wantsComposeButton() {
|
||||
@@ -72,13 +85,19 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||
E.post(new HashtagUpdatedEvent(hashtag, following));
|
||||
}
|
||||
|
||||
private void updateMuteState(boolean newMute) {
|
||||
muteButton.setTitle(getString(newMute ? R.string.unmute_user : R.string.mute_user, "#" + hashtag));
|
||||
muteButton.setIcon(newMute ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
muteButton = menu.findItem(R.id.mute_hashtag);
|
||||
updateMuteState(filter.isPresent());
|
||||
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Hashtag hashtag) {
|
||||
@@ -92,6 +111,20 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
}).exec(accountID);
|
||||
|
||||
new GetFilters().setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(List<Filter> filters) {
|
||||
if (getActivity() == null) return;
|
||||
filter=filters.stream().filter(filter->filter.title.equals("#"+hashtag)).findAny();
|
||||
updateMuteState(filter.isPresent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,10 +149,58 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||
}
|
||||
}).exec(accountID);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.mute_hashtag) {
|
||||
showMuteDialog(filter.isPresent());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void showMuteDialog(boolean mute) {
|
||||
UiUtils.showConfirmationAlert(getContext(),
|
||||
mute ? R.string.mo_unmute_hashtag : R.string.mo_mute_hashtag,
|
||||
mute ? R.string.mo_confirm_to_unmute_hashtag : R.string.mo_confirm_to_mute_hashtag,
|
||||
mute ? R.string.do_unmute : R.string.do_mute,
|
||||
mute ? R.drawable.ic_fluent_speaker_2_28_regular : R.drawable.ic_fluent_speaker_off_28_regular,
|
||||
mute ? this::unmuteHashtag : this::muteHashtag
|
||||
);
|
||||
}
|
||||
private void unmuteHashtag() {
|
||||
//safe to get, this only called if filter is present
|
||||
new DeleteFilter(filter.get().id).setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Void result){
|
||||
filter=Optional.empty();
|
||||
updateMuteState(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
private void muteHashtag() {
|
||||
FilterKeyword hashtagFilter=new FilterKeyword();
|
||||
hashtagFilter.wholeWord=true;
|
||||
hashtagFilter.keyword="#"+hashtag;
|
||||
new CreateFilter("#"+hashtag, EnumSet.of(FilterContext.HOME), FilterAction.HIDE, 0 , List.of(hashtagFilter)).setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Filter result){
|
||||
filter=Optional.of(result);
|
||||
updateMuteState(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected TimelineDefinition makeTimelineDefinition() {
|
||||
return TimelineDefinition.ofHashtag(hashtag);
|
||||
|
||||
@@ -189,6 +189,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
});
|
||||
}
|
||||
}
|
||||
tabBar.selectTab(currentTab);
|
||||
|
||||
return content;
|
||||
}
|
||||
@@ -269,7 +270,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
private void onTabSelected(@IdRes int tab){
|
||||
Fragment newFragment=fragmentForTab(tab);
|
||||
if(tab==currentTab){
|
||||
if (tab == R.id.tab_search)
|
||||
if (tab == R.id.tab_search && GlobalUserPreferences.doubleTapToSearch)
|
||||
discoverFragment.openSearch();
|
||||
else if(newFragment instanceof ScrollableToTop scrollable)
|
||||
scrollable.scrollToTop();
|
||||
@@ -309,8 +310,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
return true;
|
||||
}
|
||||
if(tab==R.id.tab_search){
|
||||
onTabSelected(R.id.tab_search);
|
||||
tabBar.selectTab(R.id.tab_search);
|
||||
if(currentTab!=R.id.tab_search){
|
||||
onTabSelected(R.id.tab_search);
|
||||
tabBar.selectTab(R.id.tab_search);
|
||||
}
|
||||
discoverFragment.openSearch();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,25 +13,24 @@ import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.TimelineMarkers;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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 HomeTimelineFragment extends StatusListFragment {
|
||||
private HomeTabFragment parent;
|
||||
@@ -176,15 +175,23 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){
|
||||
if(dataLoading)
|
||||
return;
|
||||
item.getItem().loading=true;
|
||||
V.setVisibilityAnimated(item.progress, View.VISIBLE);
|
||||
V.setVisibilityAnimated(item.text, View.GONE);
|
||||
GapStatusDisplayItem gap=item.getItem();
|
||||
gap.loading=true;
|
||||
dataLoading=true;
|
||||
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
|
||||
|
||||
String maxID = null;
|
||||
String minID = null;
|
||||
if (downwards) {
|
||||
maxID = item.getItemID();
|
||||
} else {
|
||||
int gapPos=displayItems.indexOf(gap);
|
||||
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
|
||||
minID=nextItem.parentID;
|
||||
}
|
||||
currentRequest=new GetHomeTimeline(maxID, minID, 20, null, getLocalPrefs().timelineReplyVisibility)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
@@ -204,52 +211,96 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||
}
|
||||
}else{
|
||||
Set<String> idsBelowGap=new HashSet<>();
|
||||
boolean belowGap=false;
|
||||
int gapPostIndex=0;
|
||||
for(Status s:data){
|
||||
if(belowGap){
|
||||
idsBelowGap.add(s.id);
|
||||
}else if(s.id.equals(gap.parentID)){
|
||||
belowGap=true;
|
||||
s.hasGapAfter=false;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||
}else{
|
||||
gapPostIndex++;
|
||||
if(downwards) {
|
||||
Set<String> idsBelowGap=new HashSet<>();
|
||||
boolean belowGap=false;
|
||||
int gapPostIndex=0;
|
||||
for(Status s:data){
|
||||
if(belowGap){
|
||||
idsBelowGap.add(s.id);
|
||||
}else if(s.id.equals(gap.parentID)){
|
||||
belowGap=true;
|
||||
s.hasGapAfter=false;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||
}else{
|
||||
gapPostIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
int endIndex=0;
|
||||
for(Status s:result){
|
||||
endIndex++;
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
}
|
||||
if(endIndex==result.size()){
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
}else{
|
||||
result=result.subList(0, endIndex);
|
||||
}
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
targetList.clear();
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
if(typeFilterPredicate(s) && filterPredicate.test(s)){
|
||||
int endIndex=0;
|
||||
for(Status s:result){
|
||||
endIndex++;
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
}
|
||||
if(endIndex==result.size()){
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
}else{
|
||||
result=result.subList(0, endIndex);
|
||||
}
|
||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
targetList.clear();
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
targetList.addAll(buildDisplayItems(s));
|
||||
insertedPosts.add(s);
|
||||
}
|
||||
if(targetList.isEmpty()){
|
||||
// oops. We didn't add new posts, but at least we know there are none.
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
}else{
|
||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||
}
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
} else {
|
||||
String aboveGapID = gap.parentID;
|
||||
int gapPostIndex = 0;
|
||||
for (;gapPostIndex<data.size();gapPostIndex++){
|
||||
if (Objects.equals(aboveGapID, data.get(gapPostIndex).id)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// find if there's an overlap between the new data and the current data
|
||||
int indexOfGapInResponse = 0;
|
||||
for (;indexOfGapInResponse<result.size();indexOfGapInResponse++){
|
||||
if (Objects.equals(aboveGapID, result.get(indexOfGapInResponse).id)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// there is an overlap between new and current data
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
if(indexOfGapInResponse<result.size()){
|
||||
result=result.subList(indexOfGapInResponse+1,result.size());
|
||||
Optional<Status> gapStatus=data.stream()
|
||||
.filter(s->Objects.equals(s.id, gap.parentID))
|
||||
.findFirst();
|
||||
if (gapStatus.isPresent()) {
|
||||
gapStatus.get().hasGapAfter=false;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
|
||||
}
|
||||
targetList.clear();
|
||||
} else {
|
||||
gap.loading=false;
|
||||
}
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
for(Status s:result){
|
||||
targetList.addAll(buildDisplayItems(s));
|
||||
insertedPosts.add(s);
|
||||
}
|
||||
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, FilterContext.HOME);
|
||||
if(targetList.isEmpty()){
|
||||
// oops. We didn't add new posts, but at least we know there are none.
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
}else{
|
||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||
}
|
||||
list.scrollToPosition(getMainAdapterOffset()+gapPos+targetList.size());
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
}
|
||||
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
|
||||
if(targetList.isEmpty()){
|
||||
// oops. We didn't add new posts, but at least we know there are none.
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
}else{
|
||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||
}
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -38,12 +40,11 @@ public abstract class MastodonRecyclerFragment<T> extends BaseRecyclerFragment<T
|
||||
@CallSuper
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (getParentFragment() instanceof HasElevationOnScrollListener elevator)
|
||||
list.addOnScrollListener(elevator.getElevationOnScrollListener());
|
||||
else if(wantsElevationOnScrollEffect())
|
||||
if(wantsElevationOnScrollEffect())
|
||||
list.addOnScrollListener(elevationOnScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, getViewsForElevationEffect()));
|
||||
if(refreshLayout!=null)
|
||||
if(refreshLayout!=null){
|
||||
setRefreshLayoutColors(refreshLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
@@ -27,6 +28,7 @@ import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
|
||||
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;
|
||||
@@ -36,6 +38,7 @@ import org.parceler.Parcels;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -43,7 +46,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
|
||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification> {
|
||||
private boolean onlyMentions;
|
||||
@@ -92,18 +94,18 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}else{
|
||||
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
||||
}
|
||||
if (n.type == Notification.Type.FOLLOW_REQUEST) {
|
||||
if (n.type == Notification.Type.FOLLOW_REQUEST || n.type == Notification.Type.FOLLOW) {
|
||||
ArrayList<StatusDisplayItem> items = new ArrayList<>();
|
||||
items.add(titleItem);
|
||||
items.add(new AccountCardStatusDisplayItem(n.id, this, n.account, n));
|
||||
items.add(new AccountCardStatusDisplayItem(n.id, this, accountID, n.account, n));
|
||||
return items;
|
||||
}
|
||||
if(n.status!=null){
|
||||
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS); // | StatusDisplayItem.FLAG_NO_HEADER);
|
||||
if (GlobalUserPreferences.spectatorMode)
|
||||
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
|
||||
if (!getLocalPrefs().showEmojiReactionsInLists)
|
||||
flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS;
|
||||
if (!GlobalUserPreferences.showMediaPreview)
|
||||
flags |= StatusDisplayItem.FLAG_NO_MEDIA_PREVIEW;
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, null, flags);
|
||||
if(titleItem!=null)
|
||||
items.add(0, titleItem);
|
||||
@@ -133,6 +135,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
|
||||
Set<String> needRelationships=result.items.stream()
|
||||
.filter(ntf->ntf.status==null && !relationships.containsKey(ntf.account.id))
|
||||
.map(ntf->ntf.account.id)
|
||||
.collect(Collectors.toSet());
|
||||
loadRelationships(needRelationships);
|
||||
|
||||
maxID=result.maxID;
|
||||
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
|
||||
reloadingFromCache=false;
|
||||
@@ -142,6 +151,17 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRelationshipsLoaded(){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof AccountCardStatusDisplayItem.Holder accountHolder)
|
||||
accountHolder.rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
@@ -248,8 +268,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
@Subscribe
|
||||
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||
for(Notification n:data){
|
||||
if (n.status == null) continue;
|
||||
if(n.status.getContentStatus().id.equals(ev.id)){
|
||||
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||
n.status.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
@@ -258,15 +277,36 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
footer.rebind();
|
||||
}else if(holder instanceof ExtendedFooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
|
||||
footer.rebind();
|
||||
}else if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && ev.viewHolder!=holder){
|
||||
reactions.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(Notification n:preloadedData){
|
||||
if (n.status == null) continue;
|
||||
if(n.status.getContentStatus().id.equals(ev.id)){
|
||||
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||
n.status.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||
for(Notification n : data){
|
||||
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||
n.status.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||
for(int i=0; i<list.getChildCount(); i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
|
||||
reactions.rebind();
|
||||
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
|
||||
text.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(Notification n : preloadedData){
|
||||
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||
n.status.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareFragment{
|
||||
static final int MAX_FIELDS=Integer.MAX_VALUE;
|
||||
static final int MAX_FIELDS=4;
|
||||
|
||||
public UsableRecyclerView list;
|
||||
private List<AccountField> fields=Collections.emptyList();
|
||||
|
||||
@@ -4,10 +4,12 @@ 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.assist.AssistContent;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
@@ -66,8 +68,10 @@ import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.account_list.BlocksListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.MutesListFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
@@ -135,6 +139,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private CoverImageView cover;
|
||||
private View avatarBorder;
|
||||
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
|
||||
private ImageView lockIcon, botIcon;
|
||||
private ProgressBarButton actionButton, notifyButton;
|
||||
private ViewPager2 pager;
|
||||
private NestedRecyclerScrollView scrollView;
|
||||
@@ -150,7 +155,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private TextView followsYouView;
|
||||
private ViewGroup rolesView;
|
||||
private LinearLayout countersLayout;
|
||||
private View nameEditWrap, bioEditWrap;
|
||||
private View nameEditWrap, bioEditWrap, usernameWrap;
|
||||
private View tabsDivider;
|
||||
private View actionButtonWrap;
|
||||
private CustomDrawingOrderLinearLayout scrollableContent;
|
||||
@@ -237,6 +242,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
avatarBorder=content.findViewById(R.id.avatar_border);
|
||||
name=content.findViewById(R.id.name);
|
||||
username=content.findViewById(R.id.username);
|
||||
lockIcon=content.findViewById(R.id.lock_icon);
|
||||
botIcon=content.findViewById(R.id.bot_icon);
|
||||
bio=content.findViewById(R.id.bio);
|
||||
followersCount=content.findViewById(R.id.followers_count);
|
||||
followersLabel=content.findViewById(R.id.followers_label);
|
||||
@@ -254,6 +261,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bioEdit=content.findViewById(R.id.bio_edit);
|
||||
nameEditWrap=content.findViewById(R.id.name_edit_wrap);
|
||||
bioEditWrap=content.findViewById(R.id.bio_edit_wrap);
|
||||
usernameWrap=content.findViewById(R.id.username_wrap);
|
||||
actionProgress=content.findViewById(R.id.action_progress);
|
||||
notifyProgress=content.findViewById(R.id.notify_progress);
|
||||
fab=content.findViewById(R.id.fab);
|
||||
@@ -374,6 +382,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if (position == 4) tab.view.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab){}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab){}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab){
|
||||
if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt)
|
||||
stt.scrollToTop();
|
||||
}
|
||||
});
|
||||
|
||||
cover.setOutlineProvider(new ViewOutlineProvider(){
|
||||
@Override
|
||||
@@ -409,7 +430,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
|
||||
username.setOnClickListener(v->{
|
||||
content.findViewById(R.id.username_wrap).setOnClickListener(v->{
|
||||
try {
|
||||
new GetInstance()
|
||||
.setCallback(new Callback<>(){
|
||||
@@ -430,11 +451,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
.execRemote(Uri.parse(account.url).getHost());
|
||||
} catch (NullPointerException ignored) {
|
||||
// maybe the url was malformed?
|
||||
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT);
|
||||
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
username.setOnLongClickListener(v->{
|
||||
content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{
|
||||
String usernameString=account.acct;
|
||||
if(!usernameString.contains("@")){
|
||||
usernameString+="@"+domain;
|
||||
@@ -678,10 +699,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void bindHeaderView(){
|
||||
setTitle(account.displayName);
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
|
||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
|
||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(
|
||||
TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() :
|
||||
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
|
||||
V.dp(100), V.dp(100)));
|
||||
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
|
||||
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
||||
@@ -710,36 +735,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
String acct = ((isSelf || account.isRemote)
|
||||
? account.getFullyQualifiedName()
|
||||
: account.acct);
|
||||
if(account.locked){
|
||||
ssb=new SpannableStringBuilder("@");
|
||||
ssb.append(acct);
|
||||
ssb.append(" ");
|
||||
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
|
||||
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
||||
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('@'+acct);
|
||||
}
|
||||
CharSequence parsedBio = null;
|
||||
if(account.note != null){
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
}
|
||||
|
||||
username.setText('@'+acct);
|
||||
|
||||
lockIcon.setVisibility(account.locked ? View.VISIBLE : View.GONE);
|
||||
lockIcon.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor()));
|
||||
|
||||
botIcon.setVisibility(account.bot ? View.VISIBLE : View.GONE);
|
||||
botIcon.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor()));
|
||||
|
||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
if(TextUtils.isEmpty(parsedBio)){
|
||||
bio.setVisibility(View.GONE);
|
||||
}else{
|
||||
@@ -849,7 +854,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
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);
|
||||
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_2_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()));
|
||||
@@ -928,6 +933,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
}
|
||||
Nav.go(getActivity(), ListsFragment.class, args);
|
||||
}else if(id==R.id.mutes){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||
Nav.go(getActivity(), MutesListFragment.class, args);
|
||||
}else if(id==R.id.blocks){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("targetAccount", Parcels.wrap(account));
|
||||
Nav.go(getActivity(), BlocksListFragment.class, args);
|
||||
}else if(id==R.id.followed_hashtags){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -972,11 +987,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
||||
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
||||
notifyButton.setSelected(relationship.notifying);
|
||||
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
||||
|
||||
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() {
|
||||
@@ -1169,7 +1185,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
actionButton.setText(R.string.save_changes);
|
||||
pager.setVisibility(View.GONE);
|
||||
tabbar.setVisibility(View.GONE);
|
||||
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate();
|
||||
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay, getActivity().getTheme()).mutate();
|
||||
avatar.setForeground(overlay);
|
||||
updateMetadataHeight();
|
||||
|
||||
@@ -1189,7 +1205,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
name.setVisibility(View.GONE);
|
||||
rolesView.setVisibility(View.GONE);
|
||||
username.setVisibility(View.GONE);
|
||||
usernameWrap.setVisibility(View.GONE);
|
||||
bio.setVisibility(View.GONE);
|
||||
countersLayout.setVisibility(View.GONE);
|
||||
|
||||
@@ -1238,7 +1254,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bioEditWrap.setVisibility(View.GONE);
|
||||
name.setVisibility(View.VISIBLE);
|
||||
rolesView.setVisibility(View.VISIBLE);
|
||||
username.setVisibility(View.VISIBLE);
|
||||
usernameWrap.setVisibility(View.VISIBLE);
|
||||
bio.setVisibility(View.VISIBLE);
|
||||
countersLayout.setVisibility(View.VISIBLE);
|
||||
refreshLayout.setEnabled(true);
|
||||
@@ -1340,7 +1356,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(ava==null)
|
||||
return;
|
||||
int radius=V.dp(25);
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : account.avatar, ava), 0,
|
||||
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
|
||||
}
|
||||
}
|
||||
@@ -1412,9 +1428,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
@NonNull
|
||||
@Override
|
||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
FrameLayout view=tabViews[viewType];
|
||||
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
FrameLayout view=new FrameLayout(parent.getContext());
|
||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
return new SimpleViewHolder(view);
|
||||
}
|
||||
@@ -1422,8 +1436,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||
Fragment fragment=getFragmentForPage(position);
|
||||
FrameLayout fragmentView=tabViews[position];
|
||||
fragmentView.setVisibility(View.VISIBLE);
|
||||
if(fragmentView.getParent() instanceof ViewGroup parent)
|
||||
parent.removeView(fragmentView);
|
||||
((FrameLayout)holder.itemView).addView(fragmentView);
|
||||
if(!fragment.isAdded()){
|
||||
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
|
||||
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit();
|
||||
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
@@ -1541,16 +1560,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
}
|
||||
|
||||
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder {
|
||||
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder{
|
||||
private final TextView title;
|
||||
private final LinkedTextView value;
|
||||
// private final ImageView verifiedIcon;
|
||||
|
||||
public AboutViewHolder(){
|
||||
super(R.layout.item_profile_about);
|
||||
title=findViewById(R.id.title);
|
||||
value=findViewById(R.id.value);
|
||||
// verifiedIcon=findViewById(R.id.verified_icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1558,7 +1575,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
super.onBind(item);
|
||||
title.setText(item.parsedName);
|
||||
value.setText(item.parsedValue);
|
||||
// verifiedIcon.setVisibility(item.verifiedAt!=null ? View.VISIBLE : View.GONE);
|
||||
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_starburst_20_regular, getActivity().getTheme()).mutate();
|
||||
check.setTint(textColor);
|
||||
value.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, check, null);
|
||||
}else{
|
||||
value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent));
|
||||
value.setCompoundDrawables(null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,8 +2,11 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
@@ -27,6 +30,7 @@ import java.util.List;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
||||
private String nextMaxID;
|
||||
@@ -81,7 +85,10 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, false, true, null);
|
||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, null,
|
||||
StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS |
|
||||
StatusDisplayItem.FLAG_NO_FOOTER |
|
||||
StatusDisplayItem.FLAG_NO_TRANSLATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -184,6 +191,18 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(contentView!=null){
|
||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+insets.getSystemWindowInsetBottom();
|
||||
}else{
|
||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16);
|
||||
}
|
||||
}
|
||||
super.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
// TODO: adapt when frontends finally implement a scheduled posts list
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||
@@ -143,7 +144,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
}
|
||||
}
|
||||
String sep = getString(R.string.sk_separator);
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s));
|
||||
items.add(1, new DummyStatusDisplayItem(s.id, this));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ShowEmojiReactions.ONLY_OPENED;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -7,8 +9,10 @@ import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.events.StatusMuteChangedEvent;
|
||||
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
@@ -20,7 +24,9 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
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.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
@@ -38,10 +44,13 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id);
|
||||
int flags = 0;
|
||||
AccountLocalPreferences lp=getLocalPrefs();
|
||||
if (GlobalUserPreferences.spectatorMode)
|
||||
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
|
||||
if (!getLocalPrefs().showEmojiReactionsInLists)
|
||||
if (!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
|
||||
flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS;
|
||||
if(!GlobalUserPreferences.showMediaPreview)
|
||||
flags |= StatusDisplayItem.FLAG_NO_MEDIA_PREVIEW;
|
||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags);
|
||||
}
|
||||
|
||||
@@ -237,8 +246,52 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||
footer.rebind();
|
||||
}else if(holder instanceof ExtendedFooterStatusDisplayItem.Holder footer && footer.getItem().status==s.getContentStatus()){
|
||||
footer.rebind();
|
||||
}else if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(Status s:preloadedData){
|
||||
if(s.getContentStatus().id.equals(ev.id)){
|
||||
s.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusMuteChaged(StatusMuteChangedEvent ev){
|
||||
for(Status s:data){
|
||||
if(s.getContentStatus().id.equals(ev.id)){
|
||||
s.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof HeaderStatusDisplayItem.Holder header && header.getItem().status==s.getContentStatus()){
|
||||
header.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(Status s:preloadedData){
|
||||
if(s.getContentStatus().id.equals(ev.id)){
|
||||
s.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||
for(Status s:data){
|
||||
if(s.getContentStatus().id.equals(ev.id)){
|
||||
s.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
|
||||
reactions.rebind();
|
||||
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
|
||||
text.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,17 @@ import android.view.View;
|
||||
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.GlobalUserPreferences.AutoRevealMode;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusMuteChangedEvent;
|
||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
@@ -23,6 +26,7 @@ import org.joinmastodon.android.model.StatusContext;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
@@ -66,6 +70,28 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
|
||||
transitionFinished = getArguments().getBoolean("noTransition", false);
|
||||
|
||||
E.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
E.unregister(this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onStatusMuteChaged(StatusMuteChangedEvent ev){
|
||||
for(Status s:data){
|
||||
s.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof HeaderStatusDisplayItem.Holder header && header.getItem().status==s.getContentStatus()){
|
||||
header.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -321,7 +347,7 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
|
||||
private static List<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||
return statuses.stream()
|
||||
.filter(s -> s.inReplyToId.equals(id))
|
||||
.filter(s -> id.equals(s.inReplyToId))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountBlocks;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||
|
||||
public class BlocksListFragment extends AccountRelatedAccountListFragment{
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.mo_blocked_accounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetAccountBlocks(maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||
super.onConfigureViewHolder(holder);
|
||||
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return super.getWebUri(base).buildUpon()
|
||||
.appendPath("/blocks").build();
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
refreshing=true;
|
||||
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
|
||||
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountMutes;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||
|
||||
public class MutesListFragment extends AccountRelatedAccountListFragment{
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.mo_muted_accounts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetAccountMutes(maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigureViewHolder(AccountViewHolder holder){
|
||||
super.onConfigureViewHolder(holder);
|
||||
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return super.getWebUri(base).buildUpon()
|
||||
.appendPath("/mutes").build();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,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.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.IsOnTop;
|
||||
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
@@ -319,8 +320,9 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
||||
|
||||
public AccountWrapper(Account account){
|
||||
this.account=account;
|
||||
if(!TextUtils.isEmpty(account.avatar))
|
||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
||||
avaRequest=new UrlImageLoaderRequest(
|
||||
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.getInstance().getAccount(accountID).getDefaultAvatarUrl() : account.avatar,
|
||||
V.dp(50), V.dp(50));
|
||||
if(!TextUtils.isEmpty(account.header))
|
||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.IsOnTop;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
@@ -208,6 +209,11 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
@Override
|
||||
public void scrollToTop(){
|
||||
if(!searchActive){
|
||||
if (((IsOnTop)getFragmentForPage(pager.getCurrentItem())).isOnTop() && GlobalUserPreferences.doubleTapToSwipe){
|
||||
int nextPage=(pager.getCurrentItem()+1)%tabViews.length;
|
||||
pager.setCurrentItem(nextPage, true);
|
||||
return;
|
||||
}
|
||||
((ScrollableToTop)getFragmentForPage(pager.getCurrentItem())).scrollToTop();
|
||||
}else{
|
||||
searchFragment.scrollToTop();
|
||||
@@ -289,15 +295,19 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
@NonNull
|
||||
@Override
|
||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
FrameLayout view=tabViews[viewType];
|
||||
((ViewGroup)view.getParent()).removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
FrameLayout view=new FrameLayout(parent.getContext());
|
||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
return new SimpleViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
|
||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||
FrameLayout view=tabViews[position];
|
||||
if(view.getParent() instanceof ViewGroup parent)
|
||||
parent.removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
|
||||
@@ -11,6 +11,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.model.viewmodel.CardViewModel;
|
||||
@@ -43,7 +44,7 @@ import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> implements ScrollableToTop{
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> implements ScrollableToTop, IsOnTop{
|
||||
private String accountID;
|
||||
private DiscoverInfoBannerHelper bannerHelper;
|
||||
private MergeRecyclerAdapter mergeAdapter;
|
||||
@@ -111,6 +112,11 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnTop(){
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
private class LinksAdapter extends UsableRecyclerView.Adapter<BaseLinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
private final List<CardViewModel> data;
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
@@ -31,7 +30,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
@@ -70,7 +69,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
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, FilterContext.PUBLIC, !getLocalPrefs().showEmojiReactionsInLists ? StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS : 0);
|
||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, FilterContext.PUBLIC, 0);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -113,7 +112,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
protected void doLoadData(int _offset, int count){
|
||||
GetSearchResults.Type type;
|
||||
if(currentFilter.size()==1){
|
||||
type=switch(currentFilter.iterator().next()){
|
||||
@@ -128,7 +127,21 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
dataLoaded();
|
||||
return;
|
||||
}
|
||||
currentRequest=new GetSearchResults(currentQuery, type, true)
|
||||
String maxID=null;
|
||||
// TODO server-side bug
|
||||
/*int offset=0;
|
||||
if(_offset>0){
|
||||
if(type==GetSearchResults.Type.STATUSES){
|
||||
if(!preloadedData.isEmpty())
|
||||
maxID=preloadedData.get(preloadedData.size()-1).status.id;
|
||||
else if(!data.isEmpty())
|
||||
maxID=data.get(data.size()-1).status.id;
|
||||
}else{
|
||||
offset=_offset;
|
||||
}
|
||||
}*/
|
||||
int offset=_offset;
|
||||
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults result){
|
||||
@@ -142,12 +155,15 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
results.add(new SearchResult(tag));
|
||||
}
|
||||
if(result.statuses!=null){
|
||||
for(Status status:result.statuses)
|
||||
results.add(new SearchResult(status));
|
||||
Set<String> alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet());
|
||||
for(Status status:result.statuses){
|
||||
if(!alreadyLoadedStatuses.contains(status.id))
|
||||
results.add(new SearchResult(status));
|
||||
}
|
||||
}
|
||||
prevDisplayItems=new ArrayList<>(displayItems);
|
||||
unfilteredResults=results;
|
||||
onDataLoaded(filterSearchResults(results), false);
|
||||
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -26,7 +25,7 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
@@ -38,19 +37,16 @@ import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.SearchViewHelper;
|
||||
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
|
||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
|
||||
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -126,7 +122,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
recentsHeader.setVisible(!data.isEmpty());
|
||||
});
|
||||
}else{
|
||||
currentRequest=new GetSearchResults(currentQuery, null, false)
|
||||
currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0)
|
||||
.limit(2)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
@@ -381,16 +377,56 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
}
|
||||
|
||||
private void openHashtag(SearchResult res){
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
|
||||
wrapSuicideDialog(()->{
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isInRecentMode(){
|
||||
return TextUtils.isEmpty(currentQuery);
|
||||
}
|
||||
|
||||
private void wrapSuicideDialog(Runnable r){
|
||||
if(!GlobalUserPreferences.showSuicideHelp || currentQuery==null){
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
String[] terms=getContext().getString(R.string.sk_suicide_search_terms).toLowerCase().split(",");
|
||||
String query=currentQuery.trim().toLowerCase();
|
||||
boolean termMatches=false;
|
||||
for(String term : terms){
|
||||
if(query.contains(term)){
|
||||
termMatches=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!termMatches){
|
||||
r.run();
|
||||
return;
|
||||
}
|
||||
|
||||
String url=getContext().getString(R.string.sk_suicide_helplines_url);
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_search_suicide_title)
|
||||
.setMessage(R.string.sk_search_suicide_message)
|
||||
.setNegativeButton(R.string.sk_do_not_show_again, (dialog, which)->{
|
||||
GlobalUserPreferences.showSuicideHelp = false;
|
||||
GlobalUserPreferences.save();
|
||||
r.run();
|
||||
})
|
||||
.setNeutralButton(R.string.sk_search_suicide_hotlines, (dialog, which)->UiUtils.launchWebBrowser(getContext(), url))
|
||||
.setPositiveButton(R.string.ok, (dialog, which)->r.run())
|
||||
.setOnDismissListener((dialog)->{})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void onSearchViewEnter(){
|
||||
deliverResult(currentQuery, null);
|
||||
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty())
|
||||
return;
|
||||
wrapSuicideDialog(()->deliverResult(currentQuery, null));
|
||||
}
|
||||
|
||||
private void onOpenURLClick(){
|
||||
@@ -398,10 +434,12 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
}
|
||||
|
||||
private void onGoToHashtagClick(){
|
||||
String q=searchViewHelper.getQuery();
|
||||
if(q.startsWith("#"))
|
||||
q=q.substring(1);
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
|
||||
wrapSuicideDialog(()->{
|
||||
String q=searchViewHelper.getQuery();
|
||||
if(q.startsWith("#"))
|
||||
q=q.substring(1);
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
|
||||
});
|
||||
}
|
||||
|
||||
private void onGoToAccountClick(){
|
||||
@@ -422,11 +460,11 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
}
|
||||
|
||||
private void onGoToStatusSearchClick(){
|
||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
|
||||
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS));
|
||||
}
|
||||
|
||||
private void onGoToAccountSearchClick(){
|
||||
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
|
||||
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT));
|
||||
}
|
||||
|
||||
private void onClearRecentClick(){
|
||||
@@ -439,6 +477,8 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
}
|
||||
|
||||
private void deliverResult(String query, SearchResult.Type typeFilter){
|
||||
if(query.isEmpty())
|
||||
return;
|
||||
Bundle res=new Bundle();
|
||||
res.putString("query", query);
|
||||
if(typeFilter!=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;
|
||||
|
||||
public TrendingHashtagsFragment(){
|
||||
@@ -58,6 +59,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
|
||||
|
||||
@@ -271,7 +271,7 @@ public class SignupFragment extends ToolbarFragment{
|
||||
@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 LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
||||
reportStatus=Parcels.unwrap(getArguments().getParcelable("status"));
|
||||
if(reportStatus!=null){
|
||||
Status hiddenStatus=reportStatus.clone();
|
||||
hiddenStatus.spoilerText=getString(R.string.post_hidden);
|
||||
if(hiddenStatus.spoilerText==null) hiddenStatus.spoilerText=getString(R.string.post_hidden);
|
||||
onDataLoaded(Collections.singletonList(hiddenStatus));
|
||||
setTitle(R.string.report_title_post);
|
||||
}else{
|
||||
@@ -168,17 +168,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
||||
((UsableRecyclerView)list).setIncludeMarginsInItemHitbox(false);
|
||||
|
||||
if(reportStatus!=null){
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
|
||||
outRect.left=V.dp(16);
|
||||
outRect.right=V.dp(16);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
{
|
||||
@@ -222,10 +211,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
||||
if(holder instanceof StatusDisplayItem.Holder<?>){
|
||||
outRect.left=outRect.right=V.dp(16);
|
||||
}
|
||||
int index=holder.getAbsoluteAdapterPosition()-mergeAdapter.getPositionForAdapter(adapter);
|
||||
if(index==displayItems.size()){
|
||||
outRect.top=V.dp(32);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -251,18 +236,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){
|
||||
if((Object)holder instanceof MediaGridStatusDisplayItem.Holder h){
|
||||
View layout=h.getLayout();
|
||||
layout.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
layout.setClipToOutline(true);
|
||||
View overlay=h.getSensitiveOverlay();
|
||||
overlay.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
overlay.setClipToOutline(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putRelationship(String id, Relationship rel){
|
||||
super.putRelationship(id, rel);
|
||||
|
||||
@@ -68,6 +68,10 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
filter=Parcels.unwrap(getArguments().getParcelable("filter"));
|
||||
ArrayList<Parcelable> words=getArguments().getParcelableArrayList("words");
|
||||
if (words != null) {
|
||||
words.stream().map(p->(FilterKeyword)Parcels.unwrap(p)).forEach(keywords::add);
|
||||
}
|
||||
setTitle(filter==null ? R.string.settings_add_filter : R.string.settings_edit_filter);
|
||||
onDataLoaded(List.of(
|
||||
durationItem=new ListItem<>(R.string.settings_filter_duration, 0, this::onDurationClick),
|
||||
|
||||
@@ -12,10 +12,13 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HasAccountID;
|
||||
import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
@@ -23,9 +26,11 @@ import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
|
||||
public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> implements HasAccountID{
|
||||
private ListItem<Void> mediaCacheItem;
|
||||
|
||||
// MOSHIDON
|
||||
private ListItem<Void> clearRecentEmojisItem;
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -36,6 +41,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
|
||||
new ListItem<>(R.string.mo_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.mo_repo_url))),
|
||||
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
|
||||
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
|
||||
clearRecentEmojisItem=new ListItem<>(R.string.mo_clear_recent_emoji, 0, this::onClearRecentEmojisClick),
|
||||
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick)
|
||||
));
|
||||
|
||||
@@ -51,7 +57,6 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
|
||||
adapter.addAdapter(super.getAdapter());
|
||||
|
||||
TextView versionInfo=new TextView(getActivity());
|
||||
versionInfo.setSingleLine();
|
||||
versionInfo.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(32)));
|
||||
versionInfo.setTextAppearance(R.style.m3_label_medium);
|
||||
versionInfo.setTextColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Outline));
|
||||
@@ -73,10 +78,21 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
|
||||
});
|
||||
}
|
||||
|
||||
private void onClearRecentEmojisClick(){
|
||||
getLocalPrefs().recentEmojis=new HashMap<>();
|
||||
getLocalPrefs().save();
|
||||
Toast.makeText(getContext(), R.string.mo_recent_emoji_cleared, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void updateMediaCacheItem(){
|
||||
long size=ImageCache.getInstance(getActivity()).getDiskCache().size();
|
||||
mediaCacheItem.subtitle=UiUtils.formatFileSize(getActivity(), size, false);
|
||||
mediaCacheItem.isEnabled=size>0;
|
||||
rebindItem(mediaCacheItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID(){
|
||||
return accountID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
@@ -21,6 +20,7 @@ import org.joinmastodon.android.utils.MastodonLanguage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
|
||||
private ListItem<Void> languageItem;
|
||||
@@ -34,7 +34,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
private CheckableListItem<Void> forwardReportsItem, remoteLoadingItem, showBoostsItem, showRepliesItem, loadNewPostsItem, seeNewPostsBtnItem, overlayMediaItem;
|
||||
|
||||
// MOSHIDON
|
||||
private CheckableListItem<Void> mentionRebloggerAutomaticallyItem, hapticFeedbackItem, unlistedRepliesItem;
|
||||
private CheckableListItem<Void> mentionRebloggerAutomaticallyItem, hapticFeedbackItem, unlistedRepliesItem, showPostsWithoutAltItem;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -50,6 +50,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
List<ListItem<Void>> items = new ArrayList<>(List.of(
|
||||
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick),
|
||||
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, ()->toggleCheckableItem(altTextItem)),
|
||||
showPostsWithoutAltItem=new CheckableListItem<>(R.string.mo_settings_show_posts_without_alt, R.string.mo_settings_show_posts_without_alt_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showPostsWithoutAlt, R.drawable.ic_fluent_eye_tracking_on_24_regular, ()->toggleCheckableItem(showPostsWithoutAltItem)),
|
||||
playGifsItem=new CheckableListItem<>(R.string.settings_gif, R.string.mo_setting_play_gif_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, ()->toggleCheckableItem(playGifsItem)),
|
||||
unlistedRepliesItem=new CheckableListItem<>(R.string.mo_change_default_reply_visibility_to_unlisted, R.string.mo_setting_default_reply_privacy_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.defaultToUnlistedReplies, R.drawable.ic_fluent_lock_open_24_regular, ()->toggleCheckableItem(unlistedRepliesItem)),
|
||||
overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, ()->toggleCheckableItem(overlayMediaItem)),
|
||||
@@ -60,7 +61,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
prefixRepliesItem=new ListItem<>(R.string.sk_settings_prefix_reply_cw_with_re, getPrefixWithRepliesString(), R.drawable.ic_fluent_arrow_reply_24_regular, this::onPrefixRepliesClick),
|
||||
forwardReportsItem=new CheckableListItem<>(R.string.sk_settings_forward_report_default, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.forwardReportDefault, R.drawable.ic_fluent_arrow_forward_24_regular, ()->toggleCheckableItem(forwardReportsItem)),
|
||||
loadNewPostsItem=new CheckableListItem<>(R.string.sk_settings_load_new_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.loadNewPosts, R.drawable.ic_fluent_arrow_sync_24_regular, this::onLoadNewPostsClick),
|
||||
seeNewPostsBtnItem=new CheckableListItem<>(R.string.sk_settings_show_new_posts_button, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNewPostsButton, R.drawable.ic_fluent_arrow_up_24_regular, ()->toggleCheckableItem(seeNewPostsBtnItem)),
|
||||
seeNewPostsBtnItem=new CheckableListItem<>(R.string.sk_settings_see_new_posts_button, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNewPostsButton, R.drawable.ic_fluent_arrow_up_24_regular, ()->toggleCheckableItem(seeNewPostsBtnItem)),
|
||||
remoteLoadingItem=new CheckableListItem<>(R.string.sk_settings_allow_remote_loading, R.string.sk_settings_allow_remote_loading_explanation, CheckableListItem.Style.SWITCH, GlobalUserPreferences.allowRemoteLoading, R.drawable.ic_fluent_communication_24_regular, ()->toggleCheckableItem(remoteLoadingItem)),
|
||||
mentionRebloggerAutomaticallyItem=new CheckableListItem<>(R.string.mo_mention_reblogger_automatically, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.mentionRebloggerAutomatically, R.drawable.ic_fluent_comment_mention_24_regular, ()->toggleCheckableItem(mentionRebloggerAutomaticallyItem)),
|
||||
hapticFeedbackItem=new CheckableListItem<>(R.string.mo_haptic_feedback, R.string.mo_setting_haptic_feedback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.hapticFeedback, R.drawable.ic_fluent_phone_vibrate_24_regular,()->toggleCheckableItem(hapticFeedbackItem), true),
|
||||
@@ -173,7 +174,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
GlobalUserPreferences.overlayMedia=overlayMediaItem.checked;
|
||||
GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
|
||||
GlobalUserPreferences.altTextReminders=altTextItem.checked;
|
||||
GlobalUserPreferences.confirmUnfollow=customTabsItem.checked;
|
||||
GlobalUserPreferences.confirmUnfollow=confirmUnfollowItem.checked;
|
||||
GlobalUserPreferences.confirmBoost=confirmBoostItem.checked;
|
||||
GlobalUserPreferences.confirmDeletePost=confirmDeleteItem.checked;
|
||||
GlobalUserPreferences.forwardReportDefault=forwardReportsItem.checked;
|
||||
@@ -183,6 +184,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
GlobalUserPreferences.mentionRebloggerAutomatically=mentionRebloggerAutomaticallyItem.checked;
|
||||
GlobalUserPreferences.hapticFeedback=hapticFeedbackItem.checked;
|
||||
GlobalUserPreferences.defaultToUnlistedReplies=unlistedRepliesItem.checked;
|
||||
GlobalUserPreferences.showPostsWithoutAlt=showPostsWithoutAltItem.checked;
|
||||
GlobalUserPreferences.save();
|
||||
AccountLocalPreferences lp=getLocalPrefs();
|
||||
lp.showBoosts=showBoostsItem.checked;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem;
|
||||
|
||||
// MOSHIDON
|
||||
private CheckableListItem<Void> enableDoubleTapToSwipeItem, relocatePublishButtonItem;
|
||||
private CheckableListItem<Void> enableDoubleTapToSwipeItem, relocatePublishButtonItem, showPostDividersItem, enableDoubleTapToSearchItem, showMediaPreviewItem;
|
||||
|
||||
private AccountLocalPreferences lp;
|
||||
|
||||
@@ -62,10 +62,12 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
relocatePublishButtonItem=new CheckableListItem<>(R.string.mo_relocate_publish_button, R.string.mo_setting_relocate_publish_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.relocatePublishButton, R.drawable.ic_fluent_arrow_autofit_down_24_regular, ()->toggleCheckableItem(relocatePublishButtonItem)),
|
||||
revealCWsItem=new CheckableListItem<>(R.string.sk_settings_always_reveal_content_warnings, 0, CheckableListItem.Style.SWITCH, lp.revealCWs, R.drawable.ic_fluent_chat_warning_24_regular, ()->toggleCheckableItem(revealCWsItem)),
|
||||
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_fluent_flag_24_regular, ()->toggleCheckableItem(hideSensitiveMediaItem)),
|
||||
showMediaPreviewItem=new CheckableListItem<>(R.string.mo_show_media_preview, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showMediaPreview, R.drawable.ic_fluent_image_24_regular, ()->toggleCheckableItem(showMediaPreviewItem)),
|
||||
interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, R.string.mo_setting_interaction_count_summary, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_fluent_number_row_24_regular, ()->toggleCheckableItem(interactionCountsItem)),
|
||||
emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_fluent_emoji_24_regular, ()->toggleCheckableItem(emojiInNamesItem)),
|
||||
marqueeItem=new CheckableListItem<>(R.string.sk_settings_enable_marquee, R.string.mo_setting_marquee_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.toolbarMarquee, R.drawable.ic_fluent_text_more_24_regular, ()->toggleCheckableItem(marqueeItem)),
|
||||
reduceMotionItem=new CheckableListItem<>(R.string.sk_settings_reduce_motion, R.string.mo_setting_reduced_motion_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.reduceMotion, R.drawable.ic_fluent_star_emphasis_24_regular, ()->toggleCheckableItem(reduceMotionItem)),
|
||||
enableDoubleTapToSearchItem=new CheckableListItem<>(R.string.mo_double_tap_to_search, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.doubleTapToSearch, R.drawable.ic_fluent_search_24_regular, ()->toggleCheckableItem(enableDoubleTapToSearchItem)),
|
||||
disableSwipeItem=new CheckableListItem<>(R.string.sk_settings_tabs_disable_swipe, R.string.mo_setting_disable_swipe_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableSwipe, R.drawable.ic_fluent_swipe_right_24_regular, ()->toggleCheckableItem(disableSwipeItem)),
|
||||
enableDoubleTapToSwipeItem=new CheckableListItem<>(R.string.mo_double_tap_to_swipe_between_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.doubleTapToSwipe, R.drawable.ic_fluent_double_tap_swipe_right_24_regular, ()->toggleCheckableItem(enableDoubleTapToSwipeItem)),
|
||||
altIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showAltIndicator, R.drawable.ic_fluent_scan_text_24_regular, ()->toggleCheckableItem(altIndicatorItem)),
|
||||
@@ -74,6 +76,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
spectatorModeItem=new CheckableListItem<>(R.string.sk_settings_hide_interaction, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.spectatorMode, R.drawable.ic_fluent_star_off_24_regular, ()->toggleCheckableItem(spectatorModeItem)),
|
||||
hideFabItem=new CheckableListItem<>(R.string.sk_settings_hide_fab, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.autoHideFab, R.drawable.ic_fluent_edit_24_regular, ()->toggleCheckableItem(hideFabItem)),
|
||||
translateOpenedItem=new CheckableListItem<>(R.string.sk_settings_translate_only_opened, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.translateButtonOpenedOnly, R.drawable.ic_fluent_translate_24_regular, ()->toggleCheckableItem(translateOpenedItem)),
|
||||
showPostDividersItem=new CheckableListItem<>(R.string.mo_enable_dividers, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showDividers, R.drawable.ic_fluent_timeline_24_regular, ()->toggleCheckableItem(showPostDividersItem)),
|
||||
disablePillItem=new CheckableListItem<>(R.string.sk_disable_pill_shaped_active_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableM3PillActiveIndicator, R.drawable.ic_fluent_pill_24_regular, ()->toggleCheckableItem(disablePillItem)),
|
||||
showNavigationLabelsItem=new CheckableListItem<>(R.string.sk_settings_show_labels_in_navigation_bar, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNavigationLabels, R.drawable.ic_fluent_tag_24_regular, ()->toggleCheckableItem(showNavigationLabelsItem), true),
|
||||
pronounsInTimelinesItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_timelines, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInTimelines, 0, ()->toggleCheckableItem(pronounsInTimelinesItem)),
|
||||
@@ -102,7 +105,9 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
|
||||
boolean restartPlease=
|
||||
GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked ||
|
||||
GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked;
|
||||
GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked ||
|
||||
GlobalUserPreferences.showMediaPreview !=showMediaPreviewItem.checked ||
|
||||
GlobalUserPreferences.showDividers!=showPostDividersItem.checked;
|
||||
|
||||
lp.revealCWs=revealCWsItem.checked;
|
||||
lp.hideSensitiveMedia=hideSensitiveMediaItem.checked;
|
||||
@@ -113,6 +118,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
GlobalUserPreferences.relocatePublishButton=relocatePublishButtonItem.checked;
|
||||
GlobalUserPreferences.reduceMotion=reduceMotionItem.checked;
|
||||
GlobalUserPreferences.disableSwipe=disableSwipeItem.checked;
|
||||
GlobalUserPreferences.doubleTapToSearch=enableDoubleTapToSearchItem.checked;
|
||||
GlobalUserPreferences.doubleTapToSwipe=enableDoubleTapToSwipeItem.checked;
|
||||
GlobalUserPreferences.showAltIndicator=altIndicatorItem.checked;
|
||||
GlobalUserPreferences.showNoAltIndicator=noAltIndicatorItem.checked;
|
||||
@@ -120,11 +126,13 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
GlobalUserPreferences.spectatorMode=spectatorModeItem.checked;
|
||||
GlobalUserPreferences.autoHideFab=hideFabItem.checked;
|
||||
GlobalUserPreferences.translateButtonOpenedOnly=translateOpenedItem.checked;
|
||||
GlobalUserPreferences.showDividers=showPostDividersItem.checked;
|
||||
GlobalUserPreferences.disableM3PillActiveIndicator=disablePillItem.checked;
|
||||
GlobalUserPreferences.showNavigationLabels=showNavigationLabelsItem.checked;
|
||||
GlobalUserPreferences.displayPronounsInTimelines=pronounsInTimelinesItem.checked;
|
||||
GlobalUserPreferences.displayPronounsInThreads=pronounsInThreadsItem.checked;
|
||||
GlobalUserPreferences.displayPronounsInUserListings=pronounsInUserListingsItem.checked;
|
||||
GlobalUserPreferences.showMediaPreview=showMediaPreviewItem.checked;
|
||||
GlobalUserPreferences.save();
|
||||
if(restartPlease) restartActivityToApplyNewTheme();
|
||||
else E.post(new StatusDisplaySettingsChangedEvent(accountID));
|
||||
|
||||
@@ -2,10 +2,14 @@ package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
|
||||
import org.joinmastodon.android.fragments.HasAccountID;
|
||||
import org.joinmastodon.android.model.ContentType;
|
||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||
@@ -15,12 +19,13 @@ import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
|
||||
public class SettingsInstanceFragment extends BaseSettingsFragment<Void> implements HasAccountID{
|
||||
private CheckableListItem<Void> contentTypesItem, emojiReactionsItem, emojiReactionsInListsItem, localOnlyItem, glitchModeItem;
|
||||
private ListItem<Void> defaultContentTypeItem;
|
||||
private CheckableListItem<Void> contentTypesItem, emojiReactionsItem, localOnlyItem, glitchModeItem;
|
||||
private ListItem<Void> defaultContentTypeItem, showEmojiReactionsItem;
|
||||
private AccountLocalPreferences lp;
|
||||
|
||||
@Override
|
||||
@@ -35,16 +40,16 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||
new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")),
|
||||
new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true),
|
||||
contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, this::onContentTypeClick),
|
||||
defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick),
|
||||
defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true),
|
||||
emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, this::onEmojiReactionsClick),
|
||||
emojiReactionsInListsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions_in_lists, R.string.sk_settings_emoji_reactions_in_lists_explanation, CheckableListItem.Style.SWITCH, lp.showEmojiReactionsInLists, R.drawable.ic_fluent_emoji_24_regular, ()->toggleCheckableItem(emojiReactionsInListsItem)),
|
||||
showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true),
|
||||
localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, this::onLocalOnlyClick),
|
||||
glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, ()->toggleCheckableItem(glitchModeItem))
|
||||
));
|
||||
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
|
||||
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
|
||||
emojiReactionsItem.checkedChangeListener=checked->onEmojiReactionsClick();
|
||||
emojiReactionsInListsItem.isEnabled=emojiReactionsItem.checked;
|
||||
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
|
||||
localOnlyItem.checkedChangeListener=checked->onLocalOnlyClick();
|
||||
glitchModeItem.isEnabled=localOnlyItem.checked;
|
||||
}
|
||||
@@ -57,10 +62,10 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||
super.onHidden();
|
||||
lp.contentTypesEnabled=contentTypesItem.checked;
|
||||
lp.emojiReactionsEnabled=emojiReactionsItem.checked;
|
||||
lp.showEmojiReactionsInLists=emojiReactionsInListsItem.checked;
|
||||
lp.localOnlySupported=localOnlyItem.checked;
|
||||
lp.glitchInstance=glitchModeItem.checked;
|
||||
lp.save();
|
||||
E.post(new StatusDisplaySettingsChangedEvent(accountID));
|
||||
}
|
||||
|
||||
private void onServerClick(){
|
||||
@@ -107,11 +112,34 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||
.show();
|
||||
}
|
||||
|
||||
private void onShowEmojiReactionsClick(){
|
||||
int selected=lp.showEmojiReactions.ordinal();
|
||||
int[] newSelected={selected};
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_settings_show_emoji_reactions)
|
||||
.setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_show_emoji_reactions_hide_empty, R.string.sk_settings_show_emoji_reactions_only_opened, R.string.sk_settings_show_emoji_reactions_always).mapToObj(this::getString).toArray(String[]::new),
|
||||
selected, (dlg, item)->newSelected[0]=item)
|
||||
.setPositiveButton(R.string.ok, (dlg, item)->{
|
||||
lp.showEmojiReactions=AccountLocalPreferences.ShowEmojiReactions.values()[newSelected[0]];
|
||||
showEmojiReactionsItem.subtitleRes=getShowEmojiReactionsString();
|
||||
rebindItem(showEmojiReactionsItem);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private @StringRes int getShowEmojiReactionsString(){
|
||||
return switch(lp.showEmojiReactions){
|
||||
case HIDE_EMPTY -> R.string.sk_settings_show_emoji_reactions_hide_empty;
|
||||
case ONLY_OPENED -> R.string.sk_settings_show_emoji_reactions_only_opened;
|
||||
case ALWAYS -> R.string.sk_settings_show_emoji_reactions_always;
|
||||
};
|
||||
}
|
||||
|
||||
private void onEmojiReactionsClick(){
|
||||
toggleCheckableItem(emojiReactionsItem);
|
||||
emojiReactionsInListsItem.checked=false;
|
||||
emojiReactionsInListsItem.isEnabled=emojiReactionsItem.checked;
|
||||
rebindItem(emojiReactionsInListsItem);
|
||||
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
|
||||
rebindItem(showEmojiReactionsItem);
|
||||
}
|
||||
|
||||
private void onLocalOnlyClick(){
|
||||
|
||||
@@ -55,6 +55,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
||||
onDataLoaded(List.of(
|
||||
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick),
|
||||
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick),
|
||||
// new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_privacy_tip_24px, this::onPrivacyClick),
|
||||
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick),
|
||||
new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick),
|
||||
new ListItem<>(getString(R.string.about_app, getString(R.string.mo_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true),
|
||||
@@ -133,6 +134,10 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
||||
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
|
||||
}
|
||||
|
||||
// private void onPrivacyClick(){
|
||||
// Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
|
||||
// }
|
||||
|
||||
private void onFiltersClick(){
|
||||
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
|
||||
}
|
||||
@@ -152,6 +157,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
||||
private void onLogOutClick(){
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.log_out)
|
||||
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
|
||||
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
|
||||
loggedOut=true;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
|
||||
private CheckableListItem<Void> discoverableItem, indexableItem;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.settings_privacy);
|
||||
Account self=AccountSessionManager.get(accountID).self;
|
||||
onDataLoaded(List.of(
|
||||
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, ()->toggleCheckableItem(discoverableItem)),
|
||||
indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_search_24px, ()->toggleCheckableItem(indexableItem))
|
||||
));
|
||||
if(self.source.indexable==null)
|
||||
indexableItem.isEnabled=false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){}
|
||||
|
||||
@Override
|
||||
public void onPause(){
|
||||
super.onPause();
|
||||
Account self=AccountSessionManager.get(accountID).self;
|
||||
if(self.discoverable!=discoverableItem.checked || (self.source.indexable!=null && self.source.indexable!=indexableItem.checked)){
|
||||
self.discoverable=discoverableItem.checked;
|
||||
self.source.indexable=indexableItem.checked;
|
||||
AccountSessionManager.get(accountID).savePreferencesLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,18 +153,21 @@ public class SettingsServerFragment extends AppKitFragment{
|
||||
@NonNull
|
||||
@Override
|
||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
FrameLayout view=tabViews[viewType];
|
||||
((ViewGroup)view.getParent()).removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
FrameLayout view=new FrameLayout(parent.getContext());
|
||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
return new SimpleViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
|
||||
FrameLayout view=tabViews[position];
|
||||
if(view.getParent() instanceof ViewGroup parent)
|
||||
parent.removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
Fragment fragment=getFragmentForPage(position);
|
||||
if(!fragment.isAdded()){
|
||||
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
|
||||
getChildFragmentManager().beginTransaction().add(view.getId(), fragment).commit();
|
||||
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@Override
|
||||
public boolean onPreDraw(){
|
||||
|
||||
@@ -7,12 +7,17 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
/**
|
||||
* Represents a user of Mastodon and their associated profile.
|
||||
*/
|
||||
@@ -23,22 +28,22 @@ public class Account extends BaseModel implements Searchable{
|
||||
/**
|
||||
* The account id
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String id;
|
||||
/**
|
||||
* The username of the account, not including domain.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String username;
|
||||
/**
|
||||
* The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String acct;
|
||||
/**
|
||||
* The location of the user's profile page.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String url;
|
||||
|
||||
// Display attributes
|
||||
@@ -51,12 +56,12 @@ public class Account extends BaseModel implements Searchable{
|
||||
/**
|
||||
* The profile's bio / description.
|
||||
*/
|
||||
|
||||
// @RequiredField
|
||||
public String note;
|
||||
/**
|
||||
* An image icon that is shown next to statuses and in the profile.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String avatar;
|
||||
/**
|
||||
* A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.
|
||||
@@ -134,6 +139,7 @@ public class Account extends BaseModel implements Searchable{
|
||||
* When a timed mute will expire, if applicable.
|
||||
*/
|
||||
public Instant muteExpiresAt;
|
||||
public boolean noindex;
|
||||
|
||||
public List<Role> roles;
|
||||
|
||||
@@ -157,16 +163,26 @@ public class Account extends BaseModel implements Searchable{
|
||||
if(fields!=null){
|
||||
for(AccountField f:fields)
|
||||
f.postprocess();
|
||||
} else {
|
||||
fields = Collections.emptyList();
|
||||
}
|
||||
if(emojis!=null){
|
||||
for(Emoji e:emojis)
|
||||
e.postprocess();
|
||||
} else {
|
||||
emojis = Collections.emptyList();
|
||||
}
|
||||
if(moved!=null)
|
||||
moved.postprocess();
|
||||
if(TextUtils.isEmpty(displayName))
|
||||
displayName=username;
|
||||
if(fqn == null) fqn = getFullyQualifiedName();
|
||||
if(id == null) id = "";
|
||||
if(username == null) username = "";
|
||||
if(TextUtils.isEmpty(displayName))
|
||||
displayName = !TextUtils.isEmpty(username) ? username : "";
|
||||
if(acct == null) acct = "";
|
||||
if(url == null) url = "";
|
||||
if(note == null) note = "";
|
||||
if(avatar == null) avatar = "";
|
||||
}
|
||||
|
||||
public boolean isLocal(){
|
||||
@@ -191,6 +207,8 @@ public class Account extends BaseModel implements Searchable{
|
||||
}
|
||||
|
||||
public String getFullyQualifiedName() {
|
||||
if (TextUtils.isEmpty(acct))
|
||||
return "";
|
||||
return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL();
|
||||
}
|
||||
|
||||
@@ -221,6 +239,7 @@ public class Account extends BaseModel implements Searchable{
|
||||
", source="+source+
|
||||
", suspended="+suspended+
|
||||
", muteExpiresAt="+muteExpiresAt+
|
||||
", noindex="+noindex+
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.jsoup.internal.StringUtil;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class AltTextFilter extends LegacyFilter {
|
||||
|
||||
public AltTextFilter(FilterAction filterAction, FilterContext firstContext, FilterContext... restContexts) {
|
||||
this.filterAction = filterAction;
|
||||
isRemote = false;
|
||||
context = EnumSet.of(firstContext, restContexts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Status status) {
|
||||
return status.getContentStatus().mediaAttachments.stream().map(attachment -> attachment.description).anyMatch(StringUtil::isBlank);
|
||||
}
|
||||
}
|
||||
@@ -46,26 +46,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(){
|
||||
|
||||
@@ -41,6 +41,12 @@ public class Emoji extends BaseModel{
|
||||
this.staticUrl = staticUrl;
|
||||
}
|
||||
|
||||
public String getUrl(boolean playGifs){
|
||||
String idealUrl=playGifs ? url : staticUrl;
|
||||
if(idealUrl==null) return url==null ? staticUrl : url;
|
||||
return idealUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Emoji{"+
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -22,6 +23,12 @@ public class EmojiReaction {
|
||||
|
||||
public transient ImageLoaderRequest request;
|
||||
|
||||
public String getUrl(boolean playGifs){
|
||||
String idealUrl=playGifs ? url : staticUrl;
|
||||
if(idealUrl==null) return url==null ? staticUrl : url;
|
||||
return idealUrl;
|
||||
}
|
||||
|
||||
public static EmojiReaction of(Emoji info, Account me){
|
||||
EmojiReaction reaction=new EmojiReaction();
|
||||
reaction.me=true;
|
||||
|
||||
@@ -23,6 +23,7 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
|
||||
", following="+following+
|
||||
", history="+history+
|
||||
", statusesCount="+statusesCount+
|
||||
", following="+following+
|
||||
'}';
|
||||
}
|
||||
|
||||
@@ -30,4 +31,19 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
|
||||
public String getID(){
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o){
|
||||
if(this==o) return true;
|
||||
if(o==null || getClass()!=o.getClass()) return false;
|
||||
|
||||
Hashtag hashtag=(Hashtag) o;
|
||||
|
||||
return name.equals(hashtag.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(){
|
||||
return name.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,4 +20,22 @@ public class Mention extends BaseModel{
|
||||
", url='"+url+'\''+
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o){
|
||||
if(this==o) return true;
|
||||
if(o==null || getClass()!=o.getClass()) return false;
|
||||
|
||||
Mention mention=(Mention) o;
|
||||
|
||||
if(!id.equals(mention.id)) return false;
|
||||
return url.equals(mention.url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(){
|
||||
int result=id.hashCode();
|
||||
result=31*result+url.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ public class Source extends BaseModel{
|
||||
* The number of pending follow requests.
|
||||
*/
|
||||
public int followRequestCount;
|
||||
public Boolean indexable;
|
||||
public boolean hideCollections;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
@@ -54,6 +56,8 @@ public class Source extends BaseModel{
|
||||
", sensitive="+sensitive+
|
||||
", language='"+language+'\''+
|
||||
", followRequestCount="+followRequestCount+
|
||||
", indexable="+indexable+
|
||||
", hideCollections="+hideCollections+
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusMuteChangedEvent;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
@@ -28,8 +29,6 @@ import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@Parcel
|
||||
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
@RequiredField
|
||||
@@ -120,8 +119,8 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
for(FilterResult fr:filtered)
|
||||
fr.postprocess();
|
||||
|
||||
if(!TextUtils.isEmpty(spoilerText)) sensitive=true;
|
||||
spoilerRevealed=TextUtils.isEmpty(spoilerText);
|
||||
spoilerRevealed=!hasSpoiler();
|
||||
if(!spoilerRevealed) sensitive=true;
|
||||
sensitiveRevealed=!sensitive;
|
||||
if(visibility.equals(StatusPrivacy.LOCAL)) localOnly=true;
|
||||
if(emojiReactions!=null) reactions=emojiReactions;
|
||||
@@ -181,6 +180,13 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
reblogged=ev.reblogged;
|
||||
bookmarked=ev.bookmarked;
|
||||
pinned=ev.pinned;
|
||||
}
|
||||
|
||||
public void update(StatusMuteChangedEvent ev) {
|
||||
muted=ev.muted;
|
||||
}
|
||||
|
||||
public void update(EmojiReactionsUpdatedEvent ev){
|
||||
reactions=ev.reactions;
|
||||
}
|
||||
|
||||
@@ -194,6 +200,10 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
return strippedText;
|
||||
}
|
||||
|
||||
public boolean hasSpoiler(){
|
||||
return !TextUtils.isEmpty(spoilerText);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Status clone(){
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public class Translation extends BaseModel{
|
||||
public String content;
|
||||
public String detectedSourceLanguage;
|
||||
public String provider;
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.joinmastodon.android.model.viewmodel;
|
||||
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
@@ -24,9 +26,13 @@ public class AccountViewModel{
|
||||
|
||||
public AccountViewModel(Account account, String accountID){
|
||||
this.account=account;
|
||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
|
||||
AccountSession session = AccountSessionManager.get(accountID);
|
||||
avaRequest=new UrlImageLoaderRequest(
|
||||
TextUtils.isEmpty(account.avatar) ? session.getDefaultAvatarUrl() :
|
||||
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
|
||||
V.dp(50), V.dp(50));
|
||||
emojiHelper=new CustomEmojiHelper();
|
||||
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
|
||||
if(session.getLocalPreferences().customEmojiInNames)
|
||||
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||
else
|
||||
parsedName=account.displayName;
|
||||
|
||||
@@ -130,7 +130,7 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
new M3AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.log_out)
|
||||
.setMessage(activity.getString(R.string.confirm_log_out))
|
||||
.setMessage(activity.getString(R.string.confirm_log_out, session.getFullUsername()))
|
||||
.setPositiveButton(R.string.log_out, (dialog, which) -> logOut(accountID))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -37,6 +37,16 @@ public class OutlineProviders{
|
||||
}
|
||||
};
|
||||
|
||||
private final static int BUTTON_BG_HEIGHT=V.dp(40);
|
||||
public static final ViewOutlineProvider M3_BUTTON=new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
int viewHeight=view.getHeight();
|
||||
int top=Math.floorDiv(viewHeight - BUTTON_BG_HEIGHT, 2);
|
||||
outline.setRoundRect(0, top, view.getWidth(), top + BUTTON_BG_HEIGHT, V.dp(20));
|
||||
}
|
||||
};
|
||||
|
||||
public static ViewOutlineProvider roundedRect(int dp){
|
||||
ViewOutlineProvider provider=roundedRects.get(dp);
|
||||
if(provider!=null)
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.TextView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
@@ -41,12 +42,13 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
|
||||
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||
public CharSequence parsedName, parsedBio;
|
||||
|
||||
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account, Notification notification){
|
||||
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, String accountID, Account account, Notification notification){
|
||||
super(parentID, parentFragment);
|
||||
this.account=account;
|
||||
this.notification=notification;
|
||||
if(!TextUtils.isEmpty(account.avatar))
|
||||
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
|
||||
avaRequest=new UrlImageLoaderRequest(
|
||||
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(accountID).getDefaultAvatarUrl() : account.avatar,
|
||||
V.dp(50), V.dp(50));
|
||||
if(!TextUtils.isEmpty(account.header))
|
||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), parentFragment.getAccountID());
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.announcements.AddAnnouncementReaction;
|
||||
@@ -31,11 +32,10 @@ import org.joinmastodon.android.api.requests.statuses.PleromaAddStatusReaction;
|
||||
import org.joinmastodon.android.api.requests.statuses.PleromaDeleteStatusReaction;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiReaction;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -44,8 +44,6 @@ import org.joinmastodon.android.ui.utils.TextDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -62,25 +60,24 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
public final Status status;
|
||||
private final Drawable placeholder;
|
||||
private final boolean hideAdd, forAnnouncement;
|
||||
private final boolean hideEmpty, forAnnouncement, playGifs;
|
||||
private final String accountID;
|
||||
private boolean hidden;
|
||||
private static final float ALPHA_DISABLED=0.55f;
|
||||
|
||||
public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideAdd, boolean forAnnouncement) {
|
||||
public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideEmpty, boolean forAnnouncement) {
|
||||
super(parentID, parentFragment);
|
||||
this.status=status;
|
||||
this.hideAdd=hideAdd;
|
||||
this.hideEmpty=hideEmpty;
|
||||
this.forAnnouncement=forAnnouncement;
|
||||
this.accountID=accountID;
|
||||
placeholder=parentFragment.getContext().getDrawable(R.drawable.image_placeholder).mutate();
|
||||
placeholder.setBounds(0, 0, V.sp(24), V.sp(24));
|
||||
updateHidden();
|
||||
playGifs=GlobalUserPreferences.playGifs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCount(){
|
||||
return (int) status.reactions.stream().filter(r->r.url != null).count();
|
||||
return (int) status.reactions.stream().filter(r->r.getUrl(playGifs)!=null).count();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,11 +91,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
}
|
||||
|
||||
public boolean isHidden(){
|
||||
return hidden;
|
||||
}
|
||||
|
||||
private void updateHidden(){
|
||||
hidden=status.reactions.isEmpty() && hideAdd;
|
||||
return status.reactions.isEmpty() && hideEmpty;
|
||||
}
|
||||
|
||||
// borrowed from ProfileFragment
|
||||
@@ -178,8 +171,9 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
public void onBind(EmojiReactionsStatusDisplayItem item) {
|
||||
if(emojiKeyboard != null) root.removeView(emojiKeyboard.getView());
|
||||
AccountSession session=item.parentFragment.getSession();
|
||||
item.status.reactions.forEach(r->
|
||||
r.request=r.url != null ? new UrlImageLoaderRequest(r.url, V.sp(24), V.sp(24)) : null);
|
||||
item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null
|
||||
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), V.sp(24), V.sp(24))
|
||||
: null);
|
||||
emojiKeyboard=new CustomEmojiPopupKeyboard(
|
||||
(Activity) item.parentFragment.getContext(),
|
||||
AccountSessionManager.getInstance().getCustomEmojis(session.domain),
|
||||
@@ -187,12 +181,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
emojiKeyboard.setListener(this);
|
||||
space.setVisibility(View.GONE);
|
||||
root.addView(emojiKeyboard.getView());
|
||||
item.updateHidden();
|
||||
root.setVisibility(item.hidden ? View.GONE : View.VISIBLE);
|
||||
line.setVisibility(item.hidden ? View.GONE : View.VISIBLE);
|
||||
boolean hidden=item.isHidden();
|
||||
root.setVisibility(hidden ? View.GONE : View.VISIBLE);
|
||||
line.setVisibility(hidden ? View.GONE : View.VISIBLE);
|
||||
line.setPadding(
|
||||
list.getPaddingLeft(),
|
||||
item.hidden ? 0 : V.dp(8),
|
||||
hidden ? 0 : V.dp(8),
|
||||
list.getPaddingRight(),
|
||||
item.forAnnouncement ? V.dp(8) : 0
|
||||
);
|
||||
@@ -219,6 +213,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
}
|
||||
|
||||
private void addEmojiReaction(String emoji, Emoji info) {
|
||||
int countBefore=item.status.reactions.size();
|
||||
for(int i=0; i<item.status.reactions.size(); i++){
|
||||
EmojiReaction r=item.status.reactions.get(i);
|
||||
if(r.name.equals(emoji) && r.me){
|
||||
@@ -261,7 +256,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
finalExisting.add(me);
|
||||
adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting));
|
||||
}
|
||||
E.post(new StatusCountersUpdatedEvent(item.status, adapter.parentHolder));
|
||||
E.post(new EmojiReactionsUpdatedEvent(item.status.id, item.status.reactions, countBefore==0, adapter.parentHolder));
|
||||
}, resetBtn).exec(item.accountID);
|
||||
}
|
||||
|
||||
@@ -291,7 +286,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
if(item.status.reactions.get(index).url==null) return;
|
||||
if(item.status.reactions.get(index).getUrl(item.playGifs)==null) return;
|
||||
setImage(index, item.placeholder);
|
||||
}
|
||||
|
||||
@@ -324,7 +319,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return item.status.reactions.get(position).url == null ? 0 : 1;
|
||||
return item.status.reactions.get(position).getUrl(item.playGifs)==null ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -364,7 +359,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
btn.setText(UiUtils.abbreviateNumber(reaction.count));
|
||||
btn.setContentDescription(reaction.name);
|
||||
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) btn.setTooltipText(reaction.name);
|
||||
if(reaction.url==null){
|
||||
if(reaction.getUrl(parent.playGifs)==null){
|
||||
Paint p=new Paint();
|
||||
p.setTextSize(V.sp(18));
|
||||
TextDrawable drawable=new TextDrawable(p, reaction.name);
|
||||
@@ -392,7 +387,11 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
break;
|
||||
}
|
||||
|
||||
E.post(new StatusCountersUpdatedEvent(parent.status, adapter.parentHolder));
|
||||
if(parent.isHidden()){
|
||||
adapter.parentHolder.root.setVisibility(View.GONE);
|
||||
adapter.parentHolder.line.setVisibility(View.GONE);
|
||||
}
|
||||
E.post(new EmojiReactionsUpdatedEvent(parent.status.id, parent.status.reactions, parent.status.reactions.isEmpty(), adapter.parentHolder));
|
||||
adapter.parentHolder.imgLoader.updateImages();
|
||||
}, null).exec(parent.parentFragment.getAccountID());
|
||||
});
|
||||
@@ -406,7 +405,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
args.putString("statusID", parent.status.id);
|
||||
int atSymbolIndex = emojiReaction.name.indexOf("@");
|
||||
args.putString("emoji", atSymbolIndex != -1 ? emojiReaction.name.substring(0, atSymbolIndex) : emojiReaction.name);
|
||||
args.putString("url", emojiReaction.url);
|
||||
args.putString("url", emojiReaction.getUrl(parent.playGifs));
|
||||
args.putInt("count", emojiReaction.count);
|
||||
Nav.go(parent.parentFragment.getActivity(), StatusEmojiReactionsListFragment.class, args);
|
||||
return true;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.Image;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
@@ -14,10 +16,18 @@ import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FileStatusDisplayItem extends StatusDisplayItem{
|
||||
private final Status status;
|
||||
@@ -36,26 +46,35 @@ public class FileStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<FileStatusDisplayItem>{
|
||||
private final TextView title, domain;
|
||||
private final ImageView icon;
|
||||
private final Context context;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_file, parent);
|
||||
title=findViewById(R.id.title);
|
||||
domain=findViewById(R.id.domain);
|
||||
icon=findViewById(R.id.imageView);
|
||||
this.context=context;
|
||||
|
||||
findViewById(R.id.inner).setOnClickListener(this::onClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(FileStatusDisplayItem item) {
|
||||
Uri url = Uri.parse(getUrl());
|
||||
title.setText(item.attachment.description != null
|
||||
? item.attachment.description
|
||||
: url.getLastPathSegment());
|
||||
title.setEllipsize(item.attachment.description != null ? TextUtils.TruncateAt.END : TextUtils.TruncateAt.MIDDLE);
|
||||
domain.setText(url.getHost());
|
||||
|
||||
title.setText(item.attachment.description != null
|
||||
? item.attachment.description
|
||||
: url.getLastPathSegment());
|
||||
|
||||
title.setEllipsize(item.attachment.description != null ? TextUtils.TruncateAt.END : TextUtils.TruncateAt.MIDDLE);
|
||||
domain.setText(url.getHost());
|
||||
|
||||
icon.setImageDrawable(context.getDrawable(R.drawable.ic_fluent_attach_24_regular));
|
||||
}
|
||||
|
||||
private void onClick(View v) {
|
||||
UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), getUrl());
|
||||
UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), getUrl());
|
||||
}
|
||||
|
||||
private String getUrl() {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import static org.joinmastodon.android.ui.utils.UiUtils.opacityIn;
|
||||
import static org.joinmastodon.android.ui.utils.UiUtils.opacityOut;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
|
||||
@@ -8,14 +8,21 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
// Mind the gap!
|
||||
public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean loading;
|
||||
private Status status;
|
||||
|
||||
public GapStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||
public GapStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status){
|
||||
super(parentID, parentFragment);
|
||||
this.status=status;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -24,25 +31,53 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<GapStatusDisplayItem>{
|
||||
public final ProgressBar progress;
|
||||
public final TextView text;
|
||||
public final ProgressBar progressTop, progressBottom;
|
||||
public final TextView textTop, gap, textBottom;
|
||||
public final View top, bottom;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_gap, parent);
|
||||
progress=findViewById(R.id.progress);
|
||||
text=findViewById(R.id.text);
|
||||
itemView.setForeground(new SawtoothTearDrawable(context));
|
||||
progressTop=findViewById(R.id.progress_top);
|
||||
progressBottom=findViewById(R.id.progress_bottom);
|
||||
textTop=findViewById(R.id.text_top);
|
||||
textBottom=findViewById(R.id.text_bottom);
|
||||
top=findViewById(R.id.top);
|
||||
top.setOnClickListener(this::onViewClick);
|
||||
bottom=findViewById(R.id.bottom);
|
||||
bottom.setOnClickListener(this::onViewClick);
|
||||
gap=findViewById(R.id.gap);
|
||||
gap.setForeground(new SawtoothTearDrawable(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(GapStatusDisplayItem item){
|
||||
text.setVisibility(item.loading ? View.GONE : View.VISIBLE);
|
||||
progress.setVisibility(item.loading ? View.VISIBLE : View.GONE);
|
||||
if(!item.loading){
|
||||
progressBottom.setVisibility(View.GONE);
|
||||
progressTop.setVisibility(View.GONE);
|
||||
}
|
||||
top.setClickable(!item.loading);
|
||||
bottom.setClickable(!item.loading);
|
||||
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
|
||||
Instant dateBelow=next instanceof HeaderStatusDisplayItem h ? h.status.createdAt
|
||||
: next instanceof ReblogOrReplyLineStatusDisplayItem l ? l.status.createdAt
|
||||
: null;
|
||||
String text=dateBelow!=null && item.status.createdAt!=null && dateBelow.isBefore(item.status.createdAt)
|
||||
? UiUtils.formatPeriodBetween(item.parentFragment.getContext(), dateBelow, item.status.createdAt)
|
||||
: null;
|
||||
gap.setText(text);
|
||||
int p=text==null ? V.dp(6) : V.dp(20);
|
||||
gap.setPadding(p, p, p, p);
|
||||
}
|
||||
|
||||
private void onViewClick(View v){
|
||||
if(item.loading) return;
|
||||
boolean isTop=v==top;
|
||||
(isTop ? textTop : textBottom).startAnimation(UiUtils.opacityOut);
|
||||
V.setVisibilityAnimated((isTop ? progressTop : progressBottom), View.VISIBLE);
|
||||
item.parentFragment.onGapClick(this, isTop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
item.parentFragment.onGapClick(this);
|
||||
}
|
||||
public void onClick(){}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ 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.Mention;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
@@ -56,6 +57,7 @@ import java.time.format.FormatStyle;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
@@ -86,10 +88,14 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, CharSequence extraText, Notification notification, ScheduledStatus scheduledStatus){
|
||||
super(parentID, parentFragment);
|
||||
user=scheduledStatus != null ? AccountSessionManager.getInstance().getAccount(accountID).self : user;
|
||||
AccountSession session = AccountSessionManager.get(accountID);
|
||||
user=scheduledStatus != null ? session.self : user;
|
||||
this.user=user;
|
||||
this.createdAt=createdAt;
|
||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic, V.dp(50), V.dp(50));
|
||||
avaRequest=new UrlImageLoaderRequest(
|
||||
TextUtils.isEmpty(user.avatar) ? session.getDefaultAvatarUrl() :
|
||||
GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic,
|
||||
V.dp(50), V.dp(50));
|
||||
this.accountID=accountID;
|
||||
parsedName=new SpannableStringBuilder(user.displayName);
|
||||
this.status=status;
|
||||
@@ -134,13 +140,11 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final TextView name, time, username, extraText, pronouns;
|
||||
private final View collapseBtn, timeUsernameSeparator;
|
||||
private final ImageView avatar, more, visibility, deleteNotification, unreadIndicator, markAsRead, collapseBtnIcon;
|
||||
private final ImageView avatar, more, visibility, deleteNotification, unreadIndicator, markAsRead, collapseBtnIcon, botIcon;
|
||||
private final PopupMenu optionsMenu;
|
||||
private Relationship relationship;
|
||||
private APIRequest<?> currentRelationshipRequest;
|
||||
|
||||
//TODO: readd
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
this(activity, R.layout.display_item_header, parent);
|
||||
}
|
||||
@@ -150,6 +154,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
name=findViewById(R.id.name);
|
||||
time=findViewById(R.id.time);
|
||||
username=findViewById(R.id.username);
|
||||
botIcon=findViewById(R.id.bot_icon);
|
||||
timeUsernameSeparator=findViewById(R.id.separator);
|
||||
avatar=findViewById(R.id.avatar);
|
||||
more=findViewById(R.id.more);
|
||||
@@ -159,7 +164,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
markAsRead=findViewById(R.id.mark_as_read);
|
||||
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);
|
||||
pronouns=findViewById(R.id.pronouns);
|
||||
avatar.setOnClickListener(this::onAvaClick);
|
||||
@@ -195,7 +199,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
}
|
||||
boolean isPixelfed = item.parentFragment.isInstancePixelfed();
|
||||
boolean textEmpty = TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText);
|
||||
boolean textEmpty = TextUtils.isEmpty(item.status.content) && !item.status.hasSpoiler();
|
||||
if(!redraft && (isPixelfed || textEmpty)){
|
||||
// pixelfed doesn't support /statuses/:id/source :/
|
||||
if (isPixelfed) {
|
||||
@@ -246,6 +250,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
|
||||
}else if(id==R.id.mute){
|
||||
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
|
||||
}else if (id==R.id.mute_conversation || id==R.id.unmute_conversation) {
|
||||
UiUtils.confirmToggleMuteConversation(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, ()->{});
|
||||
}else if(id==R.id.block){
|
||||
UiUtils.confirmToggleBlockUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.blocking, r->{});
|
||||
}else if(id==R.id.report){
|
||||
@@ -325,13 +331,21 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
this.time.setVisibility(time==null ? View.GONE : View.VISIBLE);
|
||||
if(time!=null) this.time.setText(time);
|
||||
|
||||
botIcon.setVisibility(item.user.bot ? View.VISIBLE : View.GONE);
|
||||
botIcon.setColorFilter(username.getCurrentTextColor());
|
||||
|
||||
deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification!=null && !item.inset ? View.VISIBLE : View.GONE);
|
||||
if (item.hasVisibilityToggle){
|
||||
boolean disabled = !item.status.sensitiveRevealed ||
|
||||
(!TextUtils.isEmpty(item.status.spoilerText) &&
|
||||
!item.status.spoilerRevealed);
|
||||
visibility.setEnabled(!disabled);
|
||||
V.setVisibilityAnimated(visibility, disabled ? View.INVISIBLE : View.VISIBLE);
|
||||
boolean hidden = !item.status.sensitiveRevealed || (item.status.hasSpoiler() && !item.status.spoilerRevealed);
|
||||
|
||||
// doing this because V.setVisibilityAnimated ignores changes between INVISIBLE and GONE
|
||||
int newVis=hidden ? View.INVISIBLE : View.VISIBLE;
|
||||
if(newVis==View.INVISIBLE && visibility.getVisibility()==View.GONE)
|
||||
visibility.setVisibility(newVis);
|
||||
else
|
||||
V.setVisibilityAnimated(visibility, newVis);
|
||||
|
||||
visibility.setEnabled(!hidden);
|
||||
visibility.setContentDescription(item.parentFragment.getString(item.status.sensitiveRevealed ? R.string.spoiler_hide : R.string.spoiler_show));
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
|
||||
visibility.setTooltipText(visibility.getContentDescription());
|
||||
@@ -424,6 +438,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
private void onAvaClick(View v){
|
||||
if (TextUtils.isEmpty(item.user.url))
|
||||
return;
|
||||
if (item.announcement != null) {
|
||||
UiUtils.openURL(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.user.url);
|
||||
return;
|
||||
@@ -495,6 +511,14 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
menu.findItem(R.id.delete_and_redraft).setVisible(!isPostScheduled && item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.pin).setVisible(!isPostScheduled && item.status!=null && isOwnPost && !item.status.pinned);
|
||||
menu.findItem(R.id.unpin).setVisible(!isPostScheduled && item.status!=null && isOwnPost && item.status.pinned);
|
||||
menu.findItem(R.id.mute_conversation).setVisible((item.status!=null && !item.status.muted && !isPostScheduled) && (isOwnPost || item.status.mentions.stream().anyMatch(m->{
|
||||
if(m==null)
|
||||
return false;
|
||||
return AccountSessionManager.get(item.parentFragment.getAccountID()).self.id.equals(m.id) ||
|
||||
AccountSessionManager.get(item.parentFragment.getAccountID()).self.getFullyQualifiedName().equals(m.username) ||
|
||||
AccountSessionManager.get(item.parentFragment.getAccountID()).self.acct.equals(m.acct);
|
||||
})));
|
||||
menu.findItem(R.id.unmute_conversation).setVisible(item.status!=null && item.status.muted);
|
||||
menu.findItem(R.id.open_in_browser).setVisible(!isPostScheduled && item.status!=null);
|
||||
menu.findItem(R.id.copy_link).setVisible(!isPostScheduled && item.status!=null);
|
||||
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||
|
||||
@@ -27,10 +27,10 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
||||
private final Status status;
|
||||
private final UrlImageLoaderRequest imgRequest;
|
||||
|
||||
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){
|
||||
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, boolean showImagePreview){
|
||||
super(parentID, parentFragment);
|
||||
this.status=status;
|
||||
if(status.card.image!=null)
|
||||
if(status.card.image!=null && showImagePreview)
|
||||
imgRequest=new UrlImageLoaderRequest(status.card.image, 1000, 1000);
|
||||
else
|
||||
imgRequest=null;
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
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.ProfileFragment;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
@@ -53,7 +55,11 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
if(notification.type==Notification.Type.POLL){
|
||||
text=parentFragment.getString(R.string.poll_ended);
|
||||
}else{
|
||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? notification.account.avatar : notification.account.avatarStatic, V.dp(50), V.dp(50));
|
||||
AccountSession session = AccountSessionManager.get(accountID);
|
||||
avaRequest=new UrlImageLoaderRequest(
|
||||
TextUtils.isEmpty(notification.account.avatar) ? session.getDefaultAvatarUrl() :
|
||||
GlobalUserPreferences.playGifs ? notification.account.avatar : notification.account.avatarStatic,
|
||||
V.dp(50), V.dp(50));
|
||||
SpannableStringBuilder parsedName=new SpannableStringBuilder(notification.account.displayName);
|
||||
HtmlParser.parseCustomEmoji(parsedName, notification.account.emojis);
|
||||
String str = parentFragment.getString(switch(notification.type){
|
||||
@@ -107,7 +113,6 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public static class Holder extends StatusDisplayItem.Holder<NotificationHeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final ImageView icon, avatar;
|
||||
private final TextView text;
|
||||
private final int selectableItemBackground;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(activity, R.layout.display_item_notification_header, parent);
|
||||
@@ -118,10 +123,8 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
avatar.setClipToOutline(true);
|
||||
|
||||
itemView.setOnClickListener(this::onItemClick);
|
||||
TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
||||
selectableItemBackground = outValue.resourceId;
|
||||
icon.setOnClickListener(this::onItemClick);
|
||||
avatar.setOnClickListener(this::onItemClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,10 +167,7 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
case POLL -> R.attr.colorPoll;
|
||||
default -> android.R.attr.colorAccent;
|
||||
})));
|
||||
itemView.setBackgroundResource(item.notification.type != Notification.Type.POLL
|
||||
&& item.notification.type != Notification.Type.REPORT ?
|
||||
selectableItemBackground : 0);
|
||||
itemView.setClickable(item.notification.type != Notification.Type.POLL);
|
||||
itemView.setBackgroundResource(0);
|
||||
}
|
||||
|
||||
public void onItemClick(View v) {
|
||||
|
||||
@@ -80,7 +80,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
||||
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
|
||||
progressBgInset=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted_inset, activity.getTheme()).mutate();
|
||||
itemView.setOnClickListener(this::onButtonClick);
|
||||
button.setOutlineProvider(OutlineProviders.roundedRect(24));
|
||||
button.setOutlineProvider(OutlineProviders.M3_BUTTON);
|
||||
button.setClipToOutline(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild;
|
||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class PreviewlessMediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private static final String TAG="PreviewlessMediaGridDisplayItem";
|
||||
|
||||
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||
private final TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, PreviewlessMediaAttachmentViewController> viewPool;
|
||||
private final List<Attachment> attachments;
|
||||
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
|
||||
public final Status status;
|
||||
public String sensitiveTitle;
|
||||
|
||||
public PreviewlessMediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> attachments, Status status){
|
||||
super(parentID, parentFragment);
|
||||
this.tiledLayout=tiledLayout;
|
||||
this.viewPool=parentFragment.getPreviewlessAttachmentViewsPool();
|
||||
this.attachments=attachments;
|
||||
this.status=status;
|
||||
// for(Attachment att:attachments){
|
||||
// requests.add(new UrlImageLoaderRequest(switch(att.type){
|
||||
// case IMAGE -> att.url;
|
||||
// case VIDEO, GIFV -> att.previewUrl == null ? att.url : att.previewUrl;
|
||||
// default -> throw new IllegalStateException("Unexpected value: "+att.url);
|
||||
// }, 1000, 1000));
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(){
|
||||
return Type.PREVIEWLESS_MEDIA_GRID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCount(){
|
||||
return attachments.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int index){
|
||||
return requests.get(index);
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<PreviewlessMediaGridStatusDisplayItem> {
|
||||
private final FrameLayout wrapper;
|
||||
private final LinearLayout layout;
|
||||
private final View.OnClickListener clickListener=this::onViewClick;
|
||||
private final ArrayList<PreviewlessMediaAttachmentViewController> controllers=new ArrayList<>();
|
||||
|
||||
// private final FrameLayout hideSensitiveButton;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(new FrameLayoutThatOnlyMeasuresFirstChild(activity));
|
||||
wrapper=(FrameLayout)itemView;
|
||||
layout= new LinearLayout(activity);
|
||||
layout.setOrientation(LinearLayout.VERTICAL);
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layout.setLayoutParams(params);
|
||||
wrapper.addView(layout);
|
||||
wrapper.setClipToPadding(false);
|
||||
|
||||
// megalodon: no sensitive hide button because the visibility toggle looks prettier imo
|
||||
// hideSensitiveButton=(FrameLayout) activity.getLayoutInflater().inflate(R.layout.alt_text_badge, overlays, false);
|
||||
// ((TextView) hideSensitiveButton.findViewById(R.id.alt_button)).setText(R.string.hide);
|
||||
// overlays.addView(hideSensitiveButton, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.END | Gravity.TOP));
|
||||
|
||||
// hideSensitiveButton.setOnClickListener(v->hideSensitive());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(PreviewlessMediaGridStatusDisplayItem item){
|
||||
wrapper.setPadding(0, 0, 0, 0); // item.inset ? 0 : V.dp(8));
|
||||
|
||||
// if(altTextAnimator!=null)
|
||||
// altTextAnimator.cancel();
|
||||
|
||||
for(PreviewlessMediaAttachmentViewController c:controllers){
|
||||
item.viewPool.reuse(c.type, c);
|
||||
}
|
||||
layout.removeAllViews();
|
||||
controllers.clear();
|
||||
|
||||
int i=0;
|
||||
// if (!item.attachments.isEmpty()) updateBlurhashInSensitiveOverlay();
|
||||
for(Attachment att:item.attachments){
|
||||
PreviewlessMediaAttachmentViewController c=item.viewPool.obtain(switch(att.type){
|
||||
case IMAGE -> MediaGridStatusDisplayItem.GridItemType.PHOTO;
|
||||
case VIDEO -> MediaGridStatusDisplayItem.GridItemType.VIDEO;
|
||||
case GIFV -> MediaGridStatusDisplayItem.GridItemType.GIFV;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+att.type);
|
||||
});
|
||||
if(c.view.getLayoutParams()==null)
|
||||
c.view.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
layout.addView(c.view);
|
||||
c.view.setOnClickListener(clickListener);
|
||||
c.view.setTag(i);
|
||||
controllers.add(c);
|
||||
c.bind(att, item.status);
|
||||
i++;
|
||||
}
|
||||
|
||||
boolean insetAndLast=item.inset && isLastDisplayItemForStatus();
|
||||
wrapper.setClipToOutline(insetAndLast);
|
||||
wrapper.setOutlineProvider(insetAndLast ? OutlineProviders.bottomRoundedRect(12) : null);
|
||||
}
|
||||
|
||||
private void onViewClick(View v){
|
||||
int index=(Integer)v.getTag();
|
||||
item.parentFragment.openPreviewlessMediaPhotoViewer(item.parentID, item.status, index, this);
|
||||
}
|
||||
|
||||
|
||||
public PreviewlessMediaAttachmentViewController getViewController(int index){
|
||||
return controllers.get(index);
|
||||
}
|
||||
|
||||
public void setClipChildren(boolean clip){
|
||||
layout.setClipChildren(clip);
|
||||
wrapper.setClipChildren(clip);
|
||||
}
|
||||
|
||||
public LinearLayout getLayout(){
|
||||
return layout;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,10 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
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;
|
||||
@@ -43,18 +45,21 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean needBottomPadding;
|
||||
ReblogOrReplyLineStatusDisplayItem extra;
|
||||
CharSequence fullText;
|
||||
Status status;
|
||||
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) {
|
||||
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text);
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, Status status) {
|
||||
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text, status);
|
||||
}
|
||||
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText) {
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText, Status status) {
|
||||
super(parentID, parentFragment);
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
||||
HtmlParser.parseCustomEmoji(ssb, emojis);
|
||||
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
|
||||
HtmlParser.parseCustomEmoji(ssb, emojis);
|
||||
this.text=ssb;
|
||||
emojiHelper.setText(ssb);
|
||||
this.icon=icon;
|
||||
this.status=status;
|
||||
this.handleClick=handleClick;
|
||||
TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
||||
|
||||
@@ -6,9 +6,9 @@ import android.graphics.drawable.LayerDrawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -29,11 +29,13 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
|
||||
private final CharSequence parsedTitle;
|
||||
private final CustomEmojiHelper emojiHelper;
|
||||
private final Type type;
|
||||
private final int attachmentCount;
|
||||
|
||||
public SpoilerStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, String title, Status statusForContent, Type type){
|
||||
super(parentID, parentFragment);
|
||||
this.status=statusForContent;
|
||||
this.type=type;
|
||||
this.attachmentCount=statusForContent.mediaAttachments.size();
|
||||
if(TextUtils.isEmpty(title)){
|
||||
parsedTitle=HtmlParser.parseCustomEmoji(statusForContent.spoilerText, statusForContent.emojis);
|
||||
emojiHelper=new CustomEmojiHelper();
|
||||
@@ -62,12 +64,14 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
|
||||
public static class Holder extends StatusDisplayItem.Holder<SpoilerStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final TextView title, action;
|
||||
private final View button;
|
||||
private final ImageView mediaIcon;
|
||||
|
||||
public Holder(Context context, ViewGroup parent, Type type){
|
||||
super(context, R.layout.display_item_spoiler, parent);
|
||||
title=findViewById(R.id.spoiler_title);
|
||||
action=findViewById(R.id.spoiler_action);
|
||||
button=findViewById(R.id.spoiler_button);
|
||||
mediaIcon=findViewById(R.id.media_icon);
|
||||
|
||||
button.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
button.setClipToOutline(true);
|
||||
@@ -94,6 +98,10 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
|
||||
itemView.getPaddingRight(),
|
||||
item.inset ? itemView.getPaddingTop() : 0
|
||||
);
|
||||
mediaIcon.setVisibility(item.attachmentCount > 0 ? View.VISIBLE : View.GONE);
|
||||
mediaIcon.setImageResource(item.attachmentCount > 1
|
||||
? R.drawable.ic_fluent_image_multiple_24_regular
|
||||
: R.drawable.ic_fluent_image_24_regular);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ShowEmojiReactions.ALWAYS;
|
||||
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ShowEmojiReactions.ONLY_OPENED;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
@@ -12,6 +15,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||
@@ -50,7 +54,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
public abstract class StatusDisplayItem{
|
||||
public final String parentID;
|
||||
public final BaseStatusListFragment<?> parentFragment;
|
||||
public boolean inset, insetPadding=true;
|
||||
public boolean inset;
|
||||
public int index;
|
||||
public boolean
|
||||
hasDescendantNeighbor = false,
|
||||
@@ -65,6 +69,7 @@ public abstract class StatusDisplayItem{
|
||||
public static final int FLAG_NO_HEADER=1 << 4;
|
||||
public static final int FLAG_NO_TRANSLATE=1 << 5;
|
||||
public static final int FLAG_NO_EMOJI_REACTIONS=1 << 6;
|
||||
public static final int FLAG_NO_MEDIA_PREVIEW=1 << 7;
|
||||
|
||||
public void setAncestryInfo(
|
||||
boolean hasDescendantNeighbor,
|
||||
@@ -111,6 +116,7 @@ public abstract class StatusDisplayItem{
|
||||
case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
|
||||
case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent);
|
||||
case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent);
|
||||
case PREVIEWLESS_MEDIA_GRID -> new PreviewlessMediaGridStatusDisplayItem.Holder(activity, parent);
|
||||
case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent);
|
||||
case FILE -> new FileStatusDisplayItem.Holder(activity, parent);
|
||||
case SPOILER, FILTER_SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent, type);
|
||||
@@ -120,19 +126,6 @@ public abstract class StatusDisplayItem{
|
||||
};
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean showReactions, boolean addFooter, boolean disableTranslate, FilterContext filterContext) {
|
||||
int flags=0;
|
||||
if(inset)
|
||||
flags|=FLAG_INSET;
|
||||
if(!addFooter)
|
||||
flags|=FLAG_NO_FOOTER;
|
||||
if (disableTranslate)
|
||||
flags|=FLAG_NO_TRANSLATE;
|
||||
if (!showReactions)
|
||||
flags|=FLAG_NO_EMOJI_REACTIONS;
|
||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, filterContext, flags);
|
||||
}
|
||||
|
||||
public static ReblogOrReplyLineStatusDisplayItem buildReplyLine(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parent, Account account, boolean threadReply) {
|
||||
String parentID = parent.getID();
|
||||
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
@@ -144,7 +137,7 @@ public abstract class StatusDisplayItem{
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
return new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText, status
|
||||
);
|
||||
}
|
||||
|
||||
@@ -179,7 +172,7 @@ public abstract class StatusDisplayItem{
|
||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
|
||||
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||
}, fullText));
|
||||
}, fullText, status));
|
||||
} else if (!(status.tags.isEmpty() ||
|
||||
fragment instanceof HashtagTimelineFragment ||
|
||||
fragment instanceof ListTimelineFragment
|
||||
@@ -195,7 +188,7 @@ public abstract class StatusDisplayItem{
|
||||
i -> {
|
||||
args.putString("hashtag", hashtag.name);
|
||||
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
|
||||
}
|
||||
}, status
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -229,7 +222,7 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
|
||||
ArrayList<StatusDisplayItem> contentItems;
|
||||
if(!TextUtils.isEmpty(statusForContent.spoilerText)){
|
||||
if(statusForContent.hasSpoiler()){
|
||||
if (AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) statusForContent.spoilerRevealed = true;
|
||||
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, statusForContent, Type.SPOILER);
|
||||
items.add(spoilerItem);
|
||||
@@ -261,7 +254,7 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
|
||||
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
|
||||
if(!imageAttachments.isEmpty()){
|
||||
if(!imageAttachments.isEmpty() && (flags & FLAG_NO_MEDIA_PREVIEW)==0){
|
||||
int color = UiUtils.getThemeColor(fragment.getContext(), R.attr.colorM3SurfaceVariant);
|
||||
for (Attachment att : imageAttachments) {
|
||||
if (att.blurhashPlaceholder == null) {
|
||||
@@ -276,6 +269,10 @@ public abstract class StatusDisplayItem{
|
||||
statusForContent.sensitiveRevealed=true;
|
||||
contentItems.add(mediaGrid);
|
||||
}
|
||||
if((flags & FLAG_NO_MEDIA_PREVIEW)!=0){
|
||||
contentItems.add(new PreviewlessMediaGridStatusDisplayItem(parentID, fragment, null, imageAttachments, statusForContent));
|
||||
|
||||
}
|
||||
for(Attachment att:statusForContent.mediaAttachments){
|
||||
if(att.type==Attachment.Type.AUDIO){
|
||||
contentItems.add(new AudioStatusDisplayItem(parentID, fragment, statusForContent, att));
|
||||
@@ -288,15 +285,17 @@ public abstract class StatusDisplayItem{
|
||||
buildPollItems(parentID, fragment, statusForContent.poll, contentItems, statusForContent);
|
||||
}
|
||||
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty()){
|
||||
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
|
||||
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent, (flags & FLAG_NO_MEDIA_PREVIEW)==0));
|
||||
}
|
||||
if(contentItems!=items && statusForContent.spoilerRevealed){
|
||||
items.addAll(contentItems);
|
||||
}
|
||||
if((flags & FLAG_NO_EMOJI_REACTIONS)==0
|
||||
&& AccountSessionManager.get(accountID).getLocalPreferences().emojiReactionsEnabled){
|
||||
AccountLocalPreferences lp=fragment.getLocalPrefs();
|
||||
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && lp.emojiReactionsEnabled &&
|
||||
(lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment)){
|
||||
boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id);
|
||||
items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !isMainStatus, false));
|
||||
boolean showAddButton=lp.showEmojiReactions==ALWAYS || isMainStatus;
|
||||
items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !showAddButton, false));
|
||||
}
|
||||
FooterStatusDisplayItem footer=null;
|
||||
if((flags & FLAG_NO_FOOTER)==0){
|
||||
@@ -304,12 +303,12 @@ public abstract class StatusDisplayItem{
|
||||
footer.hideCounts=hideCounts;
|
||||
items.add(footer);
|
||||
if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
|
||||
items.add(new GapStatusDisplayItem(parentID, fragment));
|
||||
items.add(new GapStatusDisplayItem(parentID, fragment, status));
|
||||
}
|
||||
int i=1;
|
||||
boolean inset=(flags & FLAG_INSET)!=0;
|
||||
// add inset dummy so last content item doesn't clip out of inset bounds
|
||||
if(inset || footer==null){
|
||||
if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0){
|
||||
items.add(new DummyStatusDisplayItem(parentID, fragment));
|
||||
// in case we ever need the dummy to display a margin for the media grid again:
|
||||
// (i forgot why we apparently don't need this anymore)
|
||||
@@ -363,6 +362,7 @@ public abstract class StatusDisplayItem{
|
||||
GAP,
|
||||
EXTENDED_FOOTER,
|
||||
MEDIA_GRID,
|
||||
PREVIEWLESS_MEDIA_GRID,
|
||||
WARNING,
|
||||
FILE,
|
||||
SPOILER,
|
||||
@@ -401,24 +401,17 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
// int nextNextPos=getAbsoluteAdapterPosition() + 2;
|
||||
// if(next.map(n->n instanceof EmojiReactionsStatusDisplayItem e && e.isHidden()).orElse(false)){
|
||||
// List<StatusDisplayItem> displayItems=item.parentFragment.getDisplayItems();
|
||||
// return displayItems.size() > nextNextPos
|
||||
// ? Optional.of(displayItems.get(nextNextPos))
|
||||
// : Optional.empty();
|
||||
// }else{
|
||||
// return next;
|
||||
// }
|
||||
|
||||
public Optional<StatusDisplayItem> getNextDisplayItem(){
|
||||
return getDisplayItemOffset(1);
|
||||
}
|
||||
|
||||
public Optional<StatusDisplayItem> getDisplayItemOffset(int offset){
|
||||
int nextPos=getAbsoluteAdapterPosition() + offset;
|
||||
List<StatusDisplayItem> displayItems=item.parentFragment.getDisplayItems();
|
||||
return displayItems.size() > nextPos
|
||||
? Optional.of(displayItems.get(nextPos))
|
||||
int thisPos=displayItems.indexOf(item);
|
||||
int offsetPos=thisPos + offset;
|
||||
return displayItems.size() > offsetPos && thisPos >= 0 && offsetPos >= 0
|
||||
? Optional.of(displayItems.get(offsetPos))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
|
||||
item.status.language != null &&
|
||||
// todo: compare to mastodon locale instead (how do i query that?!)
|
||||
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())));
|
||||
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())))
|
||||
&& (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
|
||||
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
|
||||
translateButton.setText(item.translationShown ? R.string.sk_translate_show_original : R.string.sk_translate_post);
|
||||
translateInfo.setText(item.translationShown ? itemView.getResources().getString(R.string.sk_translated_using, bottomText != null ? "bottom-java" : item.status.translation.provider) : "");
|
||||
@@ -184,11 +185,10 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
||||
|
||||
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
|
||||
int bottomPadding=next instanceof FooterStatusDisplayItem
|
||||
? V.dp(6)
|
||||
: (!item.inset && next instanceof DummyStatusDisplayItem) ||
|
||||
next instanceof EmojiReactionsStatusDisplayItem e && !e.isHidden()
|
||||
? 0
|
||||
if(next!=null && !next.parentID.equals(item.parentID)) next=null;
|
||||
int bottomPadding=next instanceof FooterStatusDisplayItem ? V.dp(6)
|
||||
: item.inset ? V.dp(12)
|
||||
: (next instanceof EmojiReactionsStatusDisplayItem || next==null) ? 0
|
||||
: V.dp(12);
|
||||
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding);
|
||||
|
||||
|
||||
@@ -4,18 +4,15 @@ import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.AltTextFilter;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.LegacyFilter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// Mind the gap!
|
||||
@@ -55,7 +52,8 @@ public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{
|
||||
@Override
|
||||
public void onBind(WarningFilteredStatusDisplayItem item) {
|
||||
filteredItems = item.filteredItems;
|
||||
text.setText(item.parentFragment.getString(R.string.sk_filtered, item.applyingFilter.title));
|
||||
String title = item.applyingFilter instanceof AltTextFilter ? item.parentFragment.getString(R.string.sk_no_alt_text) : item.applyingFilter.title;
|
||||
text.setText(item.parentFragment.getString(R.string.sk_filtered, title));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -99,15 +99,11 @@ public class BlurhashCrossfadeDrawable extends Drawable{
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth(){
|
||||
if(width==0)
|
||||
return imageDrawable==null ? 1920 : imageDrawable.getIntrinsicWidth();
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight(){
|
||||
if(height==0)
|
||||
return imageDrawable==null ? 1080 : imageDrawable.getIntrinsicHeight();
|
||||
return height;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ import me.grishka.appkit.utils.V;
|
||||
public class SawtoothTearDrawable extends Drawable{
|
||||
private final Paint topPaint, bottomPaint;
|
||||
|
||||
private static final int TOP_SAWTOOTH_HEIGHT=5;
|
||||
private static final int BOTTOM_SAWTOOTH_HEIGHT=3;
|
||||
private static final int STROKE_WIDTH=2;
|
||||
private static final int TOP_SAWTOOTH_HEIGHT=4;
|
||||
private static final int BOTTOM_SAWTOOTH_HEIGHT=4;
|
||||
private static final int STROKE_WIDTH=1;
|
||||
private static final int SAWTOOTH_PERIOD=14;
|
||||
|
||||
public SawtoothTearDrawable(Context context){
|
||||
|
||||
@@ -147,7 +147,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
toolbarWrap.setPadding(0, 0, 0, 0);
|
||||
videoControls.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
insets=insets.replaceSystemWindowInsets(tappable.left, tappable.top, tappable.right, tappable.bottom);
|
||||
insets=insets.replaceSystemWindowInsets(tappable.left, tappable.top, tappable.right, insets.getSystemWindowInsetBottom());
|
||||
}
|
||||
uiOverlay.dispatchApplyWindowInsets(insets);
|
||||
int bottomInset=insets.getSystemWindowInsetBottom();
|
||||
@@ -602,7 +602,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
|
||||
private void updateVideoPosition(){
|
||||
if(videoPositionNeedsUpdating){
|
||||
int currentPosition=videoInitialPosition+(int)(SystemClock.uptimeMillis()-videoInitialPositionTime);
|
||||
int currentPosition=(videoInitialPosition+(int)(SystemClock.uptimeMillis()-videoInitialPositionTime))%videoDuration;
|
||||
videoSeekBar.setProgress(Math.round((float)currentPosition/videoDuration*10000f));
|
||||
updateVideoTimeText(currentPosition);
|
||||
windowView.postOnAnimation(videoPositionUpdater);
|
||||
@@ -878,6 +878,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(MediaPlayer mp, int width, int height){
|
||||
if(width<=0 || height<=0)
|
||||
return;
|
||||
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
|
||||
params.width=width;
|
||||
params.height=height;
|
||||
|
||||
@@ -119,6 +119,9 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
|
||||
int width=right-left;
|
||||
int height=bottom-top;
|
||||
if(width==0 || height==0)
|
||||
return;
|
||||
|
||||
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
|
||||
minScale=scale;
|
||||
maxScale=Math.max(3f, height/(float)child.getHeight());
|
||||
@@ -306,8 +309,6 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
|
||||
}, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA));
|
||||
}
|
||||
}else{
|
||||
if(animatingTransition)
|
||||
Log.w(TAG, "updateViewTransform: ", new Throwable().fillInStackTrace());
|
||||
child.setScaleX(matrixValues[Matrix.MSCALE_X]);
|
||||
child.setScaleY(matrixValues[Matrix.MSCALE_Y]);
|
||||
child.setTranslationX(matrixValues[Matrix.MTRANS_X]);
|
||||
|
||||
@@ -1715,7 +1715,9 @@ public class TabLayout extends HorizontalScrollView implements CustomViewHelper{
|
||||
child.getLayoutParams().height);
|
||||
|
||||
int childWidthMeasureSpec =
|
||||
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
|
||||
MeasureSpec.makeMeasureSpec(
|
||||
getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
|
||||
MeasureSpec.EXACTLY);
|
||||
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.widget.TextView;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -44,8 +45,8 @@ public class DiscoverInfoBannerHelper{
|
||||
banner=((Activity)list.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, list, false);
|
||||
TextView text=banner.findViewById(R.id.banner_text);
|
||||
text.setText(switch(type){
|
||||
case TRENDING_POSTS -> list.getResources().getString(R.string.trending_posts_info_banner);
|
||||
case TRENDING_LINKS -> list.getResources().getString(R.string.trending_links_info_banner);
|
||||
case TRENDING_POSTS -> list.getResources().getString(R.string.sk_trending_posts_info_banner);
|
||||
case TRENDING_LINKS -> list.getResources().getString(R.string.sk_trending_links_info_banner);
|
||||
case FEDERATED_TIMELINE -> list.getResources().getString(R.string.sk_federated_timeline_info_banner);
|
||||
case POST_NOTIFICATIONS -> list.getResources().getString(R.string.sk_notify_posts_info_banner);
|
||||
case BUBBLE_TIMELINE -> list.getResources().getString(R.string.sk_bubble_timeline_info_banner);
|
||||
@@ -57,8 +58,10 @@ public class DiscoverInfoBannerHelper{
|
||||
case TRENDING_POSTS -> R.drawable.ic_fluent_arrow_trending_24_regular;
|
||||
case TRENDING_LINKS -> R.drawable.ic_fluent_news_24_regular;
|
||||
case ACCOUNTS -> R.drawable.ic_fluent_people_add_24_regular;
|
||||
// no icon because those are displayed as timelines - with icon in top left
|
||||
case LOCAL_TIMELINE, FEDERATED_TIMELINE, BUBBLE_TIMELINE, POST_NOTIFICATIONS -> 0;
|
||||
case LOCAL_TIMELINE -> TimelineDefinition.LOCAL_TIMELINE.getDefaultIcon().iconRes;
|
||||
case FEDERATED_TIMELINE -> TimelineDefinition.FEDERATED_TIMELINE.getDefaultIcon().iconRes;
|
||||
case BUBBLE_TIMELINE -> TimelineDefinition.BUBBLE_TIMELINE.getDefaultIcon().iconRes;
|
||||
case POST_NOTIFICATIONS -> TimelineDefinition.POSTS_TIMELINE.getDefaultIcon().iconRes;
|
||||
});
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(banner));
|
||||
}
|
||||
|
||||
@@ -84,24 +84,19 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||
// List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
||||
boolean inset=sdi.getItem().inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition();
|
||||
// int pos=holder.getAbsoluteAdapterPosition();
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
int pad;
|
||||
// boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
// boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
// if(holder instanceof MediaGridStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||
pad=V.dp(16);
|
||||
// else
|
||||
// pad=V.dp(12);
|
||||
boolean insetPadding=((StatusDisplayItem.Holder<?>) holder).getItem().insetPadding;
|
||||
if(insetPadding)
|
||||
outRect.left=pad;
|
||||
if(insetPadding)
|
||||
outRect.right=pad;
|
||||
int pad=V.dp(16);
|
||||
// else pad=V.dp(12);
|
||||
outRect.left=pad;
|
||||
outRect.right=pad;
|
||||
|
||||
// had to comment this out because animations with offsets aren't handled properly.
|
||||
// can be worked around by manually applying top margins to items
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.joinmastodon.android.ui.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Attachment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
|
||||
import org.joinmastodon.android.ui.drawables.PlayIconDrawable;
|
||||
|
||||
public class PreviewlessMediaAttachmentViewController{
|
||||
public final View view;
|
||||
public final MediaGridStatusDisplayItem.GridItemType type;
|
||||
private final TextView title, domain;
|
||||
public final View inner;
|
||||
private final ImageView icon;
|
||||
private final Context context;
|
||||
private Status status;
|
||||
|
||||
public PreviewlessMediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){
|
||||
view=context.getSystemService(LayoutInflater.class).inflate(R.layout.display_item_file, null);
|
||||
title=view.findViewById(R.id.title);
|
||||
domain=view.findViewById(R.id.domain);
|
||||
icon=view.findViewById(R.id.imageView);
|
||||
inner=view.findViewById(R.id.inner);
|
||||
this.context=context;
|
||||
this.type=type;
|
||||
}
|
||||
|
||||
public void bind(Attachment attachment, Status status){
|
||||
this.status=status;
|
||||
title.setText(attachment.description != null
|
||||
? attachment.description
|
||||
: context.getString(R.string.sk_no_alt_text));
|
||||
title.setSingleLine(false);
|
||||
|
||||
domain.setText(status.sensitive ? context.getString(R.string.sensitive_content_explain) : null);
|
||||
domain.setVisibility(status.sensitive ? View.VISIBLE : View.GONE);
|
||||
|
||||
if(attachment.type == Attachment.Type.IMAGE)
|
||||
icon.setImageDrawable(context.getDrawable(R.drawable.ic_fluent_image_24_regular));
|
||||
if(attachment.type == Attachment.Type.VIDEO)
|
||||
icon.setImageDrawable(context.getDrawable(R.drawable.ic_fluent_video_clip_24_regular));
|
||||
if(attachment.type == Attachment.Type.GIFV)
|
||||
icon.setImageDrawable(context.getDrawable(R.drawable.ic_fluent_gif_24_regular));
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,8 @@ import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
@@ -69,7 +71,6 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.StatusInteractionController;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByHandle;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
||||
@@ -83,9 +84,11 @@ import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusMuteChangedEvent;
|
||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||
@@ -97,7 +100,6 @@ import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsServerAboutFragment;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
@@ -139,7 +141,6 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -175,6 +176,19 @@ public class UiUtils {
|
||||
private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
|
||||
public static int MAX_WIDTH, SCROLL_TO_TOP_DELTA;
|
||||
|
||||
public static final float ALPHA_PRESSED=0.55f;
|
||||
public static final Animation opacityOut, opacityIn;
|
||||
|
||||
static {
|
||||
opacityOut = new AlphaAnimation(1, ALPHA_PRESSED);
|
||||
opacityOut.setDuration(300);
|
||||
opacityOut.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
opacityOut.setFillAfter(true);
|
||||
opacityIn = new AlphaAnimation(ALPHA_PRESSED, 1);
|
||||
opacityIn.setDuration(400);
|
||||
opacityIn.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
}
|
||||
|
||||
private UiUtils() {
|
||||
}
|
||||
|
||||
@@ -194,28 +208,42 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static String formatRelativeTimestamp(Context context, Instant instant) {
|
||||
long t = instant.toEpochMilli();
|
||||
long now = System.currentTimeMillis();
|
||||
return formatPeriodBetween(context, instant, null);
|
||||
}
|
||||
|
||||
public static String formatPeriodBetween(Context context, Instant since, Instant until) {
|
||||
boolean ago = until == null;
|
||||
long t = since.toEpochMilli();
|
||||
long now = ago ? System.currentTimeMillis() : until.toEpochMilli();
|
||||
long diff = now - t;
|
||||
if(diff<1000L){
|
||||
return context.getString(R.string.time_now);
|
||||
}else if(diff<60_000L){
|
||||
return context.getString(R.string.time_seconds_ago_short, diff/1000L);
|
||||
long time = diff/1000L;
|
||||
return ago ?
|
||||
context.getString(R.string.time_seconds_ago_short, time) :
|
||||
context.getResources().getQuantityString(R.plurals.sk_time_seconds, (int) time, time);
|
||||
}else if(diff<3600_000L){
|
||||
return context.getString(R.string.time_minutes_ago_short, diff/60_000L);
|
||||
long time = diff/60_000L;
|
||||
return ago ?
|
||||
context.getString(R.string.time_minutes_ago_short, time) :
|
||||
context.getResources().getQuantityString(R.plurals.sk_time_minutes, (int) time, time);
|
||||
}else if(diff<3600_000L*24L){
|
||||
return context.getString(R.string.time_hours_ago_short, diff/3600_000L);
|
||||
long time = diff/3600_000L;
|
||||
return ago ?
|
||||
context.getString(R.string.time_hours_ago_short, time) :
|
||||
context.getResources().getQuantityString(R.plurals.sk_time_hours, (int) time, time);
|
||||
} else {
|
||||
int days = (int) (diff / (3600_000L * 24L));
|
||||
if (days > 30) {
|
||||
ZonedDateTime dt = instant.atZone(ZoneId.systemDefault());
|
||||
if (ago && days > 30) {
|
||||
ZonedDateTime dt = since.atZone(ZoneId.systemDefault());
|
||||
if (dt.getYear() == ZonedDateTime.now().getYear()) {
|
||||
return DATE_FORMATTER_SHORT.format(dt);
|
||||
} else {
|
||||
return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt);
|
||||
}
|
||||
}
|
||||
return context.getString(R.string.time_days_ago_short, days);
|
||||
return ago ? context.getString(R.string.time_days_ago_short, days) : context.getResources().getQuantityString(R.plurals.sk_time_days, days, days);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,7 +633,7 @@ public class UiUtils {
|
||||
.exec(accountID);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setIcon(currentlyMuted ? R.drawable.ic_fluent_speaker_0_28_regular : R.drawable.ic_fluent_speaker_off_28_regular)
|
||||
.setIcon(currentlyMuted ? R.drawable.ic_fluent_speaker_2_28_regular : R.drawable.ic_fluent_speaker_off_28_regular)
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -638,6 +666,32 @@ public class UiUtils {
|
||||
);
|
||||
}
|
||||
|
||||
public static void confirmToggleMuteConversation(Activity activity, String accountID, Status status, Runnable resultCallback) {
|
||||
showConfirmationAlert(activity,
|
||||
status.muted ? R.string.mo_unmute_conversation : R.string.mo_mute_conversation,
|
||||
status.muted ? R.string.mo_confirm_to_unmute_conversation : R.string.mo_confirm_to_mute_conversation,
|
||||
status.muted ? R.string.do_unmute : R.string.do_mute,
|
||||
status.muted ? R.drawable.ic_fluent_alert_28_regular : R.drawable.ic_fluent_alert_off_28_regular,
|
||||
() -> new SetStatusMuted(status.id, !status.muted)
|
||||
.setCallback(new Callback<Status>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
resultCallback.run();
|
||||
Toast.makeText(activity, result.muted ? R.string.mo_muted_conversation_successfully : R.string.mo_unmuted_conversation_successfully, Toast.LENGTH_SHORT).show();
|
||||
E.post(new StatusMuteChangedEvent(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, status.muted ? R.string.mo_unmuting : R.string.mo_muting, false)
|
||||
.exec(accountID)
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public static void confirmDeleteScheduledPost(Activity activity, String accountID, ScheduledStatus status, Runnable resultCallback) {
|
||||
boolean isDraft = status.scheduledAt.isAfter(CreateStatus.DRAFTS_AFTER_INSTANT);
|
||||
showConfirmationAlert(activity,
|
||||
@@ -792,7 +846,7 @@ public class UiUtils {
|
||||
confirmToggleMuteUser(activity, accountID, account, true, resultCallback);
|
||||
} else if (!relationship.following && !relationship.requested) {
|
||||
follow(activity, accountID, account, true, progressCallback, resultCallback);
|
||||
} else {
|
||||
} else if (GlobalUserPreferences.confirmUnfollow){
|
||||
showConfirmationAlert(activity,
|
||||
activity.getString(R.string.mo_confirm_unfollow_title),
|
||||
activity.getString(R.string.mo_confirm_unfollow, account.getDisplayUsername()),
|
||||
@@ -800,8 +854,9 @@ public class UiUtils {
|
||||
0,
|
||||
() -> follow(activity, accountID, account, false, progressCallback, resultCallback),
|
||||
() -> progressCallback.accept(false));
|
||||
} else {
|
||||
follow(activity, accountID, account, false, progressCallback, resultCallback);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void follow(Activity activity, String accountID, Account account, boolean followed, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback) {
|
||||
@@ -1172,7 +1227,7 @@ public class UiUtils {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() {
|
||||
return Optional.of(new GetSearchResults(query.getQuery(), type, true, null, 0, 0).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
Optional<T> result = extractResult.apply(results);
|
||||
@@ -1269,7 +1324,7 @@ public class UiUtils {
|
||||
}
|
||||
public static MastodonAPIRequest<SearchResults> lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse(""));
|
||||
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true)
|
||||
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true, null, 0, 0)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
@@ -1332,7 +1387,7 @@ public class UiUtils {
|
||||
})
|
||||
.execNoAuth(uri.getHost()));
|
||||
} else if (looksLikeMastodonUrl(url)) {
|
||||
return Optional.of(new GetSearchResults(url, null, true)
|
||||
return Optional.of(new GetSearchResults(url, null, true, null, 0, 0)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user