Compare commits
976 Commits
1.1.4+fork
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b79b69d961 | ||
|
|
5118a1fb1e | ||
|
|
18275183d0 | ||
|
|
616049bff2 | ||
|
|
1a79bc0b61 | ||
|
|
d43cbe642f | ||
|
|
77cee4c46a | ||
|
|
0949ad1ce6 | ||
|
|
1e411c0c23 | ||
|
|
76d306aef7 | ||
|
|
7ff19ef481 | ||
|
|
6650bb946f | ||
|
|
bbbf1683aa | ||
|
|
5cdea99eb0 | ||
|
|
356426b5fc | ||
|
|
7577d60f42 | ||
|
|
8c7364d57d | ||
|
|
c0a2945378 | ||
|
|
1af9a71210 | ||
|
|
dc859fe91c | ||
|
|
d50c37af23 | ||
|
|
00c8a03b80 | ||
|
|
d15d222b72 | ||
|
|
34d134cb57 | ||
|
|
40eb3e2400 | ||
|
|
0af45e5f56 | ||
|
|
82e3250623 | ||
|
|
533a51fc77 | ||
|
|
6fc82cf26b | ||
|
|
6dcfdb9735 | ||
|
|
217d8348d4 | ||
|
|
9bce934944 | ||
|
|
a1ef3e1cae | ||
|
|
978c1cfdc4 | ||
|
|
9fb9ffc269 | ||
|
|
4f357637de | ||
|
|
1acd177e81 | ||
|
|
e4b1bf452f | ||
|
|
ce22cb4678 | ||
|
|
3c45215fca | ||
|
|
b17e63acae | ||
|
|
859ba5ebb9 | ||
|
|
164579cbb5 | ||
|
|
990f8189e4 | ||
|
|
4d0a642fd9 | ||
|
|
84b2994b99 | ||
|
|
efbca327c1 | ||
|
|
aa4b007d25 | ||
|
|
23dccef4b4 | ||
|
|
1ef96ed5e6 | ||
|
|
31e2a32233 | ||
|
|
168ae80743 | ||
|
|
8acf23ddac | ||
|
|
eb40211582 | ||
|
|
bf35161c9f | ||
|
|
456c50f69e | ||
|
|
587212cf46 | ||
|
|
fe800a259d | ||
|
|
c193741013 | ||
|
|
a7b752264f | ||
|
|
9075027f69 | ||
|
|
28faf4277a | ||
|
|
120ab8ca54 | ||
|
|
a3fc1a6a74 | ||
|
|
ec1fe07fea | ||
|
|
e3d054ae3e | ||
|
|
795d4b0801 | ||
|
|
0cf0f07f2d | ||
|
|
2cc5872ec7 | ||
|
|
6a151e00ac | ||
|
|
1e99862d40 | ||
|
|
3a1b12306b | ||
|
|
e31db6d506 | ||
|
|
b22c0a5d3d | ||
|
|
e7d856acf4 | ||
|
|
ee8b087b61 | ||
|
|
4e86314df5 | ||
|
|
cf5fbe3b55 | ||
|
|
952416fefc | ||
|
|
1645ce4486 | ||
|
|
2bde95b4d2 | ||
|
|
2de003c5bb | ||
|
|
a04e16c572 | ||
|
|
eb41d77d54 | ||
|
|
9325590319 | ||
|
|
d32a57a18d | ||
|
|
13b5462f63 | ||
|
|
480d4ad904 | ||
|
|
7fa52247e8 | ||
|
|
5a547015e6 | ||
|
|
513736f765 | ||
|
|
c6164b1bcd | ||
|
|
87342782d7 | ||
|
|
a4b18de72c | ||
|
|
3064b549cd | ||
|
|
6666f82329 | ||
|
|
ad87efa7e2 | ||
|
|
d06cf1bb1e | ||
|
|
096aa23f69 | ||
|
|
2464042329 | ||
|
|
928b04eda6 | ||
|
|
a31e33415e | ||
|
|
87ce6b8bb1 | ||
|
|
bb08d6585c | ||
|
|
94fa1133fd | ||
|
|
83822b8f69 | ||
|
|
9f3bd186ba | ||
|
|
58cb338cb2 | ||
|
|
6f447909eb | ||
|
|
af953e294d | ||
|
|
7504a1b9cb | ||
|
|
60ee781004 | ||
|
|
4b88ce5115 | ||
|
|
3842ecb0d1 | ||
|
|
3713063ce3 | ||
|
|
83b089457e | ||
|
|
ed9813f093 | ||
|
|
e45f3f30f3 | ||
|
|
df44d4cc4f | ||
|
|
b666048603 | ||
|
|
153542e1b4 | ||
|
|
57e0b96f36 | ||
|
|
ff65d150e3 | ||
|
|
88474ba826 | ||
|
|
dd92f1b66f | ||
|
|
7c7f3cc42a | ||
|
|
734a8049a5 | ||
|
|
417faa66f9 | ||
|
|
7223a13d08 | ||
|
|
a55002da0c | ||
|
|
ce89733f2d | ||
|
|
18811ec32a | ||
|
|
e65e6163ba | ||
|
|
9c3db24d2f | ||
|
|
19abbe199b | ||
|
|
b33003f7b0 | ||
|
|
9a5747efc8 | ||
|
|
980503ed57 | ||
|
|
c2dd858de8 | ||
|
|
d2ef6fb567 | ||
|
|
9c996b3568 | ||
|
|
2387d84bc0 | ||
|
|
3bd69b5447 | ||
|
|
71f6311598 | ||
|
|
e808977717 | ||
|
|
8594e34bb5 | ||
|
|
4591f06d63 | ||
|
|
b9c3143c6f | ||
|
|
adefb0e567 | ||
|
|
5a8fed3c06 | ||
|
|
2c1b8da475 | ||
|
|
707c51e4d6 | ||
|
|
1a075e32de | ||
|
|
73869b6ea2 | ||
|
|
3980329112 | ||
|
|
6107b21d3b | ||
|
|
42a0b881af | ||
|
|
2076818220 | ||
|
|
513bea7959 | ||
|
|
a48dc18df5 | ||
|
|
c55639e966 | ||
|
|
1fc89d7448 | ||
|
|
4fcc07fcd6 | ||
|
|
b50b1c33da | ||
|
|
25460191b0 | ||
|
|
1206ce6efc | ||
|
|
3cdcffe03d | ||
|
|
b09aab4a1d | ||
|
|
d27fe4ebd1 | ||
|
|
ab2cee08fe | ||
|
|
1fa42fd20f | ||
|
|
e343131670 | ||
|
|
f157313d9a | ||
|
|
9bd4433942 | ||
|
|
c9cc8e23c1 | ||
|
|
786dbaf92c | ||
|
|
8dd22e1853 | ||
|
|
34a4dd6d1f | ||
|
|
6e13e592d0 | ||
|
|
485ab4ee22 | ||
|
|
59a8d1d462 | ||
|
|
f4e5baf94d | ||
|
|
a782160dd3 | ||
|
|
871c17cbe2 | ||
|
|
047e72ce9c | ||
|
|
6c1424055f | ||
|
|
369902ffe5 | ||
|
|
6c778d05ea | ||
|
|
747439999d | ||
|
|
4d836f8032 | ||
|
|
f97ab73c5d | ||
|
|
147fb94442 | ||
|
|
975dc94d41 | ||
|
|
a5c1053c58 | ||
|
|
1bfbb4bf38 | ||
|
|
c2950ace90 | ||
|
|
f0846465c2 | ||
|
|
ddc4512116 | ||
|
|
1c9e4fe561 | ||
|
|
35299a7b3f | ||
|
|
2d9938e8b2 | ||
|
|
1e99940c1d | ||
|
|
2827bcffe3 | ||
|
|
3a1b71e95c | ||
|
|
ce4e762cd5 | ||
|
|
54ec1a6cf7 | ||
|
|
bed3e987b7 | ||
|
|
639ddb3f80 | ||
|
|
e645abb771 | ||
|
|
762adce054 | ||
|
|
263bde658e | ||
|
|
3951acf12e | ||
|
|
5cb640a387 | ||
|
|
c65b9ff873 | ||
|
|
13a80fb536 | ||
|
|
23e49c52e5 | ||
|
|
9fcc73984b | ||
|
|
67338b6c85 | ||
|
|
101e7efd74 | ||
|
|
a996a24b7f | ||
|
|
44dcc9fe2b | ||
|
|
58f79e06ef | ||
|
|
f197c8201d | ||
|
|
2fc5669203 | ||
|
|
0065b93060 | ||
|
|
3f9dbd6fe2 | ||
|
|
7ceff3eaa4 | ||
|
|
7cdddf06bc | ||
|
|
77e7b136ff | ||
|
|
c8d160fc35 | ||
|
|
ee0737c9c7 | ||
|
|
0f85be7114 | ||
|
|
34ba4ceb16 | ||
|
|
f2a536d0ea | ||
|
|
815c4d4cc9 | ||
|
|
969b91bba9 | ||
|
|
f24abde76e | ||
|
|
f486b1a9ce | ||
|
|
3dd6638ef3 | ||
|
|
bcfb63b57c | ||
|
|
c7f5f6827a | ||
|
|
8ed88b2b29 | ||
|
|
75db9f4623 | ||
|
|
b69015a25a | ||
|
|
1cdcc8794c | ||
|
|
a0c26b748a | ||
|
|
ce5e733c05 | ||
|
|
de485272c5 | ||
|
|
eab53b805e | ||
|
|
a60046f6ef | ||
|
|
b0d223c47c | ||
|
|
185a8c776b | ||
|
|
f510ee3b4d | ||
|
|
e7a29824e8 | ||
|
|
69b86dd98c | ||
|
|
55807dc7c6 | ||
|
|
9a6ee719c4 | ||
|
|
1b02af382c | ||
|
|
4fe87a9888 | ||
|
|
91995155e9 | ||
|
|
aae0ff5aa7 | ||
|
|
821d9b8a5e | ||
|
|
2070aed38f | ||
|
|
986979eefc | ||
|
|
ed3ce54b24 | ||
|
|
c80ebf2eda | ||
|
|
42fcd6df51 | ||
|
|
1f4031da61 | ||
|
|
d0ebee74ca | ||
|
|
e8ec042d96 | ||
|
|
bd61bf32b6 | ||
|
|
8b12fac766 | ||
|
|
ab7e6b3332 | ||
|
|
548da48615 | ||
|
|
0b6128bcdd | ||
|
|
198a7d5ad3 | ||
|
|
040f244e15 | ||
|
|
98bc6f14a9 | ||
|
|
b92e6d2c48 | ||
|
|
ac3875fe08 | ||
|
|
6fdbafc67b | ||
|
|
5aebdcaa6a | ||
|
|
2969a3e4fd | ||
|
|
e1fcf44aa6 | ||
|
|
ddbfe9de57 | ||
|
|
b2ee1527af | ||
|
|
c94706745f | ||
|
|
8aef61ff09 | ||
|
|
5d6e245ec2 | ||
|
|
8dd4d1a41d | ||
|
|
10f9230139 | ||
|
|
32c94c8948 | ||
|
|
4b947dd1f9 | ||
|
|
93a7d86f78 | ||
|
|
6ed3a57c58 | ||
|
|
ecdba3898f | ||
|
|
d82a9b25cf | ||
|
|
da4849e526 | ||
|
|
da1b47ea0d | ||
|
|
a617693f93 | ||
|
|
f4c573a95e | ||
|
|
d3476c1473 | ||
|
|
d58247d996 | ||
|
|
ee441d5b4a | ||
|
|
97a5c6f5cb | ||
|
|
b6566a2bcc | ||
|
|
af389f7a47 | ||
|
|
45577fc423 | ||
|
|
c00b9b3035 | ||
|
|
f17879783c | ||
|
|
08dfcdf508 | ||
|
|
c97e8fffc4 | ||
|
|
120585954f | ||
|
|
247669644c | ||
|
|
ff15bdeaea | ||
|
|
8b67320e20 | ||
|
|
48828ed1b7 | ||
|
|
1e615db77e | ||
|
|
629416ef2f | ||
|
|
d6b1c88085 | ||
|
|
3b7079be17 | ||
|
|
48d26017b6 | ||
|
|
8bdbe1c4d7 | ||
|
|
4093443bf8 | ||
|
|
c6709aba56 | ||
|
|
a817e03fa0 | ||
|
|
a757607f35 | ||
|
|
ecc9c45f6e | ||
|
|
2e157a9e68 | ||
|
|
8a39154bd3 | ||
|
|
b5e2aa8b7f | ||
|
|
aaea709201 | ||
|
|
c2a0f5e8bc | ||
|
|
2873a66450 | ||
|
|
7331a5a0cd | ||
|
|
c331cc95c2 | ||
|
|
91aa425f43 | ||
|
|
fd5165428e | ||
|
|
6df57aebf4 | ||
|
|
40b2e4c42e | ||
|
|
a762408df8 | ||
|
|
9fb6a0261b | ||
|
|
d90edc8cf1 | ||
|
|
eecc1242be | ||
|
|
6eedb0e156 | ||
|
|
519894a461 | ||
|
|
fe3052a359 | ||
|
|
245b91bebd | ||
|
|
2e47147367 | ||
|
|
8fec3fe56c | ||
|
|
c4d56179f3 | ||
|
|
64d24f6002 | ||
|
|
074efb0813 | ||
|
|
98b96c78d7 | ||
|
|
3a962c7c05 | ||
|
|
99e3658938 | ||
|
|
9cd7ad3601 | ||
|
|
96db2b7a8b | ||
|
|
a881f23253 | ||
|
|
54106c497b | ||
|
|
1554c6d422 | ||
|
|
dccd9dcb97 | ||
|
|
16c0866f7f | ||
|
|
db88de206b | ||
|
|
92f353e1c5 | ||
|
|
71c80dd381 | ||
|
|
d2be917bd4 | ||
|
|
ea3dc32e98 | ||
|
|
be86f1e96f | ||
|
|
d626d45f5c | ||
|
|
f007bdb39c | ||
|
|
a0cbfe9a36 | ||
|
|
21f99081f2 | ||
|
|
45952ef143 | ||
|
|
fedaaa6fc8 | ||
|
|
1eb08e40be | ||
|
|
6a4936853b | ||
|
|
b277eb4990 | ||
|
|
74efc5c332 | ||
|
|
6653dd97a6 | ||
|
|
d031acabf5 | ||
|
|
852f666b78 | ||
|
|
9f729e9ef0 | ||
|
|
027f19e710 | ||
|
|
14786f796b | ||
|
|
411e39a096 | ||
|
|
8a72c5b9ca | ||
|
|
9e88e28a59 | ||
|
|
df73c5ad8d | ||
|
|
98707dde76 | ||
|
|
444a9afabe | ||
|
|
315bcd5b1a | ||
|
|
c719ac22da | ||
|
|
f4596998aa | ||
|
|
5414f5cf41 | ||
|
|
d919827bc8 | ||
|
|
7b40bc157d | ||
|
|
c3ec64d1ff | ||
|
|
3464cb4a05 | ||
|
|
21f40e5b42 | ||
|
|
6d8ecab766 | ||
|
|
fc2e5112c0 | ||
|
|
8f6c57d5c9 | ||
|
|
8455dc7bd2 | ||
|
|
4677cef580 | ||
|
|
9ab6bf3da1 | ||
|
|
bc65fa6654 | ||
|
|
9fe5960449 | ||
|
|
b684dd5a3b | ||
|
|
5fff0cbdb0 | ||
|
|
99dc479d78 | ||
|
|
275eef912c | ||
|
|
b9de7632e6 | ||
|
|
7a000f2a44 | ||
|
|
9d91b8c0fb | ||
|
|
84d213bdd1 | ||
|
|
a2302ad318 | ||
|
|
42eacea4be | ||
|
|
664bfe895e | ||
|
|
9c1812ce08 | ||
|
|
49c83581f9 | ||
|
|
6aa0428879 | ||
|
|
838d0c678b | ||
|
|
35555d1362 | ||
|
|
6c6b5d7be9 | ||
|
|
037b8cc54e | ||
|
|
afb518cf8e | ||
|
|
b36c7375dd | ||
|
|
069356418d | ||
|
|
6b1c725ebd | ||
|
|
a46f8f1c20 | ||
|
|
3b3d98c4be | ||
|
|
3d4742e8f7 | ||
|
|
a6f1c21e1c | ||
|
|
a0b62ab434 | ||
|
|
0b8501e92b | ||
|
|
9a0851cb06 | ||
|
|
e4340f5015 | ||
|
|
0cd5d12d42 | ||
|
|
014398e050 | ||
|
|
4f77370977 | ||
|
|
5937215d3a | ||
|
|
ade1ce8e05 | ||
|
|
a4581dc61b | ||
|
|
50d4130b3f | ||
|
|
1147087531 | ||
|
|
4653a22635 | ||
|
|
e79501857f | ||
|
|
a8c05f6a32 | ||
|
|
c1d98cad00 | ||
|
|
fb54948f86 | ||
|
|
26297fbb5b | ||
|
|
cd342d1034 | ||
|
|
029650ef2d | ||
|
|
ca5f189e70 | ||
|
|
ac24f636df | ||
|
|
1688168bc1 | ||
|
|
46b842afc4 | ||
|
|
3f773a52cc | ||
|
|
48664bb580 | ||
|
|
094cd67728 | ||
|
|
a70bd4f906 | ||
|
|
4adac359e3 | ||
|
|
9adaf12c00 | ||
|
|
8bbfa2e417 | ||
|
|
9d800106cc | ||
|
|
e1ca97f323 | ||
|
|
68bb23e3b4 | ||
|
|
68397bd487 | ||
|
|
3104ddb4b6 | ||
|
|
9bddd6b274 | ||
|
|
677621f2da | ||
|
|
d40138dd99 | ||
|
|
d957e8f2fc | ||
|
|
681c327306 | ||
|
|
80c9afec7b | ||
|
|
eea8041abe | ||
|
|
1309bfe1ee | ||
|
|
70d3ef9984 | ||
|
|
52ac5f16e5 | ||
|
|
ea43070e6d | ||
|
|
14cd23c28b | ||
|
|
793668021e | ||
|
|
f0ea6ef43e | ||
|
|
2b2e4845a1 | ||
|
|
2dccec99cc | ||
|
|
b060894a6c | ||
|
|
92872edb58 | ||
|
|
c5fcf49eda | ||
|
|
d66a4c0920 | ||
|
|
e8a31cf867 | ||
|
|
c1f9a88ef4 | ||
|
|
e6200e186b | ||
|
|
5564502125 | ||
|
|
7947e7689c | ||
|
|
b53ada7ea2 | ||
|
|
584f28534a | ||
|
|
770fde7aac | ||
|
|
3d7f918132 | ||
|
|
29b8cedc7c | ||
|
|
33b65c3bf3 | ||
|
|
34ab1bcd9c | ||
|
|
cfdc88174b | ||
|
|
15123d8924 | ||
|
|
20086d76ce | ||
|
|
1ca4fb5c37 | ||
|
|
009016a835 | ||
|
|
33dfb2a30d | ||
|
|
1604c067fd | ||
|
|
0c7419e2b3 | ||
|
|
7cf30ccb98 | ||
|
|
aa2c8c5624 | ||
|
|
875695c239 | ||
|
|
61049a1302 | ||
|
|
28db90aa82 | ||
|
|
f0e7fc5e3b | ||
|
|
2169afa8e7 | ||
|
|
508ec06d93 | ||
|
|
9fb39d9403 | ||
|
|
4879d74f80 | ||
|
|
ba3f6c4f95 | ||
|
|
481241c4f6 | ||
|
|
5798587dc6 | ||
|
|
066e3e08a2 | ||
|
|
16d6c14633 | ||
|
|
80a4a3551c | ||
|
|
74f3bb8708 | ||
|
|
c3e398b3c2 | ||
|
|
dcfa812c83 | ||
|
|
065e65d708 | ||
|
|
bca0dab381 | ||
|
|
4a45c1055e | ||
|
|
7c789746ce | ||
|
|
f46ce5576c | ||
|
|
730e6fc1fa | ||
|
|
cb36cc042c | ||
|
|
5d586418f9 | ||
|
|
44f1d026d6 | ||
|
|
defaa1095c | ||
|
|
c67f2f8027 | ||
|
|
452128565f | ||
|
|
6322d3c984 | ||
|
|
d68c820e58 | ||
|
|
79f37b4813 | ||
|
|
87460a2fb6 | ||
|
|
6774a642d9 | ||
|
|
f0bd9233b7 | ||
|
|
66efe750a8 | ||
|
|
3658fc423b | ||
|
|
3c3ad7447e | ||
|
|
b570064b99 | ||
|
|
d9f6ef69fe | ||
|
|
bdac1d5bb4 | ||
|
|
1eee1ead5e | ||
|
|
0ec51f5b34 | ||
|
|
6c6fb05a7a | ||
|
|
09a0faacba | ||
|
|
d0d1d15de5 | ||
|
|
e4e9516d5d | ||
|
|
05eceecbea | ||
|
|
71ba1bb0d5 | ||
|
|
2160a26648 | ||
|
|
5433eac9c9 | ||
|
|
0a68f86200 | ||
|
|
c91dae0346 | ||
|
|
e1df7e5077 | ||
|
|
0560b54559 | ||
|
|
c78db7e835 | ||
|
|
c837a2d4b6 | ||
|
|
70b91b7a9a | ||
|
|
27079a7ec5 | ||
|
|
9563df0574 | ||
|
|
638209cc13 | ||
|
|
224c731afa | ||
|
|
0bbf937531 | ||
|
|
3556c92c3e | ||
|
|
87c5b23196 | ||
|
|
c83910c885 | ||
|
|
586622e90d | ||
|
|
e5e2430e03 | ||
|
|
04bfdba50e | ||
|
|
7abf15e9e0 | ||
|
|
6b680831b8 | ||
|
|
6cbf100828 | ||
|
|
3e7bbebe7f | ||
|
|
56d344045a | ||
|
|
7ab634cc08 | ||
|
|
9f0db3ebb5 | ||
|
|
6415eb8590 | ||
|
|
87c77b84a4 | ||
|
|
0b7bb16f22 | ||
|
|
5164b5ba78 | ||
|
|
f3c28bc66a | ||
|
|
239f7eb9e7 | ||
|
|
d6daf7a553 | ||
|
|
dfb3b230e6 | ||
|
|
484a5c878f | ||
|
|
3f27cfb13b | ||
|
|
38e2ba6ccd | ||
|
|
3dad38e614 | ||
|
|
0865c9d1bd | ||
|
|
20a8783d84 | ||
|
|
0b96fb05fc | ||
|
|
8767d62de7 | ||
|
|
74fb04e2d4 | ||
|
|
2537460e16 | ||
|
|
be3dfde3be | ||
|
|
42025035ad | ||
|
|
6a667fdf32 | ||
|
|
bfafac3d4f | ||
|
|
0cafbe9f91 | ||
|
|
2fbf172729 | ||
|
|
bb9755f4af | ||
|
|
2a01377a8a | ||
|
|
61cc6d5d07 | ||
|
|
1d74a37f60 | ||
|
|
ef9645f9e7 | ||
|
|
6a103ca3f3 | ||
|
|
c22772121b | ||
|
|
de7bc69d2a | ||
|
|
2eccd572c9 | ||
|
|
824a62024b | ||
|
|
3a3cfda919 | ||
|
|
e29120cc51 | ||
|
|
197d5c6bc3 | ||
|
|
d143cc75db | ||
|
|
1635a06c54 | ||
|
|
76de0d8c70 | ||
|
|
402a995b8f | ||
|
|
f580ba7779 | ||
|
|
bc3869b920 | ||
|
|
020f4a5a1a | ||
|
|
b054caa967 | ||
|
|
82b7633650 | ||
|
|
33497864f2 | ||
|
|
4c9d1544fa | ||
|
|
bce2367cfc | ||
|
|
390ecc48fb | ||
|
|
9ed99edd6e | ||
|
|
4362490539 | ||
|
|
f5d225fc3e | ||
|
|
063e9287fd | ||
|
|
ba376908cd | ||
|
|
caddf0021c | ||
|
|
90645f4d90 | ||
|
|
1316fcae22 | ||
|
|
27dee7297b | ||
|
|
13ecba40ae | ||
|
|
e15dd0d8b3 | ||
|
|
1ab26bc665 | ||
|
|
e6758d8c01 | ||
|
|
4621787e34 | ||
|
|
10ad35a285 | ||
|
|
d10145a6ba | ||
|
|
c9792ced32 | ||
|
|
a3fb09a33c | ||
|
|
6d875fd890 | ||
|
|
5d87fb7b67 | ||
|
|
4cbb59850b | ||
|
|
a2022b25e5 | ||
|
|
0d168f93ed | ||
|
|
94ac5b9bb7 | ||
|
|
024d358213 | ||
|
|
5562c93855 | ||
|
|
98e897d6a8 | ||
|
|
4aac6aa4f4 | ||
|
|
2bb4616e40 | ||
|
|
56e8476d2e | ||
|
|
97d81eb1b2 | ||
|
|
ffa21b26af | ||
|
|
9917712f66 | ||
|
|
11cdce6c90 | ||
|
|
8e82cf1e99 | ||
|
|
9767b11626 | ||
|
|
0f95694083 | ||
|
|
7dfc7dd9ef | ||
|
|
0407e958f1 | ||
|
|
e6a5fa1c3f | ||
|
|
6f48a7c4a4 | ||
|
|
80c56d71cb | ||
|
|
f77d9dcee2 | ||
|
|
f7195c7787 | ||
|
|
ca92cc6dc1 | ||
|
|
cd31b2ae5a | ||
|
|
00bec7174a | ||
|
|
236acab54f | ||
|
|
ba362f4457 | ||
|
|
8ed93baf8d | ||
|
|
bf953e96fa | ||
|
|
6b89a747e2 | ||
|
|
2fa1d54268 | ||
|
|
02ef34b451 | ||
|
|
1701fc71c4 | ||
|
|
fe200996db | ||
|
|
659333342f | ||
|
|
1ca5b6def2 | ||
|
|
4e8e3ee440 | ||
|
|
86dd724222 | ||
|
|
8242995027 | ||
|
|
49962a4734 | ||
|
|
509b16aee1 | ||
|
|
f3f5e4a887 | ||
|
|
7aabc1fa76 | ||
|
|
dcb5e36041 | ||
|
|
e0c072ab9c | ||
|
|
0231903868 | ||
|
|
f63bbeee79 | ||
|
|
db9e427444 | ||
|
|
4474a584df | ||
|
|
ab00ad68f1 | ||
|
|
d1e77efa1c | ||
|
|
de00353864 | ||
|
|
feec459d47 | ||
|
|
ad68d7e4f2 | ||
|
|
cf27c6bbf3 | ||
|
|
0115656d67 | ||
|
|
002687d2b1 | ||
|
|
a3267f6cd3 | ||
|
|
0ca9c536cd | ||
|
|
382a23c0b6 | ||
|
|
1f51331f67 | ||
|
|
cce6ba0746 | ||
|
|
be3c12dfb3 | ||
|
|
bfd87cf94e | ||
|
|
857bb1e483 | ||
|
|
75a131b675 | ||
|
|
231ea46f9f | ||
|
|
600be455a3 | ||
|
|
d98b1c5ee1 | ||
|
|
a4df06726f | ||
|
|
1eeab25b7d | ||
|
|
82cc0c3c09 | ||
|
|
e102faff6c | ||
|
|
34369bd7e9 | ||
|
|
c71b620402 | ||
|
|
21b4bf23a1 | ||
|
|
d034311f2d | ||
|
|
2deed69766 | ||
|
|
bfbd21b826 | ||
|
|
ba8683301d | ||
|
|
0ed178167b | ||
|
|
b34e34de51 | ||
|
|
e45e2c31d1 | ||
|
|
ba38e21e07 | ||
|
|
90bef7fddb | ||
|
|
c1b382ef34 | ||
|
|
028b88aa24 | ||
|
|
9d0ce33f5e | ||
|
|
dbb23d952c | ||
|
|
7fe7e47d53 | ||
|
|
d0c93dfd4d | ||
|
|
acdccaf80a | ||
|
|
769293ce1a | ||
|
|
8d0fe18b70 | ||
|
|
6926432a6c | ||
|
|
83f12b0840 | ||
|
|
290b7db7e4 | ||
|
|
f352c20ed9 | ||
|
|
2ccbffa165 | ||
|
|
06cd80a352 | ||
|
|
17dc0850d5 | ||
|
|
9667a32e44 | ||
|
|
4e6ba84bb3 | ||
|
|
de97493e6a | ||
|
|
3a24ff0d15 | ||
|
|
c463a3fc39 | ||
|
|
fc845685cc | ||
|
|
0ef0aa1a44 | ||
|
|
337689aa45 | ||
|
|
b79ba71228 | ||
|
|
2903874dbc | ||
|
|
f7e3423f9c | ||
|
|
b465c09cc8 | ||
|
|
202a5f9581 | ||
|
|
06f2f67f0c | ||
|
|
05c33be3f4 | ||
|
|
ac6c0651d6 | ||
|
|
18af6f5a12 | ||
|
|
d11ee3a702 | ||
|
|
6d9f9ce2d2 | ||
|
|
e274b7e6d5 | ||
|
|
ec1496a4cc | ||
|
|
41e19185e8 | ||
|
|
e15dd6024f | ||
|
|
0806d0c5ea | ||
|
|
5c67dd0188 | ||
|
|
e52dffeece | ||
|
|
b4358f51cb | ||
|
|
622c6d503d | ||
|
|
b190480d77 | ||
|
|
9a085beea8 | ||
|
|
1a42a77e24 | ||
|
|
e35794ef7d | ||
|
|
1f9611fc3e | ||
|
|
563afd487c | ||
|
|
5b85bb427d | ||
|
|
4d62388617 | ||
|
|
04b8055474 | ||
|
|
e10faeefc4 | ||
|
|
65dbbb3d61 | ||
|
|
fa69868ca1 | ||
|
|
9c18de7b90 | ||
|
|
61bd19f6ff | ||
|
|
ba0689aef7 | ||
|
|
ad54e6bb4b | ||
|
|
f15fcb43da | ||
|
|
4e5c2a9ecf | ||
|
|
66208f5694 | ||
|
|
68863f28eb | ||
|
|
7feaf093e2 | ||
|
|
4ab9e25fec | ||
|
|
e14dfda2fd | ||
|
|
c9aae828e2 | ||
|
|
f346c0af26 | ||
|
|
f2557b7815 | ||
|
|
a2726f5b61 | ||
|
|
834ec1575d | ||
|
|
a30f5bdee8 | ||
|
|
4cef005286 | ||
|
|
58a05681fe | ||
|
|
2589faf499 | ||
|
|
a5bdf34289 | ||
|
|
09fdd7f492 | ||
|
|
519d8b887d | ||
|
|
a2f2263bf7 | ||
|
|
5b73b10b34 | ||
|
|
b7a4364a28 | ||
|
|
3f075aff7b | ||
|
|
f4c33a5970 | ||
|
|
809af0ec18 | ||
|
|
4ee640e072 | ||
|
|
1cbf310555 | ||
|
|
f1fdc8aa43 | ||
|
|
d696daece3 | ||
|
|
967bb09282 | ||
|
|
136d910b3b | ||
|
|
51eb48a455 | ||
|
|
6ee8afcf96 | ||
|
|
a59f2d4609 | ||
|
|
b75d871837 | ||
|
|
c72f93b990 | ||
|
|
586d337ead | ||
|
|
d84e10a22e | ||
|
|
351ec89207 | ||
|
|
7db7bf0220 | ||
|
|
a9764c4f46 | ||
|
|
a430b6a280 | ||
|
|
6a01124d13 | ||
|
|
2843e445e2 | ||
|
|
5c947d14b2 | ||
|
|
590adba3e3 | ||
|
|
efee249173 | ||
|
|
6d2ed27364 | ||
|
|
55716d742f | ||
|
|
e4555da735 | ||
|
|
8b4b99bec7 | ||
|
|
5de4b19969 | ||
|
|
a9460f401e | ||
|
|
012cca550e | ||
|
|
0c743db412 | ||
|
|
b819ee7d6d | ||
|
|
e7e3a249b5 | ||
|
|
980c580b55 | ||
|
|
e23c530e74 | ||
|
|
a64caccca2 | ||
|
|
829bcafcf2 | ||
|
|
e2a935c647 | ||
|
|
2e7afdb49e | ||
|
|
cdc965e026 | ||
|
|
dd4faa005e | ||
|
|
726ec7159c | ||
|
|
e74256ef6f | ||
|
|
a18718ca81 | ||
|
|
5a9bc0e269 | ||
|
|
2d39c62ff0 | ||
|
|
0da4f79413 | ||
|
|
2bdef776a2 | ||
|
|
1819d6f042 | ||
|
|
2f6a707847 | ||
|
|
4aaf017824 | ||
|
|
fb05ed48d0 | ||
|
|
49203ae539 | ||
|
|
d17660d516 | ||
|
|
513ce34671 | ||
|
|
44ce48009b | ||
|
|
a57ad67308 | ||
|
|
e63d04cea9 | ||
|
|
cf48cb6f75 | ||
|
|
542e53cf6a | ||
|
|
bab1d40038 | ||
|
|
2f4a8247e8 | ||
|
|
f0b9006c55 | ||
|
|
4bc14ef797 | ||
|
|
47d2cee3f1 | ||
|
|
088f53f5a9 | ||
|
|
fee660bf6c | ||
|
|
e97ecb89a9 | ||
|
|
1ab6a4532b | ||
|
|
b43ddd0d8b | ||
|
|
d0c4c2d594 | ||
|
|
e0ae079ea0 | ||
|
|
dfddbd15a9 | ||
|
|
29242c45a1 | ||
|
|
7ff0e59f4d | ||
|
|
2d0fe57a47 | ||
|
|
ba2b87749b | ||
|
|
0806af1261 | ||
|
|
09e92f3a18 | ||
|
|
eb5d0bb795 | ||
|
|
a8afba4067 | ||
|
|
7d66141c37 | ||
|
|
b6f3ea2eec | ||
|
|
2570445133 | ||
|
|
acbd22cf22 | ||
|
|
ef251b040a | ||
|
|
6c5bb69ba9 | ||
|
|
18f605e5c5 | ||
|
|
7599406449 | ||
|
|
670e4c8538 | ||
|
|
30458b115c | ||
|
|
da8933ec58 | ||
|
|
dc8ac51c83 | ||
|
|
c4747fdc72 | ||
|
|
58ba748ade | ||
|
|
c0d51ad58a | ||
|
|
a0da73f76f | ||
|
|
34b8888c8f | ||
|
|
74ad40f67c | ||
|
|
a7a29db8d5 | ||
|
|
86a938d31d | ||
|
|
d8d0830631 | ||
|
|
bba3b7476a | ||
|
|
4880c642fe | ||
|
|
76ad896461 | ||
|
|
b49159f9e0 | ||
|
|
cd8a80a6a1 | ||
|
|
3ce8aa7894 | ||
|
|
5f3645f716 | ||
|
|
5af96597d5 | ||
|
|
c2baf4e05f | ||
|
|
b356794da9 | ||
|
|
afe8f6cf6a | ||
|
|
ed0df82fe9 | ||
|
|
d3bc7a9790 | ||
|
|
9e7923bc50 | ||
|
|
851bf94c90 | ||
|
|
ae80b7d098 | ||
|
|
633c0f870d | ||
|
|
f9fe7819f9 | ||
|
|
f3d13545e7 | ||
|
|
f6b77777b5 | ||
|
|
340990fbd9 | ||
|
|
a7687f8e35 | ||
|
|
52aa4a5289 | ||
|
|
268accea14 | ||
|
|
101cde4d84 | ||
|
|
8863446f6a | ||
|
|
28a0824f6b | ||
|
|
b1f9d0516d | ||
|
|
5b21747d5d | ||
|
|
9fda48cff0 | ||
|
|
0e6f3df212 | ||
|
|
a8c3f1555e | ||
|
|
cd797a637b | ||
|
|
53b2eb59d3 | ||
|
|
09e2224596 | ||
|
|
5999aad21b | ||
|
|
874ce07c3e | ||
|
|
1787d08718 | ||
|
|
9a12be88da | ||
|
|
8f6bb74e61 | ||
|
|
e67bd2972a | ||
|
|
de5929d8d2 | ||
|
|
d7699ef079 | ||
|
|
3ab04ebca8 | ||
|
|
78d2aa96d7 |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,9 +1,8 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: LucasGGamerM
|
||||
patreon: # mastodon
|
||||
open_collective: # Replace with a single Open Collective username e.g., user1
|
||||
ko_fi: xsk22
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username e.g., user1
|
||||
|
||||
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Screenshots and screen recordings**
|
||||
If applicable, add screenshots (and screen recordings, if possible) to help explain your problem.
|
||||
|
||||
**Version**
|
||||
Megalodon version: [e.g. v1.1.4+fork.#]
|
||||
|
||||
**Additional context**
|
||||
- Does this issue also occur with the respective upstream release? (Please test using the respective `upstream-xxxxxx.apk` provided in [Releases](https://github.com/sk22/megalodon/releases)) No / Yes (`mastodon#…`)
|
||||
|
||||
> In this case, please consider filing an [upstream bug report](https://github.com/mastodon/mastodon-android/issues) instead. If this bug is seriously impacting your usage or you think I might want to try to fix it for Megalodon, feel free to still create this issue!
|
||||
|
||||
**Crash log**
|
||||
If you know your way around Android development tools, please consider attaching a crash log, if possible.
|
||||
20
.github/ISSUE_TEMPLATE/feature-ui-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature-ui-request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature/UI request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
If applicable: a clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
10
.github/ISSUE_TEMPLATE/something-else.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/something-else.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: It's something else…
|
||||
about: Issues that can't be categorized as feature requests or bug reports
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
49
README.md
49
README.md
@@ -1,18 +1,32 @@
|
||||

|
||||

|
||||
|
||||
# Moshidon
|
||||
# Moshidon, the material you mastodon client!
|
||||
|
||||
> A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly won’t ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
|
||||
|
||||
**Warning! [The last version's integrated updater was broken](https://github.com/sk22/megalodon/issues/106) – I already published a fixed version! If you're not updating through Izzy's F-Droid repository (more sources to come, hopefully!), you'll have to download the current release manually. Sorry about that!**
|
||||
|
||||
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
||||
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Key features
|
||||
|
||||
### **Material you theme support on Android 12+ devices!**
|
||||
|
||||
### **Translate button**
|
||||
|
||||
**Allows you to translate posts in instances with the translate feature!**
|
||||
|
||||
**Screenshots**
|
||||
|
||||

|
||||

|
||||
|
||||
### **Color themes**
|
||||
|
||||
**Allows you to change theme within the app. Supports Purple, pink, green, blue, orange and yellow!**
|
||||
|
||||
### **Unlisted posting**
|
||||
|
||||
**Allows you to post publicly without having your post show up in trends, hashtags or public timelines (i.e., in the tabs “Local”, “Community” and “Posts”).**
|
||||
@@ -49,34 +63,21 @@ To bookmark a post, press the button between the Favorite and Share buttons on t
|
||||
|
||||
## Installation
|
||||
|
||||
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Megalodon will automatically notify you about new updates inside the app.**
|
||||
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Moshidon will automatically notify you about new updates inside the app.**
|
||||
|
||||
To install this app on your Android device, download the [latest release from GitHub](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/sk22/megalodon/releases) page.
|
||||
To install this app on your Android device, download the [latest release from GitHub](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||
|
||||
Megalodon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Megalodon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
||||
Moshidon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Megalodon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
||||
|
||||
### Other sources
|
||||
|
||||
* **[Izzy's F-Droid repository](https://apt.izzysoft.de/fdroid/repo)**: https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Release variants
|
||||
|
||||
All downloads can be found on the [Releases](https://github.com/sk22/megalodon/releases) page.
|
||||
All downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||
|
||||
**`megalodon.apk`**
|
||||
**`moshidon.apk`**
|
||||
|
||||
Variant with an integrated updater. If you download Megalodon from here (and not from an app store), just download the regular `megalodon.apk`.
|
||||
|
||||
**`upstream-1234abc.apk`**
|
||||
|
||||
This is an **unmodified version** of the official [Mastodon for Android](https://github.com/mastodon/mastodon-android) app the respective Megalodon release is based on. Should you find any bugs in Megalodon (which you will), try to see if it occurs with this variant, too. The last 7 digits of the file name are important to know which version of the official app you're using.
|
||||
|
||||
<!-- **`megalodon-fdroid.apk`**
|
||||
|
||||
Variant without the integrated updater. This is the variant to be published to F-Droid.org where an integrated updater is not necessary. -->
|
||||
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
|
||||
|
||||
---
|
||||
|
||||
@@ -139,4 +140,4 @@ This project is released under the [GPL-3 License](./LICENSE).
|
||||
|
||||
## Links
|
||||
|
||||
<a rel="me" href="https://floss.social/@megalodon">@megalodon<wbr>@floss.social</a>
|
||||
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
title: Megalodon
|
||||
title: Moshidon
|
||||
layout: default
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Megalodon</title>
|
||||
<title>Moshidon</title>
|
||||
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
||||
<link rel="me" href="https://floss.social/@mastodon">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
||||
@@ -14,4 +14,4 @@
|
||||
{{ content }}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
BIN
img/f-droid-badge.png
Normal file
BIN
img/f-droid-badge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -9,13 +9,10 @@ android {
|
||||
applicationId "org.joinmastodon.android.moshinda"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 54
|
||||
versionName "1.1.4+fork.54.moshinda"
|
||||
versionCode 86
|
||||
versionName "1.1.4+fork.86.moshinda"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs "en", "ar-rSA", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES",
|
||||
"eu-rES", "fi-rFI", "fr-rFR", "gl-rES", "hr-rHR", "hy-rAM", "it-rIT", "iw-rIL",
|
||||
"ja-rJP", "kab", "ko-rKR", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ru-rRU",
|
||||
"sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -32,9 +29,11 @@ android {
|
||||
githubRelease{
|
||||
initWith release
|
||||
}
|
||||
noFederatedRelease{
|
||||
playRelease{
|
||||
initWith release
|
||||
versionNameSuffix '-nofederated'
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
versionNameSuffix '-play'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
@@ -78,4 +77,4 @@ dependencies {
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
|
||||
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
info=new UpdateInfo();
|
||||
info.version=prefs.getString("version", null);
|
||||
info.size=prefs.getLong("apkSize", 0);
|
||||
info.changelog=prefs.getString("changelog", null);
|
||||
downloadID=prefs.getLong("downloadID", 0);
|
||||
if(downloadID==0 || !getUpdateApkFile().exists()){
|
||||
state=UpdateState.UPDATE_AVAILABLE;
|
||||
@@ -84,6 +85,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
.remove("apkURL")
|
||||
.remove("checkedByBuild")
|
||||
.remove("downloadID")
|
||||
.remove("changelog")
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
@@ -116,6 +118,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||
try(Response resp=call.execute()){
|
||||
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
|
||||
String changelog=obj.get("body").getAsString();
|
||||
String tag=obj.get("tag_name").getAsString();
|
||||
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
||||
Matcher matcher=pattern.matcher(tag);
|
||||
@@ -151,6 +154,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
UpdateInfo info=new UpdateInfo();
|
||||
info.size=size;
|
||||
info.version=version;
|
||||
info.changelog=changelog;
|
||||
this.info=info;
|
||||
|
||||
getPrefs().edit()
|
||||
@@ -158,6 +162,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
.putString("version", version)
|
||||
.putString("apkURL", url)
|
||||
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
||||
.putString("changelog", changelog)
|
||||
.remove("downloadID")
|
||||
.apply();
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||
<path android:pathData="M3.897 4.054L3.97 3.97c0.266-0.267 0.683-0.29 0.976-0.073L5.03 3.97 10 8.939l4.97-4.97c0.266-0.266 0.683-0.29 0.976-0.072L16.03 3.97c0.267 0.266 0.29 0.683 0.073 0.976L16.03 5.03 11.061 10l4.97 4.97c0.266 0.266 0.29 0.683 0.072 0.976L16.03 16.03c-0.266 0.267-0.683 0.29-0.976 0.073L14.97 16.03 10 11.061l-4.97 4.97c-0.266 0.266-0.683 0.29-0.976 0.072L3.97 16.03c-0.267-0.266-0.29-0.683-0.073-0.976L3.97 14.97 8.939 10l-4.97-4.97C3.704 4.764 3.68 4.347 3.898 4.054L3.97 3.97 3.897 4.054z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M22 6.5c0 3.038-2.462 5.5-5.5 5.5S11 9.538 11 6.5 13.462 1 16.5 1 22 3.462 22 6.5zm-7.146-2.354c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708L15.793 6.5l-1.647 1.646c-0.195 0.196-0.195 0.512 0 0.707 0.196 0.196 0.512 0.196 0.708 0L16.5 7.208l1.646 1.647c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.707L17.207 6.5l1.647-1.646c0.195-0.196 0.195-0.512 0-0.708-0.196-0.195-0.512-0.195-0.708 0L16.5 5.793l-1.646-1.647zM19.5 14v-1.732c0.551-0.287 1.056-0.651 1.5-1.078v7.56c0 1.733-1.357 3.15-3.066 3.245L17.75 22H6.25c-1.733 0-3.15-1.357-3.245-3.066L3 18.75V7.25C3 5.517 4.356 4.1 6.066 4.005L6.25 4h4.248c-0.198 0.474-0.34 0.977-0.422 1.5H6.25c-0.918 0-1.671 0.707-1.744 1.606L4.5 7.25V14H9c0.38 0 0.694 0.282 0.743 0.648L9.75 14.75C9.75 15.993 10.757 17 12 17c1.19 0 2.166-0.925 2.245-2.096l0.005-0.154c0-0.38 0.282-0.694 0.648-0.743L15 14h4.5zm-15 1.5v3.25c0 0.918 0.707 1.671 1.606 1.744L6.25 20.5h11.5c0.918 0 1.671-0.707 1.744-1.607L19.5 18.75V15.5h-3.825c-0.335 1.648-1.75 2.904-3.475 2.995L12 18.5c-1.747 0-3.215-1.195-3.632-2.812L8.325 15.5H4.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||
<path android:pathData="M26 7.5c0 3.59-2.91 6.5-6.5 6.5S13 11.09 13 7.5 15.91 1 19.5 1 26 3.91 26 7.5zm-9.146-3.354c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708L18.793 7.5l-2.647 2.646c-0.195 0.196-0.195 0.512 0 0.708 0.196 0.195 0.512 0.195 0.708 0L19.5 8.207l2.646 2.647c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.708L20.207 7.5l2.647-2.646c0.195-0.196 0.195-0.512 0-0.708-0.196-0.195-0.512-0.195-0.708 0L19.5 6.793l-2.646-2.647zM25 22.75V12.6c-0.443 0.476-0.947 0.896-1.5 1.245V16h-6l-0.102 0.007c-0.366 0.05-0.648 0.363-0.648 0.743 0 1.519-1.231 2.75-2.75 2.75s-2.75-1.231-2.75-2.75l-0.007-0.102C11.193 16.282 10.88 16 10.5 16h-6V7.25c0-0.966 0.784-1.75 1.75-1.75h6.02c0.145-0.525 0.345-1.028 0.595-1.5H6.25C4.455 4 3 5.455 3 7.25v15.5C3 24.545 4.455 26 6.25 26h15.5c1.795 0 3.25-1.455 3.25-3.25zm-20.5 0V17.5h5.316l0.041 0.204C10.291 19.592 11.982 21 14 21l0.215-0.005c1.994-0.1 3.627-1.574 3.969-3.495H23.5v5.25c0 0.966-0.784 1.75-1.75 1.75H6.25c-0.966 0-1.75-0.784-1.75-1.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||
<path android:pathData="M10 2c4.418 0 8 3.582 8 8 0 2.706-1.142 4.5-3 4.5-1.226 0-2.14-0.781-2.62-2.09C11.784 13.393 10.781 14 9.5 14 7.36 14 6 12.307 6 10c0-2.337 1.313-4 3.5-4 1.052 0 1.901 0.385 2.5 1.044V6.5C12 6.224 12.224 6 12.5 6c0.245 0 0.45 0.177 0.492 0.41L13 6.5V10c0 2.223 0.813 3.5 2 3.5s2-1.277 2-3.5c0-3.866-3.134-7-7-7s-7 3.134-7 7 3.134 7 7 7c0.823 0 1.626-0.142 2.383-0.416 0.26-0.094 0.547 0.04 0.64 0.3 0.095 0.26-0.04 0.546-0.3 0.64C11.859 17.838 10.94 18 10 18c-4.418 0-8-3.582-8-8s3.582-8 8-8zM9.5 7C7.924 7 7 8.17 7 10c0 1.797 0.966 3 2.5 3s2.5-1.203 2.5-3c0-1.83-0.924-3-2.5-3z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M6.25 4.5C5.283 4.5 4.5 5.284 4.5 6.25v11.5c0 0.966 0.783 1.75 1.75 1.75h11.5c0.966 0 1.75-0.784 1.75-1.75v-4c0-0.414 0.335-0.75 0.75-0.75 0.414 0 0.75 0.336 0.75 0.75v4c0 1.795-1.456 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V6.25C3 4.455 4.455 3 6.25 3h4C10.664 3 11 3.336 11 3.75S10.664 4.5 10.25 4.5h-4zM13 3.75C13 3.336 13.335 3 13.75 3h6.5C20.664 3 21 3.336 21 3.75v6.5c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75V5.56l-5.22 5.22c-0.293 0.293-0.768 0.293-1.06 0-0.293-0.293-0.293-0.768 0-1.06l5.22-5.22h-4.69C13.335 4.5 13 4.164 13 3.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M8.502 11.5c0.554 0 1.002 0.448 1.002 1.002 0 0.553-0.448 1.002-1.002 1.002-0.553 0-1.002-0.449-1.002-1.002 0-0.554 0.449-1.003 1.002-1.003zM12 4.353v6.651h7.442L17.72 9.28c-0.267-0.266-0.29-0.683-0.073-0.977L17.72 8.22c0.266-0.266 0.683-0.29 0.976-0.072L18.78 8.22l2.997 2.998c0.266 0.266 0.29 0.682 0.073 0.976l-0.073 0.084-2.996 3.003c-0.293 0.294-0.767 0.294-1.06 0.002-0.267-0.266-0.292-0.683-0.075-0.977l0.073-0.084 1.713-1.717h-7.431L12 19.25c0 0.466-0.421 0.82-0.88 0.738l-8.5-1.501C2.26 18.424 2 18.112 2 17.748V5.75c0-0.368 0.266-0.681 0.628-0.74l8.5-1.396C11.585 3.539 12 3.89 12 4.354zm-1.5 0.883l-7 1.15v10.732l7 1.236V5.237zM13 18.5h0.765l0.102-0.007c0.366-0.05 0.649-0.364 0.648-0.744l-0.007-4.25H13v5zm0.002-8.502L13 8.726V5h0.745c0.38 0 0.693 0.281 0.743 0.647l0.007 0.101L14.502 10h-1.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M14.704 3.44C14.895 3.667 15 3.953 15 4.248V19.75c0 0.69-0.56 1.25-1.25 1.25-0.296 0-0.582-0.105-0.808-0.296l-4.967-4.206H4.25c-1.243 0-2.25-1.008-2.25-2.25v-4.5c0-1.243 1.007-2.25 2.25-2.25h3.725l4.968-4.204c0.526-0.446 1.315-0.38 1.761 0.147zM13.5 4.787l-4.975 4.21H4.25c-0.414 0-0.75 0.337-0.75 0.75v4.5c0 0.415 0.336 0.75 0.75 0.75h4.275L13.5 19.21V4.787z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||
<path android:pathData="M16.5 4.814c0-1.094-1.307-1.66-2.105-0.912l-4.937 4.63C9.134 8.836 8.706 9.005 8.261 9.005H5.25C3.455 9.005 2 10.46 2 12.255v3.492c0 1.795 1.455 3.25 3.25 3.25h3.012c0.444 0 0.872 0.17 1.196 0.473l4.937 4.626c0.799 0.748 2.105 0.182 2.105-0.912V4.814zm-6.016 4.812L15 5.39v17.216l-4.516-4.232c-0.602-0.564-1.397-0.878-2.222-0.878H5.25c-0.966 0-1.75-0.784-1.75-1.75v-3.492c0-0.966 0.784-1.75 1.75-1.75h3.011c0.826 0 1.62-0.314 2.223-0.88z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:pathData="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06L6.438 7.5H4.25C3.007 7.499 2 8.506 2 9.749v4.497c0 1.243 1.007 2.25 2.25 2.25h3.68c0.183 0 0.36 0.068 0.498 0.19l4.491 3.994C13.725 21.396 15 20.824 15 19.746V16.06l5.72 5.72c0.292 0.292 0.767 0.292 1.06 0 0.293-0.293 0.293-0.768 0-1.061L3.28 2.22zM13.5 14.56v4.629l-4.075-3.624c-0.412-0.366-0.944-0.569-1.495-0.569H4.25c-0.414 0-0.75-0.335-0.75-0.75V9.75C3.5 9.335 3.836 9 4.25 9h3.688l5.562 5.56zm0-9.753v5.511l1.5 1.5V4.25c0-1.079-1.274-1.65-2.08-0.934l-3.4 3.022 1.063 1.063L13.5 4.807zm3.641 9.152l1.138 1.138C18.741 14.163 19 13.111 19 12c0-1.203-0.304-2.338-0.84-3.328-0.198-0.364-0.653-0.5-1.017-0.303-0.364 0.197-0.5 0.653-0.303 1.017 0.42 0.777 0.66 1.666 0.66 2.614 0 0.691-0.127 1.351-0.359 1.96zm2.247 2.247l1.093 1.094C21.445 15.763 22 13.946 22 12c0-2.226-0.728-4.284-1.96-5.946-0.246-0.333-0.716-0.403-1.048-0.157-0.333 0.247-0.403 0.716-0.157 1.05C19.881 8.358 20.5 10.106 20.5 12c0 1.531-0.404 2.966-1.112 4.206z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||
<path android:pathData="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06l5.724 5.725H5.25C3.455 9.005 2 10.46 2 12.255v3.492c0 1.795 1.455 3.25 3.25 3.25h3.012c0.444 0 0.872 0.17 1.196 0.473l4.937 4.626c0.799 0.748 2.105 0.182 2.105-0.912v-5.623l8.22 8.22c0.292 0.292 0.767 0.292 1.06 0 0.293-0.293 0.293-0.768 0-1.061L3.28 2.22zM15 16.06v6.547l-4.516-4.231c-0.602-0.565-1.397-0.879-2.222-0.879H5.25c-0.966 0-1.75-0.783-1.75-1.75v-3.492c0-0.966 0.784-1.75 1.75-1.75h3.011c0.35 0 0.693-0.056 1.02-0.164L15 16.061zm-4.378-8.62l1.061 1.061L15 5.392v6.427l1.5 1.5V4.814c0-1.094-1.307-1.66-2.105-0.912L10.622 7.44zm9.55 9.55l1.137 1.137C21.912 16.88 22.25 15.478 22.25 14c0-2.136-0.706-4.11-1.897-5.697-0.249-0.332-0.719-0.399-1.05-0.15-0.332 0.249-0.399 0.719-0.15 1.05C20.156 10.54 20.75 12.199 20.75 14c0 1.058-0.205 2.067-0.578 2.99zm2.803 2.803l1.095 1.096c1.224-2.008 1.93-4.366 1.93-6.89 0-3.35-1.245-6.414-3.298-8.747-0.274-0.31-0.747-0.341-1.058-0.068-0.311 0.274-0.342 0.748-0.068 1.059C23.396 8.313 24.5 11.027 24.5 14c0 2.107-0.554 4.084-1.525 5.793z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
@@ -19,7 +20,7 @@
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.Mastodon.AutoLightDark.Original"
|
||||
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
||||
android:largeHeap="true">
|
||||
|
||||
<activity android:name=".MainActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize" android:launchMode="singleTask">
|
||||
@@ -33,7 +34,7 @@
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="megalodon-android-auth" android:host="callback"/>
|
||||
<data android:scheme="moshidon-android-auth" android:host="callback"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize">
|
||||
|
||||
85
mastodon/src/main/assets/blocks.tsv
Normal file
85
mastodon/src/main/assets/blocks.tsv
Normal file
@@ -0,0 +1,85 @@
|
||||
# lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
|
||||
# https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
|
||||
# This list contains domains of toxic mastodon instances
|
||||
# Last-Modified: 1672044500
|
||||
|
||||
# gab - a neonazi social network
|
||||
gab.ai
|
||||
gab.com
|
||||
gab.protohype.net
|
||||
|
||||
# consequence-free speech
|
||||
social.unzensiert.to
|
||||
freeatlantis.com
|
||||
|
||||
# reactionary bigotry and hatespeech against magrinalized groups
|
||||
poa.st
|
||||
freespeechextremist.com
|
||||
rdrama.cc
|
||||
outpoa.st
|
||||
anime.website
|
||||
gameliberty.club
|
||||
social.byoblu.com
|
||||
yggdrasil.social
|
||||
smuglo.li
|
||||
dogeposting.social
|
||||
unsafe.space
|
||||
freezepeach.xyz
|
||||
|
||||
# + CSAM
|
||||
rojogato.com
|
||||
|
||||
# antivaxxer shitposting & fearmongering
|
||||
shadowsocial.org
|
||||
|
||||
# Kiwifarms
|
||||
kiwifarms.net
|
||||
kiwifarms.cc
|
||||
kiwifarms.is
|
||||
kiwifarms.pleroma.net
|
||||
|
||||
|
||||
# https://mastodon.art/@Curator/109649354849593592
|
||||
|
||||
poa.st antisemitic racist homophobic
|
||||
nicecrew.digital antisemitic
|
||||
beefyboys.win antisemitic racist homophobic harassment
|
||||
cawfee.club antisemitic racist homophobic
|
||||
comfyboy.club antisemitic racist homophobic
|
||||
freespeechextremist.com racist homophobic
|
||||
cum.salon racist misogynist
|
||||
bae.st racist
|
||||
natehiggers.online racist
|
||||
rapemeat.solutions misogynist
|
||||
rapist.town misogynist
|
||||
rapefeminists.network misogynist
|
||||
kiwifarms.cc harassment
|
||||
noagendasocial.com noagenda
|
||||
posting.lolicon.rocks underage
|
||||
urchan.org harassment homophobic racist
|
||||
ryona.agency harassment
|
||||
yggdrasil.social antisemitic homophobic racist
|
||||
genderheretics.xyz transphobic
|
||||
baraag.net underage
|
||||
lolison.top underage
|
||||
shota.house underage
|
||||
shota.social underage
|
||||
aethy.com underage
|
||||
taullo.social underage
|
||||
childpawn.shop underage
|
||||
posting.lolicon.rocks underage
|
||||
loli.best underage
|
||||
gothloli.club underage
|
||||
smuglo.li underage
|
||||
youjo.love underage
|
||||
pedo.school underage
|
||||
lolison.network underage
|
||||
freak.university underage
|
||||
mirr0r.city underage
|
||||
xhais.love underage
|
||||
refusal.biz underage
|
||||
refusal.llc underage
|
||||
mirr0r.city underage
|
||||
nnia.space underage
|
||||
ignorelist.com malicious
|
||||
repl.co malicious
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 15 KiB |
@@ -51,7 +51,14 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
getWindow().setBackgroundDrawable(null);
|
||||
|
||||
Intent intent=getIntent();
|
||||
String text=intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
StringBuilder builder=new StringBuilder();
|
||||
String subject = "";
|
||||
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
|
||||
subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||
if (!subject.isBlank()) builder.append(subject).append("\n\n");
|
||||
}
|
||||
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
|
||||
String text=builder.toString();
|
||||
List<Uri> mediaUris;
|
||||
if(Intent.ACTION_SEND.equals(intent.getAction())){
|
||||
Uri singleUri=intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
@@ -77,6 +84,8 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
args.putString("account", accountID);
|
||||
if(!TextUtils.isEmpty(text))
|
||||
args.putString("prefilledText", text);
|
||||
if(!subject.isBlank())
|
||||
args.putInt("selectionEnd", subject.length());
|
||||
if(mediaUris!=null && !mediaUris.isEmpty())
|
||||
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
|
||||
Fragment fragment=new ComposeFragment();
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GlobalUserPreferences{
|
||||
public static boolean playGifs;
|
||||
@@ -10,16 +21,37 @@ public class GlobalUserPreferences{
|
||||
public static boolean showReplies;
|
||||
public static boolean showBoosts;
|
||||
public static boolean loadNewPosts;
|
||||
public static boolean showFederatedTimeline;
|
||||
public static boolean showInteractionCounts;
|
||||
public static boolean alwaysExpandContentWarnings;
|
||||
public static boolean disableMarquee;
|
||||
public static boolean disableSwipe;
|
||||
public static boolean disableDividers;
|
||||
public static boolean voteButtonForSingleChoice;
|
||||
public static boolean uniformNotificationIcon;
|
||||
public static boolean enableDeleteNotifications;
|
||||
public static boolean relocatePublishButton;
|
||||
public static boolean reduceMotion;
|
||||
public static boolean keepOnlyLatestNotification;
|
||||
public static String publishButtonText;
|
||||
public static ThemePreference theme;
|
||||
public static ColorPreference color;
|
||||
|
||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||
public static Map<String, List<String>> recentLanguages;
|
||||
|
||||
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||
public static Map<String, Integer> recentEmojis;
|
||||
|
||||
private static SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private static <T> T fromJson(String json, Type type, T orElse) {
|
||||
try { return gson.fromJson(json, type); }
|
||||
catch (JsonSyntaxException ignored) { return orElse; }
|
||||
}
|
||||
|
||||
public static void load(){
|
||||
SharedPreferences prefs=getPrefs();
|
||||
playGifs=prefs.getBoolean("playGifs", true);
|
||||
@@ -28,11 +60,33 @@ public class GlobalUserPreferences{
|
||||
showReplies=prefs.getBoolean("showReplies", true);
|
||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", true);
|
||||
showFederatedTimeline=prefs.getBoolean("showFederatedTimeline", !BuildConfig.BUILD_TYPE.equals("playRelease"));
|
||||
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
||||
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
||||
disableDividers=prefs.getBoolean("disableDividers", true);
|
||||
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
|
||||
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
||||
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", true);
|
||||
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||
color=ColorPreference.values()[prefs.getInt("color", 1)];
|
||||
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
|
||||
recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
|
||||
publishButtonText=prefs.getString("publishButtonText", "");
|
||||
|
||||
try {
|
||||
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
|
||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.MATERIAL3.name()));
|
||||
}else{
|
||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PURPLE.name()));
|
||||
}
|
||||
} catch (IllegalArgumentException|ClassCastException ignored) {
|
||||
// invalid color name or color was previously saved as integer
|
||||
color=ColorPreference.PURPLE;
|
||||
}
|
||||
}
|
||||
|
||||
public static void save(){
|
||||
@@ -42,22 +96,36 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("showReplies", showReplies)
|
||||
.putBoolean("showBoosts", showBoosts)
|
||||
.putBoolean("loadNewPosts", loadNewPosts)
|
||||
.putBoolean("showFederatedTimeline", showFederatedTimeline)
|
||||
.putBoolean("trueBlackTheme", trueBlackTheme)
|
||||
.putBoolean("showInteractionCounts", showInteractionCounts)
|
||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||
.putBoolean("disableMarquee", disableMarquee)
|
||||
.putBoolean("disableSwipe", disableSwipe)
|
||||
.putBoolean("disableDividers", disableDividers)
|
||||
.putBoolean("relocatePublishButton", relocatePublishButton)
|
||||
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||
.putBoolean("reduceMotion", reduceMotion)
|
||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||
.putString("publishButtonText", publishButtonText)
|
||||
.putInt("theme", theme.ordinal())
|
||||
.putInt("color", color.ordinal())
|
||||
.putString("color", color.name())
|
||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||
.putString("recentEmojis", gson.toJson(recentEmojis))
|
||||
.apply();
|
||||
}
|
||||
|
||||
public enum ColorPreference{
|
||||
MATERIAL3,
|
||||
PINK,
|
||||
PURPLE,
|
||||
GREEN,
|
||||
BLUE,
|
||||
ORANGE,
|
||||
YELLOW
|
||||
BROWN,
|
||||
RED,
|
||||
YELLOW,
|
||||
NORD
|
||||
}
|
||||
|
||||
public enum ThemePreference{
|
||||
|
||||
@@ -14,9 +14,10 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.SplashFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
@@ -33,7 +34,7 @@ public class MainActivity extends FragmentStackActivity{
|
||||
|
||||
if(savedInstanceState==null){
|
||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||
showFragmentClearingBackStack(new SplashFragment());
|
||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
||||
}else{
|
||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
||||
AccountSession session;
|
||||
|
||||
@@ -64,7 +64,7 @@ public class OAuthActivity extends Activity{
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account account){
|
||||
AccountSessionManager.getInstance().addAccount(instance, token, account, app, true);
|
||||
AccountSessionManager.getInstance().addAccount(instance, token, account, app, null);
|
||||
progress.dismiss();
|
||||
finish();
|
||||
// not calling restartMainActivity() here on purpose to have it recreated (notice different flags)
|
||||
|
||||
@@ -8,9 +8,7 @@ import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -39,6 +37,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
private static final String TAG="PushNotificationReceive";
|
||||
|
||||
public static final int NOTIFICATION_ID=178;
|
||||
private static int notificationId = 0;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
@@ -131,25 +130,38 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
contentIntent.putExtra("fromNotification", true);
|
||||
contentIntent.putExtra("accountID", accountID);
|
||||
contentIntent.putExtra("notificationID", notificationId);
|
||||
if(notification!=null){
|
||||
contentIntent.putExtra("notification", Parcels.wrap(notification));
|
||||
}
|
||||
builder.setContentTitle(pn.title)
|
||||
.setContentText(pn.body)
|
||||
.setStyle(new Notification.BigTextStyle().bigText(pn.body))
|
||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||
.setContentIntent(PendingIntent.getActivity(context, accountID.hashCode() & 0xFFFF, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
||||
.setShowWhen(true)
|
||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||
.setAutoCancel(true)
|
||||
.setColor(context.getColor(R.color.primary_700));
|
||||
.setColor(context.getColor(R.color.shortcut_icon_background));
|
||||
if(!GlobalUserPreferences.uniformNotificationIcon){
|
||||
switch (pn.notificationType) {
|
||||
case FAVORITE -> builder.setSmallIcon(R.drawable.ic_fluent_star_24_filled);
|
||||
case REBLOG -> builder.setSmallIcon(R.drawable.ic_fluent_arrow_repeat_all_24_filled);
|
||||
case FOLLOW -> builder.setSmallIcon(R.drawable.ic_fluent_person_add_24_filled);
|
||||
case MENTION -> builder.setSmallIcon(R.drawable.ic_fluent_mention_24_filled);
|
||||
case POLL -> builder.setSmallIcon(R.drawable.ic_fluent_poll_24_filled);
|
||||
default -> builder.setSmallIcon(R.drawable.ic_ntf_logo);
|
||||
}
|
||||
}else{
|
||||
builder.setSmallIcon(R.drawable.ic_ntf_logo);
|
||||
}
|
||||
|
||||
if(avatar!=null){
|
||||
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
||||
}
|
||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().size()>1){
|
||||
builder.setSubText(accountName);
|
||||
}
|
||||
nm.notify(accountID, NOTIFICATION_ID, builder.build());
|
||||
nm.notify(accountID, GlobalUserPreferences.keepOnlyLatestNotification ? NOTIFICATION_ID : notificationId++, builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@ import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
@@ -47,9 +50,26 @@ public class MastodonAPIController{
|
||||
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
||||
|
||||
private AccountSession session;
|
||||
private static List<String> badDomains = new ArrayList<>();
|
||||
|
||||
static{
|
||||
thread.start();
|
||||
try {
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
MastodonApp.context.getAssets().open("blocks.tsv")
|
||||
));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isBlank() || line.startsWith("#")) continue;
|
||||
String[] parts = line.replaceAll("\"", "").split("[\s,;]");
|
||||
if (parts.length == 0) continue;
|
||||
String domain = parts[0].toLowerCase().trim();
|
||||
if (domain.isBlank()) continue;
|
||||
badDomains.add(domain);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public MastodonAPIController(@Nullable AccountSession session){
|
||||
@@ -57,8 +77,11 @@ public class MastodonAPIController{
|
||||
}
|
||||
|
||||
public <T> void submitRequest(final MastodonAPIRequest<T> req){
|
||||
final String host = req.getURL().getHost();
|
||||
final boolean isBad = host == null || badDomains.stream().anyMatch(h -> h.equalsIgnoreCase(host) || host.toLowerCase().endsWith("." + h));
|
||||
thread.postRunnable(()->{
|
||||
try{
|
||||
if (isBad) throw new IllegalArgumentException();
|
||||
if(req.canceled)
|
||||
return;
|
||||
Request.Builder builder=new Request.Builder()
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.StringRes;
|
||||
@@ -101,9 +102,14 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
||||
return wrapProgress(activity, message, cancelable, null);
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable, Consumer<ProgressDialog> transform){
|
||||
progressDialog=new ProgressDialog(activity);
|
||||
progressDialog.setMessage(activity.getString(message));
|
||||
progressDialog.setCancelable(cancelable);
|
||||
if (transform != null) transform.accept(progressDialog);
|
||||
if(cancelable){
|
||||
progressDialog.setOnCancelListener(dialog->cancel());
|
||||
}
|
||||
|
||||
@@ -162,6 +162,8 @@ public class PushSubscriptionManager{
|
||||
@Override
|
||||
public void onSuccess(PushSubscription result){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
result.serverKey=result.serverKey.replace('/','_');
|
||||
result.serverKey=result.serverKey.replace('+','-');
|
||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
||||
|
||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||
|
||||
@@ -9,23 +9,31 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
public class StatusInteractionController{
|
||||
private final String accountID;
|
||||
private final boolean updateCounters;
|
||||
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
||||
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
||||
|
||||
public StatusInteractionController(String accountID){
|
||||
public StatusInteractionController(String accountID, boolean updateCounters) {
|
||||
this.accountID=accountID;
|
||||
this.updateCounters=updateCounters;
|
||||
}
|
||||
|
||||
public void setFavorited(Status status, boolean favorited){
|
||||
public StatusInteractionController(String accountID){
|
||||
this(accountID, true);
|
||||
}
|
||||
|
||||
public void setFavorited(Status status, boolean favorited, Consumer<Status> cb){
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
@@ -38,7 +46,9 @@ public class StatusInteractionController{
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
runningFavoriteRequests.remove(status.id);
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1);
|
||||
cb.accept(result);
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,24 +56,17 @@ public class StatusInteractionController{
|
||||
runningFavoriteRequests.remove(status.id);
|
||||
error.showToast(MastodonApp.context);
|
||||
status.favourited=!favorited;
|
||||
if(favorited)
|
||||
status.favouritesCount--;
|
||||
else
|
||||
status.favouritesCount++;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
cb.accept(status);
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
runningFavoriteRequests.put(status.id, req);
|
||||
status.favourited=favorited;
|
||||
if(favorited)
|
||||
status.favouritesCount++;
|
||||
else
|
||||
status.favouritesCount--;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
|
||||
public void setReblogged(Status status, boolean reblogged){
|
||||
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
@@ -71,12 +74,15 @@ public class StatusInteractionController{
|
||||
if(current!=null){
|
||||
current.cancel();
|
||||
}
|
||||
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged)
|
||||
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged, visibility)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
public void onSuccess(Status reblog){
|
||||
Status result = reblog.getContentStatus();
|
||||
runningReblogRequests.remove(status.id);
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1);
|
||||
cb.accept(result);
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,24 +90,21 @@ public class StatusInteractionController{
|
||||
runningReblogRequests.remove(status.id);
|
||||
error.showToast(MastodonApp.context);
|
||||
status.reblogged=!reblogged;
|
||||
if(reblogged)
|
||||
status.reblogsCount--;
|
||||
else
|
||||
status.reblogsCount++;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
cb.accept(status);
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
runningReblogRequests.put(status.id, req);
|
||||
status.reblogged=reblogged;
|
||||
if(reblogged)
|
||||
status.reblogsCount++;
|
||||
else
|
||||
status.reblogsCount--;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
|
||||
public void setBookmarked(Status status, boolean bookmarked){
|
||||
setBookmarked(status, bookmarked, r->{});
|
||||
}
|
||||
|
||||
public void setBookmarked(Status status, boolean bookmarked, Consumer<Status> cb){
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
@@ -114,7 +117,8 @@ public class StatusInteractionController{
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
runningBookmarkRequests.remove(status.id);
|
||||
E.post(new StatusCountersUpdatedEvent(result));
|
||||
cb.accept(result);
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,12 +126,13 @@ public class StatusInteractionController{
|
||||
runningBookmarkRequests.remove(status.id);
|
||||
error.showToast(MastodonApp.context);
|
||||
status.bookmarked=!bookmarked;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
cb.accept(status);
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
runningBookmarkRequests.put(status.id, req);
|
||||
status.bookmarked=bookmarked;
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.joinmastodon.android.api.requests.accounts;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
public class SetPrivateNote extends MastodonAPIRequest<Relationship>{
|
||||
public SetPrivateNote(String id, String comment){
|
||||
super(MastodonAPIRequest.HttpMethod.POST, "/accounts/"+id+"/note", Relationship.class);
|
||||
Request req = new Request(comment);
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public String comment;
|
||||
public Request(String comment){
|
||||
this.comment=comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.announcements;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
|
||||
public class DismissAnnouncement extends MastodonAPIRequest<Object>{
|
||||
public DismissAnnouncement(String id){
|
||||
super(HttpMethod.POST, "/announcements/" + id + "/dismiss", Object.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.joinmastodon.android.api.requests.announcements;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetAnnouncements extends MastodonAPIRequest<List<Announcement>> {
|
||||
public GetAnnouncements(boolean withDismissed) {
|
||||
super(MastodonAPIRequest.HttpMethod.GET, "/announcements", new TypeToken<>(){});
|
||||
addQueryParameter("with_dismissed", withDismissed ? "true" : "false");
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,15 @@ public class GetInstance extends MastodonAPIRequest<Instance>{
|
||||
public GetInstance(){
|
||||
super(HttpMethod.GET, "/instance", Instance.class);
|
||||
}
|
||||
|
||||
public static class V2 extends MastodonAPIRequest<Instance.V2>{
|
||||
public V2(){
|
||||
super(HttpMethod.GET, "/instance", Instance.V2.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPathPrefix() {
|
||||
return "/api/v2";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import java.util.List;
|
||||
|
||||
public class AddList extends MastodonAPIRequest<Object> {
|
||||
public AddList(String listName){
|
||||
super(HttpMethod.POST, "/lists", Object.class);
|
||||
Request req = new Request();
|
||||
req.title = listName;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
public static class Request{
|
||||
public String title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
|
||||
public class CreateList extends MastodonAPIRequest<ListTimeline> {
|
||||
public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||
super(HttpMethod.POST, "/lists", ListTimeline.class);
|
||||
Request req = new Request();
|
||||
req.title = title;
|
||||
req.repliesPolicy = repliesPolicy;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
public static class Request {
|
||||
public String title;
|
||||
public ListTimeline.RepliesPolicy repliesPolicy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
|
||||
public class DeleteList extends MastodonAPIRequest<Object> {
|
||||
public DeleteList(String id) {
|
||||
super(HttpMethod.DELETE, "/lists/" + id, Object.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import java.util.List;
|
||||
|
||||
public class EditListName extends MastodonAPIRequest<Object> {
|
||||
public EditListName(String newListName, String listId){
|
||||
super(HttpMethod.PUT, "/lists/"+listId, Object.class);
|
||||
Request req = new Request();
|
||||
req.title = newListName;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
public static class Request{
|
||||
public String title;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import java.util.List;
|
||||
|
||||
public class RemoveList extends MastodonAPIRequest<Object> {
|
||||
public RemoveList(String listId){
|
||||
super(HttpMethod.DELETE, "/lists/"+listId, Object.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.joinmastodon.android.api.requests.lists;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
|
||||
public class UpdateList extends MastodonAPIRequest<ListTimeline> {
|
||||
public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
||||
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
|
||||
CreateList.Request req = new CreateList.Request();
|
||||
req.title = title;
|
||||
req.repliesPolicy = repliesPolicy;
|
||||
setRequestBody(req);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.ApiUtils;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class DismissNotification extends MastodonAPIRequest<Object>{
|
||||
public DismissNotification(String id){
|
||||
super(HttpMethod.POST, "/notifications/" + (id != null ? id + "/dismiss" : "clear"), Object.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,9 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public String clientName="Megalodon";
|
||||
public String clientName="Moshidon";
|
||||
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
||||
public String scopes=AccountSessionManager.SCOPE;
|
||||
public String website="https://sk22.github.io/megalodon";
|
||||
public String website="https://github.com/LucasGGamerM/moshidon";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
@@ -9,12 +10,29 @@ 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 Instant getDraftInstant() {
|
||||
// returns an instant between 9999-01-01 00:00:00 and 9999-12-31 23:59:59
|
||||
// yes, this is a weird implementation for something that hardly matters
|
||||
return DRAFTS_AFTER_INSTANT.plusMillis(1 + (long) (System.currentTimeMillis() * draftFactor));
|
||||
}
|
||||
|
||||
public CreateStatus(CreateStatus.Request req, String uuid){
|
||||
super(HttpMethod.POST, "/statuses", Status.class);
|
||||
setRequestBody(req);
|
||||
addHeader("Idempotency-Key", uuid);
|
||||
}
|
||||
|
||||
public static class Scheduled extends MastodonAPIRequest<ScheduledStatus>{
|
||||
public Scheduled(CreateStatus.Request req, String uuid){
|
||||
super(HttpMethod.POST, "/statuses", ScheduledStatus.class);
|
||||
setRequestBody(req);
|
||||
addHeader("Idempotency-Key", uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Request{
|
||||
public String status;
|
||||
public List<String> mediaIds;
|
||||
|
||||
@@ -7,4 +7,10 @@ public class DeleteStatus extends MastodonAPIRequest<Status>{
|
||||
public DeleteStatus(String id){
|
||||
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
|
||||
}
|
||||
|
||||
public static class Scheduled extends MastodonAPIRequest<Object> {
|
||||
public Scheduled(String id) {
|
||||
super(HttpMethod.DELETE, "/scheduled_statuses/"+id, Object.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
|
||||
public class GetScheduledStatuses extends HeaderPaginationRequest<ScheduledStatus>{
|
||||
public GetScheduledStatuses(String maxID, int limit){
|
||||
super(HttpMethod.GET, "/scheduled_statuses", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,17 @@ package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
public class SetStatusReblogged extends MastodonAPIRequest<Status>{
|
||||
public SetStatusReblogged(String id, boolean reblogged){
|
||||
public SetStatusReblogged(String id, boolean reblogged, StatusPrivacy visibility){
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class);
|
||||
setRequestBody(new Object());
|
||||
Request req = new Request();
|
||||
req.visibility = visibility;
|
||||
setRequestBody(req);
|
||||
}
|
||||
|
||||
public static class Request {
|
||||
public StatusPrivacy visibility;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.TranslatedStatus;
|
||||
|
||||
public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> {
|
||||
public TranslateStatus(String id) {
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class);
|
||||
setRequestBody(new Object());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.joinmastodon.android.api.requests.tags;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GetFollowedHashtags extends HeaderPaginationRequest<Hashtag> {
|
||||
public GetFollowedHashtags(String maxID, String minID, int limit, String sinceID){
|
||||
super(HttpMethod.GET, "/followed_tags", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(minID!=null)
|
||||
addQueryParameter("min_id", minID);
|
||||
if(sinceID!=null)
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import org.joinmastodon.android.model.Status;
|
||||
import java.util.List;
|
||||
|
||||
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
||||
public GetTrendingStatuses(int limit){
|
||||
public GetTrendingStatuses(int offset, int limit){
|
||||
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(offset>0)
|
||||
addQueryParameter("offset", ""+offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.joinmastodon.android.api.session;
|
||||
|
||||
public class AccountActivationInfo{
|
||||
public String email;
|
||||
public long lastEmailConfirmationResend;
|
||||
|
||||
public AccountActivationInfo(String email, long lastEmailConfirmationResend){
|
||||
this.email=email;
|
||||
this.lastEmailConfirmationResend=lastEmailConfirmationResend;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import org.joinmastodon.android.api.StatusInteractionController;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Application;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
|
||||
@@ -28,17 +29,20 @@ public class AccountSession{
|
||||
public long filtersLastUpdated;
|
||||
public List<Filter> wordFilters=new ArrayList<>();
|
||||
public String pushAccountID;
|
||||
public Preferences preferences;
|
||||
public AccountActivationInfo activationInfo;
|
||||
private transient MastodonAPIController apiController;
|
||||
private transient StatusInteractionController statusInteractionController;
|
||||
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
||||
private transient CacheController cacheController;
|
||||
private transient PushSubscriptionManager pushSubscriptionManager;
|
||||
|
||||
AccountSession(Token token, Account self, Application app, String domain, boolean activated){
|
||||
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
|
||||
this.token=token;
|
||||
this.self=self;
|
||||
this.domain=domain;
|
||||
this.app=app;
|
||||
this.activated=activated;
|
||||
this.activationInfo=activationInfo;
|
||||
infoLastUpdated=System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@@ -48,6 +52,10 @@ public class AccountSession{
|
||||
return domain+"_"+self.id;
|
||||
}
|
||||
|
||||
public String getFullUsername() {
|
||||
return "@"+self.username+"@"+domain;
|
||||
}
|
||||
|
||||
public MastodonAPIController getApiController(){
|
||||
if(apiController==null)
|
||||
apiController=new MastodonAPIController(this);
|
||||
@@ -60,6 +68,12 @@ public class AccountSession{
|
||||
return statusInteractionController;
|
||||
}
|
||||
|
||||
public StatusInteractionController getRemoteStatusInteractionController(){
|
||||
if(remoteStatusInteractionController==null)
|
||||
remoteStatusInteractionController=new StatusInteractionController(getID(), false);
|
||||
return remoteStatusInteractionController;
|
||||
}
|
||||
|
||||
public CacheController getCacheController(){
|
||||
if(cacheController==null)
|
||||
cacheController=new CacheController(getID());
|
||||
|
||||
@@ -13,8 +13,6 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
@@ -22,6 +20,7 @@ import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
|
||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
@@ -34,6 +33,7 @@ import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiCategory;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
|
||||
import java.io.File;
|
||||
@@ -61,7 +61,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
public class AccountSessionManager{
|
||||
private static final String TAG="AccountSessionManager";
|
||||
public static final String SCOPE="read write follow push";
|
||||
public static final String REDIRECT_URI="megalodon-android-auth://callback";
|
||||
public static final String REDIRECT_URI="moshidon-android-auth://callback";
|
||||
|
||||
private static final AccountSessionManager instance=new AccountSessionManager();
|
||||
|
||||
@@ -100,13 +100,13 @@ public class AccountSessionManager{
|
||||
maybeUpdateShortcuts();
|
||||
}
|
||||
|
||||
public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){
|
||||
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
||||
instances.put(instance.uri, instance);
|
||||
AccountSession session=new AccountSession(token, self, app, instance.uri, active);
|
||||
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
|
||||
sessions.put(session.getID(), session);
|
||||
lastActiveAccountID=session.getID();
|
||||
writeAccountsFile();
|
||||
updateInstanceEmojis(instance, instance.uri);
|
||||
updateMoreInstanceInfo(instance, instance.uri);
|
||||
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||
}
|
||||
@@ -211,7 +211,7 @@ public class AccountSessionManager{
|
||||
.path("/oauth/authorize")
|
||||
.appendQueryParameter("response_type", "code")
|
||||
.appendQueryParameter("client_id", result.clientId)
|
||||
.appendQueryParameter("redirect_uri", "megalodon-android-auth://callback")
|
||||
.appendQueryParameter("redirect_uri", "moshidon-android-auth://callback")
|
||||
.appendQueryParameter("scope", SCOPE)
|
||||
.build();
|
||||
|
||||
@@ -248,12 +248,13 @@ public class AccountSessionManager{
|
||||
HashSet<String> domains=new HashSet<>();
|
||||
for(AccountSession session:sessions.values()){
|
||||
domains.add(session.domain.toLowerCase());
|
||||
if(now-session.infoLastUpdated>24L*3600_000L){
|
||||
updateSessionLocalInfo(session);
|
||||
}
|
||||
if(now-session.filtersLastUpdated>3600_000L){
|
||||
updateSessionWordFilters(session);
|
||||
}
|
||||
// if(now-session.infoLastUpdated>24L*3600_000L){
|
||||
updateSessionPreferences(session);
|
||||
updateSessionLocalInfo(session);
|
||||
// }
|
||||
// if(now-session.filtersLastUpdated>3600_000L){
|
||||
updateSessionWordFilters(session);
|
||||
// }
|
||||
}
|
||||
if(loadedInstances){
|
||||
maybeUpdateCustomEmojis(domains);
|
||||
@@ -263,10 +264,10 @@ public class AccountSessionManager{
|
||||
private void maybeUpdateCustomEmojis(Set<String> domains){
|
||||
long now=System.currentTimeMillis();
|
||||
for(String domain:domains){
|
||||
Long lastUpdated=instancesLastUpdated.get(domain);
|
||||
if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
||||
updateInstanceInfo(domain);
|
||||
}
|
||||
// Long lastUpdated=instancesLastUpdated.get(domain);
|
||||
// if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
||||
updateInstanceInfo(domain);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +289,18 @@ public class AccountSessionManager{
|
||||
.exec(session.getID());
|
||||
}
|
||||
|
||||
private void updateSessionPreferences(AccountSession session){
|
||||
new GetPreferences().setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Preferences preferences) {
|
||||
session.preferences=preferences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {}
|
||||
}).exec(session.getID());
|
||||
}
|
||||
|
||||
private void updateSessionWordFilters(AccountSession session){
|
||||
new GetWordFilters()
|
||||
.setCallback(new Callback<>(){
|
||||
@@ -312,7 +325,7 @@ public class AccountSessionManager{
|
||||
@Override
|
||||
public void onSuccess(Instance instance){
|
||||
instances.put(domain, instance);
|
||||
updateInstanceEmojis(instance, domain);
|
||||
updateMoreInstanceInfo(instance, domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -323,6 +336,21 @@ public class AccountSessionManager{
|
||||
.execNoAuth(domain);
|
||||
}
|
||||
|
||||
public void updateMoreInstanceInfo(Instance instance, String domain) {
|
||||
new GetInstance.V2().setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Instance.V2 v2) {
|
||||
if (instance != null) instance.v2 = v2;
|
||||
updateInstanceEmojis(instance, domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse errorResponse) {
|
||||
updateInstanceEmojis(instance, domain);
|
||||
}
|
||||
}).execNoAuth(instance.uri);
|
||||
}
|
||||
|
||||
private void updateInstanceEmojis(Instance instance, String domain){
|
||||
new GetCustomEmojis()
|
||||
.setCallback(new Callback<>(){
|
||||
@@ -398,6 +426,10 @@ public class AccountSessionManager{
|
||||
return instances.get(domain);
|
||||
}
|
||||
|
||||
public Instance getInstanceInfoForAccount(String account) {
|
||||
return AccountSessionManager.getInstance().getInstanceInfo(instance.getAccount(account).domain);
|
||||
}
|
||||
|
||||
public void updateAccountInfo(String id, Account account){
|
||||
AccountSession session=getAccount(id);
|
||||
session.self=account;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
|
||||
public class ScheduledStatusCreatedEvent {
|
||||
public final ScheduledStatus scheduledStatus;
|
||||
public final String accountID;
|
||||
|
||||
public ScheduledStatusCreatedEvent(ScheduledStatus scheduledStatus, String accountID){
|
||||
this.scheduledStatus = scheduledStatus;
|
||||
this.accountID=accountID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
|
||||
public class ScheduledStatusDeletedEvent{
|
||||
public final String id;
|
||||
public final String accountID;
|
||||
|
||||
public ScheduledStatusDeletedEvent(String id, String accountID){
|
||||
this.id=id;
|
||||
this.accountID=accountID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.PaginatedList;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class AnnouncementsFragment extends BaseStatusListFragment<Announcement> {
|
||||
private Instance instance;
|
||||
private AccountSession session;
|
||||
private List<String> unreadIDs = null;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.sk_announcements);
|
||||
session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||
instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Announcement a) {
|
||||
if(TextUtils.isEmpty(a.content)) return List.of();
|
||||
Account instanceUser = new Account();
|
||||
instanceUser.id = instanceUser.acct = instanceUser.username = session.domain;
|
||||
instanceUser.displayName = instance.title;
|
||||
instanceUser.url = "https://"+session.domain+"/about";
|
||||
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
|
||||
instanceUser.emojis = List.of();
|
||||
Status fakeStatus = a.toStatus();
|
||||
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
|
||||
textItem.textSelectable = true;
|
||||
return List.of(
|
||||
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
|
||||
textItem
|
||||
);
|
||||
}
|
||||
|
||||
public void onMarkAsRead(String id) {
|
||||
if (unreadIDs == null) return;
|
||||
unreadIDs.remove(id);
|
||||
if (unreadIDs.size() == 0) setResult(true, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(Announcement s) {}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id) {}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetAnnouncements(true)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Announcement> result){
|
||||
List<Announcement> unread = result.stream().filter(a -> !a.read).collect(toList());
|
||||
List<Announcement> read = result.stream().filter(a -> a.read).collect(toList());
|
||||
onDataLoaded(unread, true);
|
||||
onDataLoaded(read, false);
|
||||
unreadIDs = unread.stream().map(a -> a.id).collect(toList());
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
@@ -400,10 +400,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
public void onPollOptionClick(PollOptionStatusDisplayItem.Holder holder){
|
||||
Poll poll=holder.getItem().poll;
|
||||
Poll.Option option=holder.getItem().option;
|
||||
if(poll.multiple){
|
||||
if(poll.multiple || GlobalUserPreferences.voteButtonForSingleChoice){
|
||||
if(poll.selectedOptions==null)
|
||||
poll.selectedOptions=new ArrayList<>();
|
||||
if(poll.selectedOptions.contains(option)){
|
||||
boolean optionContained=poll.selectedOptions.contains(option);
|
||||
if(!poll.multiple) poll.selectedOptions.clear();
|
||||
if(optionContained){
|
||||
poll.selectedOptions.remove(option);
|
||||
holder.itemView.setSelected(false);
|
||||
}else{
|
||||
@@ -412,6 +414,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder vh=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(!poll.multiple && vh instanceof PollOptionStatusDisplayItem.Holder item){
|
||||
if (item != holder) item.itemView.setSelected(false);
|
||||
}
|
||||
if(vh instanceof PollFooterStatusDisplayItem.Holder footer){
|
||||
if(footer.getItemID().equals(holder.getItemID())){
|
||||
footer.rebind();
|
||||
@@ -670,7 +675,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
private int currentMediaHiddenLayoutsWidth=0;
|
||||
|
||||
{
|
||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted));
|
||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.disableDividers ? R.attr.colorWindowBackground : R.attr.colorPollVoted));
|
||||
dividerPaint.setStyle(Paint.Style.STROKE);
|
||||
dividerPaint.setStrokeWidth(V.dp(1));
|
||||
}
|
||||
@@ -782,7 +787,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
currentMediaHiddenLayoutsWidth=width;
|
||||
String title=getString(R.string.sensitive_content);
|
||||
TextPaint titlePaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
titlePaint.setColor(getResources().getColor(R.color.gray_50));
|
||||
titlePaint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorGray50));
|
||||
titlePaint.setTextSize(V.dp(22));
|
||||
titlePaint.setTypeface(mediumTypeface);
|
||||
mediaHiddenTitleLayout=StaticLayout.Builder.obtain(title, 0, title.length(), titlePaint, width)
|
||||
@@ -793,7 +798,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||
.build();
|
||||
TextPaint textPaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
textPaint.setColor(getResources().getColor(R.color.gray_200));
|
||||
textPaint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorGray200));
|
||||
textPaint.setTextSize(V.dp(16));
|
||||
String text=getString(R.string.sensitive_content_explain);
|
||||
mediaHiddenTextLayout=StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, width)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.follow_requests);
|
||||
setTitle(R.string.sk_follow_requests);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -302,7 +302,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
||||
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
||||
if (!rel.requested && !rel.followedBy && adapter != null) {
|
||||
data.remove(item);
|
||||
adapter.notifyItemRemoved(getBindingAdapterPosition());
|
||||
adapter.notifyItemRemoved(getLayoutPosition());
|
||||
} else {
|
||||
rebind();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop {
|
||||
private String nextMaxID;
|
||||
private String accountId;
|
||||
|
||||
public FollowedHashtagsFragment() {
|
||||
super(20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle args=getArguments();
|
||||
accountId=args.getString("account");
|
||||
setTitle(R.string.sk_hashtags_you_follow);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetFollowedHashtags(offset==0 ? null : nextMaxID, null, count, null)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
onDataLoaded(result, nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter() {
|
||||
return new HashtagsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scrollToTop() {
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
public HashtagViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new HashtagViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull HashtagViewHolder holder, int position) {
|
||||
holder.bind(data.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
|
||||
private class HashtagViewHolder extends BindableViewHolder<Hashtag> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title;
|
||||
|
||||
public HashtagViewHolder(){
|
||||
super(getActivity(), R.layout.item_text, list);
|
||||
title=findViewById(R.id.title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Hashtag item) {
|
||||
title.setText(item.name);
|
||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_number_symbol_24_regular), null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
UiUtils.openHashtagTimeline(getActivity(), accountId, item.name, item.following);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -117,6 +118,7 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '));
|
||||
}
|
||||
|
||||
private void onFabClick(View v){
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.app.NotificationManager;
|
||||
import android.graphics.Outline;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -57,7 +58,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.app_name);
|
||||
setTitle(R.string.sk_app_name);
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
setRetainInstance(true);
|
||||
@@ -202,6 +203,17 @@ 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(newFragment instanceof ScrollableToTop scrollable)
|
||||
scrollable.scrollToTop();
|
||||
searchFragment.selectSearch();
|
||||
return;
|
||||
}
|
||||
if(newFragment instanceof ScrollableToTop scrollable)
|
||||
scrollable.scrollToTop();
|
||||
return;
|
||||
}
|
||||
if(tab==currentTab && tab == R.id.tab_search){
|
||||
if(newFragment instanceof ScrollableToTop scrollable)
|
||||
scrollable.scrollToTop();
|
||||
return;
|
||||
@@ -222,7 +234,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
((NotificationsFragment) newFragment).loadData();
|
||||
// TODO make an interface?
|
||||
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
||||
nm.cancel(accountID, PushNotificationReceiver.NOTIFICATION_ID);
|
||||
for (StatusBarNotification notification : nm.getActiveNotifications()) {
|
||||
if (accountID.equals(notification.getTag())) {
|
||||
nm.cancel(accountID, notification.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +251,12 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
return true;
|
||||
}
|
||||
if(tab==R.id.tab_search){
|
||||
onTabSelected(R.id.tab_search);
|
||||
tabBar.selectTab(R.id.tab_search);
|
||||
searchFragment.selectSearch();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,18 +19,22 @@ import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import 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.R;
|
||||
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -43,12 +47,9 @@ import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -57,11 +58,14 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HomeTimelineFragment extends StatusListFragment{
|
||||
private static final int ANNOUNCEMENTS_RESULT = 654;
|
||||
|
||||
private ImageButton fab;
|
||||
private ImageView toolbarLogo;
|
||||
private Button toolbarShowNewPostsBtn;
|
||||
private boolean newPostsBtnShown;
|
||||
private AnimatorSet currentNewPostsAnim;
|
||||
private MenuItem announcements;
|
||||
|
||||
private String maxID;
|
||||
|
||||
@@ -77,6 +81,7 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
}
|
||||
|
||||
private List<Status> filterPosts(List<Status> items) {
|
||||
// This is the function I must use to solve the filters thing for real
|
||||
return items.stream().filter(i ->
|
||||
(GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
|
||||
(GlobalUserPreferences.showBoosts || i.reblog == null)
|
||||
@@ -106,6 +111,8 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
fab.setOnLongClickListener(v->UiUtils.pickAccountForCompose(getActivity(), accountID));
|
||||
|
||||
updateToolbarLogo();
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||
@Override
|
||||
@@ -125,16 +132,40 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
inflater.inflate(R.menu.home, menu);
|
||||
announcements = menu.findItem(R.id.announcements);
|
||||
|
||||
new GetAnnouncements(false).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(List<Announcement> result) {
|
||||
boolean hasUnread = result.stream().anyMatch(a -> !a.read);
|
||||
announcements.setIcon(hasUnread ? R.drawable.ic_announcements_24_badged : R.drawable.ic_fluent_megaphone_24_regular);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||
if (item.getItemId() == R.id.settings) Nav.go(getActivity(), SettingsFragment.class, args);
|
||||
if (item.getItemId() == R.id.announcements) {
|
||||
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean noMoreUnread, Bundle result){
|
||||
if (reqCode == ANNOUNCEMENTS_RESULT && noMoreUnread) {
|
||||
announcements.setIcon(R.drawable.ic_fluent_megaphone_24_regular);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
super.onConfigurationChanged(newConfig);
|
||||
@@ -187,10 +218,8 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
toAdd=result;
|
||||
}
|
||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||
if(!filters.isEmpty()){
|
||||
toAdd=toAdd.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList());
|
||||
}
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
||||
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
||||
if(!toAdd.isEmpty()){
|
||||
prependItems(toAdd, true);
|
||||
showNewPostsButton();
|
||||
@@ -264,18 +293,14 @@ public class HomeTimelineFragment extends StatusListFragment{
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
targetList.clear();
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||
outer:
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(s)){
|
||||
continue outer;
|
||||
}
|
||||
if(filterPredicate.test(s)){
|
||||
targetList.addAll(buildDisplayItems(s));
|
||||
insertedPosts.add(s);
|
||||
}
|
||||
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.
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.media.MediaRouter;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.lists.CreateList;
|
||||
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
@@ -23,6 +31,7 @@ import me.grishka.appkit.utils.V;
|
||||
public class ListTimelineFragment extends StatusListFragment {
|
||||
private String listID;
|
||||
private String listTitle;
|
||||
private ListTimeline.RepliesPolicy repliesPolicy;
|
||||
private ImageButton fab;
|
||||
|
||||
public ListTimelineFragment() {
|
||||
@@ -32,8 +41,11 @@ public class ListTimelineFragment extends StatusListFragment {
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
listID=getArguments().getString("listID");
|
||||
listTitle=getArguments().getString("listTitle");
|
||||
Bundle args = getArguments();
|
||||
listID = args.getString("listID");
|
||||
listTitle = args.getString("listTitle");
|
||||
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
|
||||
|
||||
setTitle(listTitle);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
@@ -41,8 +53,48 @@ public class ListTimelineFragment extends StatusListFragment {
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
// TODO: implement edit, delete
|
||||
// inflater.inflate(R.menu.list, menu);
|
||||
inflater.inflate(R.menu.list, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("listID", listID);
|
||||
if (item.getItemId() == R.id.edit) {
|
||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||
editor.applyList(listTitle, repliesPolicy);
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_edit_list_title)
|
||||
.setIcon(R.drawable.ic_fluent_people_list_28_regular)
|
||||
.setView(editor)
|
||||
.setPositiveButton(R.string.save, (d, which) -> {
|
||||
new UpdateList(listID, editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(ListTimeline list) {
|
||||
setTitle(list.title);
|
||||
listTitle = list.title;
|
||||
repliesPolicy = list.repliesPolicy;
|
||||
args.putString("listTitle", listTitle);
|
||||
args.putInt("repliesPolicy", repliesPolicy.ordinal());
|
||||
setResult(true, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountID);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||
.show();
|
||||
} else if (item.getItemId() == R.id.delete) {
|
||||
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
|
||||
args.putBoolean("deleted", true);
|
||||
setResult(true, args);
|
||||
Nav.finish(this);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +121,7 @@ public class ListTimelineFragment extends StatusListFragment {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID));
|
||||
}
|
||||
|
||||
private void onFabClick(View v){
|
||||
|
||||
@@ -6,9 +6,7 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -17,32 +15,37 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
||||
import org.joinmastodon.android.api.requests.lists.CreateList;
|
||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
||||
import org.joinmastodon.android.model.ListTimeline;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||
private static final int LIST_CHANGED_RESULT = 987;
|
||||
|
||||
private String accountId;
|
||||
private String profileAccountId;
|
||||
private String profileDisplayUsername;
|
||||
private HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||
private HashMap<String, Boolean> userInList = new HashMap<>();
|
||||
private int inProgress = 0;
|
||||
private ListsAdapter adapter;
|
||||
|
||||
public ListTimelinesFragment() {
|
||||
super(10);
|
||||
@@ -53,12 +56,14 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle args=getArguments();
|
||||
accountId=args.getString("account");
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
if(args.containsKey("profileAccount")){
|
||||
profileAccountId=args.getString("profileAccount");
|
||||
profileDisplayUsername=args.getString("profileDisplayUsername");
|
||||
setTitle(getString(R.string.lists_with_user, profileDisplayUsername));
|
||||
// setHasOptionsMenu(true);
|
||||
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
||||
} else {
|
||||
setTitle(R.string.sk_your_lists);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,20 +74,45 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
||||
loadData();
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
// Button saveButton=new Button(getActivity());
|
||||
// saveButton.setText(R.string.save);
|
||||
// saveButton.setOnClickListener(this::onSaveClick);
|
||||
// LinearLayout wrap=new LinearLayout(getActivity());
|
||||
// wrap.setOrientation(LinearLayout.HORIZONTAL);
|
||||
// wrap.addView(saveButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
// wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
|
||||
// wrap.setClipToPadding(false);
|
||||
// MenuItem item=menu.add(R.string.save);
|
||||
// item.setActionView(wrap);
|
||||
// item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
// }
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_list, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.create) {
|
||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_create_list_title)
|
||||
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
||||
.setView(editor)
|
||||
.setPositiveButton(R.string.sk_create, (d, which) -> {
|
||||
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(ListTimeline list) {
|
||||
saveListMembership(list.id, true);
|
||||
data.add(0, list);
|
||||
adapter.notifyItemRangeInserted(0, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountId);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||
.show();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void saveListMembership(String listId, boolean isMember) {
|
||||
userInList.put(listId, isMember);
|
||||
@@ -127,8 +157,29 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter() {
|
||||
return new ListsAdapter();
|
||||
public void onFragmentResult(int reqCode, boolean listChanged, Bundle result){
|
||||
if (reqCode == LIST_CHANGED_RESULT && listChanged) {
|
||||
String listID = result.getString("listID");
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
ListTimeline item = data.get(i);
|
||||
if (item.id.equals(listID)) {
|
||||
if (result.getBoolean("deleted")) {
|
||||
data.remove(i);
|
||||
adapter.notifyItemRemoved(i);
|
||||
} else {
|
||||
item.title = result.getString("listTitle", item.title);
|
||||
item.repliesPolicy = ListTimeline.RepliesPolicy.values()[result.getInt("repliesPolicy")];
|
||||
adapter.notifyItemChanged(i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
|
||||
return adapter = new ListsAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -159,7 +210,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
||||
private final CheckBox listToggle;
|
||||
|
||||
public ListViewHolder(){
|
||||
super(getActivity(), R.layout.item_list_timeline, list);
|
||||
super(getActivity(), R.layout.item_text, list);
|
||||
title=findViewById(R.id.title);
|
||||
listToggle=findViewById(R.id.list_toggle);
|
||||
}
|
||||
@@ -167,8 +218,10 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
||||
@Override
|
||||
public void onBind(ListTimeline item) {
|
||||
title.setText(item.title);
|
||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_list_24_regular), null, null, null);
|
||||
if (profileAccountId != null) {
|
||||
Boolean checked = userInList.get(item.id);
|
||||
listToggle.setVisibility(View.VISIBLE);
|
||||
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
||||
listToggle.setOnClickListener(this::onClickToggle);
|
||||
} else {
|
||||
@@ -182,7 +235,12 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
UiUtils.openListTimeline(getActivity(), accountId, item);
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountId);
|
||||
args.putString("listID", item.id);
|
||||
args.putString("listTitle", item.title);
|
||||
args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
|
||||
Nav.goForResult(getActivity(), ListTimelineFragment.class, args, LIST_CHANGED_RESULT, ListTimelinesFragment.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||
@@ -73,15 +74,26 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||
inflater.inflate(R.menu.notifications, menu);
|
||||
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
|
||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() != R.id.follow_requests) return false;
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), FollowRequestsListFragment.class, args);
|
||||
return true;
|
||||
if (item.getItemId() == R.id.follow_requests) {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), FollowRequestsListFragment.class, args);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.clear_notifications) {
|
||||
UiUtils.confirmDeleteNotification(getActivity(), accountID, null, ()->{
|
||||
for (int i = 0; i < tabViews.length; i++) {
|
||||
getFragmentForPage(i).reload();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,6 +121,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||
pager.setAdapter(new DiscoverPagerAdapter());
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
@@ -10,7 +12,6 @@ import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
@@ -78,9 +79,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
case FAVORITE -> getString(R.string.user_favorited);
|
||||
case POLL -> getString(R.string.poll_ended);
|
||||
};
|
||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText) : null;
|
||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText, n, null) : null;
|
||||
if(n.status!=null){
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null);
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n);
|
||||
if(titleItem!=null){
|
||||
for(StatusDisplayItem item:items){
|
||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
||||
@@ -210,7 +211,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNotification(Notification n){
|
||||
public void removeNotification(Notification n){
|
||||
data.remove(n);
|
||||
preloadedData.remove(n);
|
||||
int index=-1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
@@ -12,11 +13,23 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
@@ -26,11 +39,8 @@ import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
@@ -46,6 +56,11 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
private static final int MAX_FIELDS=4;
|
||||
|
||||
public UsableRecyclerView list;
|
||||
public FrameLayout noteWrap;
|
||||
public EditText noteEdit;
|
||||
private String accountID;
|
||||
private String profileAccountID;
|
||||
private String note;
|
||||
private List<AccountField> fields=Collections.emptyList();
|
||||
private AboutAdapter adapter;
|
||||
private Paint dividerPaint=new Paint();
|
||||
@@ -64,11 +79,49 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setNote(String note, String accountID, String profileAccountID){
|
||||
this.note=note;
|
||||
this.accountID=accountID;
|
||||
this.profileAccountID=profileAccountID;
|
||||
// noteWrap.setVisibility(View.VISIBLE);
|
||||
// noteEdit.setVisibility(View.VISIBLE);
|
||||
// noteEdit.setText(note);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
list=new UsableRecyclerView(getActivity());
|
||||
list.setId(R.id.list);
|
||||
View view = inflater.inflate(R.layout.fragment_profile_about, null);
|
||||
|
||||
noteEdit = view.findViewById(R.id.note_edit);
|
||||
noteWrap = view.findViewById(R.id.note_edit_wrap);
|
||||
ImageButton noteEditConfirm = view.findViewById(R.id.note_edit_confirm);
|
||||
|
||||
|
||||
noteEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
noteEditConfirm.setVisibility(View.VISIBLE);
|
||||
noteEditConfirm.animate()
|
||||
.alpha(1.0f)
|
||||
.setDuration(700);
|
||||
} else {
|
||||
noteEditConfirm.animate()
|
||||
.alpha(0.0f)
|
||||
.setDuration(700);
|
||||
noteEditConfirm.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
noteEditConfirm.setOnClickListener((v -> {
|
||||
if (!noteEdit.getText().toString().trim().equals(note)) {
|
||||
savePrivateNote();
|
||||
}
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
|
||||
noteEdit.clearFocus();
|
||||
}));
|
||||
|
||||
list = view.findViewById(R.id.list);
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
list.setDrawSelectorOnTop(true);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
@@ -95,8 +148,20 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
}
|
||||
}
|
||||
});
|
||||
return list;
|
||||
return view;
|
||||
}
|
||||
private void savePrivateNote(){
|
||||
new SetPrivateNote(profileAccountID, noteEdit.getText().toString()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Relationship result) {}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse result) {
|
||||
Toast.makeText(getActivity(), getString(R.string.sk_personal_note_update_failed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
|
||||
public void enterEditMode(List<AccountField> editableFields){
|
||||
isInEditMode=true;
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import static android.content.Context.CLIPBOARD_SERVICE;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Outline;
|
||||
@@ -31,17 +27,23 @@ import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||
@@ -49,6 +51,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
|
||||
@@ -80,10 +83,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -120,6 +119,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
private TextView followsYouView;
|
||||
|
||||
public FrameLayout noteWrap;
|
||||
public EditText noteEdit;
|
||||
private String note;
|
||||
private Account account;
|
||||
private String accountID;
|
||||
private Relationship relationship;
|
||||
@@ -161,6 +163,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
}
|
||||
|
||||
private String getPrefilledText() {
|
||||
return account == null || AccountSessionManager.getInstance().isSelf(accountID, account)
|
||||
? null : '@'+account.acct+' ';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
@@ -183,6 +190,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followingCount=content.findViewById(R.id.following_count);
|
||||
followingLabel=content.findViewById(R.id.following_label);
|
||||
followingBtn=content.findViewById(R.id.following_btn);
|
||||
|
||||
postsCount=content.findViewById(R.id.posts_count);
|
||||
postsLabel=content.findViewById(R.id.posts_label);
|
||||
postsBtn=content.findViewById(R.id.posts_btn);
|
||||
@@ -199,6 +207,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
fab=content.findViewById(R.id.fab);
|
||||
followsYouView=content.findViewById(R.id.follows_you);
|
||||
|
||||
noteEdit = content.findViewById(R.id.note_edit);
|
||||
noteWrap = content.findViewById(R.id.note_edit_wrap);
|
||||
Button noteEditConfirm = content.findViewById(R.id.note_edit_confirm);
|
||||
|
||||
avatar.setOutlineProvider(new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
@@ -207,6 +219,31 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
});
|
||||
avatar.setClipToOutline(true);
|
||||
|
||||
noteEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
fab.setVisibility(View.GONE);
|
||||
noteEditConfirm.setVisibility(View.VISIBLE);
|
||||
noteEditConfirm.animate()
|
||||
.alpha(1.0f)
|
||||
.setDuration(700);
|
||||
} else {
|
||||
fab.setVisibility(View.VISIBLE);
|
||||
noteEditConfirm.animate()
|
||||
.alpha(0.0f)
|
||||
.setDuration(700);
|
||||
noteEditConfirm.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
noteEditConfirm.setOnClickListener((v -> {
|
||||
if (!noteEdit.getText().toString().trim().equals(note)) {
|
||||
savePrivateNote();
|
||||
}
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
|
||||
noteEdit.clearFocus();
|
||||
}));
|
||||
|
||||
FrameLayout sizeWrapper=new FrameLayout(getActivity()){
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
|
||||
@@ -234,6 +271,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
|
||||
pager.setOffscreenPageLimit(5);
|
||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||
pager.setAdapter(new ProfilePagerAdapter());
|
||||
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
||||
|
||||
@@ -249,7 +287,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.posts;
|
||||
case 1 -> R.string.posts_and_replies;
|
||||
case 2 -> R.string.pinned_posts;
|
||||
case 2 -> R.string.sk_pinned_posts;
|
||||
case 3 -> R.string.media;
|
||||
case 4 -> R.string.profile_about;
|
||||
default -> throw new IllegalStateException();
|
||||
@@ -271,6 +309,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
cover.setOnClickListener(this::onCoverClick);
|
||||
refreshLayout.setOnRefreshListener(this);
|
||||
fab.setOnClickListener(this::onFabClick);
|
||||
fab.setOnLongClickListener(v->UiUtils.pickAccountForCompose(getActivity(), accountID, getPrefilledText()));
|
||||
|
||||
if(loaded){
|
||||
bindHeaderView();
|
||||
@@ -284,20 +323,36 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||
|
||||
username.setOnLongClickListener(v->{
|
||||
String username=account.acct;
|
||||
if(!username.contains("@")){
|
||||
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||
}
|
||||
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, "@"+username));
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||
Toast.makeText(getActivity(), R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||
String usernameString=account.acct;
|
||||
if(!usernameString.contains("@")){
|
||||
usernameString+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||
}
|
||||
UiUtils.copyText(username, '@'+usernameString);
|
||||
return true;
|
||||
});
|
||||
|
||||
return sizeWrapper;
|
||||
}
|
||||
|
||||
public void setNote(String note){
|
||||
this.note=note;
|
||||
noteWrap.setVisibility(View.VISIBLE);
|
||||
noteEdit.setVisibility(View.VISIBLE);
|
||||
noteEdit.setText(note);
|
||||
}
|
||||
|
||||
private void savePrivateNote(){
|
||||
new SetPrivateNote(profileAccountID, noteEdit.getText().toString()).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Relationship result) {}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse result) {
|
||||
Toast.makeText(getActivity(), getString(R.string.sk_personal_note_update_failed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(){
|
||||
currentRequest=new GetAccountByID(profileAccountID)
|
||||
@@ -445,6 +500,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||
|
||||
|
||||
if(account.locked){
|
||||
ssb=new SpannableStringBuilder("@");
|
||||
ssb.append(account.acct);
|
||||
@@ -458,6 +514,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
lock.setTint(username.getCurrentTextColor());
|
||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BASELINE), 0);
|
||||
username.setText(ssb);
|
||||
}else if(account.bot){
|
||||
ssb=new SpannableStringBuilder("@");
|
||||
ssb.append(account.acct);
|
||||
if(isSelf){
|
||||
ssb.append('@');
|
||||
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
|
||||
}
|
||||
ssb.append(" ");
|
||||
Drawable botIcon=username.getResources().getDrawable(R.drawable.ic_bot, getActivity().getTheme()).mutate();
|
||||
botIcon.setBounds(0, 0, botIcon.getIntrinsicWidth(), botIcon.getIntrinsicHeight());
|
||||
botIcon.setTint(username.getCurrentTextColor());
|
||||
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(botIcon, ImageSpan.ALIGN_BASELINE), 0);
|
||||
username.setText(ssb);
|
||||
}else{
|
||||
// noinspection SetTextI18n
|
||||
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
||||
@@ -469,6 +538,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
bio.setVisibility(View.VISIBLE);
|
||||
bio.setText(parsedBio);
|
||||
}
|
||||
|
||||
|
||||
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
||||
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
||||
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
||||
@@ -548,18 +619,32 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(relationship==null && !isOwnProfile)
|
||||
return;
|
||||
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
||||
if(isOwnProfile){
|
||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.manage_user_lists, R.id.bookmarks, R.id.followed_hashtags, R.id.favorites, R.id.scheduled, R.id.share);
|
||||
}else{
|
||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags, R.id.favorites, R.id.scheduled);
|
||||
}
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
||||
if(isOwnProfile)
|
||||
return;
|
||||
|
||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
||||
MenuItem mute = menu.findItem(R.id.mute);
|
||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
||||
|
||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
||||
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
|
||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
||||
if(relationship.following) {
|
||||
menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
|
||||
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
|
||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
||||
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
|
||||
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||
}else {
|
||||
menu.findItem(R.id.hide_boosts).setVisible(false);
|
||||
menu.findItem(R.id.manage_user_lists).setVisible(false);
|
||||
}
|
||||
if(!account.isLocal())
|
||||
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||
@@ -579,6 +664,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
confirmToggleMuted();
|
||||
}else if(id==R.id.block){
|
||||
confirmToggleBlocked();
|
||||
}else if(id==R.id.soft_block){
|
||||
confirmSoftBlockUser();
|
||||
}else if(id==R.id.report){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -617,9 +704,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}else if(id==R.id.manage_user_lists){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("profileAccount", profileAccountID);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
if (!isOwnProfile) {
|
||||
args.putString("profileAccount", profileAccountID);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
}
|
||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||
}else if(id==R.id.followed_hashtags){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), FollowedHashtagsFragment.class, args);
|
||||
}else if(id==R.id.scheduled){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), ScheduledStatusListFragment.class, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -658,7 +755,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
||||
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
||||
notifyButton.setSelected(relationship.notifying);
|
||||
if (getActivity() != null) notifyButton.setContentDescription(getString(relationship.notifying ? R.string.user_post_notifications_on : R.string.user_post_notifications_off, '@'+account.username));
|
||||
if (!isOwnProfile) {
|
||||
setNote(relationship.note);
|
||||
aboutFragment.setNote(relationship.note, accountID, profileAccountID);
|
||||
}
|
||||
if (getActivity() != null) notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
||||
}
|
||||
|
||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||
@@ -875,6 +976,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
||||
}
|
||||
|
||||
private void confirmSoftBlockUser(){
|
||||
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
|
||||
}
|
||||
|
||||
private void updateRelationship(Relationship r){
|
||||
relationship=r;
|
||||
updateRelationship();
|
||||
@@ -882,6 +987,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(noteEdit.hasFocus()) {
|
||||
savePrivateNote();
|
||||
}
|
||||
if(isInEditMode){
|
||||
exitEditMode();
|
||||
return true;
|
||||
@@ -931,9 +1039,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private void onFabClick(View v){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
if(!AccountSessionManager.getInstance().isSelf(accountID, account)){
|
||||
args.putString("prefilledText", '@'+account.acct+' ');
|
||||
}
|
||||
if(getPrefilledText() != null) args.putString("prefilledText", getPrefilledText());
|
||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses;
|
||||
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
||||
private String nextMaxID;
|
||||
private ImageButton fab;
|
||||
private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
|
||||
|
||||
public ScheduledStatusListFragment() {
|
||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
E.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
E.unregister(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
setTitle(R.string.sk_unsent_posts);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
fab=view.findViewById(R.id.fab);
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
||||
fab.setOnClickListener(v -> Nav.go(getActivity(), ComposeFragment.class, args));
|
||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, args));
|
||||
if (getArguments().getBoolean("hide_fab", false)) fab.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(ScheduledStatus s) {}
|
||||
|
||||
@Override
|
||||
public void onItemClick(String id) {
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
ScheduledStatus scheduledStatus = getStatusByID(id);
|
||||
Status status = scheduledStatus.toStatus();
|
||||
args.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
|
||||
args.putParcelable("editStatus", Parcels.wrap(status));
|
||||
args.putString("sourceText", status.text);
|
||||
args.putString("sourceSpoiler", status.spoilerText);
|
||||
args.putBoolean("redraftStatus", true);
|
||||
setResult(true, null);
|
||||
|
||||
// closing this scheduled status list if another status list is opened from compose fragment
|
||||
Nav.goForResult(getActivity(), ComposeFragment.class, args, SCHEDULED_STATUS_LIST_OPENED, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentResult(int reqCode, boolean success, Bundle result) {
|
||||
if (reqCode == SCHEDULED_STATUS_LIST_OPENED && success && getActivity() != null) {
|
||||
Nav.finish(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<ScheduledStatus> result){
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
onDataLoaded(result, nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
// copied from StatusListFragment.java
|
||||
@Subscribe
|
||||
public void onScheduledStatusDeleted(ScheduledStatusDeletedEvent ev){
|
||||
if(!ev.accountID.equals(accountID)) return;
|
||||
ScheduledStatus status=getStatusByID(ev.id);
|
||||
if(status==null) return;
|
||||
removeStatus(status);
|
||||
}
|
||||
|
||||
// copied from StatusListFragment.java
|
||||
@Subscribe
|
||||
public void onScheduledStatusCreated(ScheduledStatusCreatedEvent ev){
|
||||
if(!ev.accountID.equals(accountID)) return;
|
||||
prependItems(Collections.singletonList(ev.scheduledStatus), true);
|
||||
scrollToTop();
|
||||
}
|
||||
|
||||
// copied from StatusListFragment.java
|
||||
protected void removeStatus(ScheduledStatus status){
|
||||
data.remove(status);
|
||||
preloadedData.remove(status);
|
||||
int index=-1;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
if(status.id.equals(displayItems.get(i).parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(index==-1)
|
||||
return;
|
||||
int lastIndex;
|
||||
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
||||
if(!displayItems.get(lastIndex).parentID.equals(status.id))
|
||||
break;
|
||||
}
|
||||
displayItems.subList(index, lastIndex).clear();
|
||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||
}
|
||||
|
||||
// copied from StatusListFragment.java
|
||||
protected ScheduledStatus getStatusByID(String id){
|
||||
for(ScheduledStatus s:data){
|
||||
if(s.id.equals(id)){
|
||||
return s;
|
||||
}
|
||||
}
|
||||
for(ScheduledStatus s:preloadedData){
|
||||
if(s.id.equals(id)){
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -9,18 +9,21 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
@@ -33,6 +36,7 @@ import com.squareup.otto.Subscribe;
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -42,12 +46,16 @@ import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
@@ -57,6 +65,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
@@ -71,10 +81,12 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
private NotificationPolicyItem notificationPolicyItem;
|
||||
private String accountID;
|
||||
private boolean needUpdateNotificationSettings;
|
||||
private boolean needAppRestart;
|
||||
private PushSubscription pushSubscription;
|
||||
|
||||
private ImageView themeTransitionWindowView;
|
||||
private TextItem checkForUpdateItem;
|
||||
private TextItem checkForUpdateItem, clearImageCacheItem;
|
||||
private ImageCache imageCache;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -82,8 +94,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
setRetainInstance(true);
|
||||
setTitle(R.string.settings);
|
||||
imageCache = ImageCache.getInstance(getActivity());
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||
String instanceName = UiUtils.getInstanceName(accountID);
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
@@ -96,13 +111,78 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
items.add(new HeaderItem(R.string.settings_theme));
|
||||
items.add(themeItem=new ThemeItem());
|
||||
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
||||
items.add(new SwitchItem(R.string.disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
|
||||
items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
|
||||
GlobalUserPreferences.disableMarquee=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new ColorPicker());
|
||||
items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
|
||||
GlobalUserPreferences.uniformNotificationIcon=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
|
||||
GlobalUserPreferences.reduceMotion=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.color_palettes);
|
||||
popupMenu.getMenu().findItem(R.id.m3_color).setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S);
|
||||
popupMenu.setOnMenuItemClickListener(SettingsFragment.this::onColorPreferenceClick);
|
||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
b.setOnClickListener(v->popupMenu.show());
|
||||
b.setText(switch(GlobalUserPreferences.color){
|
||||
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
||||
case PINK -> R.string.sk_color_palette_pink;
|
||||
case PURPLE -> R.string.sk_color_palette_purple;
|
||||
case GREEN -> R.string.sk_color_palette_green;
|
||||
case BLUE -> R.string.sk_color_palette_blue;
|
||||
case BROWN -> R.string.sk_color_palette_brown;
|
||||
case RED -> R.string.sk_color_palette_red;
|
||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
||||
case NORD -> R.string.sk_color_palette_nord;
|
||||
});
|
||||
}));
|
||||
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
|
||||
updatePublishText(b);
|
||||
if (GlobalUserPreferences.relocatePublishButton) {
|
||||
b.setOnClickListener(l -> {
|
||||
Toast.makeText(getActivity(), R.string.sk_disable_relocate_publish_button_to_enable_customization,
|
||||
Toast.LENGTH_LONG).show();
|
||||
});
|
||||
} else {
|
||||
b.setOnClickListener(l -> {
|
||||
FrameLayout inputWrap = new FrameLayout(getContext());
|
||||
EditText input = new EditText(getContext());
|
||||
input.setHint(R.string.publish);
|
||||
input.setText(GlobalUserPreferences.publishButtonText.trim());
|
||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
|
||||
input.setLayoutParams(params);
|
||||
inputWrap.addView(input);
|
||||
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap)
|
||||
.setPositiveButton(R.string.save, (d, which) -> {
|
||||
GlobalUserPreferences.publishButtonText = input.getText().toString().trim();
|
||||
GlobalUserPreferences.save();
|
||||
updatePublishText(b);
|
||||
})
|
||||
.setNeutralButton(R.string.clear, (d, which) -> {
|
||||
GlobalUserPreferences.publishButtonText = "";
|
||||
GlobalUserPreferences.save();
|
||||
updatePublishText(b);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {
|
||||
})
|
||||
.show();
|
||||
});}
|
||||
}));
|
||||
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_behavior));
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_federated_timeline, R.drawable.ic_fluent_earth_24_regular, GlobalUserPreferences.showFederatedTimeline, i->{
|
||||
GlobalUserPreferences.showFederatedTimeline=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||
GlobalUserPreferences.playGifs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -111,25 +191,50 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
GlobalUserPreferences.useCustomTabs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
|
||||
GlobalUserPreferences.showInteractionCounts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
||||
items.add(new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
||||
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_tabs_disable_swipe, R.drawable.ic_fluent_swipe_right_24_regular, GlobalUserPreferences.disableSwipe, i->{
|
||||
GlobalUserPreferences.disableSwipe=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
// items.add(new SwitchItem(R.string.sk_settings_show_differentiated_notification_icons, R.drawable.ic_ntf_logo, GlobalUserPreferences.showUniformPushNoticationIcons, this::onNotificationStyleChanged));
|
||||
items.add(new SwitchItem(R.string.sk_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{
|
||||
GlobalUserPreferences.disableDividers=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
// items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_delete_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
||||
// GlobalUserPreferences.enableDeleteNotifications=i.checked;
|
||||
// GlobalUserPreferences.save();
|
||||
// needAppRestart=true;
|
||||
// }));
|
||||
items.add(new SwitchItem(R.string.sk_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
|
||||
GlobalUserPreferences.relocatePublishButton=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
// items.add(new SwitchItem(R.string.sk_settings_hide_translate_in_timeline, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
||||
// GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
||||
// GlobalUserPreferences.save();
|
||||
// needAppRestart=true;
|
||||
// }));
|
||||
|
||||
items.add(new HeaderItem(R.string.home_timeline));
|
||||
items.add(new SwitchItem(R.string.settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||
GlobalUserPreferences.showReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
|
||||
GlobalUserPreferences.showBoosts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.settings_load_new_posts, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
||||
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
||||
GlobalUserPreferences.loadNewPosts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
@@ -140,23 +245,63 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i->onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked)));
|
||||
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i->onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked)));
|
||||
items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i->onNotificationsChanged(PushNotification.Type.REBLOG, i.checked)));
|
||||
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_at_symbol, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked)));
|
||||
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked)));
|
||||
items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_alert_24_regular, pushSubscription.alerts.status, i->onNotificationsChanged(PushNotification.Type.STATUS, i.checked)));
|
||||
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
|
||||
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_boring));
|
||||
items.add(new TextItem(R.string.settings_account, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit")));
|
||||
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
||||
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
||||
items.add(new HeaderItem(R.string.settings_account));
|
||||
items.add(new TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_posting, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/preferences/other"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_filters, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/filters"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
|
||||
|
||||
items.add(new RedHeaderItem(R.string.settings_spicy));
|
||||
items.add(new HeaderItem(instanceName));
|
||||
items.add(new TextItem(R.string.sk_settings_rules, ()->{
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}, R.drawable.ic_fluent_task_list_ltr_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_about_instance , ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_info_24_regular));
|
||||
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
|
||||
boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled;
|
||||
items.add(new SmallTextItem(getString(translationAvailable ?
|
||||
R.string.sk_settings_translation_availability_note_available :
|
||||
R.string.sk_settings_translation_availability_note_unavailable, instance.title)));
|
||||
|
||||
|
||||
items.add(new HeaderItem(R.string.sk_settings_about));
|
||||
// items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon"), R.drawable.ic_fluent_open_24_regular));
|
||||
// items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://ko-fi.com/xsk22"), R.drawable.ic_fluent_heart_24_regular));
|
||||
if (GithubSelfUpdater.needSelfUpdating()) {
|
||||
checkForUpdateItem = new TextItem(R.string.check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||
items.add(checkForUpdateItem);
|
||||
}
|
||||
items.add(new TextItem(R.string.settings_contribute_fork, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon")));
|
||||
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||
items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
|
||||
// items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||
clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), imageCache.getDiskCache().size(), true), this::clearImageCache, 0);
|
||||
items.add(clearImageCacheItem);
|
||||
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
|
||||
GlobalUserPreferences.recentLanguages.remove(accountID);
|
||||
GlobalUserPreferences.save();
|
||||
})));
|
||||
items.add(new TextItem(R.string.sk_clear_recent_emoji, ()-> {
|
||||
GlobalUserPreferences.recentEmojis.clear();
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
// items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||
|
||||
items.add(new FooterItem(getString(R.string.settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||
items.add(new FooterItem(getString(R.string.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||
}
|
||||
|
||||
private void updatePublishText(Button btn) {
|
||||
if (GlobalUserPreferences.publishButtonText.isBlank()) btn.setText(R.string.publish);
|
||||
else btn.setText(GlobalUserPreferences.publishButtonText);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -204,6 +349,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
||||
}
|
||||
if(needAppRestart){
|
||||
Intent intent = Intent.makeRestartActivityTask(MastodonApp.context.getPackageManager().getLaunchIntentForPackage(MastodonApp.context.getPackageName()).getComponent());
|
||||
MastodonApp.context.startActivity(intent);
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,12 +376,29 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
restartActivityToApplyNewTheme();
|
||||
}
|
||||
|
||||
private void onColorPreferenceClick(GlobalUserPreferences.ColorPreference color){
|
||||
GlobalUserPreferences.color=color;
|
||||
private boolean onColorPreferenceClick(MenuItem item){
|
||||
ColorPreference pref = null;
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == R.id.m3_color) pref = ColorPreference.MATERIAL3;
|
||||
else if (id == R.id.pink_color) pref = ColorPreference.PINK;
|
||||
else if (id == R.id.purple_color) pref = ColorPreference.PURPLE;
|
||||
else if (id == R.id.green_color) pref = ColorPreference.GREEN;
|
||||
else if (id == R.id.blue_color) pref = ColorPreference.BLUE;
|
||||
else if (id == R.id.brown_color) pref = ColorPreference.BROWN;
|
||||
else if (id == R.id.red_color) pref = ColorPreference.RED;
|
||||
else if (id == R.id.yellow_color) pref = ColorPreference.YELLOW;
|
||||
else if (id == R.id.nord_color) pref = ColorPreference.NORD;
|
||||
|
||||
if (pref == null) return false;
|
||||
|
||||
GlobalUserPreferences.color=pref;
|
||||
GlobalUserPreferences.save();
|
||||
restartActivityToApplyNewTheme();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void onTrueBlackThemeChanged(SwitchItem item){
|
||||
GlobalUserPreferences.trueBlackTheme=item.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -291,10 +458,17 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
case FOLLOW -> subscription.alerts.follow=enabled;
|
||||
case REBLOG -> subscription.alerts.reblog=enabled;
|
||||
case MENTION -> subscription.alerts.mention=subscription.alerts.poll=enabled;
|
||||
case STATUS -> subscription.alerts.status=enabled;
|
||||
}
|
||||
needUpdateNotificationSettings=true;
|
||||
}
|
||||
|
||||
private void onNotificationStyleChanged(SwitchItem item){
|
||||
GlobalUserPreferences.uniformNotificationIcon=item.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}
|
||||
|
||||
|
||||
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
|
||||
PushSubscription subscription=getPushSubscription();
|
||||
PushSubscription.Policy prevPolicy=subscription.policy;
|
||||
@@ -360,9 +534,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
private void clearImageCache(){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
Activity activity=getActivity();
|
||||
ImageCache.getInstance(getActivity()).clear();
|
||||
imageCache.clear();
|
||||
Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(clearImageCacheItem)) instanceof TextViewHolder tvh) {
|
||||
clearImageCacheItem.secondaryText = UiUtils.formatFileSize(getContext(), 0, true);
|
||||
tvh.rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
@@ -385,7 +563,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
|
||||
if (ev.state == GithubSelfUpdater.UpdateState.NO_UPDATE) {
|
||||
Toast.makeText(getActivity(), R.string.no_update_available, Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(getActivity(), R.string.sk_no_update_available, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,6 +578,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
this.text=getString(text);
|
||||
}
|
||||
|
||||
public HeaderItem(String text) {
|
||||
this.text=text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 0;
|
||||
@@ -434,6 +616,23 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
public class ButtonItem extends Item{
|
||||
private int text;
|
||||
private int icon;
|
||||
private Consumer<Button> buttonConsumer;
|
||||
|
||||
public ButtonItem(@StringRes int text, @DrawableRes int icon, Consumer<Button> buttonConsumer) {
|
||||
this.text = text;
|
||||
this.icon = icon;
|
||||
this.buttonConsumer = buttonConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorPicker extends Item{
|
||||
@Override
|
||||
public int getViewType(){
|
||||
@@ -457,19 +656,44 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private class TextItem extends Item{
|
||||
private class SmallTextItem extends Item {
|
||||
private String text;
|
||||
private Runnable onClick;
|
||||
private boolean loading;
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick) {
|
||||
this(text, onClick, false);
|
||||
public SmallTextItem(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick, boolean loading){
|
||||
@Override
|
||||
public int getViewType() {
|
||||
return 9;
|
||||
}
|
||||
}
|
||||
|
||||
private class TextItem extends Item{
|
||||
private String text;
|
||||
private String secondaryText;
|
||||
private Runnable onClick;
|
||||
private boolean loading;
|
||||
private int icon;
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick) {
|
||||
this(text, null, onClick, false, 0);
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
|
||||
this(text, null, onClick, false, icon);
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, @DrawableRes int icon) {
|
||||
this(text, secondaryText, onClick, false, icon);
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, boolean loading, @DrawableRes int icon){
|
||||
this.text=getString(text);
|
||||
this.onClick=onClick;
|
||||
this.loading=loading;
|
||||
this.icon=icon;
|
||||
this.secondaryText = secondaryText;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -525,7 +749,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
case 5 -> new HeaderViewHolder(true);
|
||||
case 6 -> new FooterViewHolder();
|
||||
case 7 -> new UpdateViewHolder();
|
||||
case 8 -> new ColorPickerViewHolder();
|
||||
case 8 -> new ButtonViewHolder();
|
||||
case 9 -> new SmallTextViewHolder();
|
||||
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
||||
};
|
||||
}
|
||||
@@ -654,65 +879,25 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
}
|
||||
private class ColorPickerViewHolder extends BindableViewHolder<ColorPicker>{
|
||||
|
||||
private class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
|
||||
private final Button button;
|
||||
private final PopupMenu popupMenu;
|
||||
private final ImageView icon;
|
||||
private final TextView text;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public ColorPickerViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_color_picker, list);
|
||||
public ButtonViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_button, list);
|
||||
text=findViewById(R.id.text);
|
||||
icon=findViewById(R.id.icon);
|
||||
button=findViewById(R.id.color_picker_button);
|
||||
popupMenu=new PopupMenu(getActivity(), button, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.color_picker);
|
||||
popupMenu.setOnMenuItemClickListener(item->{
|
||||
GlobalUserPreferences.ColorPreference pref;
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.pink_color) {
|
||||
pref = GlobalUserPreferences.ColorPreference.PINK;
|
||||
onColorPreferenceClick(pref);
|
||||
}
|
||||
else if(id==R.id.purple_color) {
|
||||
pref = GlobalUserPreferences.ColorPreference.PURPLE;
|
||||
onColorPreferenceClick(pref);
|
||||
}
|
||||
else if(id==R.id.green_color) {
|
||||
pref = GlobalUserPreferences.ColorPreference.GREEN;
|
||||
onColorPreferenceClick(pref);
|
||||
}
|
||||
else if(id==R.id.blue_color) {
|
||||
pref = GlobalUserPreferences.ColorPreference.BLUE;
|
||||
onColorPreferenceClick(pref);
|
||||
}
|
||||
else if(id==R.id.orange_color) {
|
||||
pref = GlobalUserPreferences.ColorPreference.ORANGE;
|
||||
onColorPreferenceClick(pref);
|
||||
}
|
||||
else if(id==R.id.yellow_color) {
|
||||
pref = GlobalUserPreferences.ColorPreference.YELLOW;
|
||||
onColorPreferenceClick(pref);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
// UiUtils.enablePopupMenuIcons(getActivity(), popupMenu);
|
||||
button.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
button.setOnClickListener(v->popupMenu.show());
|
||||
button=findViewById(R.id.button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ColorPicker item){
|
||||
icon.setImageResource(R.drawable.ic_color_theme_preference);
|
||||
button.setText(switch(GlobalUserPreferences.color){
|
||||
case PINK -> R.string.pink_color;
|
||||
case PURPLE -> R.string.purple_color;
|
||||
case GREEN -> R.string.green_color;
|
||||
case BLUE -> R.string.blue_color;
|
||||
case ORANGE -> R.string.orange_color;
|
||||
case YELLOW -> R.string.yellow_color;
|
||||
});
|
||||
public void onBind(ButtonItem item){
|
||||
text.setText(item.text);
|
||||
icon.setImageResource(item.icon);
|
||||
item.buttonConsumer.accept(button);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -760,19 +945,27 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
|
||||
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
||||
private final TextView text;
|
||||
private final TextView text, secondaryText;
|
||||
private final ProgressBar progress;
|
||||
private final ImageView icon;
|
||||
|
||||
public TextViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_text, list);
|
||||
text = itemView.findViewById(R.id.text);
|
||||
secondaryText = itemView.findViewById(R.id.secondary_text);
|
||||
progress = itemView.findViewById(R.id.progress);
|
||||
icon = itemView.findViewById(R.id.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(TextItem item){
|
||||
icon.setVisibility(item.icon != 0 ? View.VISIBLE : View.GONE);
|
||||
secondaryText.setVisibility(item.secondaryText != null ? View.VISIBLE : View.GONE);
|
||||
|
||||
text.setText(item.text);
|
||||
progress.animate().alpha(item.loading ? 1 : 0);
|
||||
icon.setImageResource(item.icon);
|
||||
secondaryText.setText(item.secondaryText);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -781,6 +974,26 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> {
|
||||
private final TextView text;
|
||||
;
|
||||
|
||||
public SmallTextViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_text, list);
|
||||
text = itemView.findViewById(R.id.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(SmallTextItem item){
|
||||
text.setText(item.text);
|
||||
TypedValue val = new TypedValue();
|
||||
getContext().getTheme().resolveAttribute(android.R.attr.textColorSecondary, val, true);
|
||||
text.setTextColor(getResources().getColor(val.resourceId, getContext().getTheme()));
|
||||
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||
}
|
||||
}
|
||||
|
||||
private class FooterViewHolder extends BindableViewHolder<FooterItem>{
|
||||
private final TextView text;
|
||||
public FooterViewHolder(){
|
||||
@@ -796,7 +1009,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
|
||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||
|
||||
private final TextView text;
|
||||
private final TextView text, changelog;
|
||||
private final Button button;
|
||||
private final ImageButton cancelBtn;
|
||||
private final ProgressBar progress;
|
||||
@@ -807,6 +1020,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
public UpdateViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_update, list);
|
||||
text=findViewById(R.id.text);
|
||||
changelog=findViewById(R.id.changelog);
|
||||
button=findViewById(R.id.button);
|
||||
cancelBtn=findViewById(R.id.cancel_btn);
|
||||
progress=findViewById(R.id.progress);
|
||||
@@ -831,10 +1045,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
||||
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
||||
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
||||
text.setText(getString(R.string.update_available, info.version));
|
||||
text.setText(getString(R.string.sk_update_available, info.version));
|
||||
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
||||
}else{
|
||||
text.setText(getString(R.string.update_ready, info.version));
|
||||
text.setText(getString(R.string.sk_update_ready, info.version));
|
||||
button.setText(R.string.install_update);
|
||||
}
|
||||
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
||||
@@ -850,6 +1064,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
progress.setVisibility(View.GONE);
|
||||
progress.removeCallbacks(progressUpdater);
|
||||
}
|
||||
changelog.setText(info.changelog);
|
||||
// changelog.setText(getString(R.string.sk_changelog, info.changelog));
|
||||
}
|
||||
|
||||
private void updateProgress(){
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogFragment;
|
||||
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
@@ -23,12 +31,13 @@ public class SplashFragment extends AppKitFragment{
|
||||
|
||||
private SizeListenerFrameLayout contentView;
|
||||
private View artContainer, blueFill, greenFill;
|
||||
private InterpolatingMotionEffect motionEffect;
|
||||
private ViewPager2 pager;
|
||||
private ViewGroup pagerDots;
|
||||
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -37,15 +46,44 @@ public class SplashFragment extends AppKitFragment{
|
||||
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
||||
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
||||
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
||||
artClouds=contentView.findViewById(R.id.art_clouds);
|
||||
artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant);
|
||||
artRightHill=contentView.findViewById(R.id.art_right_hill);
|
||||
artLeftHill=contentView.findViewById(R.id.art_left_hill);
|
||||
artCenterHill=contentView.findViewById(R.id.art_center_hill);
|
||||
pager=contentView.findViewById(R.id.pager);
|
||||
pagerDots=contentView.findViewById(R.id.pager_dots);
|
||||
pager.setAdapter(new PagerAdapter());
|
||||
pager.setOffscreenPageLimit(3);
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
|
||||
for(int i=0;i<pagerDots.getChildCount();i++){
|
||||
float alpha;
|
||||
if(i==position){
|
||||
alpha=0.3f+0.7f*(1f-positionOffset);
|
||||
}else if(i==position+1){
|
||||
alpha=0.3f+0.7f*positionOffset;
|
||||
}else{
|
||||
alpha=0.3f;
|
||||
}
|
||||
pagerDots.getChildAt(i).setAlpha(alpha);
|
||||
}
|
||||
|
||||
float parallaxProgress=(position+positionOffset)/2f;
|
||||
artClouds.setTranslationX(V.dp(-27)*(position>=1 ? 1f : positionOffset));
|
||||
artPlaneElephant.setTranslationX(V.dp(101.55f)*parallaxProgress);
|
||||
artLeftHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
||||
artLeftHill.setTranslationY(V.dp(24)*parallaxProgress);
|
||||
artRightHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
||||
artRightHill.setTranslationY(V.dp(-24)*parallaxProgress);
|
||||
artCenterHill.setTranslationX(V.dp(-40)*parallaxProgress);
|
||||
}
|
||||
});
|
||||
|
||||
artContainer=contentView.findViewById(R.id.art_container);
|
||||
blueFill=contentView.findViewById(R.id.blue_fill);
|
||||
greenFill=contentView.findViewById(R.id.green_fill);
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_clouds), V.dp(-5), V.dp(5), V.dp(-5), V.dp(5)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_right_hill), V.dp(-15), V.dp(25), V.dp(-10), V.dp(10)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_left_hill), V.dp(-25), V.dp(15), V.dp(-15), V.dp(15)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_center_hill), V.dp(-14), V.dp(14), V.dp(-5), V.dp(25)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_plane_elephant), V.dp(-20), V.dp(12), V.dp(-20), V.dp(12)));
|
||||
|
||||
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
|
||||
@Override
|
||||
@@ -66,15 +104,16 @@ public class SplashFragment extends AppKitFragment{
|
||||
|
||||
private void onButtonClick(View v){
|
||||
Bundle extras=new Bundle();
|
||||
extras.putBoolean("signup", v.getId()==R.id.btn_get_started);
|
||||
Nav.go(getActivity(), InstanceCatalogFragment.class, extras);
|
||||
boolean isSignup=v.getId()==R.id.btn_get_started;
|
||||
extras.putBoolean("signup", isSignup);
|
||||
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
|
||||
}
|
||||
|
||||
private void updateArtSize(int w, int h){
|
||||
float scale=w/(float)V.dp(412);
|
||||
float scale=w/(float)V.dp(360);
|
||||
artContainer.setScaleX(scale);
|
||||
artContainer.setScaleY(scale);
|
||||
blueFill.setScaleY(h/2f);
|
||||
blueFill.setScaleY(artContainer.getBottom()-V.dp(90));
|
||||
greenFill.setScaleY(h-artContainer.getBottom()+V.dp(90));
|
||||
}
|
||||
|
||||
@@ -100,15 +139,91 @@ public class SplashFragment extends AppKitFragment{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
motionEffect.activate();
|
||||
private class PagerAdapter extends RecyclerView.Adapter<PagerViewHolder>{
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new PagerViewHolder(viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PagerViewHolder holder, int position){}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
motionEffect.deactivate();
|
||||
private class PagerViewHolder extends RecyclerView.ViewHolder{
|
||||
public PagerViewHolder(int page){
|
||||
super(new LinearLayout(getActivity()));
|
||||
LinearLayout ll=(LinearLayout) itemView;
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
int pad=V.dp(16);
|
||||
ll.setPadding(pad, pad, pad, pad);
|
||||
ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
TextView title=new TextView(getActivity());
|
||||
title.setTextAppearance(R.style.m3_headline_medium);
|
||||
title.setText(switch(page){
|
||||
case 0 -> {
|
||||
String src=getString(R.string.welcome_page1_title);
|
||||
SpannableString ss=new SpannableString(src);
|
||||
int start=src.indexOf("{logo}");
|
||||
if(start!=-1){
|
||||
LogoSpan span=new LogoSpan(getResources().getDrawable(R.drawable.splash_logo, getActivity().getTheme()));
|
||||
ss.setSpan(span, start, start+6, 0);
|
||||
}
|
||||
yield ss;
|
||||
}
|
||||
case 1 -> getString(R.string.welcome_page2_title);
|
||||
case 2 -> getString(R.string.welcome_page3_title);
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
});
|
||||
title.setTextColor(0xFF17063B);
|
||||
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(page==0 ? 46 : 36));
|
||||
lp.bottomMargin=V.dp(page==0 ? 4 : 14);
|
||||
ll.addView(title, lp);
|
||||
|
||||
TextView text=new TextView(getActivity());
|
||||
text.setTextAppearance(R.style.m3_body_medium);
|
||||
text.setText(switch(page){
|
||||
case 0 -> R.string.welcome_page1_text;
|
||||
case 1 -> R.string.welcome_page2_text;
|
||||
case 2 -> R.string.welcome_page3_text;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
});
|
||||
text.setTextColor(0xFF17063B);
|
||||
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
}
|
||||
}
|
||||
|
||||
private class LogoSpan extends ReplacementSpan{
|
||||
private final Drawable drawable;
|
||||
|
||||
private LogoSpan(Drawable drawable){
|
||||
this.drawable=drawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
|
||||
return drawable.getIntrinsicWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
canvas.save();
|
||||
canvas.translate(x, y-V.dp(20));
|
||||
drawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false);
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
||||
int idx=data.indexOf(s);
|
||||
if(idx>=0){
|
||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||
@@ -139,7 +139,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
action=getString(R.string.edit_multiple_changed);
|
||||
}
|
||||
}
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null));
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null, null));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
protected EventListener eventListener=new EventListener();
|
||||
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true);
|
||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,7 +177,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||
for(Status s:data){
|
||||
if(s.getContentStatus().id.equals(ev.id)){
|
||||
s.update(ev);
|
||||
s.getContentStatus().update(ev);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==s.getContentStatus()){
|
||||
@@ -189,8 +189,8 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
}
|
||||
}
|
||||
for(Status s:preloadedData){
|
||||
if(s.id.equals(ev.id)){
|
||||
s.update(ev);
|
||||
if(s.getContentStatus().id.equals(ev.id)){
|
||||
s.getContentStatus().update(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
@@ -17,6 +16,7 @@ import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -92,16 +92,10 @@ public class ThreadFragment extends StatusListFragment{
|
||||
}
|
||||
|
||||
private List<Status> filterStatuses(List<Status> statuses){
|
||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.THREAD)).collect(Collectors.toList());
|
||||
if(filters.isEmpty())
|
||||
return statuses;
|
||||
return statuses.stream().filter(status->{
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(status))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).collect(Collectors.toList());
|
||||
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
|
||||
return statuses.stream()
|
||||
.filter(statusFilterPredicate)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
@@ -224,6 +225,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
contextMenu=new PopupMenu(getActivity(), menuAnchor);
|
||||
contextMenu.inflate(R.menu.profile);
|
||||
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
|
||||
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,25 +284,32 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
Menu menu=contextMenu.getMenu();
|
||||
Account account=item.account;
|
||||
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.lists_with_user, account.getDisplayUsername()));
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
||||
|
||||
MenuItem mute = menu.findItem(R.id.mute);
|
||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
||||
|
||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername())).setVisible(relationship.following);
|
||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
||||
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
||||
if(relationship.following){
|
||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
|
||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
||||
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
|
||||
hideBoosts.setVisible(true);
|
||||
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
||||
|
||||
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||
manageUserLists.setVisible(true);
|
||||
}else{
|
||||
hideBoosts.setVisible(false);
|
||||
manageUserLists.setVisible(true);
|
||||
}
|
||||
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||
if(!account.isLocal()){
|
||||
blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||
blockDomain.setVisible(true);
|
||||
}else{
|
||||
blockDomain.setVisible(false);
|
||||
}
|
||||
menu.findItem(R.id.block_domain).setVisible(false);
|
||||
|
||||
menuAnchor.setTranslationX(x);
|
||||
menuAnchor.setTranslationY(y);
|
||||
@@ -341,6 +350,8 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
||||
}else if(id==R.id.block){
|
||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
||||
}else if(id==R.id.soft_block){
|
||||
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
|
||||
}else if(id==R.id.report){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -369,6 +380,12 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}else if(id==R.id.manage_user_lists){
|
||||
final Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putString("profileAccount", account.id);
|
||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||
@@ -61,7 +62,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private String accountID;
|
||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
|
||||
private static final boolean noFederated = BuildConfig.BUILD_TYPE.equals("noFederatedRelease");
|
||||
private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -87,11 +88,11 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabView.setId(switch(switchIndex){
|
||||
case 0 -> R.id.discover_local_timeline;
|
||||
case 1 -> R.id.discover_federated_timeline;
|
||||
case 2 -> R.id.discover_hashtags;
|
||||
case 3 -> R.id.discover_posts;
|
||||
case 4 -> R.id.discover_news;
|
||||
case 5 -> R.id.discover_users;
|
||||
case 6 -> R.id.discover_lists;
|
||||
case 2 -> R.id.discover_lists;
|
||||
case 3 -> R.id.discover_hashtags;
|
||||
case 4 -> R.id.discover_posts;
|
||||
case 5 -> R.id.discover_news;
|
||||
case 6 -> R.id.discover_users;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+switchIndex);
|
||||
});
|
||||
tabView.setVisibility(View.GONE);
|
||||
@@ -103,6 +104,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||
|
||||
pager.setOffscreenPageLimit(4);
|
||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||
pager.setAdapter(new DiscoverPagerAdapter());
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
@Override
|
||||
@@ -163,12 +165,13 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
if (noFederated && position > 0) position++;
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.local_timeline;
|
||||
case 1 -> R.string.federated_timeline;
|
||||
case 2 -> R.string.hashtags;
|
||||
case 3 -> R.string.posts;
|
||||
case 4 -> R.string.news;
|
||||
case 5 -> R.string.for_you;
|
||||
case 6 -> R.string.list_timelines;
|
||||
case 1 -> R.string.sk_federated_timeline;
|
||||
case 2 -> R.string.sk_list_timelines;
|
||||
case 3 -> R.string.hashtags;
|
||||
case 4 -> R.string.posts;
|
||||
case 5 -> R.string.news;
|
||||
case 6 -> R.string.for_you;
|
||||
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
tab.view.textView.setAllCaps(true);
|
||||
@@ -355,4 +358,10 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
public void selectSearch(){
|
||||
searchEdit.requestFocus();
|
||||
onSearchEditFocusChanged(searchEdit, true);
|
||||
getActivity().getSystemService(InputMethodManager.class).showSoftInput(searchEdit, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ public class DiscoverPostsFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetTrendingStatuses(count)
|
||||
currentRequest=new GetTrendingStatuses(offset, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, false);
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,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, false, true);
|
||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -12,19 +12,21 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail;
|
||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.SettingsFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -35,40 +37,50 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.APIRequest;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class AccountActivationFragment extends AppKitFragment{
|
||||
public class AccountActivationFragment extends ToolbarFragment{
|
||||
private String accountID;
|
||||
|
||||
private Button btn, backBtn;
|
||||
private View buttonBar;
|
||||
private Button openEmailBtn, resendBtn;
|
||||
private View contentView;
|
||||
private Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||
private Runnable pollRunnable=this::tryGetAccount;
|
||||
private APIRequest currentRequest;
|
||||
private Runnable resendTimer=this::updateResendTimer;
|
||||
private long lastResendTime;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.confirm_email_title);
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
lastResendTime=session.activationInfo!=null ? session.activationInfo.lastEmailConfirmationResend : 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_activation, container, false);
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
btn.setOnLongClickListener(v->{
|
||||
openEmailBtn=view.findViewById(R.id.btn_next);
|
||||
openEmailBtn.setOnClickListener(this::onOpenEmailClick);
|
||||
openEmailBtn.setOnLongClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||
return true;
|
||||
});
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->onBackButtonClick());
|
||||
resendBtn=view.findViewById(R.id.btn_resend);
|
||||
resendBtn.setOnClickListener(this::onResendClick);
|
||||
TextView text=view.findViewById(R.id.subtitle);
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
text.setText(getString(R.string.confirm_email_subtitle, session.activationInfo!=null ? session.activationInfo.email : "?"));
|
||||
updateResendTimer();
|
||||
|
||||
contentView=view;
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -80,14 +92,32 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
}
|
||||
|
||||
// @Override
|
||||
protected void onUpdateToolbar(){
|
||||
// super.onUpdateToolbar();
|
||||
getToolbar().setBackground(null);
|
||||
getToolbar().setElevation(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canGoBack(){
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onToolbarNavigationClick(){
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=27){
|
||||
int inset=insets.getSystemWindowInsetBottom();
|
||||
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
contentView.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||
}else{
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
@@ -111,7 +141,7 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private void onButtonClick(){
|
||||
private void onOpenEmailClick(View v){
|
||||
try{
|
||||
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}catch(ActivityNotFoundException x){
|
||||
@@ -119,12 +149,21 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private void onBackButtonClick(){
|
||||
private void onResendClick(View v){
|
||||
new ResendConfirmationEmail(null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
Toast.makeText(getActivity(), R.string.resent_email, Toast.LENGTH_SHORT).show();
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
if(session.activationInfo==null){
|
||||
session.activationInfo=new AccountActivationInfo("?", System.currentTimeMillis());
|
||||
}else{
|
||||
session.activationInfo.lastEmailConfirmationResend=System.currentTimeMillis();
|
||||
}
|
||||
lastResendTime=session.activationInfo.lastEmailConfirmationResend;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
updateResendTimer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,7 +191,7 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
AccountSessionManager mgr=AccountSessionManager.getInstance();
|
||||
AccountSession session=mgr.getAccount(accountID);
|
||||
mgr.removeAccount(accountID);
|
||||
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, true);
|
||||
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
|
||||
String newID=mgr.getLastActiveAccountID();
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", newID);
|
||||
@@ -189,4 +228,25 @@ public class AccountActivationFragment extends AppKitFragment{
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
private void updateResendTimer(){
|
||||
long sinceResend=System.currentTimeMillis()-lastResendTime;
|
||||
if(sinceResend>59_000L){
|
||||
resendBtn.setText(R.string.resend);
|
||||
resendBtn.setEnabled(true);
|
||||
return;
|
||||
}
|
||||
int seconds=(int)((60_000L-sinceResend)/1000L);
|
||||
resendBtn.setText(String.format("%s (%d)", getString(R.string.resend), seconds));
|
||||
if(resendBtn.isEnabled())
|
||||
resendBtn.setEnabled(false);
|
||||
resendBtn.postDelayed(resendTimer, 500);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView(){
|
||||
super.onDestroyView();
|
||||
resendBtn.removeCallbacks(resendTimer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
||||
private View headerView;
|
||||
|
||||
public CustomWelcomeFragment() {
|
||||
super(R.layout.fragment_welcome_custom, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context){
|
||||
super.onAttach(context);
|
||||
setRefreshEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
dataLoaded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateToolbar(){
|
||||
super.onUpdateToolbar();
|
||||
|
||||
if (!canGoBack()) {
|
||||
ImageView toolbarLogo=new ImageView(getActivity());
|
||||
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
|
||||
toolbarLogo.setImageResource(R.drawable.logo);
|
||||
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
||||
|
||||
FrameLayout logoWrap=new FrameLayout(getActivity());
|
||||
FrameLayout.LayoutParams logoParams=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
|
||||
logoParams.setMargins(0, V.dp(2), 0, 0);
|
||||
logoWrap.addView(toolbarLogo, logoParams);
|
||||
|
||||
getToolbar().addView(logoWrap, new Toolbar.LayoutParams(Gravity.CENTER));
|
||||
} else {
|
||||
setTitle(R.string.add_account);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void proceedWithAuthOrSignup(Instance instance) {
|
||||
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateFilteredList(){
|
||||
boolean addFakeInstance = currentSearchQuery.length()>0 && currentSearchQuery.matches("^\\S+\\.[^\\.]+$");
|
||||
if(addFakeInstance){
|
||||
fakeInstance.domain=fakeInstance.normalizedDomain=currentSearchQuery;
|
||||
fakeInstance.description=getString(R.string.loading_instance);
|
||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
|
||||
ivh.rebind();
|
||||
}
|
||||
}
|
||||
if(filteredData.isEmpty()){
|
||||
filteredData.add(fakeInstance);
|
||||
adapter.notifyItemInserted(0);
|
||||
}
|
||||
}
|
||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||
filteredData.clear();
|
||||
if(currentSearchQuery.length()>0){
|
||||
boolean foundExactMatch=false;
|
||||
for(CatalogInstance inst:data){
|
||||
if(inst.normalizedDomain.contains(currentSearchQuery)){
|
||||
filteredData.add(inst);
|
||||
if(inst.normalizedDomain.equals(currentSearchQuery))
|
||||
foundExactMatch=true;
|
||||
}
|
||||
}
|
||||
if(!foundExactMatch && addFakeInstance) {
|
||||
filteredData.add(0, fakeInstance);
|
||||
adapter.notifyItemChanged(0);
|
||||
}
|
||||
}
|
||||
UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
list.getChildAt(i).invalidateOutline();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorWindowBackground));
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count) {}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_welcome_custom, list, false);
|
||||
searchEdit=headerView.findViewById(R.id.search_edit);
|
||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||
|
||||
headerView.findViewById(R.id.more).setVisibility(View.GONE);
|
||||
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
||||
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
|
||||
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
|
||||
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
|
||||
((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
|
||||
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
||||
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
|
||||
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
|
||||
|
||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
nextButton.setEnabled(false);
|
||||
chosenInstance = null;
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
searchEdit.postDelayed(searchDebouncer, 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){}
|
||||
});
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||
View spacer = new Space(getActivity());
|
||||
spacer.setMinimumHeight(V.dp(8));
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(spacer));
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder> {
|
||||
public InstancesAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new InstanceViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(InstanceViewHolder holder, int position){
|
||||
holder.bind(filteredData.get(position));
|
||||
chosenInstance = filteredData.get(position);
|
||||
if (chosenInstance != fakeInstance) nextButton.setEnabled(true);
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return filteredData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title, description, userCount, lang;
|
||||
private final RadioButton radioButton;
|
||||
|
||||
public InstanceViewHolder(){
|
||||
super(getActivity(), R.layout.item_instance_custom, list);
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
userCount=findViewById(R.id.user_count);
|
||||
lang=findViewById(R.id.lang);
|
||||
radioButton=findViewById(R.id.radiobtn);
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(CatalogInstance item){
|
||||
title.setText(item.normalizedDomain);
|
||||
description.setText(item.description);
|
||||
if (item == fakeInstance) {
|
||||
userCount.setVisibility(View.GONE);
|
||||
lang.setVisibility(View.GONE);
|
||||
} else {
|
||||
userCount.setVisibility(View.VISIBLE);
|
||||
lang.setVisibility(View.VISIBLE);
|
||||
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
|
||||
lang.setText(item.language.toUpperCase());
|
||||
}
|
||||
radioButton.setChecked(chosenInstance==item);
|
||||
radioButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
if(chosenInstance!=null){
|
||||
int idx=filteredData.indexOf(chosenInstance);
|
||||
if(idx!=-1){
|
||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
|
||||
if(holder instanceof InstanceViewHolder ivh){
|
||||
ivh.radioButton.setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
radioButton.setChecked(true);
|
||||
if(chosenInstance==null)
|
||||
nextButton.setEnabled(true);
|
||||
chosenInstance=item;
|
||||
loadInstanceInfo(chosenInstance.domain, false);
|
||||
onNextClick(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -15,6 +16,7 @@ import android.widget.TextView;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
@@ -33,6 +35,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
@@ -46,7 +49,7 @@ import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||
private UsableRecyclerView list;
|
||||
private MergeRecyclerAdapter adapter;
|
||||
private Button btn;
|
||||
@@ -60,6 +63,7 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
setTitle(R.string.privacy_policy_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,37 +86,24 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||
|
||||
list=view.findViewById(R.id.list);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
||||
TextView title=headerView.findViewById(R.id.title);
|
||||
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
||||
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
|
||||
title.setText(R.string.privacy_policy_title);
|
||||
subtitle.setText(R.string.privacy_policy_subtitle);
|
||||
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
||||
TextView text=headerView.findViewById(R.id.text);
|
||||
text.setText(R.string.privacy_policy_subtitle);
|
||||
|
||||
adapter=new MergeRecyclerAdapter();
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
||||
list.setAdapter(adapter);
|
||||
list.setSelector(null);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
if(parent.getChildViewHolder(view) instanceof ItemViewHolder){
|
||||
outRect.left=outRect.right=V.dp(18.5f);
|
||||
outRect.top=V.dp(16);
|
||||
}
|
||||
}
|
||||
});
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -120,7 +111,15 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
}
|
||||
|
||||
// @Override
|
||||
protected void onUpdateToolbar(){
|
||||
// super.onUpdateToolbar();
|
||||
getToolbar().setBackground(null);
|
||||
getToolbar().setElevation(0);
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
@@ -192,24 +191,17 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
||||
private final TextView domain, title;
|
||||
private final ImageView favicon;
|
||||
private final TextView title;
|
||||
|
||||
public ItemViewHolder(){
|
||||
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
||||
domain=findViewById(R.id.domain);
|
||||
title=findViewById(R.id.title);
|
||||
favicon=findViewById(R.id.favicon);
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(10));
|
||||
itemView.setClipToOutline(true);
|
||||
title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Item item){
|
||||
domain.setText(item.domain);
|
||||
title.setText(item.title);
|
||||
|
||||
ViewImageLoader.load(favicon, null, new UrlImageLoaderRequest(item.faviconUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,46 +2,30 @@ package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.LocaleList;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogCategory;
|
||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
@@ -59,317 +43,53 @@ import java.util.stream.Collectors;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstance>{
|
||||
private InstancesAdapter adapter;
|
||||
private MergeRecyclerAdapter mergeAdapter;
|
||||
private View headerView;
|
||||
private CatalogInstance chosenInstance;
|
||||
private List<CatalogInstance> filteredData=new ArrayList<>();
|
||||
private Button nextButton;
|
||||
private MastodonAPIRequest<?> getCategoriesRequest;
|
||||
private EditText searchEdit;
|
||||
private TabLayout categoriesList;
|
||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
private String currentSearchQuery;
|
||||
private String currentCategory="all";
|
||||
private List<CatalogCategory> categories=new ArrayList<>();
|
||||
private String loadingInstanceDomain;
|
||||
private GetInstance loadingInstanceRequest;
|
||||
private Call loadingInstanceRedirectRequest;
|
||||
private HashMap<String, Instance> instancesCache=new HashMap<>();
|
||||
private ProgressDialog instanceProgressDialog;
|
||||
private View buttonBar;
|
||||
private HashMap<String, String> redirects=new HashMap<>(), redirectsInverse=new HashMap<>();
|
||||
|
||||
private boolean isSignup;
|
||||
abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstance>{
|
||||
protected RecyclerView.Adapter adapter;
|
||||
protected MergeRecyclerAdapter mergeAdapter;
|
||||
protected CatalogInstance chosenInstance;
|
||||
protected Button nextButton;
|
||||
protected EditText searchEdit;
|
||||
protected Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||
protected String currentSearchQuery;
|
||||
protected String loadingInstanceDomain;
|
||||
protected HashMap<String, Instance> instancesCache=new HashMap<>();
|
||||
protected View buttonBar;
|
||||
protected List<CatalogInstance> filteredData=new ArrayList<>();
|
||||
protected GetInstance loadingInstanceRequest;
|
||||
protected Call loadingInstanceRedirectRequest;
|
||||
protected ProgressDialog instanceProgressDialog;
|
||||
protected HashMap<String, String> redirects=new HashMap<>();
|
||||
protected HashMap<String, String> redirectsInverse=new HashMap<>();
|
||||
protected boolean isSignup;
|
||||
protected CatalogInstance fakeInstance=new CatalogInstance();
|
||||
|
||||
private static final double DUNBAR=Math.log(800);
|
||||
|
||||
public InstanceCatalogFragment(){
|
||||
super(R.layout.fragment_onboarding_common, 10);
|
||||
public InstanceCatalogFragment(int layout, int perPage){
|
||||
super(layout, perPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
isSignup=getArguments().getBoolean("signup");
|
||||
isSignup=getArguments() != null && getArguments().getBoolean("signup");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context){
|
||||
super.onAttach(context);
|
||||
setRefreshEnabled(false);
|
||||
loadData();
|
||||
}
|
||||
protected abstract void proceedWithAuthOrSignup(Instance instance);
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetCatalogInstances(null, null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogInstance> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
Map<String, List<CatalogInstance>> byLang=result.stream().collect(Collectors.groupingBy(ci->ci.language));
|
||||
for(List<CatalogInstance> group:byLang.values()){
|
||||
Collections.sort(group, (a, b)->{
|
||||
double aa=Math.abs(DUNBAR-Math.log(a.lastWeekUsers));
|
||||
double bb=Math.abs(DUNBAR-Math.log(b.lastWeekUsers));
|
||||
return Double.compare(aa, bb);
|
||||
});
|
||||
}
|
||||
// get the list of user-configured system languages
|
||||
List<String> userLangs;
|
||||
if(Build.VERSION.SDK_INT<24){
|
||||
userLangs=Collections.singletonList(getResources().getConfiguration().locale.getLanguage());
|
||||
}else{
|
||||
LocaleList ll=getResources().getConfiguration().getLocales();
|
||||
userLangs=new ArrayList<>(ll.size());
|
||||
for(int i=0;i<ll.size();i++){
|
||||
userLangs.add(ll.get(i).getLanguage());
|
||||
}
|
||||
}
|
||||
// add instances in preferred languages to the top of the list, in the order of preference
|
||||
ArrayList<CatalogInstance> sortedList=new ArrayList<>();
|
||||
for(String lang:userLangs){
|
||||
List<CatalogInstance> langInstances=byLang.remove(lang);
|
||||
if(langInstances!=null){
|
||||
sortedList.addAll(langInstances);
|
||||
}
|
||||
}
|
||||
// sort the remaining language groups by aggregate lastWeekUsers
|
||||
class InstanceGroup{
|
||||
public int activeUsers;
|
||||
public List<CatalogInstance> instances;
|
||||
}
|
||||
byLang.values().stream().map(il->{
|
||||
InstanceGroup group=new InstanceGroup();
|
||||
group.instances=il;
|
||||
for(CatalogInstance instance:il){
|
||||
group.activeUsers+=instance.lastWeekUsers;
|
||||
}
|
||||
return group;
|
||||
}).sorted(Comparator.comparingInt((InstanceGroup g)->g.activeUsers).reversed()).forEachOrdered(ig->sortedList.addAll(ig.instances));
|
||||
onDataLoaded(sortedList, false);
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
onDataLoaded(Collections.emptyList(), false);
|
||||
}
|
||||
})
|
||||
.execNoAuth("");
|
||||
getCategoriesRequest=new GetCatalogCategories(null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogCategory> result){
|
||||
getCategoriesRequest=null;
|
||||
CatalogCategory all=new CatalogCategory();
|
||||
all.category="all";
|
||||
categories.add(all);
|
||||
result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add);
|
||||
updateCategories();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
getCategoriesRequest=null;
|
||||
error.showToast(getActivity());
|
||||
CatalogCategory all=new CatalogCategory();
|
||||
all.category="all";
|
||||
categories.add(all);
|
||||
updateCategories();
|
||||
}
|
||||
})
|
||||
.execNoAuth("");
|
||||
}
|
||||
|
||||
private void updateCategories(){
|
||||
categoriesList.removeAllTabs();
|
||||
for(CatalogCategory cat:categories){
|
||||
int titleRes=getTitleForCategory(cat.category);
|
||||
TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
|
||||
ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
|
||||
emoji.setImageResource(getEmojiForCategory(cat.category));
|
||||
categoriesList.addTab(tab);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
if(getCategoriesRequest!=null)
|
||||
getCategoriesRequest.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false);
|
||||
searchEdit=headerView.findViewById(R.id.search_edit);
|
||||
categoriesList=headerView.findViewById(R.id.categories_list);
|
||||
categoriesList.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab){
|
||||
CatalogCategory category=categories.get(tab.getPosition());
|
||||
currentCategory=category.category;
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab){
|
||||
|
||||
}
|
||||
});
|
||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
searchEdit.postDelayed(searchDebouncer, 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
}
|
||||
});
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
nextButton=view.findViewById(R.id.btn_next);
|
||||
nextButton.setOnClickListener(this::onNextClick);
|
||||
nextButton.setEnabled(chosenInstance!=null);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
}
|
||||
|
||||
private void onNextClick(View v){
|
||||
String domain=chosenInstance.domain;
|
||||
Instance instance=instancesCache.get(domain);
|
||||
if(instance!=null){
|
||||
proceedWithAuthOrSignup(instance);
|
||||
}else{
|
||||
showProgressDialog();
|
||||
if(!domain.equals(loadingInstanceDomain)){
|
||||
loadInstanceInfo(domain, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void proceedWithAuthOrSignup(Instance instance){
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||
if(isSignup){
|
||||
if(!instance.registrations){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}else{
|
||||
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
|
||||
}
|
||||
}
|
||||
|
||||
// private String getEmojiForCategory(String category){
|
||||
// return switch(category){
|
||||
// case "all" -> "💬";
|
||||
// case "academia" -> "📚";
|
||||
// case "activism" -> "✊";
|
||||
// case "food" -> "🍕";
|
||||
// case "furry" -> "🦁";
|
||||
// case "games" -> "🕹";
|
||||
// case "general" -> "🐘";
|
||||
// case "journalism" -> "📰";
|
||||
// case "lgbt" -> "🏳️🌈";
|
||||
// case "regional" -> "📍";
|
||||
// case "art" -> "🎨";
|
||||
// case "music" -> "🎼";
|
||||
// case "tech" -> "📱";
|
||||
// default -> "❓";
|
||||
// };
|
||||
// }
|
||||
|
||||
private int getEmojiForCategory(String category){
|
||||
return switch(category){
|
||||
case "all" -> R.drawable.ic_category_all;
|
||||
case "academia" -> R.drawable.ic_category_academia;
|
||||
case "activism" -> R.drawable.ic_category_activism;
|
||||
case "food" -> R.drawable.ic_category_food;
|
||||
case "furry" -> R.drawable.ic_category_furry;
|
||||
case "games" -> R.drawable.ic_category_games;
|
||||
case "general" -> R.drawable.ic_category_general;
|
||||
case "journalism" -> R.drawable.ic_category_journalism;
|
||||
case "lgbt" -> R.drawable.ic_category_lgbt;
|
||||
case "regional" -> R.drawable.ic_category_regional;
|
||||
case "art" -> R.drawable.ic_category_art;
|
||||
case "music" -> R.drawable.ic_category_music;
|
||||
case "tech" -> R.drawable.ic_category_tech;
|
||||
default -> R.drawable.ic_category_unknown;
|
||||
};
|
||||
}
|
||||
|
||||
private int getTitleForCategory(String category){
|
||||
return switch(category){
|
||||
case "all" -> R.string.category_all;
|
||||
case "academia" -> R.string.category_academia;
|
||||
case "activism" -> R.string.category_activism;
|
||||
case "food" -> R.string.category_food;
|
||||
case "furry" -> R.string.category_furry;
|
||||
case "games" -> R.string.category_games;
|
||||
case "general" -> R.string.category_general;
|
||||
case "journalism" -> R.string.category_journalism;
|
||||
case "lgbt" -> R.string.category_lgbt;
|
||||
case "regional" -> R.string.category_regional;
|
||||
case "art" -> R.string.category_art;
|
||||
case "music" -> R.string.category_music;
|
||||
case "tech" -> R.string.category_tech;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
||||
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||
return true;
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
||||
@@ -385,60 +105,73 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onSearchChangedDebounced(){
|
||||
protected void onSearchChangedDebounced(){
|
||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
||||
updateFilteredList();
|
||||
loadInstanceInfo(currentSearchQuery, false);
|
||||
}
|
||||
|
||||
private void updateFilteredList(){
|
||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||
filteredData.clear();
|
||||
for(CatalogInstance instance:data){
|
||||
if(currentCategory.equals("all") || instance.categories.contains(currentCategory)){
|
||||
if(TextUtils.isEmpty(currentSearchQuery) || instance.domain.contains(currentSearchQuery)){
|
||||
if(instance.domain.equals(currentSearchQuery) || !isSignup || !instance.approvalRequired)
|
||||
filteredData.add(instance);
|
||||
}
|
||||
protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){
|
||||
Map<String, List<CatalogInstance>> byLang=result.stream().collect(Collectors.groupingBy(ci->ci.language));
|
||||
for(List<CatalogInstance> group:byLang.values()){
|
||||
Collections.sort(group, (a, b)->{
|
||||
double aa=Math.abs(DUNBAR-Math.log(a.lastWeekUsers));
|
||||
double bb=Math.abs(DUNBAR-Math.log(b.lastWeekUsers));
|
||||
return Double.compare(aa, bb);
|
||||
});
|
||||
}
|
||||
// get the list of user-configured system languages
|
||||
List<String> userLangs;
|
||||
if(Build.VERSION.SDK_INT<24){
|
||||
userLangs=Collections.singletonList(getResources().getConfiguration().locale.getLanguage());
|
||||
}else{
|
||||
LocaleList ll=getResources().getConfiguration().getLocales();
|
||||
userLangs=new ArrayList<>(ll.size());
|
||||
for(int i=0;i<ll.size();i++){
|
||||
userLangs.add(ll.get(i).getLanguage());
|
||||
}
|
||||
}
|
||||
DiffUtil.calculateDiff(new DiffUtil.Callback(){
|
||||
@Override
|
||||
public int getOldListSize(){
|
||||
return prevData.size();
|
||||
// add instances in preferred languages to the top of the list, in the order of preference
|
||||
ArrayList<CatalogInstance> sortedList=new ArrayList<>();
|
||||
for(String lang:userLangs){
|
||||
List<CatalogInstance> langInstances=byLang.remove(lang);
|
||||
if(langInstances!=null){
|
||||
sortedList.addAll(langInstances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize(){
|
||||
return filteredData.size();
|
||||
}
|
||||
// sort the remaining language groups by aggregate lastWeekUsers
|
||||
class InstanceGroup{
|
||||
public int activeUsers;
|
||||
public List<CatalogInstance> instances;
|
||||
}
|
||||
byLang.values().stream().map(il->{
|
||||
InstanceGroup group=new InstanceGroup();
|
||||
group.instances=il;
|
||||
for(CatalogInstance instance:il){
|
||||
group.activeUsers+=instance.lastWeekUsers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
|
||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
|
||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||
}
|
||||
}).dispatchUpdatesTo(adapter);
|
||||
return group;
|
||||
}).sorted(Comparator.comparingInt((InstanceGroup g)->g.activeUsers).reversed()).forEachOrdered(ig->sortedList.addAll(ig.instances));
|
||||
return sortedList;
|
||||
}
|
||||
|
||||
private void showProgressDialog(){
|
||||
protected abstract void updateFilteredList();
|
||||
|
||||
protected void showProgressDialog(){
|
||||
instanceProgressDialog=new ProgressDialog(getActivity());
|
||||
instanceProgressDialog.setMessage(getString(R.string.loading_instance));
|
||||
instanceProgressDialog.setOnCancelListener(dialog->cancelLoadingInstanceInfo());
|
||||
instanceProgressDialog.show();
|
||||
}
|
||||
|
||||
private String normalizeInstanceDomain(String _domain){
|
||||
protected String normalizeInstanceDomain(String _domain){
|
||||
if(TextUtils.isEmpty(_domain))
|
||||
return null;
|
||||
if(_domain.contains(":")){
|
||||
try{
|
||||
_domain=Uri.parse(_domain).getAuthority();
|
||||
}catch(Exception ignore){}
|
||||
}catch(Exception ignore){
|
||||
}
|
||||
if(TextUtils.isEmpty(_domain))
|
||||
return null;
|
||||
}
|
||||
@@ -453,12 +186,14 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
|
||||
return domain;
|
||||
}
|
||||
|
||||
private void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
||||
if(TextUtils.isEmpty(_domain))
|
||||
return;
|
||||
String domain=normalizeInstanceDomain(_domain);
|
||||
Instance cachedInstance=instancesCache.get(domain);
|
||||
if(cachedInstance!=null){
|
||||
for(CatalogInstance ci:filteredData){
|
||||
if(ci.domain.equals(domain))
|
||||
for(CatalogInstance ci : filteredData){
|
||||
if(ci.domain.equals(domain) && ci!=fakeInstance)
|
||||
return;
|
||||
}
|
||||
CatalogInstance ci=cachedInstance.toCatalogInstance();
|
||||
@@ -476,44 +211,57 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
|
||||
loadingInstanceDomain=domain;
|
||||
loadingInstanceRequest=new GetInstance();
|
||||
loadingInstanceRequest.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Instance result){
|
||||
loadingInstanceRequest=null;
|
||||
loadingInstanceDomain=null;
|
||||
result.uri=domain; // needed for instances that use domain redirection
|
||||
instancesCache.put(domain, result);
|
||||
if(instanceProgressDialog!=null){
|
||||
instanceProgressDialog.dismiss();
|
||||
instanceProgressDialog=null;
|
||||
proceedWithAuthOrSignup(result);
|
||||
}
|
||||
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
||||
boolean found=false;
|
||||
for(CatalogInstance ci:filteredData){
|
||||
if(ci.domain.equals(domain)){
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found){
|
||||
CatalogInstance ci=result.toCatalogInstance();
|
||||
filteredData.add(0, ci);
|
||||
adapter.notifyItemInserted(0);
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(Instance result){
|
||||
loadingInstanceRequest=null;
|
||||
loadingInstanceDomain=null;
|
||||
result.uri=domain; // needed for instances that use domain redirection
|
||||
instancesCache.put(domain, result);
|
||||
if(instanceProgressDialog!=null){
|
||||
instanceProgressDialog.dismiss();
|
||||
instanceProgressDialog=null;
|
||||
proceedWithAuthOrSignup(result);
|
||||
}
|
||||
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
||||
boolean found=false;
|
||||
for(CatalogInstance ci:filteredData){
|
||||
if(ci.domain.equals(domain) && ci!=fakeInstance){
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found){
|
||||
CatalogInstance ci=result.toCatalogInstance();
|
||||
if(filteredData.size()==1 && filteredData.get(0)==fakeInstance){
|
||||
filteredData.set(0, ci);
|
||||
adapter.notifyItemChanged(0);
|
||||
}else{
|
||||
filteredData.add(0, ci);
|
||||
adapter.notifyItemInserted(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
loadingInstanceRequest=null;
|
||||
if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){
|
||||
fetchDomainFromHostMetaAndMaybeRetry(domain, error);
|
||||
return;
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
loadingInstanceRequest=null;
|
||||
if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){
|
||||
fetchDomainFromHostMetaAndMaybeRetry(domain, error);
|
||||
return;
|
||||
}
|
||||
loadingInstanceDomain=null;
|
||||
showInstanceInfoLoadError(domain, error);
|
||||
if(fakeInstance!=null){
|
||||
fakeInstance.description=getString(R.string.error);
|
||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||
if(list.findViewHolderForAdapterPosition(1) instanceof BindableViewHolder<?> ivh){
|
||||
ivh.rebind();
|
||||
}
|
||||
loadingInstanceDomain=null;
|
||||
showInstanceInfoLoadError(domain, error);
|
||||
}
|
||||
}).execNoAuth(domain);
|
||||
}
|
||||
}
|
||||
}).execNoAuth(domain);
|
||||
}
|
||||
|
||||
private void cancelLoadingInstanceInfo(){
|
||||
@@ -584,7 +332,7 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
|
||||
InputSource source=new InputSource(response.body().charStream());
|
||||
Document doc=DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(source);
|
||||
NodeList list=doc.getElementsByTagName("Link");
|
||||
for(int i=0;i<list.getLength();i++){
|
||||
for(int i=0; i<list.getLength(); i++){
|
||||
if(list.item(i) instanceof Element el){
|
||||
String template=el.getAttribute("template");
|
||||
if("lrdd".equals(el.getAttribute("rel")) && !TextUtils.isEmpty(template) && template.contains("{uri}")){
|
||||
@@ -616,78 +364,26 @@ public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstanc
|
||||
}
|
||||
}
|
||||
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder>{
|
||||
public InstancesAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new InstanceViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(InstanceViewHolder holder, int position){
|
||||
holder.bind(filteredData.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return filteredData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return -1;
|
||||
}
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
nextButton=view.findViewById(R.id.btn_next);
|
||||
nextButton.setOnClickListener(this::onNextClick);
|
||||
nextButton.setEnabled(chosenInstance!=null);
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
setRefreshEnabled(false);
|
||||
}
|
||||
|
||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title, description, userCount, lang;
|
||||
private final RadioButton radioButton;
|
||||
|
||||
public InstanceViewHolder(){
|
||||
super(getActivity(), R.layout.item_instance_catalog, list);
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
userCount=findViewById(R.id.user_count);
|
||||
lang=findViewById(R.id.lang);
|
||||
radioButton=findViewById(R.id.radiobtn);
|
||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
|
||||
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
|
||||
protected void onNextClick(View v){
|
||||
String domain=chosenInstance.domain;
|
||||
Instance instance=instancesCache.get(domain);
|
||||
if(instance!=null){
|
||||
proceedWithAuthOrSignup(instance);
|
||||
}else{
|
||||
showProgressDialog();
|
||||
if(!domain.equals(loadingInstanceDomain)){
|
||||
loadInstanceInfo(domain, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(CatalogInstance item){
|
||||
title.setText(item.normalizedDomain);
|
||||
description.setText(item.description);
|
||||
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
|
||||
lang.setText(item.language.toUpperCase());
|
||||
radioButton.setChecked(chosenInstance==item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
if(chosenInstance==item)
|
||||
return;
|
||||
if(chosenInstance!=null){
|
||||
int idx=filteredData.indexOf(chosenInstance);
|
||||
if(idx!=-1){
|
||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
|
||||
if(holder instanceof InstanceViewHolder ivh){
|
||||
ivh.radioButton.setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
radioButton.setChecked(true);
|
||||
if(chosenInstance==null)
|
||||
nextButton.setEnabled(true);
|
||||
chosenInstance=item;
|
||||
loadInstanceInfo(chosenInstance.domain, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,698 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogCategory;
|
||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FilterChipView;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment implements OnBackPressedListener{
|
||||
private MastodonAPIRequest<?> getCategoriesRequest;
|
||||
private String currentCategory="all";
|
||||
private List<CatalogCategory> categories=new ArrayList<>();
|
||||
private View topBar;
|
||||
|
||||
private List<String> languages=Collections.emptyList();
|
||||
private PopupMenu langFilterMenu, speedFilterMenu;
|
||||
private SignupSpeedFilter currentSignupSpeedFilter=SignupSpeedFilter.INSTANT;
|
||||
private String currentLanguage=null;
|
||||
private boolean searchQueryMode;
|
||||
private LinearLayout filtersWrap;
|
||||
private HorizontalScrollView filtersScroll;
|
||||
private ImageButton backBtn, clearSearchBtn;
|
||||
|
||||
private FilterChipView categoryGeneral, categorySpecialInterests;
|
||||
private List<FilterChipView> regionalFilters;
|
||||
private CatalogInstance.Region chosenRegion;
|
||||
private CategoryChoice categoryChoice;
|
||||
|
||||
public InstanceCatalogSignupFragment(){
|
||||
super(R.layout.fragment_onboarding_common, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context){
|
||||
super.onAttach(context);
|
||||
setRefreshEnabled(false);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetCatalogInstances(null, null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogInstance> result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
onDataLoaded(sortInstances(result), false);
|
||||
|
||||
if(langFilterMenu!=null){
|
||||
Menu menu=langFilterMenu.getMenu();
|
||||
menu.clear();
|
||||
menu.add(0, 0, 0, R.string.server_filter_any_language);
|
||||
languages=result.stream().map(i->i.language).distinct().filter(s->s.length()>0).sorted().collect(Collectors.toList());
|
||||
int i=1;
|
||||
for(String lang:languages){
|
||||
Locale locale=Locale.forLanguageTag(lang);
|
||||
String name=locale.getDisplayLanguage(locale);
|
||||
if(name.equals(lang))
|
||||
name=lang.toUpperCase();
|
||||
else
|
||||
name=name.substring(0, 1).toUpperCase()+name.substring(1);
|
||||
menu.add(0, i, 0, name);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getActivity());
|
||||
onDataLoaded(Collections.emptyList(), false);
|
||||
}
|
||||
})
|
||||
.execNoAuth("");
|
||||
getCategoriesRequest=new GetCatalogCategories(null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogCategory> result){
|
||||
getCategoriesRequest=null;
|
||||
CatalogCategory all=new CatalogCategory();
|
||||
all.category="all";
|
||||
categories.add(all);
|
||||
result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add);
|
||||
updateCategories();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
getCategoriesRequest=null;
|
||||
error.showToast(getActivity());
|
||||
CatalogCategory all=new CatalogCategory();
|
||||
all.category="all";
|
||||
categories.add(all);
|
||||
updateCategories();
|
||||
}
|
||||
})
|
||||
.execNoAuth("");
|
||||
}
|
||||
|
||||
private void updateCategories(){
|
||||
// categoriesList.removeAllTabs();
|
||||
// for(CatalogCategory cat:categories){
|
||||
// int titleRes=getTitleForCategory(cat.category);
|
||||
// TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
|
||||
// ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
|
||||
// emoji.setImageResource(getEmojiForCategory(cat.category));
|
||||
// categoriesList.addTab(tab);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
if(getCategoriesRequest!=null)
|
||||
getCategoriesRequest.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
View headerView=new View(getActivity());
|
||||
headerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
backBtn=view.findViewById(R.id.btn_back);
|
||||
backBtn.setOnClickListener(v->{
|
||||
if(searchQueryMode){
|
||||
setSearchQueryMode(false);
|
||||
}else{
|
||||
Nav.finish(this);
|
||||
}
|
||||
});
|
||||
clearSearchBtn=view.findViewById(R.id.clear);
|
||||
clearSearchBtn.setOnClickListener(v->searchEdit.setText(""));
|
||||
nextButton.setEnabled(true);
|
||||
list.setItemAnimator(new BetterItemAnimator());
|
||||
setStatusBarColor(0);
|
||||
topBar=view.findViewById(R.id.top_bar);
|
||||
|
||||
LayerDrawable topBg=(LayerDrawable) topBar.getBackground().mutate();
|
||||
topBar.setBackground(topBg);
|
||||
Drawable topOverlay=topBg.findDrawableByLayerId(R.id.color_overlay);
|
||||
topOverlay.setAlpha(0);
|
||||
|
||||
LayerDrawable btmBg=(LayerDrawable) buttonBar.getBackground().mutate();
|
||||
buttonBar.setBackground(btmBg);
|
||||
Drawable btmOverlay=btmBg.findDrawableByLayerId(R.id.color_overlay);
|
||||
btmOverlay.setAlpha(0);
|
||||
|
||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||
private boolean isAtTop=true;
|
||||
private Animator currentPanelsAnim;
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||
boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop());
|
||||
if(newAtTop!=isAtTop){
|
||||
isAtTop=newAtTop;
|
||||
if(currentPanelsAnim!=null)
|
||||
currentPanelsAnim.cancel();
|
||||
|
||||
AnimatorSet set=new AnimatorSet();
|
||||
set.playTogether(
|
||||
ObjectAnimator.ofInt(topOverlay, "alpha", isAtTop ? 0 : 20),
|
||||
ObjectAnimator.ofInt(btmOverlay, "alpha", isAtTop ? 0 : 20),
|
||||
ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)),
|
||||
ObjectAnimator.ofFloat(buttonBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3))
|
||||
);
|
||||
set.setDuration(150);
|
||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
set.addListener(new AnimatorListenerAdapter(){
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation){
|
||||
currentPanelsAnim=null;
|
||||
}
|
||||
});
|
||||
set.start();
|
||||
currentPanelsAnim=set;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
searchEdit=view.findViewById(R.id.search_edit);
|
||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
searchEdit.postDelayed(searchDebouncer, 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
if((clearSearchBtn.getVisibility()==View.VISIBLE)!=(s.length()>0)){
|
||||
clearSearchBtn.setVisibility(s.length()>0 ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
searchEdit.setOnFocusChangeListener((v, hasFocus)->{
|
||||
if(hasFocus && !searchQueryMode){
|
||||
setSearchQueryMode(true);
|
||||
}
|
||||
});
|
||||
|
||||
FilterChipView langFilter=new FilterChipView(getActivity());
|
||||
langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
|
||||
langFilter.setText(R.string.server_filter_any_language);
|
||||
langFilterMenu=new PopupMenu(getContext(), langFilter);
|
||||
langFilter.setOnTouchListener(langFilterMenu.getDragToOpenListener());
|
||||
langFilter.setOnClickListener(v->langFilterMenu.show());
|
||||
filtersWrap=view.findViewById(R.id.filters_container);
|
||||
filtersScroll=view.findViewById(R.id.filters_scroll);
|
||||
filtersWrap.addView(langFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
FilterChipView speedFilter=new FilterChipView(getActivity());
|
||||
speedFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
|
||||
speedFilterMenu=new PopupMenu(getContext(), speedFilter);
|
||||
speedFilterMenu.getMenu().add(0, 0, 0, R.string.server_filter_any_signup_speed);
|
||||
speedFilterMenu.getMenu().add(0, 1, 0, R.string.server_filter_instant_signup);
|
||||
speedFilterMenu.getMenu().add(0, 2, 0, R.string.server_filter_manual_review);
|
||||
speedFilter.setOnTouchListener(speedFilterMenu.getDragToOpenListener());
|
||||
speedFilter.setOnClickListener(v->speedFilterMenu.show());
|
||||
speedFilter.setText(R.string.server_filter_instant_signup);
|
||||
speedFilter.setSelected(true);
|
||||
filtersWrap.addView(speedFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
speedFilterMenu.setOnMenuItemClickListener(item->{
|
||||
speedFilter.setText(item.getTitle());
|
||||
speedFilter.setSelected(item.getItemId()>0);
|
||||
currentSignupSpeedFilter=SignupSpeedFilter.values()[item.getItemId()];
|
||||
updateFilteredList();
|
||||
return true;
|
||||
});
|
||||
langFilterMenu.setOnMenuItemClickListener(item->{
|
||||
langFilter.setText(item.getTitle());
|
||||
langFilter.setSelected(item.getItemId()>0);
|
||||
currentLanguage=item.getItemId()==0 ? null : languages.get(item.getItemId()-1);
|
||||
updateFilteredList();
|
||||
return true;
|
||||
});
|
||||
|
||||
View divider=new View(getActivity());
|
||||
divider.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Outline));
|
||||
filtersWrap.addView(divider, new LinearLayout.LayoutParams(V.dp(.5f), ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
categoryGeneral=new FilterChipView(getActivity());
|
||||
categoryGeneral.setText(R.string.category_general);
|
||||
categoryGeneral.setTag(CategoryChoice.GENERAL);
|
||||
categoryGeneral.setOnClickListener(this::onCategoryFilterClick);
|
||||
filtersWrap.addView(categoryGeneral, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
categorySpecialInterests=new FilterChipView(getActivity());
|
||||
categorySpecialInterests.setText(R.string.category_special_interests);
|
||||
categorySpecialInterests.setTag(CategoryChoice.SPECIAL);
|
||||
categorySpecialInterests.setOnClickListener(this::onCategoryFilterClick);
|
||||
filtersWrap.addView(categorySpecialInterests, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
regionalFilters=Arrays.stream(CatalogInstance.Region.values()).map(r->{
|
||||
FilterChipView fv=new FilterChipView(getActivity());
|
||||
fv.setTag(r);
|
||||
fv.setText(switch(r){
|
||||
case EUROPE -> R.string.server_filter_region_europe;
|
||||
case NORTH_AMERICA -> R.string.server_filter_region_north_america;
|
||||
case SOUTH_AMERICA -> R.string.server_filter_region_south_america;
|
||||
case AFRICA -> R.string.server_filter_region_africa;
|
||||
case ASIA -> R.string.server_filter_region_asia;
|
||||
case OCEANIA -> R.string.server_filter_region_oceania;
|
||||
});
|
||||
fv.setSelected(r==chosenRegion);
|
||||
fv.setOnClickListener(this::onRegionFilterClick);
|
||||
filtersWrap.addView(fv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return fv;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void onRegionFilterClick(View v){
|
||||
CatalogInstance.Region r=(CatalogInstance.Region) v.getTag();
|
||||
if(chosenRegion==r){
|
||||
chosenRegion=null;
|
||||
v.setSelected(false);
|
||||
}else{
|
||||
if(chosenRegion!=null)
|
||||
filtersWrap.findViewWithTag(chosenRegion).setSelected(false);
|
||||
chosenRegion=r;
|
||||
v.setSelected(true);
|
||||
}
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
private void onCategoryFilterClick(View v){
|
||||
CategoryChoice c=(CategoryChoice) v.getTag();
|
||||
if(categoryChoice==c){
|
||||
categoryChoice=null;
|
||||
v.setSelected(false);
|
||||
}else{
|
||||
if(categoryChoice!=null)
|
||||
filtersWrap.findViewWithTag(categoryChoice).setSelected(false);
|
||||
categoryChoice=c;
|
||||
v.setSelected(true);
|
||||
}
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNextClick(View v){
|
||||
if(chosenInstance==null){
|
||||
String lang=Locale.getDefault().getLanguage();
|
||||
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
|
||||
if(instances.isEmpty()){
|
||||
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||
}
|
||||
if(instances.isEmpty()){
|
||||
return;
|
||||
}
|
||||
chosenInstance=instances.get(new Random().nextInt(instances.size()));
|
||||
}
|
||||
super.onNextClick(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void proceedWithAuthOrSignup(Instance instance){
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||
if(!instance.registrations){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.instance_signup_closed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(instance));
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}
|
||||
|
||||
// private String getEmojiForCategory(String category){
|
||||
// return switch(category){
|
||||
// case "all" -> "💬";
|
||||
// case "academia" -> "📚";
|
||||
// case "activism" -> "✊";
|
||||
// case "food" -> "🍕";
|
||||
// case "furry" -> "🦁";
|
||||
// case "games" -> "🕹";
|
||||
// case "general" -> "🐘";
|
||||
// case "journalism" -> "📰";
|
||||
// case "lgbt" -> "🏳️🌈";
|
||||
// case "regional" -> "📍";
|
||||
// case "art" -> "🎨";
|
||||
// case "music" -> "🎼";
|
||||
// case "tech" -> "📱";
|
||||
// default -> "❓";
|
||||
// };
|
||||
// }
|
||||
|
||||
private int getEmojiForCategory(String category){
|
||||
return switch(category){
|
||||
case "all" -> R.drawable.ic_category_all;
|
||||
case "academia" -> R.drawable.ic_category_academia;
|
||||
case "activism" -> R.drawable.ic_category_activism;
|
||||
case "food" -> R.drawable.ic_category_food;
|
||||
case "furry" -> R.drawable.ic_category_furry;
|
||||
case "games" -> R.drawable.ic_category_games;
|
||||
case "general" -> R.drawable.ic_category_general;
|
||||
case "journalism" -> R.drawable.ic_category_journalism;
|
||||
case "lgbt" -> R.drawable.ic_category_lgbt;
|
||||
case "regional" -> R.drawable.ic_category_regional;
|
||||
case "art" -> R.drawable.ic_category_art;
|
||||
case "music" -> R.drawable.ic_category_music;
|
||||
case "tech" -> R.drawable.ic_category_tech;
|
||||
default -> R.drawable.ic_category_unknown;
|
||||
};
|
||||
}
|
||||
|
||||
private int getTitleForCategory(String category){
|
||||
return switch(category){
|
||||
case "all" -> R.string.category_all;
|
||||
case "academia" -> R.string.category_academia;
|
||||
case "activism" -> R.string.category_activism;
|
||||
case "food" -> R.string.category_food;
|
||||
case "furry" -> R.string.category_furry;
|
||||
case "games" -> R.string.category_games;
|
||||
case "general" -> R.string.category_general;
|
||||
case "journalism" -> R.string.category_journalism;
|
||||
case "lgbt" -> R.string.category_lgbt;
|
||||
case "regional" -> R.string.category_regional;
|
||||
case "art" -> R.string.category_art;
|
||||
case "music" -> R.string.category_music;
|
||||
case "tech" -> R.string.category_tech;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateFilteredList(){
|
||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||
filteredData.clear();
|
||||
if(searchQueryMode){
|
||||
if(!TextUtils.isEmpty(currentSearchQuery)){
|
||||
for(CatalogInstance instance:data){
|
||||
if(instance.domain.contains(currentSearchQuery)){
|
||||
filteredData.add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
for(CatalogInstance instance:data){
|
||||
if(categoryChoice==null || categoryChoice.matches(instance.category)){
|
||||
if(chosenRegion==null || instance.region==chosenRegion){
|
||||
boolean signupSpeedMatches=switch(currentSignupSpeedFilter){
|
||||
case ANY -> true;
|
||||
case INSTANT -> !instance.approvalRequired;
|
||||
case REVIEWED -> instance.approvalRequired;
|
||||
};
|
||||
if(signupSpeedMatches){
|
||||
if(currentLanguage==null || instance.languages.contains(currentLanguage)){
|
||||
filteredData.add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DiffUtil.calculateDiff(new DiffUtil.Callback(){
|
||||
@Override
|
||||
public int getOldListSize(){
|
||||
return prevData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNewListSize(){
|
||||
return filteredData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
|
||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
|
||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||
}
|
||||
}).dispatchUpdatesTo(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
topBar.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0);
|
||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed(){
|
||||
if(searchQueryMode){
|
||||
setSearchQueryMode(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setSearchQueryMode(boolean enabled){
|
||||
searchQueryMode=enabled;
|
||||
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) searchEdit.getLayoutParams();
|
||||
if(searchQueryMode){
|
||||
filtersScroll.setVisibility(View.GONE);
|
||||
lp.removeRule(RelativeLayout.END_OF);
|
||||
backBtn.setScaleX(0.83333333f);
|
||||
backBtn.setScaleY(0.83333333f);
|
||||
backBtn.setTranslationX(V.dp(8));
|
||||
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0));
|
||||
}else{
|
||||
filtersScroll.setVisibility(View.VISIBLE);
|
||||
searchEdit.clearFocus();
|
||||
searchEdit.setText("");
|
||||
lp.addRule(RelativeLayout.END_OF, R.id.btn_back);
|
||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
||||
backBtn.setScaleX(1);
|
||||
backBtn.setScaleY(1);
|
||||
backBtn.setTranslationX(0);
|
||||
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant)));
|
||||
}
|
||||
updateFilteredList();
|
||||
}
|
||||
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
public InstancesAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public InstanceCatalogSignupFragment.InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new InstanceCatalogSignupFragment.InstanceViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(InstanceCatalogSignupFragment.InstanceViewHolder holder, int position){
|
||||
holder.bind(filteredData.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return filteredData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageCountForItem(int position){
|
||||
return filteredData.get(position).thumbnailRequest!=null ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
return filteredData.get(position).thumbnailRequest;
|
||||
}
|
||||
}
|
||||
|
||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable, ImageLoaderViewHolder{
|
||||
private final TextView title, description;
|
||||
private final RadioButton radioButton;
|
||||
private final ImageView thumbnail;
|
||||
private boolean enabled;
|
||||
|
||||
public InstanceViewHolder(){
|
||||
super(getActivity(), R.layout.item_instance_catalog, list);
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
radioButton=findViewById(R.id.radiobtn);
|
||||
thumbnail=findViewById(R.id.image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(CatalogInstance item){
|
||||
title.setText(item.normalizedDomain);
|
||||
radioButton.setChecked(chosenInstance==item);
|
||||
if(item.thumbnailRequest==null)
|
||||
thumbnail.setImageDrawable(null);
|
||||
Instance realInstance=instancesCache.get(item.normalizedDomain);
|
||||
float alpha;
|
||||
if(realInstance!=null && !realInstance.registrations){
|
||||
alpha=0.38f;
|
||||
description.setText(R.string.not_accepting_new_members);
|
||||
enabled=false;
|
||||
}else{
|
||||
alpha=1f;
|
||||
description.setText(item.description);
|
||||
enabled=true;
|
||||
}
|
||||
title.setAlpha(alpha);
|
||||
description.setAlpha(alpha);
|
||||
radioButton.setAlpha(alpha);
|
||||
thumbnail.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
if(chosenInstance==item)
|
||||
return;
|
||||
if(chosenInstance!=null){
|
||||
int idx=filteredData.indexOf(chosenInstance);
|
||||
if(idx!=-1){
|
||||
boolean found=false;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder.getAbsoluteAdapterPosition()==mergeAdapter.getPositionForAdapter(adapter)+idx && holder instanceof InstanceViewHolder ivh){
|
||||
ivh.radioButton.setChecked(false);
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
adapter.notifyItemChanged(idx);
|
||||
}
|
||||
}
|
||||
radioButton.setChecked(true);
|
||||
if(chosenInstance==null)
|
||||
nextButton.setEnabled(true);
|
||||
chosenInstance=item;
|
||||
loadInstanceInfo(chosenInstance.domain, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
thumbnail.setImageDrawable(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
setImage(index, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
|
||||
private enum SignupSpeedFilter{
|
||||
ANY,
|
||||
INSTANT,
|
||||
REVIEWED
|
||||
}
|
||||
|
||||
private enum CategoryChoice{
|
||||
GENERAL,
|
||||
SPECIAL;
|
||||
|
||||
public boolean matches(String category){
|
||||
boolean isGeneral=(category==null || "general".equals(category));
|
||||
return (this==GENERAL)==isGeneral;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
|
||||
private View headerView;
|
||||
private boolean loadedAutocomplete;
|
||||
private ImageButton clearBtn;
|
||||
|
||||
public InstanceChooserLoginFragment(){
|
||||
super(R.layout.fragment_login, 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
dataLoaded();
|
||||
setTitle(R.string.login_title);
|
||||
if(!loadedAutocomplete){
|
||||
loadAutocompleteServers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void proceedWithAuthOrSignup(Instance instance){
|
||||
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateFilteredList(){
|
||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||
filteredData.clear();
|
||||
if(currentSearchQuery.length()>0){
|
||||
boolean foundExactMatch=false;
|
||||
for(CatalogInstance inst:data){
|
||||
if(inst.normalizedDomain.contains(currentSearchQuery)){
|
||||
filteredData.add(inst);
|
||||
if(inst.normalizedDomain.equals(currentSearchQuery))
|
||||
foundExactMatch=true;
|
||||
}
|
||||
}
|
||||
if(!foundExactMatch)
|
||||
filteredData.add(0, fakeInstance);
|
||||
}
|
||||
UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
list.getChildAt(i).invalidateOutline();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
|
||||
}
|
||||
|
||||
private void loadAutocompleteServers(){
|
||||
loadedAutocomplete=true;
|
||||
new GetCatalogInstances(null, null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<CatalogInstance> result){
|
||||
data.clear();
|
||||
data.addAll(sortInstances(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
|
||||
}
|
||||
})
|
||||
.execNoAuth("");
|
||||
}
|
||||
|
||||
// @Override
|
||||
// protected void onUpdateToolbar(){
|
||||
// super.onUpdateToolbar();
|
||||
// Toolbar toolbar=getToolbar();
|
||||
// toolbar.setElevation(0);
|
||||
// toolbar.setBackground(null);
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected RecyclerView.Adapter getAdapter(){
|
||||
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_login, list, false);
|
||||
clearBtn=headerView.findViewById(R.id.search_clear);
|
||||
searchEdit=headerView.findViewById(R.id.search_edit);
|
||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||
searchEdit.removeCallbacks(searchDebouncer);
|
||||
searchEdit.postDelayed(searchDebouncer, 300);
|
||||
|
||||
if(s.length()>0){
|
||||
fakeInstance.domain=fakeInstance.normalizedDomain=s.toString();
|
||||
fakeInstance.description=getString(R.string.loading_instance);
|
||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
||||
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
|
||||
ivh.rebind();
|
||||
}
|
||||
}
|
||||
if(filteredData.isEmpty()){
|
||||
filteredData.add(fakeInstance);
|
||||
adapter.notifyItemInserted(0);
|
||||
}
|
||||
clearBtn.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
clearBtn.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s){
|
||||
}
|
||||
});
|
||||
clearBtn.setOnClickListener(v->searchEdit.setText(""));
|
||||
|
||||
mergeAdapter=new MergeRecyclerAdapter();
|
||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||
return mergeAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
if(parent.getChildViewHolder(view) instanceof InstanceViewHolder){
|
||||
outRect.left=outRect.right=V.dp(16);
|
||||
}
|
||||
}
|
||||
});
|
||||
((UsableRecyclerView)list).setDrawSelectorOnTop(true);
|
||||
}
|
||||
|
||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder>{
|
||||
public InstancesAdapter(){
|
||||
super(imgLoader);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new InstanceViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(InstanceViewHolder holder, int position){
|
||||
holder.bind(filteredData.get(position));
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return filteredData.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
||||
private final TextView title, description;
|
||||
private final RadioButton radioButton;
|
||||
|
||||
public InstanceViewHolder(){
|
||||
super(getActivity(), R.layout.item_instance_login, list);
|
||||
title=findViewById(R.id.title);
|
||||
description=findViewById(R.id.description);
|
||||
radioButton=findViewById(R.id.radiobtn);
|
||||
radioButton.setMinWidth(0);
|
||||
radioButton.setMinHeight(0);
|
||||
|
||||
itemView.setOutlineProvider(new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
outline.setRoundRect(0, getAbsoluteAdapterPosition()==1 ? 0 : V.dp(-4), view.getWidth(), view.getHeight()+(getAbsoluteAdapterPosition()==filteredData.size() ? 0 : V.dp(4)), V.dp(4));
|
||||
}
|
||||
});
|
||||
itemView.setClipToOutline(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(CatalogInstance item){
|
||||
title.setText(item.normalizedDomain);
|
||||
description.setText(item.description);
|
||||
radioButton.setChecked(chosenInstance==item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
if(chosenInstance==item)
|
||||
return;
|
||||
if(chosenInstance!=null){
|
||||
int idx=filteredData.indexOf(chosenInstance);
|
||||
if(idx!=-1){
|
||||
boolean found=false;
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder.getAbsoluteAdapterPosition()==mergeAdapter.getPositionForAdapter(adapter)+idx && holder instanceof InstanceViewHolder ivh){
|
||||
ivh.radioButton.setChecked(false);
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
adapter.notifyItemChanged(idx);
|
||||
}
|
||||
}
|
||||
radioButton.setChecked(true);
|
||||
if(chosenInstance==null)
|
||||
nextButton.setEnabled(true);
|
||||
chosenInstance=item;
|
||||
loadInstanceInfo(chosenInstance.domain, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.joinmastodon.android.fragments.onboarding;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -22,14 +24,14 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class InstanceRulesFragment extends AppKitFragment{
|
||||
public class InstanceRulesFragment extends ToolbarFragment{
|
||||
private UsableRecyclerView list;
|
||||
private MergeRecyclerAdapter adapter;
|
||||
private Button btn;
|
||||
@@ -47,31 +49,28 @@ public class InstanceRulesFragment extends AppKitFragment{
|
||||
super.onAttach(activity);
|
||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||
setTitle(R.string.instance_rules_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||
|
||||
list=view.findViewById(R.id.list);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
||||
TextView title=headerView.findViewById(R.id.title);
|
||||
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
||||
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
|
||||
title.setText(R.string.instance_rules_title);
|
||||
subtitle.setText(getString(R.string.instance_rules_subtitle, instance.uri));
|
||||
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
||||
TextView text=headerView.findViewById(R.id.text);
|
||||
text.setText(getString(R.string.instance_rules_subtitle, instance.uri));
|
||||
|
||||
adapter=new MergeRecyclerAdapter();
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||
adapter.addAdapter(new ItemsAdapter());
|
||||
list.setAdapter(adapter);
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
|
||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
||||
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -79,7 +78,15 @@ public class InstanceRulesFragment extends AppKitFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
// setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
// view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
}
|
||||
|
||||
// @Override
|
||||
protected void onUpdateToolbar(){
|
||||
// super.onUpdateToolbar();
|
||||
getToolbar().setBackground(null);
|
||||
getToolbar().setElevation(0);
|
||||
}
|
||||
|
||||
protected void onButtonClick(){
|
||||
@@ -119,23 +126,22 @@ public class InstanceRulesFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private class ItemViewHolder extends BindableViewHolder<Instance.Rule>{
|
||||
private final TextView title, subtitle;
|
||||
private final ImageView checkbox;
|
||||
private final TextView text, number;
|
||||
|
||||
public ItemViewHolder(){
|
||||
super(getActivity(), R.layout.item_report_choice, list);
|
||||
title=findViewById(R.id.title);
|
||||
subtitle=findViewById(R.id.subtitle);
|
||||
checkbox=findViewById(R.id.checkbox);
|
||||
subtitle.setVisibility(View.GONE);
|
||||
super(getActivity(), R.layout.item_server_rule, list);
|
||||
text=findViewById(R.id.text);
|
||||
number=findViewById(R.id.number);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public void onBind(Instance.Rule item){
|
||||
if(item.parsedText==null){
|
||||
item.parsedText=HtmlParser.parseLinks(item.text);
|
||||
}
|
||||
title.setText(item.parsedText);
|
||||
text.setText(item.parsedText);
|
||||
number.setText(String.format("%d", getAbsoluteAdapterPosition()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,15 @@ import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Application;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.io.File;
|
||||
@@ -49,30 +50,28 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.APIRequest;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class SignupFragment extends AppKitFragment{
|
||||
public class SignupFragment extends ToolbarFragment{
|
||||
private static final int AVATAR_RESULT=198;
|
||||
private static final String TAG="SignupFragment";
|
||||
|
||||
private Instance instance;
|
||||
|
||||
private EditText displayName, username, email, password, reason;
|
||||
private EditText displayName, username, email, password, passwordConfirm, reason;
|
||||
private FloatingHintEditTextLayout displayNameWrap, usernameWrap, emailWrap, passwordWrap, passwordConfirmWrap, reasonWrap;
|
||||
private TextView reasonExplain;
|
||||
private Button btn;
|
||||
private View buttonBar;
|
||||
private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState());
|
||||
private ImageView avatar;
|
||||
private APIRequest currentBackgroundRequest;
|
||||
private Application apiApplication;
|
||||
private Token apiToken;
|
||||
private boolean submitAfterGettingToken;
|
||||
private ProgressDialog progressDialog;
|
||||
private Uri avatarUri;
|
||||
private File avatarFile;
|
||||
private HashSet<EditText> errorFields=new HashSet<>();
|
||||
|
||||
@Override
|
||||
@@ -81,25 +80,30 @@ public class SignupFragment extends AppKitFragment{
|
||||
setRetainInstance(true);
|
||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||
createAppAndGetToken();
|
||||
setTitle(R.string.signup_title);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||
View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false);
|
||||
|
||||
TextView title=view.findViewById(R.id.title);
|
||||
TextView domain=view.findViewById(R.id.domain);
|
||||
displayName=view.findViewById(R.id.display_name);
|
||||
username=view.findViewById(R.id.username);
|
||||
email=view.findViewById(R.id.email);
|
||||
password=view.findViewById(R.id.password);
|
||||
avatar=view.findViewById(R.id.avatar);
|
||||
passwordConfirm=view.findViewById(R.id.password_confirm);
|
||||
reason=view.findViewById(R.id.reason);
|
||||
reasonExplain=view.findViewById(R.id.reason_explain);
|
||||
View avaWrap=view.findViewById(R.id.ava_wrap);
|
||||
|
||||
title.setText(getString(R.string.signup_title, instance.uri));
|
||||
displayNameWrap=view.findViewById(R.id.display_name_wrap);
|
||||
usernameWrap=view.findViewById(R.id.username_wrap);
|
||||
emailWrap=view.findViewById(R.id.email_wrap);
|
||||
passwordWrap=view.findViewById(R.id.password_wrap);
|
||||
passwordConfirmWrap=view.findViewById(R.id.password_confirm_wrap);
|
||||
reasonWrap=view.findViewById(R.id.reason_wrap);
|
||||
|
||||
domain.setText('@'+instance.uri);
|
||||
|
||||
username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||
@@ -114,23 +118,20 @@ public class SignupFragment extends AppKitFragment{
|
||||
btn=view.findViewById(R.id.btn_next);
|
||||
btn.setOnClickListener(v->onButtonClick());
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||
updateButtonState();
|
||||
|
||||
username.addTextChangedListener(buttonStateUpdater);
|
||||
email.addTextChangedListener(buttonStateUpdater);
|
||||
password.addTextChangedListener(buttonStateUpdater);
|
||||
passwordConfirm.addTextChangedListener(buttonStateUpdater);
|
||||
reason.addTextChangedListener(buttonStateUpdater);
|
||||
|
||||
username.addTextChangedListener(new ErrorClearingListener(username));
|
||||
email.addTextChangedListener(new ErrorClearingListener(email));
|
||||
password.addTextChangedListener(new ErrorClearingListener(password));
|
||||
passwordConfirm.addTextChangedListener(new ErrorClearingListener(passwordConfirm));
|
||||
reason.addTextChangedListener(new ErrorClearingListener(reason));
|
||||
|
||||
avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22));
|
||||
avaWrap.setClipToOutline(true);
|
||||
avaWrap.setOnClickListener(v->onAvatarClick());
|
||||
|
||||
if(!instance.approvalRequired){
|
||||
reason.setVisibility(View.GONE);
|
||||
reasonExplain.setVisibility(View.GONE);
|
||||
@@ -142,10 +143,23 @@ public class SignupFragment extends AppKitFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
}
|
||||
|
||||
// @Override
|
||||
protected void onUpdateToolbar(){
|
||||
// super.onUpdateToolbar();
|
||||
getToolbar().setBackground(null);
|
||||
getToolbar().setElevation(0);
|
||||
}
|
||||
|
||||
private void onButtonClick(){
|
||||
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
|
||||
passwordConfirm.setError(getString(R.string.signup_passwords_dont_match));
|
||||
passwordConfirmWrap.setErrorState();
|
||||
return;
|
||||
}
|
||||
showProgressDialog();
|
||||
if(currentBackgroundRequest!=null){
|
||||
submitAfterGettingToken=true;
|
||||
@@ -160,32 +174,8 @@ public class SignupFragment extends AppKitFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAvatar(Runnable onDone){
|
||||
// Need to copy the avatar from the content provider to somewhere accessible in case the app gets killed between signup and account activation
|
||||
Activity activity=getActivity();
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
String origName=UiUtils.getFileName(avatarUri);
|
||||
avatarFile=new File(activity.getCacheDir(), System.currentTimeMillis()+origName.substring(origName.lastIndexOf('.')));
|
||||
try(InputStream in=activity.getContentResolver().openInputStream(avatarUri);
|
||||
FileOutputStream out=new FileOutputStream(avatarFile)){
|
||||
byte[] buf=new byte[10240];
|
||||
int read;
|
||||
while((read=in.read(buf))>0){
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "copyAvatar: error copying", x);
|
||||
}
|
||||
activity.runOnUiThread(onDone);
|
||||
});
|
||||
}
|
||||
|
||||
private void submit(){
|
||||
if(avatarUri!=null && (avatarFile==null || !avatarFile.exists())){
|
||||
copyAvatar(this::actuallySubmit);
|
||||
}else{
|
||||
actuallySubmit();
|
||||
}
|
||||
actuallySubmit();
|
||||
}
|
||||
|
||||
private void actuallySubmit(){
|
||||
@@ -204,9 +194,7 @@ public class SignupFragment extends AppKitFragment{
|
||||
fakeAccount.acct=fakeAccount.username=username;
|
||||
fakeAccount.id="tmp"+System.currentTimeMillis();
|
||||
fakeAccount.displayName=displayName.getText().toString();
|
||||
if(avatarFile!=null)
|
||||
fakeAccount.avatar=avatarFile.getAbsolutePath();
|
||||
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, false);
|
||||
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, new AccountActivationInfo(email, System.currentTimeMillis()));
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
|
||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||
@@ -225,6 +213,7 @@ public class SignupFragment extends AppKitFragment{
|
||||
continue;
|
||||
}
|
||||
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
||||
getFieldWrapByName(fieldName).setErrorState();
|
||||
errorFields.add(field);
|
||||
if(first){
|
||||
first=false;
|
||||
@@ -252,6 +241,16 @@ public class SignupFragment extends AppKitFragment{
|
||||
};
|
||||
}
|
||||
|
||||
private FloatingHintEditTextLayout getFieldWrapByName(String name){
|
||||
return switch(name){
|
||||
case "email" -> emailWrap;
|
||||
case "username" -> usernameWrap;
|
||||
case "password" -> passwordWrap;
|
||||
case "reason" -> reasonWrap;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private void showProgressDialog(){
|
||||
if(progressDialog==null){
|
||||
progressDialog=new ProgressDialog(getActivity());
|
||||
@@ -262,7 +261,7 @@ public class SignupFragment extends AppKitFragment{
|
||||
}
|
||||
|
||||
private void updateButtonState(){
|
||||
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && (!instance.approvalRequired || reason.length()>0));
|
||||
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && passwordConfirm.length()>=8 && (!instance.approvalRequired || reason.length()>0));
|
||||
}
|
||||
|
||||
private void createAppAndGetToken(){
|
||||
@@ -324,20 +323,6 @@ public class SignupFragment extends AppKitFragment{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||
if(requestCode==AVATAR_RESULT && resultCode==Activity.RESULT_OK){
|
||||
avatarUri=data.getData();
|
||||
if(avatarFile!=null && avatarFile.exists())
|
||||
avatarFile.delete();
|
||||
ViewImageLoader.load(avatar, getResources().getDrawable(R.drawable.default_avatar), new UrlImageLoaderRequest(avatarUri, V.dp(100), V.dp(100)));
|
||||
}
|
||||
}
|
||||
|
||||
private void onAvatarClick(){
|
||||
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*").addCategory(Intent.CATEGORY_OPENABLE), AVATAR_RESULT);
|
||||
}
|
||||
|
||||
private class ErrorClearingListener implements TextWatcher{
|
||||
public final EditText editText;
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false);
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
||||
for(StatusDisplayItem item:items){
|
||||
if(item instanceof ImageStatusDisplayItem isdi){
|
||||
isdi.horizontalInset=V.dp(40+32);
|
||||
|
||||
@@ -164,6 +164,10 @@ public class Account extends BaseModel{
|
||||
return '@'+acct;
|
||||
}
|
||||
|
||||
public String getShortUsername() {
|
||||
return '@'+acct.split("@")[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Account{"+
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Parcel
|
||||
public class Announcement extends BaseModel implements DisplayItemsParent {
|
||||
@RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
public String content;
|
||||
public Instant startsAt;
|
||||
public Instant endsAt;
|
||||
public boolean published;
|
||||
public boolean allDay;
|
||||
public Instant publishedAt;
|
||||
public Instant updatedAt;
|
||||
public boolean read;
|
||||
public List<Emoji> emojis;
|
||||
public List<Mention> mentions;
|
||||
public List<Hashtag> tags;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Announcement{" +
|
||||
"id='" + id + '\'' +
|
||||
", content='" + content + '\'' +
|
||||
", startsAt=" + startsAt +
|
||||
", endsAt=" + endsAt +
|
||||
", published=" + published +
|
||||
", allDay=" + allDay +
|
||||
", publishedAt=" + publishedAt +
|
||||
", updatedAt=" + updatedAt +
|
||||
", read=" + read +
|
||||
", emojis=" + emojis +
|
||||
", mentions=" + mentions +
|
||||
", tags=" + tags +
|
||||
'}';
|
||||
}
|
||||
|
||||
public Status toStatus() {
|
||||
Status s = new Status();
|
||||
s.id = id;
|
||||
s.mediaAttachments = List.of();
|
||||
s.createdAt = startsAt != null ? startsAt : publishedAt;
|
||||
if (updatedAt != null) s.editedAt = updatedAt;
|
||||
s.content = s.text = content;
|
||||
s.spoilerText = "";
|
||||
s.visibility = StatusPrivacy.PUBLIC;
|
||||
s.mentions = List.of();
|
||||
s.tags = List.of();
|
||||
s.emojis = List.of();
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,11 @@ import org.parceler.Parcel;
|
||||
import org.parceler.ParcelConstructor;
|
||||
import org.parceler.ParcelProperty;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Parcel
|
||||
public class Attachment extends BaseModel{
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
public Type type;
|
||||
@@ -85,6 +87,12 @@ public class Attachment extends BaseModel{
|
||||
if(placeholder!=null)
|
||||
blurhashPlaceholder=new BlurHashDrawable(placeholder, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
if (id == null) {
|
||||
// akkoma servers doesn't provide IDs for attachments,
|
||||
// but IDs are needed by the AudioPlayerService
|
||||
id = "" + this.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,12 +6,14 @@ import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Parcel
|
||||
public class Filter extends BaseModel{
|
||||
@RequiredField
|
||||
public String id;
|
||||
@@ -21,6 +23,7 @@ public class Filter extends BaseModel{
|
||||
public Instant expiresAt;
|
||||
public boolean irreversible;
|
||||
public boolean wholeWord;
|
||||
public FilterAction filterAction;
|
||||
|
||||
@SerializedName("context")
|
||||
private List<FilterContext> _context;
|
||||
@@ -76,4 +79,11 @@ public class Filter extends BaseModel{
|
||||
@SerializedName("thread")
|
||||
THREAD
|
||||
}
|
||||
|
||||
public enum FilterAction{
|
||||
@SerializedName("hide")
|
||||
HIDE,
|
||||
@SerializedName("warn")
|
||||
WARN
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.parceler.Parcel;
|
||||
|
||||
@Parcel
|
||||
public class FilterResult extends BaseModel {
|
||||
public Filter filter;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class Instance extends BaseModel{
|
||||
/**
|
||||
* Admin-defined description of the Mastodon site.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String description;
|
||||
/**
|
||||
* A shorter description defined by the admin.
|
||||
@@ -37,7 +37,7 @@ public class Instance extends BaseModel{
|
||||
/**
|
||||
* An email that may be contacted for any inquiries.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String email;
|
||||
/**
|
||||
* The version of Mastodon installed on the instance.
|
||||
@@ -82,6 +82,8 @@ public class Instance extends BaseModel{
|
||||
// non-standard field in some Mastodon forks
|
||||
public int maxTootChars;
|
||||
|
||||
public V2 v2;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException{
|
||||
super.postprocess();
|
||||
@@ -176,4 +178,19 @@ public class Instance extends BaseModel{
|
||||
public int minExpiration;
|
||||
public int maxExpiration;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class V2 extends BaseModel {
|
||||
public V2.Configuration configuration;
|
||||
|
||||
@Parcel
|
||||
public static class Configuration {
|
||||
public TranslationConfiguration translation;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class TranslationConfiguration{
|
||||
public boolean enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ public class Poll extends BaseModel{
|
||||
public String title;
|
||||
public Integer votesCount;
|
||||
|
||||
public Option() {}
|
||||
public Option(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Option{"+
|
||||
|
||||
@@ -43,7 +43,9 @@ public class PushNotification extends BaseModel{
|
||||
@SerializedName("follow")
|
||||
FOLLOW(R.string.notification_type_follow),
|
||||
@SerializedName("poll")
|
||||
POLL(R.string.notification_type_poll);
|
||||
POLL(R.string.notification_type_poll),
|
||||
@SerializedName("status")
|
||||
STATUS(R.string.sk_notification_type_status);
|
||||
|
||||
@StringRes
|
||||
public final int localizedName;
|
||||
|
||||
@@ -43,10 +43,11 @@ public class PushSubscription extends BaseModel implements Cloneable{
|
||||
public boolean reblog;
|
||||
public boolean mention;
|
||||
public boolean poll;
|
||||
public boolean status;
|
||||
|
||||
public static Alerts ofAll(){
|
||||
Alerts alerts=new Alerts();
|
||||
alerts.follow=alerts.favourite=alerts.reblog=alerts.mention=alerts.poll=true;
|
||||
alerts.follow=alerts.favourite=alerts.reblog=alerts.mention=alerts.poll=alerts.status=true;
|
||||
return alerts;
|
||||
}
|
||||
|
||||
@@ -58,6 +59,7 @@ public class PushSubscription extends BaseModel implements Cloneable{
|
||||
", reblog="+reblog+
|
||||
", mention="+mention+
|
||||
", poll="+poll+
|
||||
", status="+status+
|
||||
'}';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.model.Poll.Option;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Parcel
|
||||
public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
||||
@RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
public Instant scheduledAt;
|
||||
@RequiredField
|
||||
public Params params;
|
||||
@RequiredField
|
||||
public List<Attachment> mediaAttachments;
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class Params {
|
||||
@RequiredField
|
||||
public String text;
|
||||
public String spoilerText;
|
||||
@RequiredField
|
||||
public StatusPrivacy visibility;
|
||||
public long inReplyToId;
|
||||
public ScheduledPoll poll;
|
||||
public boolean sensitive;
|
||||
public boolean withRateLimit;
|
||||
public String language;
|
||||
public String idempotency;
|
||||
public String applicationId;
|
||||
public List<String> mediaIds;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class ScheduledPoll {
|
||||
@RequiredField
|
||||
public String expiresIn;
|
||||
@RequiredField
|
||||
public List<String> options;
|
||||
public boolean multiple;
|
||||
public boolean hideTotals;
|
||||
|
||||
public Poll toPoll() {
|
||||
Poll p = new Poll();
|
||||
p.voted = true;
|
||||
p.emojis = List.of();
|
||||
p.ownVotes = List.of();
|
||||
p.multiple = multiple;
|
||||
p.options = options.stream().map(Option::new).collect(Collectors.toList());
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
public Status toStatus() {
|
||||
Status s = new Status();
|
||||
s.id = id;
|
||||
s.mediaAttachments = mediaAttachments;
|
||||
s.createdAt = scheduledAt;
|
||||
s.inReplyToId = params.inReplyToId > 0 ? "" + params.inReplyToId : null;
|
||||
s.content = s.text = params.text;
|
||||
s.spoilerText = params.spoilerText;
|
||||
s.visibility = params.visibility;
|
||||
s.language = params.language;
|
||||
s.sensitive = params.sensitive;
|
||||
s.mentions = List.of();
|
||||
s.tags = List.of();
|
||||
s.emojis = List.of();
|
||||
if (params.poll != null) s.poll = params.poll.toPoll();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
public Instant createdAt;
|
||||
@RequiredField
|
||||
public Account account;
|
||||
// @RequiredField
|
||||
// @RequiredField
|
||||
public String content;
|
||||
@RequiredField
|
||||
public StatusPrivacy visibility;
|
||||
@@ -40,6 +40,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
||||
public long favouritesCount;
|
||||
public long repliesCount;
|
||||
public Instant editedAt;
|
||||
public List<FilterResult> filtered;
|
||||
|
||||
public String url;
|
||||
public String inReplyToId;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user