Compare commits
580 Commits
v1.1.4+for
...
bugfix/#34
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
101e7efd74 | ||
|
|
58f79e06ef | ||
|
|
0065b93060 | ||
|
|
3f9dbd6fe2 | ||
|
|
7ceff3eaa4 | ||
|
|
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 | ||
|
|
ca5f189e70 | ||
|
|
a70bd4f906 | ||
|
|
4adac359e3 | ||
|
|
9adaf12c00 | ||
|
|
8bbfa2e417 | ||
|
|
e1ca97f323 | ||
|
|
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 | ||
|
|
de97493e6a | ||
|
|
3a24ff0d15 | ||
|
|
c463a3fc39 | ||
|
|
fc845685cc | ||
|
|
0ef0aa1a44 | ||
|
|
337689aa45 | ||
|
|
f7e3423f9c | ||
|
|
b465c09cc8 | ||
|
|
ac6c0651d6 | ||
|
|
18af6f5a12 | ||
|
|
d11ee3a702 | ||
|
|
6d9f9ce2d2 | ||
|
|
ec1496a4cc | ||
|
|
41e19185e8 | ||
|
|
e15dd6024f | ||
|
|
e52dffeece | ||
|
|
5b85bb427d | ||
|
|
4d62388617 | ||
|
|
04b8055474 | ||
|
|
3c34b6a7d2 | ||
|
|
de4964c2cd | ||
|
|
fbcaa05c03 | ||
|
|
883f28696e | ||
|
|
df52230837 | ||
|
|
a90f26a37a | ||
|
|
8c1f76d7fa | ||
|
|
f384d44f8f | ||
|
|
4ab6ed55f5 | ||
|
|
cf99bf5152 | ||
|
|
10779717cf | ||
|
|
4e5c2a9ecf | ||
|
|
db4c1bfe47 | ||
|
|
27afba1cf2 | ||
|
|
4895425b40 | ||
|
|
004c414fba | ||
|
|
c8e38b134c | ||
|
|
de5a911286 | ||
|
|
606cd7442e | ||
|
|
3ebc972268 | ||
|
|
4e39bb381c | ||
|
|
b6178681b0 | ||
|
|
29abf70cec | ||
|
|
8d63be513d | ||
|
|
e63b9d0dd6 | ||
|
|
b1fda17ac7 | ||
|
|
bad44b145c | ||
|
|
77669cedf6 | ||
|
|
19238c389f | ||
|
|
1747ff98b5 | ||
|
|
8fa5824e3e | ||
|
|
6a674d7a7e | ||
|
|
dad3b8cd6b | ||
|
|
9179d2198d | ||
|
|
d096bef234 | ||
|
|
f2c47a1b84 | ||
|
|
bc2ac4e915 | ||
|
|
ff215412c8 |
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,9 +1,8 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: LucasGGamerM
|
||||||
patreon: # mastodon
|
patreon: # mastodon
|
||||||
open_collective: # Replace with a single Open Collective username e.g., user1
|
open_collective: # Replace with a single Open Collective username e.g., user1
|
||||||
ko_fi: xsk22
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
liberapay: # Replace with a single Liberapay username e.g., user1
|
liberapay: # Replace with a single Liberapay username e.g., user1
|
||||||
|
|||||||
101
README.md
101
README.md
@@ -1,23 +1,32 @@
|
|||||||

|

|
||||||
|
|
||||||
# Megalodon
|
# Moshidon, the material you mastodon client!
|
||||||
|
|
||||||
[](https://translate.codeberg.org/engage/megalodon/)
|
> A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly won’t ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
|
||||||
|
|
||||||
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
|
|
||||||
|
|
||||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk"><img height="50" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a>
|
|
||||||
|
|
||||||
> A fork of the [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly won’t ever be implemented, such as the federated timeline, unlisted posting and an image description viewer.
|
|
||||||
|
|
||||||
|
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## Key features
|
## 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**
|
### **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”).**
|
**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”).**
|
||||||
@@ -46,63 +55,29 @@ This is important to **ensure the content you’re sharing is as accessible as p
|
|||||||
|
|
||||||
On the Fediverse, it’s quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
|
On the Fediverse, it’s quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
|
||||||
|
|
||||||
|
### **Bookmarks**
|
||||||
|
|
||||||
|
**They allow for quickly saving posts and viewing them through the Bookmarks button on the top right of your profile.**
|
||||||
|
|
||||||
|
To bookmark a post, press the button between the Favorite and Share buttons on the bottom of the post. Bookmarks are saved privately, so the post authors won’t know you saved their post – the list of bookmarked posts is only visible to you.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### From app stores
|
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Moshidon will automatically notify you about new updates inside the app.**
|
||||||
|
|
||||||
* **[Izzy's F-Droid repository](https://apt.izzysoft.de/fdroid/repo)**: [apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
|
To install this app on your Android device, download the [latest release from GitHub](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||||
|
|
||||||
Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first:
|
Moshidon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Megalodon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
||||||
|
|
||||||
`https://apt.izzysoft.de/fdroid/repo`
|
|
||||||
|
|
||||||
* **[Google Play Store](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)**: [play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
|
|
||||||
|
|
||||||
* **[F-Droid.org](https://f-droid.org)?** Not yet, sorry!
|
|
||||||
|
|
||||||
If you want, you can help me figure out if something's missing in the [Issue #47: F-Droid.org](https://github.com/sk22/megalodon/issues/47)
|
|
||||||
|
|
||||||
### Directly from GitHub
|
|
||||||
|
|
||||||
Press the download button to download the APK. Open the downloaded file on your Android device to install it. Megalodon will automatically notify you about new updates inside the app.
|
|
||||||
|
|
||||||
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
|
||||||
|
|
||||||
You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/sk22/megalodon/releases) page.
|
|
||||||
|
|
||||||
Megalodon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Megalodon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
## Release variants
|
## Release variants
|
||||||
|
|
||||||
All downloads can be found on the [Releases](https://github.com/sk22/megalodon/releases) page.
|
All downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||||
|
|
||||||
**`megalodon.apk`**
|
**`moshidon.apk`**
|
||||||
|
|
||||||
Variant with an integrated updater. If you download Megalodon from here (and not from an app store), just download the regular `megalodon.apk`.
|
|
||||||
|
|
||||||
**`upstream-1234abc.apk`**
|
|
||||||
|
|
||||||
This is an **unmodified version** of the official [Mastodon for Android](https://github.com/mastodon/mastodon-android) app the respective Megalodon release is based on. Should you find any bugs in Megalodon (which you will), try to see if it occurs with this variant, too. The last 7 digits of the file name are important to know which version of the official app you're using.
|
|
||||||
|
|
||||||
<!-- **`megalodon-fdroid.apk`**
|
|
||||||
|
|
||||||
Variant without the integrated updater. This is the variant to be published to F-Droid.org where an integrated updater is not necessary. -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contribution
|
|
||||||
|
|
||||||
### Translation
|
|
||||||
|
|
||||||
As with the source code, the translation is sourced from the official project, which you can contribute to on the official “**Mastodon for Android**” Crowdin project: https://crowdin.com/project/mastodon-for-android
|
|
||||||
|
|
||||||
There's also a handful of custom strings exclusive to this projects that would need to be translated. You can help translate **Megalodon** on Weblate: https://translate.codeberg.org/projects/megalodon/
|
|
||||||
|
|
||||||
[](https://translate.codeberg.org/engage/megalodon/)
|
|
||||||
|
|
||||||
|
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -131,10 +106,6 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Show visibility of original post when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-reply-visibility)
|
* [Show visibility of original post when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-reply-visibility)
|
||||||
* [Clickable reply/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
|
* [Clickable reply/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
|
||||||
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
|
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
|
||||||
* [Add push notification setting for post notifications](https://github.com/sk22/megalodon/commit/b190480d7739be47f23543d9e7644660f9b4b4ee)
|
|
||||||
* [Add option to allow voting for multiple options on polls](https://github.com/sk22/megalodon/commit/5b28468efd49387b4f8b83f142f3adf3104ca60c)
|
|
||||||
* [Add translate function](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/translate-button)
|
|
||||||
* [Add language selector](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/language-selector)
|
|
||||||
|
|
||||||
|
|
||||||
### Behavior
|
### Behavior
|
||||||
@@ -146,12 +117,6 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
|
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
|
||||||
* [Option to always reveal content warnings](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
|
* [Option to always reveal content warnings](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
|
||||||
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
|
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
|
||||||
* [No ellipsis for long poll answers](https://github.com/mastodon/mastodon-android/commit/c9aae828e2518adccdc092e41f8d1f0489636271)
|
|
||||||
* [Show poll vote button for multiple and single answer polls](https://github.com/mastodon/mastodon-android/commit/e14dfda2fdf32f0fa3043504ac5831683a87559a)
|
|
||||||
* [Show own vote after voting](https://github.com/mastodon/mastodon-android/commit/4ab9e25fec4fd9c10b7a8ddd1be522b3cc12cf28) ([Closes issue](https://github.com/mastodon/mastodon-android/commit/4ab9e25fec4fd9c10b7a8ddd1be522b3cc12cf28))
|
|
||||||
* [Make inline emoji search case-insensitive and don't only search from start of emoji names](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:better-inline-emoji-search) ([Pull request](https://github.com/mastodon/mastodon-android/pull/445))
|
|
||||||
* [Include subject line when sharing e.g. a website to Megalodon](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:external-share-include-subject)
|
|
||||||
* [Improve semantics for voting on polls (radio buttons and checkboxes)](https://github.com/sk22/megalodon/commit/6fd58c96827cb1d2da329cebdc170a1425dd18d7)
|
|
||||||
|
|
||||||
|
|
||||||
### Visual
|
### Visual
|
||||||
@@ -159,8 +124,6 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
|
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
|
||||||
* [Improvements to the true black mode](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:true-black-improvements)
|
* [Improvements to the true black mode](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:true-black-improvements)
|
||||||
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)
|
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)
|
||||||
* [Custom color themes](https://github.com/sk22/megalodon/pull/124) by [@LucasGGamerM](https://github.com/LucasGGamerM)
|
|
||||||
* [Custom "megalodon" text logo](https://github.com/sk22/megalodon/commit/563afd487ca5c608cfbb00fa3909d3c27384acc0) by [@LucasGGamerM](https://github.com/LucasGGamerM)
|
|
||||||
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
@@ -177,4 +140,4 @@ This project is released under the [GPL-3 License](./LICENSE).
|
|||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
<a rel="me" href="https://floss.social/@megalodon">@megalodon<wbr>@floss.social</a>
|
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
title: Megalodon
|
title: Moshidon
|
||||||
layout: default
|
layout: default
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Megalodon</title>
|
<title>Moshidon</title>
|
||||||
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
||||||
<link rel="me" href="https://floss.social/@mastodon">
|
<link rel="me" href="https://floss.social/@mastodon">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
||||||
@@ -14,4 +14,4 @@
|
|||||||
{{ content }}
|
{{ content }}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,17 +5,14 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
compileSdk 33
|
compileSdk 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
archivesBaseName = "megalodon"
|
archivesBaseName = "moshidon"
|
||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.moshinda"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 58
|
versionCode 85
|
||||||
versionName "1.1.4+fork.58"
|
versionName "1.1.4+fork.85.moshinda"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "en", "ar-rSA", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES",
|
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"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -80,4 +77,4 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
|
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
mastodon/src/github/ic_launcher-playstore.png
Normal file
BIN
mastodon/src/github/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -61,6 +61,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
info=new UpdateInfo();
|
info=new UpdateInfo();
|
||||||
info.version=prefs.getString("version", null);
|
info.version=prefs.getString("version", null);
|
||||||
info.size=prefs.getLong("apkSize", 0);
|
info.size=prefs.getLong("apkSize", 0);
|
||||||
|
info.changelog=prefs.getString("changelog", null);
|
||||||
downloadID=prefs.getLong("downloadID", 0);
|
downloadID=prefs.getLong("downloadID", 0);
|
||||||
if(downloadID==0 || !getUpdateApkFile().exists()){
|
if(downloadID==0 || !getUpdateApkFile().exists()){
|
||||||
state=UpdateState.UPDATE_AVAILABLE;
|
state=UpdateState.UPDATE_AVAILABLE;
|
||||||
@@ -84,6 +85,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
.remove("apkURL")
|
.remove("apkURL")
|
||||||
.remove("checkedByBuild")
|
.remove("checkedByBuild")
|
||||||
.remove("downloadID")
|
.remove("downloadID")
|
||||||
|
.remove("changelog")
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,11 +113,12 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
|
|
||||||
private void actuallyCheckForUpdates(){
|
private void actuallyCheckForUpdates(){
|
||||||
Request req=new Request.Builder()
|
Request req=new Request.Builder()
|
||||||
.url("https://api.github.com/repos/sk22/megalodon/releases/latest")
|
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases/latest")
|
||||||
.build();
|
.build();
|
||||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||||
try(Response resp=call.execute()){
|
try(Response resp=call.execute()){
|
||||||
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
|
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
|
||||||
|
String changelog=obj.get("body").getAsString();
|
||||||
String tag=obj.get("tag_name").getAsString();
|
String tag=obj.get("tag_name").getAsString();
|
||||||
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
||||||
Matcher matcher=pattern.matcher(tag);
|
Matcher matcher=pattern.matcher(tag);
|
||||||
@@ -144,13 +147,14 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
||||||
for(JsonElement el:obj.getAsJsonArray("assets")){
|
for(JsonElement el:obj.getAsJsonArray("assets")){
|
||||||
JsonObject asset=el.getAsJsonObject();
|
JsonObject asset=el.getAsJsonObject();
|
||||||
if("megalodon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
if("moshidon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
||||||
long size=asset.get("size").getAsLong();
|
long size=asset.get("size").getAsLong();
|
||||||
String url=asset.get("browser_download_url").getAsString();
|
String url=asset.get("browser_download_url").getAsString();
|
||||||
|
|
||||||
UpdateInfo info=new UpdateInfo();
|
UpdateInfo info=new UpdateInfo();
|
||||||
info.size=size;
|
info.size=size;
|
||||||
info.version=version;
|
info.version=version;
|
||||||
|
info.changelog=changelog;
|
||||||
this.info=info;
|
this.info=info;
|
||||||
|
|
||||||
getPrefs().edit()
|
getPrefs().edit()
|
||||||
@@ -158,6 +162,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
.putString("version", version)
|
.putString("version", version)
|
||||||
.putString("apkURL", url)
|
.putString("apkURL", url)
|
||||||
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
||||||
|
.putString("changelog", changelog)
|
||||||
.remove("downloadID")
|
.remove("downloadID")
|
||||||
.apply();
|
.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.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<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="${applicationId}.permission.C2D_MESSAGE"/>
|
||||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
<application
|
<application
|
||||||
android:name=".MastodonApp"
|
android:name=".MastodonApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:label="@string/sk_app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<data android:scheme="megalodon-android-auth" android:host="callback"/>
|
<data android:scheme="moshidon-android-auth" android:host="callback"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize">
|
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize">
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 15 KiB |
@@ -52,7 +52,8 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
|
|
||||||
Intent intent=getIntent();
|
Intent intent=getIntent();
|
||||||
StringBuilder builder=new StringBuilder();
|
StringBuilder builder=new StringBuilder();
|
||||||
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) builder.append(intent.getStringExtra(Intent.EXTRA_SUBJECT)).append("\n");
|
String subject = "";
|
||||||
|
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) builder.append(subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)).append("\n\n");
|
||||||
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
|
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
|
||||||
String text=builder.toString();
|
String text=builder.toString();
|
||||||
List<Uri> mediaUris;
|
List<Uri> mediaUris;
|
||||||
@@ -80,6 +81,8 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
if(!TextUtils.isEmpty(text))
|
if(!TextUtils.isEmpty(text))
|
||||||
args.putString("prefilledText", text);
|
args.putString("prefilledText", text);
|
||||||
|
if(!subject.isBlank())
|
||||||
|
args.putInt("selectionEnd", subject.length());
|
||||||
if(mediaUris!=null && !mediaUris.isEmpty())
|
if(mediaUris!=null && !mediaUris.isEmpty())
|
||||||
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
|
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
|
||||||
Fragment fragment=new ComposeFragment();
|
Fragment fragment=new ComposeFragment();
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package org.joinmastodon.android;
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.LocaleList;
|
|
||||||
import java.util.ArrayList;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import java.util.Arrays;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class GlobalUserPreferences{
|
public class GlobalUserPreferences{
|
||||||
public static boolean playGifs;
|
public static boolean playGifs;
|
||||||
@@ -22,29 +25,32 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean showInteractionCounts;
|
public static boolean showInteractionCounts;
|
||||||
public static boolean alwaysExpandContentWarnings;
|
public static boolean alwaysExpandContentWarnings;
|
||||||
public static boolean disableMarquee;
|
public static boolean disableMarquee;
|
||||||
|
public static boolean disableSwipe;
|
||||||
|
public static boolean disableDividers;
|
||||||
public static boolean voteButtonForSingleChoice;
|
public static boolean voteButtonForSingleChoice;
|
||||||
|
public static boolean uniformNotificationIcon;
|
||||||
|
public static boolean enableDeleteNotifications;
|
||||||
|
public static boolean relocatePublishButton;
|
||||||
|
public static boolean reduceMotion;
|
||||||
|
public static String publishButtonText;
|
||||||
public static ThemePreference theme;
|
public static ThemePreference theme;
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
public static List<String> recentLanguages;
|
|
||||||
|
|
||||||
private static String defaultRecentLanguages;
|
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||||
|
public static Map<String, List<String>> recentLanguages;
|
||||||
|
|
||||||
static {
|
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||||
List<Locale> systemLocales = new ArrayList<>();;
|
public static Map<String, Integer> recentEmojis;
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
|
||||||
systemLocales.add(Resources.getSystem().getConfiguration().locale);
|
|
||||||
} else {
|
|
||||||
LocaleList localeList = Resources.getSystem().getConfiguration().getLocales();
|
|
||||||
for (int i = 0; i < localeList.size(); i++) systemLocales.add(localeList.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultRecentLanguages = systemLocales.stream().map(Locale::getLanguage).collect(Collectors.joining(","));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SharedPreferences getPrefs(){
|
private static SharedPreferences getPrefs(){
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static <T> T fromJson(String json, Type type, T orElse) {
|
||||||
|
try { return gson.fromJson(json, type); }
|
||||||
|
catch (JsonSyntaxException ignored) { return orElse; }
|
||||||
|
}
|
||||||
|
|
||||||
public static void load(){
|
public static void load(){
|
||||||
SharedPreferences prefs=getPrefs();
|
SharedPreferences prefs=getPrefs();
|
||||||
playGifs=prefs.getBoolean("playGifs", true);
|
playGifs=prefs.getBoolean("playGifs", true);
|
||||||
@@ -53,14 +59,32 @@ public class GlobalUserPreferences{
|
|||||||
showReplies=prefs.getBoolean("showReplies", true);
|
showReplies=prefs.getBoolean("showReplies", true);
|
||||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||||
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
||||||
|
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", true);
|
||||||
showFederatedTimeline=prefs.getBoolean("showFederatedTimeline", !BuildConfig.BUILD_TYPE.equals("playRelease"));
|
showFederatedTimeline=prefs.getBoolean("showFederatedTimeline", !BuildConfig.BUILD_TYPE.equals("playRelease"));
|
||||||
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
||||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
||||||
|
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
||||||
|
disableDividers=prefs.getBoolean("disableDividers", true);
|
||||||
|
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
|
||||||
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
||||||
|
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", true);
|
||||||
|
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
||||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||||
color=ColorPreference.values()[prefs.getInt("color", 0)];
|
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
|
||||||
recentLanguages=Arrays.stream(prefs.getString("recentLanguages", defaultRecentLanguages).split(",")).filter(s->!s.isEmpty()).collect(Collectors.toList());
|
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(){
|
public static void save(){
|
||||||
@@ -75,19 +99,30 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("showInteractionCounts", showInteractionCounts)
|
.putBoolean("showInteractionCounts", showInteractionCounts)
|
||||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||||
.putBoolean("disableMarquee", disableMarquee)
|
.putBoolean("disableMarquee", disableMarquee)
|
||||||
|
.putBoolean("disableSwipe", disableSwipe)
|
||||||
|
.putBoolean("disableDividers", disableDividers)
|
||||||
|
.putBoolean("relocatePublishButton", relocatePublishButton)
|
||||||
|
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||||
|
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||||
|
.putBoolean("reduceMotion", reduceMotion)
|
||||||
|
.putString("publishButtonText", publishButtonText)
|
||||||
.putInt("theme", theme.ordinal())
|
.putInt("theme", theme.ordinal())
|
||||||
.putInt("color", color.ordinal())
|
.putString("color", color.name())
|
||||||
.putString("recentLanguages", String.join(",", recentLanguages))
|
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||||
|
.putString("recentEmojis", gson.toJson(recentEmojis))
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ColorPreference{
|
public enum ColorPreference{
|
||||||
|
MATERIAL3,
|
||||||
PINK,
|
PINK,
|
||||||
PURPLE,
|
PURPLE,
|
||||||
GREEN,
|
GREEN,
|
||||||
BLUE,
|
BLUE,
|
||||||
BROWN,
|
BROWN,
|
||||||
YELLOW
|
RED,
|
||||||
|
YELLOW,
|
||||||
|
NORD
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ThemePreference{
|
public enum ThemePreference{
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
|||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.fragments.HomeFragment;
|
import org.joinmastodon.android.fragments.HomeFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.SplashFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.CustomLoginFragment;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
@@ -33,7 +33,7 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||||
showFragmentClearingBackStack(new SplashFragment());
|
showFragmentClearingBackStack(new CustomLoginFragment());
|
||||||
}else{
|
}else{
|
||||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
||||||
AccountSession session;
|
AccountSession session;
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class OAuthActivity extends Activity{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account account){
|
public void onSuccess(Account account){
|
||||||
AccountSessionManager.getInstance().addAccount(instance, token, account, app, true);
|
AccountSessionManager.getInstance().addAccount(instance, token, account, app, null);
|
||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
finish();
|
finish();
|
||||||
// not calling restartMainActivity() here on purpose to have it recreated (notice different flags)
|
// 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.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.Icon;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -137,13 +135,25 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
builder.setContentTitle(pn.title)
|
builder.setContentTitle(pn.title)
|
||||||
.setContentText(pn.body)
|
.setContentText(pn.body)
|
||||||
.setStyle(new Notification.BigTextStyle().bigText(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, accountID.hashCode() & 0xFFFF, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setColor(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){
|
if(avatar!=null){
|
||||||
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.StringRes;
|
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){
|
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=new ProgressDialog(activity);
|
||||||
progressDialog.setMessage(activity.getString(message));
|
progressDialog.setMessage(activity.getString(message));
|
||||||
progressDialog.setCancelable(cancelable);
|
progressDialog.setCancelable(cancelable);
|
||||||
|
if (transform != null) transform.accept(progressDialog);
|
||||||
if(cancelable){
|
if(cancelable){
|
||||||
progressDialog.setOnCancelListener(dialog->cancel());
|
progressDialog.setOnCancelListener(dialog->cancel());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,23 +9,31 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
|||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
public class StatusInteractionController{
|
public class StatusInteractionController{
|
||||||
private final String accountID;
|
private final String accountID;
|
||||||
|
private final boolean updateCounters;
|
||||||
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
||||||
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
||||||
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
||||||
|
|
||||||
public StatusInteractionController(String accountID){
|
public StatusInteractionController(String accountID, boolean updateCounters) {
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
|
this.updateCounters=updateCounters;
|
||||||
}
|
}
|
||||||
|
|
||||||
public 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())
|
if(!Looper.getMainLooper().isCurrentThread())
|
||||||
throw new IllegalStateException("Can only be called from main thread");
|
throw new IllegalStateException("Can only be called from main thread");
|
||||||
|
|
||||||
@@ -38,7 +46,9 @@ public class StatusInteractionController{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
runningFavoriteRequests.remove(status.id);
|
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
|
@Override
|
||||||
@@ -46,24 +56,17 @@ public class StatusInteractionController{
|
|||||||
runningFavoriteRequests.remove(status.id);
|
runningFavoriteRequests.remove(status.id);
|
||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.favourited=!favorited;
|
status.favourited=!favorited;
|
||||||
if(favorited)
|
cb.accept(status);
|
||||||
status.favouritesCount--;
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
else
|
|
||||||
status.favouritesCount++;
|
|
||||||
E.post(new StatusCountersUpdatedEvent(status));
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningFavoriteRequests.put(status.id, req);
|
runningFavoriteRequests.put(status.id, req);
|
||||||
status.favourited=favorited;
|
status.favourited=favorited;
|
||||||
if(favorited)
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
status.favouritesCount++;
|
|
||||||
else
|
|
||||||
status.favouritesCount--;
|
|
||||||
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())
|
if(!Looper.getMainLooper().isCurrentThread())
|
||||||
throw new IllegalStateException("Can only be called from main thread");
|
throw new IllegalStateException("Can only be called from main thread");
|
||||||
|
|
||||||
@@ -71,12 +74,15 @@ public class StatusInteractionController{
|
|||||||
if(current!=null){
|
if(current!=null){
|
||||||
current.cancel();
|
current.cancel();
|
||||||
}
|
}
|
||||||
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged)
|
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged, visibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status reblog){
|
||||||
|
Status result = reblog.getContentStatus();
|
||||||
runningReblogRequests.remove(status.id);
|
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
|
@Override
|
||||||
@@ -84,24 +90,21 @@ public class StatusInteractionController{
|
|||||||
runningReblogRequests.remove(status.id);
|
runningReblogRequests.remove(status.id);
|
||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.reblogged=!reblogged;
|
status.reblogged=!reblogged;
|
||||||
if(reblogged)
|
cb.accept(status);
|
||||||
status.reblogsCount--;
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
else
|
|
||||||
status.reblogsCount++;
|
|
||||||
E.post(new StatusCountersUpdatedEvent(status));
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningReblogRequests.put(status.id, req);
|
runningReblogRequests.put(status.id, req);
|
||||||
status.reblogged=reblogged;
|
status.reblogged=reblogged;
|
||||||
if(reblogged)
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
status.reblogsCount++;
|
|
||||||
else
|
|
||||||
status.reblogsCount--;
|
|
||||||
E.post(new StatusCountersUpdatedEvent(status));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBookmarked(Status status, boolean bookmarked){
|
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())
|
if(!Looper.getMainLooper().isCurrentThread())
|
||||||
throw new IllegalStateException("Can only be called from main thread");
|
throw new IllegalStateException("Can only be called from main thread");
|
||||||
|
|
||||||
@@ -114,7 +117,8 @@ public class StatusInteractionController{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
runningBookmarkRequests.remove(status.id);
|
runningBookmarkRequests.remove(status.id);
|
||||||
E.post(new StatusCountersUpdatedEvent(result));
|
cb.accept(result);
|
||||||
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -122,12 +126,13 @@ public class StatusInteractionController{
|
|||||||
runningBookmarkRequests.remove(status.id);
|
runningBookmarkRequests.remove(status.id);
|
||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.bookmarked=!bookmarked;
|
status.bookmarked=!bookmarked;
|
||||||
E.post(new StatusCountersUpdatedEvent(status));
|
cb.accept(status);
|
||||||
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningBookmarkRequests.put(status.id, req);
|
runningBookmarkRequests.put(status.id, req);
|
||||||
status.bookmarked=bookmarked;
|
status.bookmarked=bookmarked;
|
||||||
E.post(new StatusCountersUpdatedEvent(status));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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,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{
|
private static class Request{
|
||||||
public String clientName="Megalodon";
|
public String clientName="Moshidon";
|
||||||
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
||||||
public String scopes=AccountSessionManager.SCOPE;
|
public String scopes=AccountSessionManager.SCOPE;
|
||||||
public String website="https://sk22.github.io/megalodon";
|
public String website="https://github.com/LucasGGamerM/moshidon";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
@@ -9,12 +10,29 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class CreateStatus extends MastodonAPIRequest<Status>{
|
public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||||
|
public static final Instant DRAFTS_AFTER_INSTANT = Instant.ofEpochMilli(253370764799999L) /* end of 9998 */;
|
||||||
|
private static final float draftFactor = 31536000000f /* one year */ / 253370764799999f /* end of 9998 */;
|
||||||
|
|
||||||
|
public static Instant getDraftInstant() {
|
||||||
|
// returns an instant between 9999-01-01 00:00:00 and 9999-12-31 23:59:59
|
||||||
|
// yes, this is a weird implementation for something that hardly matters
|
||||||
|
return DRAFTS_AFTER_INSTANT.plusMillis(1 + (long) (System.currentTimeMillis() * draftFactor));
|
||||||
|
}
|
||||||
|
|
||||||
public CreateStatus(CreateStatus.Request req, String uuid){
|
public CreateStatus(CreateStatus.Request req, String uuid){
|
||||||
super(HttpMethod.POST, "/statuses", Status.class);
|
super(HttpMethod.POST, "/statuses", Status.class);
|
||||||
setRequestBody(req);
|
setRequestBody(req);
|
||||||
addHeader("Idempotency-Key", uuid);
|
addHeader("Idempotency-Key", uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Scheduled extends MastodonAPIRequest<ScheduledStatus>{
|
||||||
|
public Scheduled(CreateStatus.Request req, String uuid){
|
||||||
|
super(HttpMethod.POST, "/statuses", ScheduledStatus.class);
|
||||||
|
setRequestBody(req);
|
||||||
|
addHeader("Idempotency-Key", uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class Request{
|
public static class Request{
|
||||||
public String status;
|
public String status;
|
||||||
public List<String> mediaIds;
|
public List<String> mediaIds;
|
||||||
|
|||||||
@@ -7,4 +7,10 @@ public class DeleteStatus extends MastodonAPIRequest<Status>{
|
|||||||
public DeleteStatus(String id){
|
public DeleteStatus(String id){
|
||||||
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
|
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Scheduled extends MastodonAPIRequest<Object> {
|
||||||
|
public Scheduled(String id) {
|
||||||
|
super(HttpMethod.DELETE, "/scheduled_statuses/"+id, Object.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
|
|
||||||
|
public class GetScheduledStatuses extends HeaderPaginationRequest<ScheduledStatus>{
|
||||||
|
public GetScheduledStatuses(String maxID, int limit){
|
||||||
|
super(HttpMethod.GET, "/scheduled_statuses", new TypeToken<>(){});
|
||||||
|
if(maxID!=null)
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", limit+"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,17 @@ package org.joinmastodon.android.api.requests.statuses;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
public class SetStatusReblogged extends MastodonAPIRequest<Status>{
|
public class SetStatusReblogged extends MastodonAPIRequest<Status>{
|
||||||
public SetStatusReblogged(String id, boolean reblogged){
|
public SetStatusReblogged(String id, boolean reblogged, StatusPrivacy visibility){
|
||||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class);
|
super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class);
|
||||||
setRequestBody(new Object());
|
Request req = new Request();
|
||||||
|
req.visibility = visibility;
|
||||||
|
setRequestBody(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Request {
|
||||||
|
public StatusPrivacy visibility;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,17 +30,19 @@ public class AccountSession{
|
|||||||
public List<Filter> wordFilters=new ArrayList<>();
|
public List<Filter> wordFilters=new ArrayList<>();
|
||||||
public String pushAccountID;
|
public String pushAccountID;
|
||||||
public Preferences preferences;
|
public Preferences preferences;
|
||||||
|
public AccountActivationInfo activationInfo;
|
||||||
private transient MastodonAPIController apiController;
|
private transient MastodonAPIController apiController;
|
||||||
private transient StatusInteractionController statusInteractionController;
|
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
||||||
private transient CacheController cacheController;
|
private transient CacheController cacheController;
|
||||||
private transient PushSubscriptionManager pushSubscriptionManager;
|
private transient PushSubscriptionManager pushSubscriptionManager;
|
||||||
|
|
||||||
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.token=token;
|
||||||
this.self=self;
|
this.self=self;
|
||||||
this.domain=domain;
|
this.domain=domain;
|
||||||
this.app=app;
|
this.app=app;
|
||||||
this.activated=activated;
|
this.activated=activated;
|
||||||
|
this.activationInfo=activationInfo;
|
||||||
infoLastUpdated=System.currentTimeMillis();
|
infoLastUpdated=System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +52,10 @@ public class AccountSession{
|
|||||||
return domain+"_"+self.id;
|
return domain+"_"+self.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFullUsername() {
|
||||||
|
return "@"+self.username+"@"+domain;
|
||||||
|
}
|
||||||
|
|
||||||
public MastodonAPIController getApiController(){
|
public MastodonAPIController getApiController(){
|
||||||
if(apiController==null)
|
if(apiController==null)
|
||||||
apiController=new MastodonAPIController(this);
|
apiController=new MastodonAPIController(this);
|
||||||
@@ -62,6 +68,12 @@ public class AccountSession{
|
|||||||
return statusInteractionController;
|
return statusInteractionController;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StatusInteractionController getRemoteStatusInteractionController(){
|
||||||
|
if(remoteStatusInteractionController==null)
|
||||||
|
remoteStatusInteractionController=new StatusInteractionController(getID(), false);
|
||||||
|
return remoteStatusInteractionController;
|
||||||
|
}
|
||||||
|
|
||||||
public CacheController getCacheController(){
|
public CacheController getCacheController(){
|
||||||
if(cacheController==null)
|
if(cacheController==null)
|
||||||
cacheController=new CacheController(getID());
|
cacheController=new CacheController(getID());
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
@@ -63,7 +61,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
public class AccountSessionManager{
|
public class AccountSessionManager{
|
||||||
private static final String TAG="AccountSessionManager";
|
private static final String TAG="AccountSessionManager";
|
||||||
public static final String SCOPE="read write follow push";
|
public static final String SCOPE="read write follow push";
|
||||||
public static final String REDIRECT_URI="megalodon-android-auth://callback";
|
public static final String REDIRECT_URI="moshidon-android-auth://callback";
|
||||||
|
|
||||||
private static final AccountSessionManager instance=new AccountSessionManager();
|
private static final AccountSessionManager instance=new AccountSessionManager();
|
||||||
|
|
||||||
@@ -102,13 +100,13 @@ public class AccountSessionManager{
|
|||||||
maybeUpdateShortcuts();
|
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);
|
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);
|
sessions.put(session.getID(), session);
|
||||||
lastActiveAccountID=session.getID();
|
lastActiveAccountID=session.getID();
|
||||||
writeAccountsFile();
|
writeAccountsFile();
|
||||||
updateInstanceEmojis(instance, instance.uri);
|
updateMoreInstanceInfo(instance, instance.uri);
|
||||||
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||||
}
|
}
|
||||||
@@ -213,7 +211,7 @@ public class AccountSessionManager{
|
|||||||
.path("/oauth/authorize")
|
.path("/oauth/authorize")
|
||||||
.appendQueryParameter("response_type", "code")
|
.appendQueryParameter("response_type", "code")
|
||||||
.appendQueryParameter("client_id", result.clientId)
|
.appendQueryParameter("client_id", result.clientId)
|
||||||
.appendQueryParameter("redirect_uri", "megalodon-android-auth://callback")
|
.appendQueryParameter("redirect_uri", "moshidon-android-auth://callback")
|
||||||
.appendQueryParameter("scope", SCOPE)
|
.appendQueryParameter("scope", SCOPE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -327,12 +325,7 @@ public class AccountSessionManager{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Instance instance){
|
public void onSuccess(Instance instance){
|
||||||
instances.put(domain, instance);
|
instances.put(domain, instance);
|
||||||
updateInstanceEmojis(instance, domain);
|
updateMoreInstanceInfo(instance, domain);
|
||||||
try {
|
|
||||||
if (Integer.parseInt(instance.version.split("\\.")[0]) >= 4) {
|
|
||||||
updateInstanceInfoV2(domain);
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -343,17 +336,19 @@ public class AccountSessionManager{
|
|||||||
.execNoAuth(domain);
|
.execNoAuth(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateInstanceInfoV2(String domain) {
|
public void updateMoreInstanceInfo(Instance instance, String domain) {
|
||||||
new GetInstance.V2().setCallback(new Callback<>() {
|
new GetInstance.V2().setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Instance.V2 v2) {
|
public void onSuccess(Instance.V2 v2) {
|
||||||
Instance instanceInfo = instances.get(domain);
|
if (instance != null) instance.v2 = v2;
|
||||||
if (instanceInfo != null) instanceInfo.v2 = v2;
|
updateInstanceEmojis(instance, domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse errorResponse) {}
|
public void onError(ErrorResponse errorResponse) {
|
||||||
}).execNoAuth(domain);
|
updateInstanceEmojis(instance, domain);
|
||||||
|
}
|
||||||
|
}).execNoAuth(instance.uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInstanceEmojis(Instance instance, String domain){
|
private void updateInstanceEmojis(Instance instance, String domain){
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -675,7 +675,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
private int currentMediaHiddenLayoutsWidth=0;
|
private int currentMediaHiddenLayoutsWidth=0;
|
||||||
|
|
||||||
{
|
{
|
||||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted));
|
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.disableDividers ? R.attr.colorWindowBackground : R.attr.colorPollVoted));
|
||||||
dividerPaint.setStyle(Paint.Style.STROKE);
|
dividerPaint.setStyle(Paint.Style.STROKE);
|
||||||
dividerPaint.setStrokeWidth(V.dp(1));
|
dividerPaint.setStrokeWidth(V.dp(1));
|
||||||
}
|
}
|
||||||
@@ -787,7 +787,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
currentMediaHiddenLayoutsWidth=width;
|
currentMediaHiddenLayoutsWidth=width;
|
||||||
String title=getString(R.string.sensitive_content);
|
String title=getString(R.string.sensitive_content);
|
||||||
TextPaint titlePaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
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.setTextSize(V.dp(22));
|
||||||
titlePaint.setTypeface(mediumTypeface);
|
titlePaint.setTypeface(mediumTypeface);
|
||||||
mediaHiddenTitleLayout=StaticLayout.Builder.obtain(title, 0, title.length(), titlePaint, width)
|
mediaHiddenTitleLayout=StaticLayout.Builder.obtain(title, 0, title.length(), titlePaint, width)
|
||||||
@@ -798,7 +798,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||||
.build();
|
.build();
|
||||||
TextPaint textPaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
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));
|
textPaint.setTextSize(V.dp(16));
|
||||||
String text=getString(R.string.sensitive_content_explain);
|
String text=getString(R.string.sensitive_content_explain);
|
||||||
mediaHiddenTextLayout=StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, width)
|
mediaHiddenTextLayout=StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, width)
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages;
|
||||||
|
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.DRAFTS_AFTER_INSTANT;
|
||||||
|
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDraftInstant;
|
||||||
|
import static org.joinmastodon.android.utils.MastodonLanguage.allLanguages;
|
||||||
|
import static org.joinmastodon.android.utils.MastodonLanguage.defaultRecentLanguages;
|
||||||
|
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
|
import android.app.TimePickerDialog;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -25,11 +33,12 @@ import android.provider.OpenableColumns;
|
|||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -51,6 +60,7 @@ import android.widget.ImageView;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -63,13 +73,15 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.ProgressListener;
|
import org.joinmastodon.android.api.ProgressListener;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
|
||||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
||||||
import org.joinmastodon.android.api.requests.statuses.EditStatus;
|
import org.joinmastodon.android.api.requests.statuses.EditStatus;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetAttachmentByID;
|
import org.joinmastodon.android.api.requests.statuses.GetAttachmentByID;
|
||||||
import org.joinmastodon.android.api.requests.statuses.UploadAttachment;
|
import org.joinmastodon.android.api.requests.statuses.UploadAttachment;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
||||||
|
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
@@ -81,6 +93,7 @@ import org.joinmastodon.android.model.Instance;
|
|||||||
import org.joinmastodon.android.model.Mention;
|
import org.joinmastodon.android.model.Mention;
|
||||||
import org.joinmastodon.android.model.Poll;
|
import org.joinmastodon.android.model.Poll;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
import org.joinmastodon.android.model.Preferences;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
import org.joinmastodon.android.ui.ComposeAutocompleteViewController;
|
import org.joinmastodon.android.ui.ComposeAutocompleteViewController;
|
||||||
@@ -96,20 +109,27 @@ import org.joinmastodon.android.ui.utils.TransferSpeedTracker;
|
|||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ComposeEditText;
|
import org.joinmastodon.android.ui.views.ComposeEditText;
|
||||||
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
||||||
|
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||||
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
|
||||||
import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
|
import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
|
||||||
|
import org.joinmastodon.android.utils.MastodonLanguage;
|
||||||
import org.parceler.Parcel;
|
import org.parceler.Parcel;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.FormatStyle;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -127,6 +147,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
private static final int MEDIA_RESULT=717;
|
private static final int MEDIA_RESULT=717;
|
||||||
private static final int IMAGE_DESCRIPTION_RESULT=363;
|
private static final int IMAGE_DESCRIPTION_RESULT=363;
|
||||||
|
private static final int SCHEDULED_STATUS_OPENED_RESULT=161;
|
||||||
private static final int MAX_ATTACHMENTS=4;
|
private static final int MAX_ATTACHMENTS=4;
|
||||||
private static final String TAG="ComposeFragment";
|
private static final String TAG="ComposeFragment";
|
||||||
|
|
||||||
@@ -135,7 +156,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
// from https://github.com/mastodon/mastodon-ios/blob/main/Mastodon/Helper/MastodonRegex.swift
|
// from https://github.com/mastodon/mastodon-ios/blob/main/Mastodon/Helper/MastodonRegex.swift
|
||||||
private static final Pattern AUTO_COMPLETE_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+)|:([a-zA-Z0-9_]+))");
|
private static final Pattern AUTO_COMPLETE_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+)|:([a-zA-Z0-9_]+))");
|
||||||
private static final Pattern HIGHLIGHT_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))");
|
private static final Pattern HIGHLIGHT_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))");
|
||||||
private static final List<Locale> allIsoLanguages;
|
|
||||||
|
|
||||||
@SuppressLint("NewApi") // this class actually exists on 6.0
|
@SuppressLint("NewApi") // this class actually exists on 6.0
|
||||||
private final BreakIterator breakIterator=BreakIterator.getCharacterInstance();
|
private final BreakIterator breakIterator=BreakIterator.getCharacterInstance();
|
||||||
@@ -151,9 +171,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private String accountID;
|
private String accountID;
|
||||||
private int charCount, charLimit, trimmedCharCount;
|
private int charCount, charLimit, trimmedCharCount;
|
||||||
|
|
||||||
private Button publishButton, languageButton;
|
private Button publishButton, languageButton, scheduleTimeBtn, draftsBtn;
|
||||||
private PopupMenu languagePopup;
|
private PopupMenu languagePopup, visibilityPopup, draftOptionsPopup;
|
||||||
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn;
|
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn, scheduleDraftDismiss;
|
||||||
private ImageView sensitiveIcon;
|
private ImageView sensitiveIcon;
|
||||||
private ComposeMediaLayout attachmentsView;
|
private ComposeMediaLayout attachmentsView;
|
||||||
private TextView replyText;
|
private TextView replyText;
|
||||||
@@ -162,8 +182,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private View addPollOptionBtn;
|
private View addPollOptionBtn;
|
||||||
private View sensitiveItem;
|
private View sensitiveItem;
|
||||||
private View pollAllowMultipleItem;
|
private View pollAllowMultipleItem;
|
||||||
|
private View scheduleDraftView;
|
||||||
|
private ScrollView scrollView;
|
||||||
|
private boolean initiallyScrolled = false;
|
||||||
|
private TextView scheduleDraftText;
|
||||||
private CheckBox pollAllowMultipleCheckbox;
|
private CheckBox pollAllowMultipleCheckbox;
|
||||||
private TextView pollDurationView;
|
private TextView pollDurationView;
|
||||||
|
private MenuItem draftMenuItem, undraftMenuItem, scheduleMenuItem, unscheduleMenuItem;
|
||||||
|
|
||||||
private ArrayList<DraftPollOption> pollOptions=new ArrayList<>();
|
private ArrayList<DraftPollOption> pollOptions=new ArrayList<>();
|
||||||
|
|
||||||
@@ -179,6 +204,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private EditText spoilerEdit;
|
private EditText spoilerEdit;
|
||||||
private boolean hasSpoiler;
|
private boolean hasSpoiler;
|
||||||
private boolean sensitive;
|
private boolean sensitive;
|
||||||
|
private Instant scheduledAt = null;
|
||||||
private ProgressBar sendProgress;
|
private ProgressBar sendProgress;
|
||||||
private ImageView sendError;
|
private ImageView sendError;
|
||||||
private View sendingOverlay;
|
private View sendingOverlay;
|
||||||
@@ -191,6 +217,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private boolean attachmentsErrorShowing;
|
private boolean attachmentsErrorShowing;
|
||||||
|
|
||||||
private Status editingStatus;
|
private Status editingStatus;
|
||||||
|
private ScheduledStatus scheduledStatus;
|
||||||
private boolean redraftStatus;
|
private boolean redraftStatus;
|
||||||
private boolean pollChanged;
|
private boolean pollChanged;
|
||||||
private boolean creatingView;
|
private boolean creatingView;
|
||||||
@@ -198,27 +225,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private Runnable updateUploadEtaRunnable;
|
private Runnable updateUploadEtaRunnable;
|
||||||
|
|
||||||
private String language;
|
private String language;
|
||||||
|
private MastodonLanguage.LanguageResolver languageResolver;
|
||||||
|
|
||||||
static {
|
private int navigationBarColorBefore;
|
||||||
Locale[] locales = Locale.getAvailableLocales();
|
|
||||||
List<Locale> allLocales = new ArrayList<>();
|
|
||||||
List<String> addedLanguages = new ArrayList<>();
|
|
||||||
for (Locale locale : locales) {
|
|
||||||
String lang = locale.getLanguage();
|
|
||||||
if (!addedLanguages.contains(lang)) {
|
|
||||||
allLocales.add(locale);
|
|
||||||
addedLanguages.add(lang);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.sort(allLocales, Comparator.comparing(l -> l.getDisplayLanguage(Locale.getDefault())));
|
|
||||||
allIsoLanguages = allLocales;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
navigationBarColorBefore = getActivity().getWindow().getNavigationBarColor();
|
||||||
|
getActivity().getWindow().setNavigationBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLightest));
|
||||||
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
@@ -226,9 +242,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
instanceDomain=session.domain;
|
instanceDomain=session.domain;
|
||||||
customEmojis=AccountSessionManager.getInstance().getCustomEmojis(instanceDomain);
|
customEmojis=AccountSessionManager.getInstance().getCustomEmojis(instanceDomain);
|
||||||
instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain);
|
instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain);
|
||||||
|
languageResolver=new MastodonLanguage.LanguageResolver(instance);
|
||||||
|
redraftStatus=getArguments().getBoolean("redraftStatus", false);
|
||||||
if(getArguments().containsKey("editStatus")){
|
if(getArguments().containsKey("editStatus")){
|
||||||
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
|
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
|
||||||
redraftStatus=getArguments().getBoolean("redraftStatus");
|
|
||||||
}
|
}
|
||||||
if(instance==null){
|
if(instance==null){
|
||||||
Nav.finish(this);
|
Nav.finish(this);
|
||||||
@@ -238,6 +255,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
AccountSessionManager.getInstance().updateInstanceInfo(instanceDomain);
|
AccountSessionManager.getInstance().updateInstanceInfo(instanceDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bundle bundle = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||||
|
if (bundle.containsKey("scheduledStatus")) scheduledStatus=Parcels.unwrap(bundle.getParcelable("scheduledStatus"));
|
||||||
|
if (bundle.containsKey("scheduledAt")) scheduledAt=(Instant) bundle.getSerializable("scheduledAt");
|
||||||
|
|
||||||
if(instance.maxTootChars>0)
|
if(instance.maxTootChars>0)
|
||||||
charLimit=instance.maxTootChars;
|
charLimit=instance.maxTootChars;
|
||||||
else if(instance.configuration!=null && instance.configuration.statuses!=null && instance.configuration.statuses.maxCharacters>0)
|
else if(instance.configuration!=null && instance.configuration.statuses!=null && instance.configuration.statuses.maxCharacters>0)
|
||||||
@@ -257,6 +278,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
UiUtils.removeCallbacks(updateUploadEtaRunnable);
|
UiUtils.removeCallbacks(updateUploadEtaRunnable);
|
||||||
updateUploadEtaRunnable=null;
|
updateUploadEtaRunnable=null;
|
||||||
}
|
}
|
||||||
|
getActivity().getWindow().setNavigationBarColor(navigationBarColorBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -266,6 +288,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
wm=activity.getSystemService(WindowManager.class);
|
wm=activity.getSystemService(WindowManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
creatingView=true;
|
creatingView=true;
|
||||||
@@ -273,14 +296,28 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
emojiKeyboard.setListener(this::onCustomEmojiClick);
|
emojiKeyboard.setListener(this::onCustomEmojiClick);
|
||||||
|
|
||||||
View view=inflater.inflate(R.layout.fragment_compose, container, false);
|
View view=inflater.inflate(R.layout.fragment_compose, container, false);
|
||||||
|
|
||||||
|
if(GlobalUserPreferences.relocatePublishButton){
|
||||||
|
publishButton=view.findViewById(R.id.publish);
|
||||||
|
// publishButton.setText(editingStatus==null || redraftStatus ? R.string.publish : R.string.save);
|
||||||
|
publishButton.setEllipsize(TextUtils.TruncateAt.END);
|
||||||
|
publishButton.setOnClickListener(this::onPublishClick);
|
||||||
|
publishButton.setSingleLine(true);
|
||||||
|
publishButton.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
draftsBtn=view.findViewById(R.id.drafts_btn);
|
||||||
|
draftsBtn.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
mainEditText=view.findViewById(R.id.toot_text);
|
mainEditText=view.findViewById(R.id.toot_text);
|
||||||
mainEditTextWrap=view.findViewById(R.id.toot_text_wrap);
|
mainEditTextWrap=view.findViewById(R.id.toot_text_wrap);
|
||||||
charCounter=view.findViewById(R.id.char_counter);
|
charCounter=view.findViewById(R.id.char_counter);
|
||||||
charCounter.setText(String.valueOf(charLimit));
|
charCounter.setText(String.valueOf(charLimit));
|
||||||
|
scrollView=view.findViewById(R.id.scroll_view);
|
||||||
|
|
||||||
selfName=view.findViewById(R.id.name);
|
selfName=view.findViewById(R.id.self_name);
|
||||||
selfUsername=view.findViewById(R.id.username);
|
selfUsername=view.findViewById(R.id.self_username);
|
||||||
selfAvatar=view.findViewById(R.id.avatar);
|
selfAvatar=view.findViewById(R.id.self_avatar);
|
||||||
HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis);
|
HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis);
|
||||||
selfUsername.setText('@'+self.username+'@'+instanceDomain);
|
selfUsername.setText('@'+self.username+'@'+instanceDomain);
|
||||||
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
|
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
|
||||||
@@ -298,6 +335,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
emojiBtn=view.findViewById(R.id.btn_emoji);
|
emojiBtn=view.findViewById(R.id.btn_emoji);
|
||||||
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
||||||
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
||||||
|
scheduleDraftView=view.findViewById(R.id.schedule_draft_view);
|
||||||
|
scheduleDraftText=view.findViewById(R.id.schedule_draft_text);
|
||||||
|
scheduleDraftDismiss=view.findViewById(R.id.schedule_draft_dismiss);
|
||||||
|
scheduleTimeBtn=view.findViewById(R.id.scheduled_time_btn);
|
||||||
sensitiveIcon=view.findViewById(R.id.sensitive_icon);
|
sensitiveIcon=view.findViewById(R.id.sensitive_icon);
|
||||||
sensitiveItem=view.findViewById(R.id.sensitive_item);
|
sensitiveItem=view.findViewById(R.id.sensitive_item);
|
||||||
replyText=view.findViewById(R.id.reply_text);
|
replyText=view.findViewById(R.id.reply_text);
|
||||||
@@ -306,7 +347,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
pollBtn.setOnClickListener(v->togglePoll());
|
pollBtn.setOnClickListener(v->togglePoll());
|
||||||
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
||||||
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
||||||
visibilityBtn.setOnClickListener(this::onVisibilityClick);
|
buildVisibilityPopup(visibilityBtn);
|
||||||
|
visibilityBtn.setOnClickListener(v->visibilityPopup.show());
|
||||||
|
visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener());
|
||||||
|
|
||||||
|
scheduleDraftDismiss.setOnClickListener(v->updateScheduledAt(null));
|
||||||
|
scheduleTimeBtn.setOnClickListener(v->pickScheduledDateTime());
|
||||||
|
|
||||||
sensitiveItem.setOnClickListener(v->toggleSensitive());
|
sensitiveItem.setOnClickListener(v->toggleSensitive());
|
||||||
emojiKeyboard.setOnIconChangedListener(new PopupKeyboard.OnIconChangeListener(){
|
emojiKeyboard.setOnIconChangedListener(new PopupKeyboard.OnIconChangeListener(){
|
||||||
@Override
|
@Override
|
||||||
@@ -356,8 +403,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
DraftPollOption opt=createDraftPollOption();
|
DraftPollOption opt=createDraftPollOption();
|
||||||
opt.edit.setText(eopt.title);
|
opt.edit.setText(eopt.title);
|
||||||
}
|
}
|
||||||
pollDuration=(int)editingStatus.poll.expiresAt.minus(System.currentTimeMillis(), ChronoUnit.MILLIS).getEpochSecond();
|
pollDuration=scheduledStatus == null
|
||||||
pollDurationStr=UiUtils.formatTimeLeft(getActivity(), editingStatus.poll.expiresAt);
|
? (int)editingStatus.poll.expiresAt.minus(System.currentTimeMillis(), ChronoUnit.MILLIS).getEpochSecond()
|
||||||
|
: Integer.parseInt(scheduledStatus.params.poll.expiresIn);
|
||||||
|
pollDurationStr=UiUtils.formatTimeLeft(getActivity(), scheduledStatus == null
|
||||||
|
? editingStatus.poll.expiresAt
|
||||||
|
: Instant.now().plus(pollDuration, ChronoUnit.SECONDS));
|
||||||
updatePollOptionHints();
|
updatePollOptionHints();
|
||||||
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr));
|
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr));
|
||||||
}else{
|
}else{
|
||||||
@@ -396,10 +447,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
if(editingStatus!=null && editingStatus.visibility!=null) {
|
if(editingStatus!=null && editingStatus.visibility!=null) {
|
||||||
statusVisibility=editingStatus.visibility;
|
statusVisibility=editingStatus.visibility;
|
||||||
|
} else {
|
||||||
|
loadDefaultStatusVisibility(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDefaultStatusVisibility(savedInstanceState);
|
|
||||||
updateVisibilityIcon();
|
updateVisibilityIcon();
|
||||||
|
visibilityPopup.getMenu().findItem(switch(statusVisibility){
|
||||||
|
case PUBLIC -> R.id.vis_public;
|
||||||
|
case UNLISTED -> R.id.vis_unlisted;
|
||||||
|
case PRIVATE -> R.id.vis_followers;
|
||||||
|
case DIRECT -> R.id.vis_private;
|
||||||
|
}).setChecked(true);
|
||||||
|
|
||||||
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
|
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
|
||||||
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
|
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
|
||||||
@@ -436,6 +494,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
outState.putParcelableArrayList("attachments", serializedAttachments);
|
outState.putParcelableArrayList("attachments", serializedAttachments);
|
||||||
}
|
}
|
||||||
outState.putSerializable("visibility", statusVisibility);
|
outState.putSerializable("visibility", statusVisibility);
|
||||||
|
if (scheduledAt != null) outState.putSerializable("scheduledAt", scheduledAt);
|
||||||
|
if (scheduledStatus != null) outState.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -518,32 +578,86 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
});
|
});
|
||||||
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
|
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
|
||||||
if(replyTo!=null){
|
if(replyTo!=null){
|
||||||
replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName));
|
View replyWrap = view.findViewById(R.id.reply_wrap);
|
||||||
int visibilityNameRes = switch (statusVisibility) {
|
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||||
case PUBLIC -> R.string.visibility_public;
|
int scrollHeight = scrollView.getHeight();
|
||||||
case UNLISTED -> R.string.sk_visibility_unlisted;
|
if (replyWrap.getMinimumHeight() != scrollHeight) {
|
||||||
case PRIVATE -> R.string.visibility_followers_only;
|
replyWrap.setMinimumHeight(scrollHeight);
|
||||||
case DIRECT -> R.string.visibility_private;
|
if (!initiallyScrolled) {
|
||||||
};
|
initiallyScrolled = true;
|
||||||
replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
|
scrollView.post(() -> {
|
||||||
Drawable visibilityIcon = getActivity().getDrawable(switch(statusVisibility){
|
int bottom = scrollView.getChildAt(0).getBottom();
|
||||||
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
int delta = bottom - (scrollView.getScrollY() + scrollView.getHeight());
|
||||||
case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
|
int space = GlobalUserPreferences.reduceMotion ? 0 : Math.min(V.dp(120), delta);
|
||||||
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
|
scrollView.scrollBy(0, delta - space);
|
||||||
case DIRECT -> R.drawable.ic_at_symbol;
|
if (!GlobalUserPreferences.reduceMotion) {
|
||||||
|
scrollView.postDelayed(() -> scrollView.smoothScrollBy(0, space), 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
visibilityIcon.setBounds(0, 0, V.dp(20), V.dp(20));
|
View originalPost = view.findViewById(R.id.original_post);
|
||||||
Drawable replyArrow = getActivity().getDrawable(R.drawable.ic_fluent_arrow_reply_20_filled);
|
originalPost.setVisibility(View.VISIBLE);
|
||||||
replyArrow.setBounds(0, 0, V.dp(20), V.dp(20));
|
originalPost.setOnClickListener(v->{
|
||||||
replyText.setCompoundDrawables(replyArrow, null, visibilityIcon, null);
|
|
||||||
|
|
||||||
replyText.setOnClickListener(v->{
|
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelable("status", Parcels.wrap(replyTo));
|
args.putParcelable("status", Parcels.wrap(replyTo));
|
||||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ImageView avatar = view.findViewById(R.id.avatar);
|
||||||
|
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(replyTo.account.avatar));
|
||||||
|
ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
|
||||||
|
@Override
|
||||||
|
public void getOutline(View view, Outline outline){
|
||||||
|
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), V.dp(12));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
avatar.setOutlineProvider(roundCornersOutline);
|
||||||
|
avatar.setClipToOutline(true);
|
||||||
|
avatar.setOnClickListener(v->{
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putParcelable("profileAccount", Parcels.wrap(replyTo.account));
|
||||||
|
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||||
|
});
|
||||||
|
|
||||||
|
((TextView) view.findViewById(R.id.name)).setText(replyTo.account.displayName);
|
||||||
|
((TextView) view.findViewById(R.id.username)).setText(replyTo.account.getDisplayUsername());
|
||||||
|
view.findViewById(R.id.visibility).setVisibility(View.GONE);
|
||||||
|
Drawable visibilityIcon = getActivity().getDrawable(switch(replyTo.visibility){
|
||||||
|
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
||||||
|
case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
|
||||||
|
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
|
||||||
|
case DIRECT -> R.drawable.ic_fluent_mention_20_regular;
|
||||||
|
});
|
||||||
|
ImageView moreBtn = view.findViewById(R.id.more);
|
||||||
|
moreBtn.setImageDrawable(visibilityIcon);
|
||||||
|
moreBtn.setBackground(null);
|
||||||
|
TextView timestamp = view.findViewById(R.id.timestamp);
|
||||||
|
if (replyTo.editedAt==null) timestamp.setText(UiUtils.formatRelativeTimestamp(getContext(), replyTo.createdAt));
|
||||||
|
else timestamp.setText(getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), replyTo.editedAt)));
|
||||||
|
if (replyTo.spoilerText != null && !replyTo.spoilerText.isBlank()) {
|
||||||
|
view.findViewById(R.id.spoiler_header).setVisibility(View.VISIBLE);
|
||||||
|
((TextView) view.findViewById(R.id.spoiler_title_inline)).setText(replyTo.spoilerText);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpannableStringBuilder content = HtmlParser.parse(replyTo.content, replyTo.emojis, replyTo.mentions, replyTo.tags, accountID);
|
||||||
|
LinkedTextView text = view.findViewById(R.id.text);
|
||||||
|
if (content.length() > 0) text.setText(content);
|
||||||
|
else view.findViewById(R.id.display_item_text).setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
|
||||||
|
|
||||||
|
replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName));
|
||||||
|
int visibilityNameRes = switch (replyTo.visibility) {
|
||||||
|
case PUBLIC -> R.string.visibility_public;
|
||||||
|
case UNLISTED -> R.string.sk_visibility_unlisted;
|
||||||
|
case PRIVATE -> R.string.visibility_followers_only;
|
||||||
|
case DIRECT -> R.string.visibility_private;
|
||||||
|
};
|
||||||
|
replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
|
||||||
|
|
||||||
ArrayList<String> mentions=new ArrayList<>();
|
ArrayList<String> mentions=new ArrayList<>();
|
||||||
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
||||||
if(!replyTo.account.id.equals(ownID))
|
if(!replyTo.account.id.equals(ownID))
|
||||||
@@ -603,6 +717,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
ignoreSelectionChanges=false;
|
ignoreSelectionChanges=false;
|
||||||
initialText=prefilledText;
|
initialText=prefilledText;
|
||||||
}
|
}
|
||||||
|
if (getArguments().containsKey("selectionStart") || getArguments().containsKey("selectionEnd")) {
|
||||||
|
int selectionStart=getArguments().getInt("selectionStart", 0);
|
||||||
|
int selectionEnd=getArguments().getInt("selectionEnd", selectionStart);
|
||||||
|
mainEditText.setSelection(selectionStart, selectionEnd);
|
||||||
|
}
|
||||||
ArrayList<Uri> mediaUris=getArguments().getParcelableArrayList("mediaAttachments");
|
ArrayList<Uri> mediaUris=getArguments().getParcelableArrayList("mediaAttachments");
|
||||||
if(mediaUris!=null && !mediaUris.isEmpty()){
|
if(mediaUris!=null && !mediaUris.isEmpty()){
|
||||||
for(Uri uri:mediaUris){
|
for(Uri uri:mediaUris){
|
||||||
@@ -622,91 +741,103 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
publishButton=new Button(getActivity());
|
|
||||||
publishButton.setText(editingStatus==null || redraftStatus ? R.string.publish : R.string.save);
|
|
||||||
publishButton.setOnClickListener(this::onPublishClick);
|
|
||||||
LinearLayout wrap=new LinearLayout(getActivity());
|
|
||||||
wrap.setOrientation(LinearLayout.HORIZONTAL);
|
|
||||||
|
|
||||||
sendProgress=new ProgressBar(getActivity());
|
|
||||||
LinearLayout.LayoutParams progressLP=new LinearLayout.LayoutParams(V.dp(24), V.dp(24));
|
|
||||||
progressLP.setMarginEnd(V.dp(16));
|
|
||||||
progressLP.gravity=Gravity.CENTER_VERTICAL;
|
|
||||||
wrap.addView(sendProgress, progressLP);
|
|
||||||
|
|
||||||
sendError=new ImageView(getActivity());
|
|
||||||
sendError.setImageResource(R.drawable.ic_fluent_error_circle_24_regular);
|
|
||||||
sendError.setImageTintList(getResources().getColorStateList(R.color.error_600));
|
|
||||||
sendError.setScaleType(ImageView.ScaleType.CENTER);
|
|
||||||
wrap.addView(sendError, progressLP);
|
|
||||||
|
|
||||||
sendError.setVisibility(View.GONE);
|
|
||||||
sendProgress.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
LinearLayout.LayoutParams langParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
||||||
langParams.setMarginEnd(V.dp(8));
|
|
||||||
wrap.addView(buildLanguageSelector(), langParams);
|
|
||||||
|
|
||||||
wrap.addView(publishButton, 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(editingStatus==null ? R.string.publish : R.string.save);
|
MenuItem item=menu.add(editingStatus==null ? R.string.publish : R.string.save);
|
||||||
|
LinearLayout wrap=new LinearLayout(getActivity());
|
||||||
|
getActivity().getLayoutInflater().inflate(R.layout.compose_action, wrap);
|
||||||
item.setActionView(wrap);
|
item.setActionView(wrap);
|
||||||
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
updatePublishButtonState();
|
|
||||||
|
if(!GlobalUserPreferences.relocatePublishButton){
|
||||||
|
publishButton = wrap.findViewById(R.id.publish_btn);
|
||||||
|
publishButton.setOnClickListener(this::onPublishClick);
|
||||||
|
publishButton.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
draftsBtn = wrap.findViewById(R.id.drafts_btn);
|
||||||
|
draftsBtn.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draftsBtn = wrap.findViewById(R.id.drafts_btn);
|
||||||
|
draftOptionsPopup = new PopupMenu(getContext(), draftsBtn);
|
||||||
|
draftOptionsPopup.inflate(R.menu.compose_more);
|
||||||
|
draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft);
|
||||||
|
undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft);
|
||||||
|
scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule);
|
||||||
|
unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule);
|
||||||
|
draftOptionsPopup.setOnMenuItemClickListener(i->{
|
||||||
|
int id = i.getItemId();
|
||||||
|
if (id == R.id.draft) updateScheduledAt(getDraftInstant());
|
||||||
|
else if (id == R.id.schedule) pickScheduledDateTime();
|
||||||
|
else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null);
|
||||||
|
else navigateToUnsentPosts();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
|
||||||
|
|
||||||
|
|
||||||
|
languageButton = wrap.findViewById(R.id.language_btn);
|
||||||
|
sendProgress = wrap.findViewById(R.id.send_progress);
|
||||||
|
sendError = wrap.findViewById(R.id.send_error);
|
||||||
|
|
||||||
|
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
|
||||||
|
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
|
||||||
|
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
|
||||||
|
buildLanguageSelector(languageButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateToUnsentPosts() {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putBoolean("hide_fab", true);
|
||||||
|
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
||||||
|
imm.hideSoftInputFromWindow(draftsBtn.getWindowToken(), 0);
|
||||||
|
if (hasDraft()) {
|
||||||
|
Nav.go(getActivity(), ScheduledStatusListFragment.class, args);
|
||||||
|
} else {
|
||||||
|
// result for the previous ScheduledStatusList
|
||||||
|
setResult(true, null);
|
||||||
|
// finishing fragment in "onFragmentResult"
|
||||||
|
Nav.goForResult(getActivity(), ScheduledStatusListFragment.class, args, SCHEDULED_STATUS_OPENED_RESULT, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLanguage(String lang) {
|
private void updateLanguage(String lang) {
|
||||||
updateLanguage(new Locale(lang));
|
updateLanguage(lang == null ? languageResolver.getDefault() : languageResolver.from(lang));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateLanguage(Locale loc) {
|
private void updateLanguage(MastodonLanguage loc) {
|
||||||
language = loc.getLanguage();
|
language = loc.getLanguage();
|
||||||
languageButton.setText(loc.getDisplayLanguage(loc));
|
languageButton.setText(loc.getLanguageName());
|
||||||
languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, loc.getDisplayLanguage(Locale.getDefault())));
|
languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, loc.getDefaultName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private Button buildLanguageSelector() {
|
private void buildLanguageSelector(Button btn) {
|
||||||
languageButton=new Button(getActivity());
|
|
||||||
TypedValue typedValue = new TypedValue();
|
|
||||||
getActivity().getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true);
|
|
||||||
languageButton.setTextColor(typedValue.data);
|
|
||||||
languageButton.setBackground(getActivity().getDrawable(R.drawable.bg_text_button));
|
|
||||||
languageButton.setPadding(V.dp(8), 0, V.dp(8), 0);
|
|
||||||
languageButton.setCompoundDrawablesRelativeWithIntrinsicBounds(getActivity().getDrawable(R.drawable.ic_fluent_local_language_16_regular), null, null, null);
|
|
||||||
languageButton.setCompoundDrawableTintList(languageButton.getTextColors());
|
|
||||||
languageButton.setCompoundDrawablePadding(V.dp(6));
|
|
||||||
|
|
||||||
languagePopup=new PopupMenu(getActivity(), languageButton);
|
languagePopup=new PopupMenu(getActivity(), languageButton);
|
||||||
languageButton.setOnTouchListener(languagePopup.getDragToOpenListener());
|
btn.setOnTouchListener(languagePopup.getDragToOpenListener());
|
||||||
languageButton.setOnClickListener(v->languagePopup.show());
|
btn.setOnClickListener(v->languagePopup.show());
|
||||||
|
|
||||||
Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences;
|
Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences;
|
||||||
String defaultLang = prefs != null ? prefs.postingDefaultLanguage : null;
|
updateLanguage(prefs != null && prefs.postingDefaultLanguage != null && prefs.postingDefaultLanguage.length() > 0
|
||||||
if (defaultLang != null) updateLanguage(defaultLang);
|
? languageResolver.from(prefs.postingDefaultLanguage)
|
||||||
else updateLanguage(Locale.getDefault());
|
: languageResolver.getDefault());
|
||||||
|
|
||||||
Menu languageMenu = languagePopup.getMenu();
|
Menu languageMenu = languagePopup.getMenu();
|
||||||
|
for (String recentLanguage : Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages)) {
|
||||||
for (String recentLanguage : GlobalUserPreferences.recentLanguages) {
|
MastodonLanguage l = languageResolver.from(recentLanguage);
|
||||||
Locale loc = new Locale(recentLanguage);
|
languageMenu.add(0, allLanguages.indexOf(l), Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
|
||||||
languageMenu.add(0, allIsoLanguages.indexOf(loc), Menu.NONE, loc.getDisplayLanguage(Locale.getDefault()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubMenu allLanguagesMenu = languageMenu.addSubMenu(R.string.sk_all_languages);
|
SubMenu allLanguagesMenu = languageMenu.addSubMenu(R.string.sk_available_languages);
|
||||||
for (int i = 0; i < allIsoLanguages.size(); i++) {
|
for (int i = 0; i < allLanguages.size(); i++) {
|
||||||
Locale loc = allIsoLanguages.get(i);
|
MastodonLanguage l = allLanguages.get(i);
|
||||||
allLanguagesMenu.add(0, i, Menu.NONE, loc.getDisplayLanguage(Locale.getDefault()));
|
allLanguagesMenu.add(0, i, Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
languagePopup.setOnMenuItemClickListener(i->{
|
languagePopup.setOnMenuItemClickListener(i->{
|
||||||
if (i.hasSubMenu()) return false;
|
if (i.hasSubMenu()) return false;
|
||||||
updateLanguage(allIsoLanguages.get(i.getItemId()));
|
updateLanguage(allLanguages.get(i.getItemId()));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return languageButton;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -743,6 +874,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
updatePublishButtonState();
|
updatePublishButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resetPublishButtonText() {
|
||||||
|
int publishText = editingStatus==null || redraftStatus ? R.string.publish : R.string.save;
|
||||||
|
if(GlobalUserPreferences.relocatePublishButton){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (publishText == R.string.publish && !GlobalUserPreferences.publishButtonText.isEmpty()) {
|
||||||
|
publishButton.setText(GlobalUserPreferences.publishButtonText);
|
||||||
|
} else {
|
||||||
|
publishButton.setText(publishText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updatePublishButtonState(){
|
private void updatePublishButtonState(){
|
||||||
uuid=null;
|
uuid=null;
|
||||||
int nonEmptyPollOptionsCount=0;
|
int nonEmptyPollOptionsCount=0;
|
||||||
@@ -758,6 +901,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
nonDoneAttachmentCount++;
|
nonDoneAttachmentCount++;
|
||||||
}
|
}
|
||||||
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
||||||
|
sendError.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCustomEmojiClick(Emoji emoji){
|
private void onCustomEmojiClick(Emoji emoji){
|
||||||
@@ -773,7 +917,55 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onPublishClick(View v){
|
private void onPublishClick(View v){
|
||||||
publish();
|
if (!attachments.isEmpty()
|
||||||
|
&& statusVisibility != StatusPrivacy.DIRECT
|
||||||
|
&& !attachments.stream().allMatch(attachment -> attachment.description != null && !attachment.description.isBlank())) {
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_no_image_desc_title)
|
||||||
|
.setMessage(R.string.sk_no_image_desc)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.publish, (dialog, i)-> publish())
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
publish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishErrorCallback(ErrorResponse error) {
|
||||||
|
wm.removeView(sendingOverlay);
|
||||||
|
sendingOverlay=null;
|
||||||
|
sendProgress.setVisibility(View.GONE);
|
||||||
|
sendError.setVisibility(View.VISIBLE);
|
||||||
|
publishButton.setEnabled(true);
|
||||||
|
if (error != null) error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createScheduledStatusFinish(ScheduledStatus result) {
|
||||||
|
wm.removeView(sendingOverlay);
|
||||||
|
sendingOverlay=null;
|
||||||
|
Toast.makeText(getContext(), scheduledAt.isAfter(DRAFTS_AFTER_INSTANT) ?
|
||||||
|
R.string.sk_draft_saved : R.string.sk_post_scheduled, Toast.LENGTH_SHORT).show();
|
||||||
|
Nav.finish(ComposeFragment.this);
|
||||||
|
E.post(new ScheduledStatusCreatedEvent(result, accountID));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeDeleteScheduledPost(Runnable callback) {
|
||||||
|
if (scheduledStatus != null) {
|
||||||
|
new DeleteStatus.Scheduled(scheduledStatus.id).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object o) {
|
||||||
|
E.post(new ScheduledStatusDeletedEvent(scheduledStatus.id, accountID));
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
publishErrorCallback(error);
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
} else {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publish(){
|
private void publish(){
|
||||||
@@ -783,6 +975,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
req.visibility=statusVisibility;
|
req.visibility=statusVisibility;
|
||||||
req.sensitive=sensitive;
|
req.sensitive=sensitive;
|
||||||
req.language=language;
|
req.language=language;
|
||||||
|
req.scheduledAt = scheduledAt;
|
||||||
if(!attachments.isEmpty()){
|
if(!attachments.isEmpty()){
|
||||||
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
@@ -819,53 +1012,80 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
Callback<Status> resCallback=new Callback<>(){
|
Callback<Status> resCallback=new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
wm.removeView(sendingOverlay);
|
maybeDeleteScheduledPost(() -> {
|
||||||
sendingOverlay=null;
|
wm.removeView(sendingOverlay);
|
||||||
if(editingStatus==null){
|
sendingOverlay=null;
|
||||||
E.post(new StatusCreatedEvent(result, accountID));
|
if(editingStatus==null){
|
||||||
if(replyTo!=null){
|
E.post(new StatusCreatedEvent(result, accountID));
|
||||||
replyTo.repliesCount++;
|
if(replyTo!=null){
|
||||||
E.post(new StatusCountersUpdatedEvent(replyTo));
|
replyTo.repliesCount++;
|
||||||
|
E.post(new StatusCountersUpdatedEvent(replyTo));
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
E.post(new StatusUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
}else{
|
Nav.finish(ComposeFragment.this);
|
||||||
E.post(new StatusUpdatedEvent(result));
|
if (getArguments().getBoolean("navigateToStatus", false)) {
|
||||||
}
|
Bundle args=new Bundle();
|
||||||
Nav.finish(ComposeFragment.this);
|
args.putString("account", accountID);
|
||||||
if (getArguments().getBoolean("navigateToStatus", false)) {
|
args.putParcelable("status", Parcels.wrap(result));
|
||||||
Bundle args=new Bundle();
|
if(replyTo!=null) args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo));
|
||||||
args.putString("account", accountID);
|
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||||
args.putParcelable("status", Parcels.wrap(result));
|
}
|
||||||
if(replyTo!=null) args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo));
|
});
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
wm.removeView(sendingOverlay);
|
publishErrorCallback(error);
|
||||||
sendingOverlay=null;
|
|
||||||
sendProgress.setVisibility(View.GONE);
|
|
||||||
sendError.setVisibility(View.VISIBLE);
|
|
||||||
publishButton.setEnabled(true);
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if(editingStatus!=null && !redraftStatus){
|
if(editingStatus!=null && !redraftStatus){
|
||||||
new EditStatus(req, editingStatus.id)
|
new EditStatus(req, editingStatus.id)
|
||||||
.setCallback(resCallback)
|
.setCallback(resCallback)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}else{
|
}else if(req.scheduledAt == null){
|
||||||
new CreateStatus(req, uuid)
|
new CreateStatus(req, uuid)
|
||||||
.setCallback(resCallback)
|
.setCallback(resCallback)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
}else if(req.scheduledAt.isAfter(Instant.now().plus(10, ChronoUnit.MINUTES))){
|
||||||
|
// checking for 10 instead of 5 minutes (as per mastodon) because i really don't want
|
||||||
|
// bugs to occur because the client's clock is wrong by a minute or two - the api
|
||||||
|
// returns a status instead of a scheduled status if scheduled time is less than 5
|
||||||
|
// minutes into the future and this is 1. unexpected for the user and 2. hard to handle
|
||||||
|
new CreateStatus.Scheduled(req, uuid)
|
||||||
|
.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(ScheduledStatus result) {
|
||||||
|
maybeDeleteScheduledPost(() -> {
|
||||||
|
createScheduledStatusFinish(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
publishErrorCallback(error);
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}else{
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_scheduled_too_soon_title)
|
||||||
|
.setMessage(R.string.sk_scheduled_too_soon)
|
||||||
|
.setPositiveButton(R.string.ok, (a, b)->{})
|
||||||
|
.show();
|
||||||
|
publishErrorCallback(null);
|
||||||
|
publishButton.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> recentLanguages = new ArrayList<>(GlobalUserPreferences.recentLanguages);
|
if (replyTo == null) {
|
||||||
recentLanguages.remove(language);
|
List<String> newRecentLanguages = new ArrayList<>(Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages));
|
||||||
recentLanguages.add(0, language);
|
newRecentLanguages.remove(language);
|
||||||
GlobalUserPreferences.recentLanguages = recentLanguages.stream().limit(4).collect(Collectors.toList());
|
newRecentLanguages.add(0, language);
|
||||||
GlobalUserPreferences.save();
|
recentLanguages.put(accountID, newRecentLanguages.stream().limit(4).collect(Collectors.toList()));
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasDraft(){
|
private boolean hasDraft(){
|
||||||
@@ -876,6 +1096,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
List<String> existingMediaIDs=editingStatus.mediaAttachments.stream().map(a->a.id).collect(Collectors.toList());
|
List<String> existingMediaIDs=editingStatus.mediaAttachments.stream().map(a->a.id).collect(Collectors.toList());
|
||||||
if(!existingMediaIDs.equals(attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList())))
|
if(!existingMediaIDs.equals(attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList())))
|
||||||
return true;
|
return true;
|
||||||
|
if(!statusVisibility.equals(editingStatus.visibility)) return true;
|
||||||
|
if(scheduledStatus != null && !scheduledStatus.scheduledAt.equals(scheduledAt)) return true;
|
||||||
return pollChanged;
|
return pollChanged;
|
||||||
}
|
}
|
||||||
boolean pollFieldsHaveContent=false;
|
boolean pollFieldsHaveContent=false;
|
||||||
@@ -920,14 +1142,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (reqCode == SCHEDULED_STATUS_OPENED_RESULT && success && getActivity() != null) {
|
||||||
|
Nav.finish(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmDiscardDraftAndFinish(){
|
private void confirmDiscardDraftAndFinish(){
|
||||||
new M3AlertDialogBuilder(getActivity())
|
new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(editingStatus==null ? R.string.discard_draft : R.string.discard_changes)
|
.setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
|
||||||
.setPositiveButton(R.string.discard, (dialog, which)->Nav.finish(this))
|
.setPositiveButton(R.string.save, (d, w) -> {
|
||||||
.setNegativeButton(R.string.cancel, null)
|
updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt);
|
||||||
|
publish();
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1081,7 +1308,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
private void uploadMediaAttachment(DraftMediaAttachment attachment){
|
private void uploadMediaAttachment(DraftMediaAttachment attachment){
|
||||||
if(areThereAnyUploadingAttachments()){
|
if(areThereAnyUploadingAttachments()){
|
||||||
throw new IllegalStateException("there is already an attachment being uploaded");
|
throw new IllegalStateException("there is already an attachment being uploaded");
|
||||||
}
|
}
|
||||||
attachment.state=AttachmentUploadState.UPLOADING;
|
attachment.state=AttachmentUploadState.UPLOADING;
|
||||||
attachment.progressBar.setVisibility(View.VISIBLE);
|
attachment.progressBar.setVisibility(View.VISIBLE);
|
||||||
@@ -1106,7 +1333,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
@Override
|
@Override
|
||||||
public void onProgress(long transferred, long total){
|
public void onProgress(long transferred, long total){
|
||||||
if(updateUploadEtaRunnable==null){
|
if(updateUploadEtaRunnable==null){
|
||||||
UiUtils.runOnUiThread(updateUploadEtaRunnable=ComposeFragment.this::updateUploadETAs, 100);
|
// getting a NoSuchMethodError: No static method -$$Nest$mupdateUploadETAs(ComposeFragment;)V in class ComposeFragment
|
||||||
|
// when using method reference out of nowhere after changing code elsewhere. no idea. programming is awful, actually
|
||||||
|
// noinspection Convert2MethodRef
|
||||||
|
UiUtils.runOnUiThread(updateUploadEtaRunnable=()->ComposeFragment.this.updateUploadETAs(), 50);
|
||||||
}
|
}
|
||||||
int progress=Math.round(transferred/(float)total*attachment.progressBar.getMax());
|
int progress=Math.round(transferred/(float)total*attachment.progressBar.getMax());
|
||||||
if(Build.VERSION.SDK_INT>=24)
|
if(Build.VERSION.SDK_INT>=24)
|
||||||
@@ -1269,7 +1499,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
att.uploadStateText.setText(getString(R.string.file_upload_time_remaining, time));
|
att.uploadStateText.setText(getString(R.string.file_upload_time_remaining, time));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UiUtils.runOnUiThread(updateUploadEtaRunnable, 100);
|
UiUtils.runOnUiThread(updateUploadEtaRunnable, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onEditMediaDescriptionClick(View v){
|
private void onEditMediaDescriptionClick(View v){
|
||||||
@@ -1401,23 +1631,84 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
if (attachments.isEmpty()) sensitive = false;
|
if (attachments.isEmpty()) sensitive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void pickScheduledDateTime() {
|
||||||
|
LocalDateTime soon = LocalDateTime.now()
|
||||||
|
.plus(15, ChronoUnit.MINUTES) // so 14:59 doesn't get rounded up to…
|
||||||
|
.plus(1, ChronoUnit.HOURS) // …15:00, but rather 16:00
|
||||||
|
.withMinute(0);
|
||||||
|
new DatePickerDialog(getActivity(), (datePicker, year, arrayMonth, dayOfMonth) -> {
|
||||||
|
new TimePickerDialog(getActivity(), (timePicker, hour, minute) -> {
|
||||||
|
updateScheduledAt(LocalDateTime.of(year, arrayMonth + 1, dayOfMonth, hour, minute)
|
||||||
|
.toInstant(OffsetDateTime.now().getOffset()));
|
||||||
|
}, soon.getHour(), soon.getMinute(), DateFormat.is24HourFormat(getActivity())).show();
|
||||||
|
}, soon.getYear(), soon.getMonthValue() - 1, soon.getDayOfMonth()).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateScheduledAt(Instant scheduledAt) {
|
||||||
|
this.scheduledAt = scheduledAt;
|
||||||
|
updatePublishButtonState();
|
||||||
|
scheduleDraftView.setVisibility(scheduledAt == null ? View.GONE : View.VISIBLE);
|
||||||
|
draftMenuItem.setVisible(true);
|
||||||
|
scheduleMenuItem.setVisible(true);
|
||||||
|
undraftMenuItem.setVisible(false);
|
||||||
|
unscheduleMenuItem.setVisible(false);
|
||||||
|
if (scheduledAt != null) {
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
|
||||||
|
if (scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
|
||||||
|
draftMenuItem.setVisible(false);
|
||||||
|
undraftMenuItem.setVisible(true);
|
||||||
|
scheduleTimeBtn.setVisibility(View.GONE);
|
||||||
|
scheduleDraftText.setText(R.string.sk_compose_draft);
|
||||||
|
scheduleDraftText.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_fluent_drafts_20_regular, 0, 0, 0);
|
||||||
|
scheduleDraftDismiss.setContentDescription(getString(R.string.sk_compose_no_draft));
|
||||||
|
draftsBtn.setCompoundDrawablesWithIntrinsicBounds(GlobalUserPreferences.relocatePublishButton ? R.drawable.ic_fluent_drafts_24_regular : R.drawable.ic_fluent_drafts_20_filled, 0, 0, 0);
|
||||||
|
|
||||||
|
if(GlobalUserPreferences.relocatePublishButton){
|
||||||
|
publishButton.setCompoundDrawablesWithIntrinsicBounds(scheduledStatus != null && scheduledStatus.scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)
|
||||||
|
? R.drawable.ic_fluent_save_24_selector : R.drawable.ic_fluent_drafts_24_selector, 0, 0, 0);
|
||||||
|
}else{
|
||||||
|
publishButton.setText(scheduledStatus != null && scheduledStatus.scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)
|
||||||
|
? R.string.save : R.string.sk_draft);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scheduleMenuItem.setVisible(false);
|
||||||
|
unscheduleMenuItem.setVisible(true);
|
||||||
|
String at = scheduledAt.atZone(ZoneId.systemDefault()).format(formatter);
|
||||||
|
scheduleTimeBtn.setVisibility(View.VISIBLE);
|
||||||
|
scheduleTimeBtn.setText(at);
|
||||||
|
scheduleDraftText.setText(R.string.sk_compose_scheduled);
|
||||||
|
scheduleDraftText.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||||
|
scheduleDraftDismiss.setContentDescription(getString(R.string.sk_compose_no_schedule));
|
||||||
|
draftsBtn.setCompoundDrawablesWithIntrinsicBounds(GlobalUserPreferences.relocatePublishButton ? R.drawable.ic_fluent_clock_24_filled : R.drawable.ic_fluent_clock_20_filled, 0, 0, 0);
|
||||||
|
if(GlobalUserPreferences.relocatePublishButton)
|
||||||
|
{
|
||||||
|
publishButton.setCompoundDrawablesWithIntrinsicBounds(scheduledStatus != null && scheduledStatus.scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)
|
||||||
|
? R.drawable.ic_fluent_save_24_selector : R.drawable.ic_fluent_clock_24_selector, 0, 0, 0);
|
||||||
|
}else{
|
||||||
|
publishButton.setText(scheduledStatus != null && scheduledStatus.scheduledAt.equals(scheduledAt)
|
||||||
|
? R.string.save : R.string.sk_schedule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
draftsBtn.setCompoundDrawablesWithIntrinsicBounds(GlobalUserPreferences.relocatePublishButton ? R.drawable.ic_fluent_clock_24_regular : R.drawable.ic_fluent_clock_20_regular, 0, 0, 0);
|
||||||
|
if(GlobalUserPreferences.relocatePublishButton){
|
||||||
|
publishButton.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_fluent_send_24_selector, 0, 0, 0);
|
||||||
|
}
|
||||||
|
resetPublishButtonText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int getMediaAttachmentsCount(){
|
private int getMediaAttachmentsCount(){
|
||||||
return attachments.size();
|
return attachments.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onVisibilityClick(View v){
|
private void buildVisibilityPopup(View v){
|
||||||
PopupMenu menu=new PopupMenu(getActivity(), v);
|
visibilityPopup=new PopupMenu(getActivity(), v);
|
||||||
menu.inflate(R.menu.compose_visibility);
|
visibilityPopup.inflate(R.menu.compose_visibility);
|
||||||
Menu m=menu.getMenu();
|
Menu m=visibilityPopup.getMenu();
|
||||||
UiUtils.enablePopupMenuIcons(getActivity(), menu);
|
UiUtils.enablePopupMenuIcons(getActivity(), visibilityPopup);
|
||||||
m.setGroupCheckable(0, true, true);
|
m.setGroupCheckable(0, true, true);
|
||||||
m.findItem(switch(statusVisibility){
|
visibilityPopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
|
||||||
case PUBLIC -> R.id.vis_public;
|
|
||||||
case UNLISTED -> R.id.vis_unlisted;
|
|
||||||
case PRIVATE -> R.id.vis_followers;
|
|
||||||
case DIRECT -> R.id.vis_private;
|
|
||||||
}).setChecked(true);
|
|
||||||
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemClick(MenuItem item){
|
public boolean onMenuItemClick(MenuItem item){
|
||||||
int id=item.getItemId();
|
int id=item.getItemId();
|
||||||
@@ -1435,7 +1726,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
menu.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
||||||
@@ -1476,7 +1766,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
case PUBLIC -> R.drawable.ic_fluent_earth_24_regular;
|
case PUBLIC -> R.drawable.ic_fluent_earth_24_regular;
|
||||||
case UNLISTED -> R.drawable.ic_fluent_people_community_24_regular;
|
case UNLISTED -> R.drawable.ic_fluent_people_community_24_regular;
|
||||||
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_24_regular;
|
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_24_regular;
|
||||||
case DIRECT -> R.drawable.ic_at_symbol;
|
case DIRECT -> R.drawable.ic_fluent_mention_24_regular;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
|||||||
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
||||||
if (!rel.requested && !rel.followedBy && adapter != null) {
|
if (!rel.requested && !rel.followedBy && adapter != null) {
|
||||||
data.remove(item);
|
data.remove(item);
|
||||||
adapter.notifyItemRemoved(getBindingAdapterPosition());
|
adapter.notifyItemRemoved(getLayoutPosition());
|
||||||
} else {
|
} else {
|
||||||
rebind();
|
rebind();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
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.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
|
||||||
|
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.api.requests.timelines.GetHashtagTimeline;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -117,6 +118,7 @@ public class HashtagTimelineFragment extends StatusListFragment{
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
fab=view.findViewById(R.id.fab);
|
fab=view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
|
|||||||
@@ -202,6 +202,17 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
private void onTabSelected(@IdRes int tab){
|
private void onTabSelected(@IdRes int tab){
|
||||||
Fragment newFragment=fragmentForTab(tab);
|
Fragment newFragment=fragmentForTab(tab);
|
||||||
if(tab==currentTab){
|
if(tab==currentTab){
|
||||||
|
if(tab == R.id.tab_search){
|
||||||
|
if(newFragment instanceof ScrollableToTop scrollable)
|
||||||
|
scrollable.scrollToTop();
|
||||||
|
searchFragment.selectSearch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(newFragment instanceof ScrollableToTop scrollable)
|
||||||
|
scrollable.scrollToTop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(tab==currentTab && tab == R.id.tab_search){
|
||||||
if(newFragment instanceof ScrollableToTop scrollable)
|
if(newFragment instanceof ScrollableToTop scrollable)
|
||||||
scrollable.scrollToTop();
|
scrollable.scrollToTop();
|
||||||
return;
|
return;
|
||||||
@@ -235,6 +246,12 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
new AccountSwitcherSheet(getActivity()).show();
|
new AccountSwitcherSheet(getActivity()).show();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if(tab==R.id.tab_search){
|
||||||
|
onTabSelected(R.id.tab_search);
|
||||||
|
tabBar.selectTab(R.id.tab_search);
|
||||||
|
searchFragment.selectSearch();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ import android.widget.Button;
|
|||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
@@ -43,12 +45,9 @@ import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
@@ -106,6 +105,8 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
fab=view.findViewById(R.id.fab);
|
fab=view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
fab.setOnLongClickListener(v->UiUtils.pickAccountForCompose(getActivity(), accountID));
|
||||||
|
|
||||||
updateToolbarLogo();
|
updateToolbarLogo();
|
||||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||||
@Override
|
@Override
|
||||||
@@ -264,18 +265,14 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
targetList.clear();
|
targetList.clear();
|
||||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
||||||
outer:
|
|
||||||
for(Status s:result){
|
for(Status s:result){
|
||||||
if(idsBelowGap.contains(s.id))
|
if(idsBelowGap.contains(s.id))
|
||||||
break;
|
break;
|
||||||
for(Filter filter:filters){
|
if(filterPredicate.test(s)){
|
||||||
if(filter.matches(s)){
|
targetList.addAll(buildDisplayItems(s));
|
||||||
continue outer;
|
insertedPosts.add(s);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
targetList.addAll(buildDisplayItems(s));
|
|
||||||
insertedPosts.add(s);
|
|
||||||
}
|
}
|
||||||
if(targetList.isEmpty()){
|
if(targetList.isEmpty()){
|
||||||
// oops. We didn't add new posts, but at least we know there are none.
|
// oops. We didn't add new posts, but at least we know there are none.
|
||||||
@@ -321,6 +318,9 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
|
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
|
||||||
toolbarLogo.setImageResource(R.drawable.logo);
|
toolbarLogo.setImageResource(R.drawable.logo);
|
||||||
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
||||||
|
// toolbarLogo =new TextView(getActivity());
|
||||||
|
// toolbarLogo.setText(getString(R.string.app_name).toLowerCase(Locale.getDefault()));
|
||||||
|
// toolbarLogo.setTextAppearance(R.style.app_title);
|
||||||
|
|
||||||
toolbarShowNewPostsBtn=new Button(getActivity());
|
toolbarShowNewPostsBtn=new Button(getActivity());
|
||||||
toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium);
|
toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium);
|
||||||
@@ -348,9 +348,7 @@ public class HomeTimelineFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
FrameLayout logoWrap=new FrameLayout(getActivity());
|
FrameLayout logoWrap=new FrameLayout(getActivity());
|
||||||
FrameLayout.LayoutParams logoParams=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
|
logoWrap.addView(toolbarLogo, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||||
logoParams.setMargins(0, V.dp(2), 0, 0);
|
|
||||||
logoWrap.addView(toolbarLogo, logoParams);
|
|
||||||
logoWrap.addView(toolbarShowNewPostsBtn, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(32), Gravity.CENTER));
|
logoWrap.addView(toolbarShowNewPostsBtn, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(32), Gravity.CENTER));
|
||||||
|
|
||||||
Toolbar toolbar=getToolbar();
|
Toolbar toolbar=getToolbar();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.media.MediaRouter;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -12,6 +11,7 @@ import android.widget.ImageButton;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -69,6 +69,7 @@ public class ListTimelineFragment extends StatusListFragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
fab=view.findViewById(R.id.fab);
|
fab=view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -23,17 +18,13 @@ import org.joinmastodon.android.model.ListTimeline;
|
|||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||||
@@ -159,7 +150,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
|||||||
private final CheckBox listToggle;
|
private final CheckBox listToggle;
|
||||||
|
|
||||||
public ListViewHolder(){
|
public ListViewHolder(){
|
||||||
super(getActivity(), R.layout.item_list_timeline, list);
|
super(getActivity(), R.layout.item_text, list);
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
listToggle=findViewById(R.id.list_toggle);
|
listToggle=findViewById(R.id.list_toggle);
|
||||||
}
|
}
|
||||||
@@ -167,8 +158,10 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
|||||||
@Override
|
@Override
|
||||||
public void onBind(ListTimeline item) {
|
public void onBind(ListTimeline item) {
|
||||||
title.setText(item.title);
|
title.setText(item.title);
|
||||||
|
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_community_24_regular), null, null, null);
|
||||||
if (profileAccountId != null) {
|
if (profileAccountId != null) {
|
||||||
Boolean checked = userInList.get(item.id);
|
Boolean checked = userInList.get(item.id);
|
||||||
|
listToggle.setVisibility(View.VISIBLE);
|
||||||
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
||||||
listToggle.setOnClickListener(this::onClickToggle);
|
listToggle.setOnClickListener(this::onClickToggle);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||||
@@ -73,15 +74,26 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(R.menu.notifications, menu);
|
inflater.inflate(R.menu.notifications, menu);
|
||||||
|
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
|
||||||
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (item.getItemId() != R.id.follow_requests) return false;
|
if (item.getItemId() == R.id.follow_requests) {
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), FollowRequestsListFragment.class, args);
|
Nav.go(getActivity(), FollowRequestsListFragment.class, args);
|
||||||
return true;
|
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
|
@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));
|
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||||
|
|
||||||
pager.setOffscreenPageLimit(4);
|
pager.setOffscreenPageLimit(4);
|
||||||
|
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||||
pager.setAdapter(new DiscoverPagerAdapter());
|
pager.setAdapter(new DiscoverPagerAdapter());
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
@@ -10,7 +12,6 @@ import org.joinmastodon.android.E;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
@@ -78,9 +79,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
case FAVORITE -> getString(R.string.user_favorited);
|
case FAVORITE -> getString(R.string.user_favorited);
|
||||||
case POLL -> getString(R.string.poll_ended);
|
case POLL -> getString(R.string.poll_ended);
|
||||||
};
|
};
|
||||||
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){
|
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){
|
if(titleItem!=null){
|
||||||
for(StatusDisplayItem item:items){
|
for(StatusDisplayItem item:items){
|
||||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
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);
|
data.remove(n);
|
||||||
preloadedData.remove(n);
|
preloadedData.remove(n);
|
||||||
int index=-1;
|
int index=-1;
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import static android.content.Context.CLIPBOARD_SERVICE;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
@@ -35,13 +31,16 @@ import android.widget.Button;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||||
@@ -80,10 +79,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
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.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
@@ -161,6 +156,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getPrefilledText() {
|
||||||
|
return account == null || AccountSessionManager.getInstance().isSelf(accountID, account)
|
||||||
|
? null : '@'+account.acct+' ';
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
@@ -183,6 +183,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
followingCount=content.findViewById(R.id.following_count);
|
followingCount=content.findViewById(R.id.following_count);
|
||||||
followingLabel=content.findViewById(R.id.following_label);
|
followingLabel=content.findViewById(R.id.following_label);
|
||||||
followingBtn=content.findViewById(R.id.following_btn);
|
followingBtn=content.findViewById(R.id.following_btn);
|
||||||
|
|
||||||
postsCount=content.findViewById(R.id.posts_count);
|
postsCount=content.findViewById(R.id.posts_count);
|
||||||
postsLabel=content.findViewById(R.id.posts_label);
|
postsLabel=content.findViewById(R.id.posts_label);
|
||||||
postsBtn=content.findViewById(R.id.posts_btn);
|
postsBtn=content.findViewById(R.id.posts_btn);
|
||||||
@@ -234,6 +235,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
pager.setOffscreenPageLimit(5);
|
pager.setOffscreenPageLimit(5);
|
||||||
|
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||||
pager.setAdapter(new ProfilePagerAdapter());
|
pager.setAdapter(new ProfilePagerAdapter());
|
||||||
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
||||||
|
|
||||||
@@ -271,6 +273,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
cover.setOnClickListener(this::onCoverClick);
|
cover.setOnClickListener(this::onCoverClick);
|
||||||
refreshLayout.setOnRefreshListener(this);
|
refreshLayout.setOnRefreshListener(this);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
fab.setOnLongClickListener(v->UiUtils.pickAccountForCompose(getActivity(), accountID, getPrefilledText()));
|
||||||
|
|
||||||
if(loaded){
|
if(loaded){
|
||||||
bindHeaderView();
|
bindHeaderView();
|
||||||
@@ -283,15 +286,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||||
|
|
||||||
|
if (account != null && account.bot) {
|
||||||
|
username.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_fluent_bot_24_filled, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
username.setOnLongClickListener(v->{
|
username.setOnLongClickListener(v->{
|
||||||
String username=account.acct;
|
String usernameString=account.acct;
|
||||||
if(!username.contains("@")){
|
if(!usernameString.contains("@")){
|
||||||
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
usernameString+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
UiUtils.copyText(username, '@'+usernameString);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -548,17 +552,29 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(relationship==null && !isOwnProfile)
|
if(relationship==null && !isOwnProfile)
|
||||||
return;
|
return;
|
||||||
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
||||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
if(isOwnProfile){
|
||||||
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.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)
|
if(isOwnProfile)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
MenuItem mute = menu.findItem(R.id.mute);
|
||||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||||
|
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()));
|
||||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
||||||
if(relationship.following) {
|
if(relationship.following) {
|
||||||
menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
|
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
|
||||||
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getDisplayUsername()));
|
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
||||||
|
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
|
||||||
|
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
||||||
|
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
||||||
manageUserLists.setVisible(true);
|
manageUserLists.setVisible(true);
|
||||||
}else {
|
}else {
|
||||||
menu.findItem(R.id.hide_boosts).setVisible(false);
|
menu.findItem(R.id.hide_boosts).setVisible(false);
|
||||||
@@ -623,6 +639,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
args.putString("profileAccount", profileAccountID);
|
args.putString("profileAccount", profileAccountID);
|
||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||||
|
}else if(id==R.id.followed_hashtags){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), FollowedHashtagsFragment.class, args);
|
||||||
|
}else if(id==R.id.scheduled){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), ScheduledStatusListFragment.class, args);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -934,9 +958,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
if(!AccountSessionManager.getInstance().isSelf(accountID, account)){
|
if(getPrefilledText() != null) args.putString("prefilledText", getPrefilledText());
|
||||||
args.putString("prefilledText", '@'+account.acct+' ');
|
|
||||||
}
|
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,16 +9,21 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.LinearInterpolator;
|
import android.view.animation.LinearInterpolator;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
@@ -31,6 +36,7 @@ import com.squareup.otto.Subscribe;
|
|||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
@@ -40,12 +46,15 @@ import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
|||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -55,6 +64,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.imageloader.ImageCache;
|
import me.grishka.appkit.imageloader.ImageCache;
|
||||||
@@ -83,6 +94,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
setTitle(R.string.settings);
|
setTitle(R.string.settings);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||||
|
String instanceName = UiUtils.getInstanceName(accountID);
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||||
@@ -99,9 +112,73 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.disableMarquee=i.checked;
|
GlobalUserPreferences.disableMarquee=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new ColorPicker());
|
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 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 HeaderItem(R.string.settings_behavior));
|
items.add(new HeaderItem(R.string.settings_behavior));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_show_federated_timeline, R.drawable.ic_fluent_earth_24_regular, GlobalUserPreferences.showFederatedTimeline, i->{
|
||||||
|
GlobalUserPreferences.showFederatedTimeline=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
needAppRestart=true;
|
||||||
|
}));
|
||||||
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||||
GlobalUserPreferences.playGifs=i.checked;
|
GlobalUserPreferences.playGifs=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
@@ -118,6 +195,31 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
||||||
GlobalUserPreferences.save();
|
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 HeaderItem(R.string.home_timeline));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||||
@@ -132,11 +234,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.loadNewPosts=i.checked;
|
GlobalUserPreferences.loadNewPosts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
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 HeaderItem(R.string.settings_notifications));
|
items.add(new HeaderItem(R.string.settings_notifications));
|
||||||
items.add(notificationPolicyItem=new NotificationPolicyItem());
|
items.add(notificationPolicyItem=new NotificationPolicyItem());
|
||||||
@@ -144,30 +241,58 @@ 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_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_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_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_notify_posts, R.drawable.ic_fluent_alert_24_regular, pushSubscription.alerts.status, i->onNotificationsChanged(PushNotification.Type.STATUS, i.checked)));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_boring));
|
items.add(new HeaderItem(R.string.settings_account));
|
||||||
items.add(new TextItem(R.string.settings_account, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit")));
|
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.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
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.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
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.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()) {
|
if (GithubSelfUpdater.needSelfUpdating()) {
|
||||||
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||||
items.add(checkForUpdateItem);
|
items.add(checkForUpdateItem);
|
||||||
}
|
}
|
||||||
items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon")));
|
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));
|
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||||
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
|
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
|
||||||
GlobalUserPreferences.recentLanguages.clear();
|
GlobalUserPreferences.recentLanguages.remove(accountID);
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
})));
|
})));
|
||||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
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.sk_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
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
@@ -240,12 +365,29 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
restartActivityToApplyNewTheme();
|
restartActivityToApplyNewTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onColorPreferenceClick(GlobalUserPreferences.ColorPreference color){
|
private boolean onColorPreferenceClick(MenuItem item){
|
||||||
GlobalUserPreferences.color=color;
|
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();
|
GlobalUserPreferences.save();
|
||||||
restartActivityToApplyNewTheme();
|
restartActivityToApplyNewTheme();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void onTrueBlackThemeChanged(SwitchItem item){
|
private void onTrueBlackThemeChanged(SwitchItem item){
|
||||||
GlobalUserPreferences.trueBlackTheme=item.checked;
|
GlobalUserPreferences.trueBlackTheme=item.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
@@ -310,6 +452,12 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
needUpdateNotificationSettings=true;
|
needUpdateNotificationSettings=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onNotificationStyleChanged(SwitchItem item){
|
||||||
|
GlobalUserPreferences.uniformNotificationIcon=item.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
|
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
|
||||||
PushSubscription subscription=getPushSubscription();
|
PushSubscription subscription=getPushSubscription();
|
||||||
PushSubscription.Policy prevPolicy=subscription.policy;
|
PushSubscription.Policy prevPolicy=subscription.policy;
|
||||||
@@ -415,6 +563,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
this.text=getString(text);
|
this.text=getString(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HeaderItem(String text) {
|
||||||
|
this.text=text;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
return 0;
|
return 0;
|
||||||
@@ -449,6 +601,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{
|
public class ColorPicker extends Item{
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
@@ -472,19 +641,42 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class SmallTextItem extends Item {
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public SmallTextItem(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewType() {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class TextItem extends Item{
|
private class TextItem extends Item{
|
||||||
private String text;
|
private String text;
|
||||||
private Runnable onClick;
|
private Runnable onClick;
|
||||||
private boolean loading;
|
private boolean loading;
|
||||||
|
private int icon;
|
||||||
|
|
||||||
public TextItem(@StringRes int text, Runnable onClick) {
|
public TextItem(@StringRes int text, Runnable onClick) {
|
||||||
this(text, onClick, false);
|
this(text, onClick, false, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextItem(@StringRes int text, Runnable onClick, boolean loading){
|
public TextItem(@StringRes int text, Runnable onClick, boolean loading) {
|
||||||
|
this(text, onClick, loading, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
|
||||||
|
this(text, onClick, false, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextItem(@StringRes int text, Runnable onClick, boolean loading, @DrawableRes int icon){
|
||||||
this.text=getString(text);
|
this.text=getString(text);
|
||||||
this.onClick=onClick;
|
this.onClick=onClick;
|
||||||
this.loading=loading;
|
this.loading=loading;
|
||||||
|
this.icon=icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -540,7 +732,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
case 5 -> new HeaderViewHolder(true);
|
case 5 -> new HeaderViewHolder(true);
|
||||||
case 6 -> new FooterViewHolder();
|
case 6 -> new FooterViewHolder();
|
||||||
case 7 -> new UpdateViewHolder();
|
case 7 -> new UpdateViewHolder();
|
||||||
case 8 -> new ColorPickerViewHolder();
|
case 8 -> new ButtonViewHolder();
|
||||||
|
case 9 -> new SmallTextViewHolder();
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -669,65 +862,25 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private class ColorPickerViewHolder extends BindableViewHolder<ColorPicker>{
|
|
||||||
|
private class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
|
||||||
private final Button button;
|
private final Button button;
|
||||||
private final PopupMenu popupMenu;
|
|
||||||
private final ImageView icon;
|
private final ImageView icon;
|
||||||
|
private final TextView text;
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
public ColorPickerViewHolder(){
|
public ButtonViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_color_picker, list);
|
super(getActivity(), R.layout.item_settings_button, list);
|
||||||
|
text=findViewById(R.id.text);
|
||||||
icon=findViewById(R.id.icon);
|
icon=findViewById(R.id.icon);
|
||||||
button=findViewById(R.id.color_picker_button);
|
button=findViewById(R.id.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.brown_color) {
|
|
||||||
pref = GlobalUserPreferences.ColorPreference.BROWN;
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(ColorPicker item){
|
public void onBind(ButtonItem item){
|
||||||
icon.setImageResource(R.drawable.ic_fluent_color_24_regular);
|
text.setText(item.text);
|
||||||
button.setText(switch(GlobalUserPreferences.color){
|
icon.setImageResource(item.icon);
|
||||||
case PINK -> R.string.sk_color_theme_pink;
|
item.buttonConsumer.accept(button);
|
||||||
case PURPLE -> R.string.sk_color_theme_purple;
|
|
||||||
case GREEN -> R.string.sk_color_theme_green;
|
|
||||||
case BLUE -> R.string.sk_color_theme_blue;
|
|
||||||
case BROWN -> R.string.sk_color_theme_brown;
|
|
||||||
case YELLOW -> R.string.sk_color_theme_yellow;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,17 +930,20 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
private final ProgressBar progress;
|
private final ProgressBar progress;
|
||||||
|
private final ImageView icon;
|
||||||
|
|
||||||
public TextViewHolder(){
|
public TextViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_text, list);
|
super(getActivity(), R.layout.item_settings_text, list);
|
||||||
text = itemView.findViewById(R.id.text);
|
text = itemView.findViewById(R.id.text);
|
||||||
progress = itemView.findViewById(R.id.progress);
|
progress = itemView.findViewById(R.id.progress);
|
||||||
|
icon = itemView.findViewById(R.id.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(TextItem item){
|
public void onBind(TextItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
progress.animate().alpha(item.loading ? 1 : 0);
|
progress.animate().alpha(item.loading ? 1 : 0);
|
||||||
|
if (item.icon != 0) icon.setImageDrawable(getActivity().getTheme().getDrawable(item.icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -796,6 +952,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 class FooterViewHolder extends BindableViewHolder<FooterItem>{
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
public FooterViewHolder(){
|
public FooterViewHolder(){
|
||||||
@@ -811,7 +987,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
|
|
||||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||||
|
|
||||||
private final TextView text;
|
private final TextView text, changelog;
|
||||||
private final Button button;
|
private final Button button;
|
||||||
private final ImageButton cancelBtn;
|
private final ImageButton cancelBtn;
|
||||||
private final ProgressBar progress;
|
private final ProgressBar progress;
|
||||||
@@ -822,6 +998,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
public UpdateViewHolder(){
|
public UpdateViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_update, list);
|
super(getActivity(), R.layout.item_settings_update, list);
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
|
changelog=findViewById(R.id.changelog);
|
||||||
button=findViewById(R.id.button);
|
button=findViewById(R.id.button);
|
||||||
cancelBtn=findViewById(R.id.cancel_btn);
|
cancelBtn=findViewById(R.id.cancel_btn);
|
||||||
progress=findViewById(R.id.progress);
|
progress=findViewById(R.id.progress);
|
||||||
@@ -865,6 +1042,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
progress.setVisibility(View.GONE);
|
progress.setVisibility(View.GONE);
|
||||||
progress.removeCallbacks(progressUpdater);
|
progress.removeCallbacks(progressUpdater);
|
||||||
}
|
}
|
||||||
|
changelog.setText(info.changelog);
|
||||||
|
// changelog.setText(getString(R.string.sk_changelog, info.changelog));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateProgress(){
|
private void updateProgress(){
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.graphics.Paint;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.ImageSpan;
|
|
||||||
import android.text.style.ReplacementSpan;
|
import android.text.style.ReplacementSpan;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -15,17 +14,14 @@ import android.view.WindowInsets;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.MastodonApp;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
||||||
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
|
||||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
||||||
int idx=data.indexOf(s);
|
int idx=data.indexOf(s);
|
||||||
if(idx>=0){
|
if(idx>=0){
|
||||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||||
@@ -139,7 +139,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
action=getString(R.string.edit_multiple_changed);
|
action=getString(R.string.edit_multiple_changed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null));
|
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null, null));
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
protected EventListener eventListener=new EventListener();
|
protected EventListener eventListener=new EventListener();
|
||||||
|
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true);
|
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -177,7 +177,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||||
for(Status s:data){
|
for(Status s:data){
|
||||||
if(s.getContentStatus().id.equals(ev.id)){
|
if(s.getContentStatus().id.equals(ev.id)){
|
||||||
s.update(ev);
|
s.getContentStatus().update(ev);
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==s.getContentStatus()){
|
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){
|
for(Status s:preloadedData){
|
||||||
if(s.id.equals(ev.id)){
|
if(s.getContentStatus().id.equals(ev.id)){
|
||||||
s.update(ev);
|
s.getContentStatus().update(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
@@ -17,6 +16,7 @@ import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
|||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -92,16 +92,10 @@ public class ThreadFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> filterStatuses(List<Status> statuses){
|
private List<Status> filterStatuses(List<Status> statuses){
|
||||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.THREAD)).collect(Collectors.toList());
|
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
|
||||||
if(filters.isEmpty())
|
return statuses.stream()
|
||||||
return statuses;
|
.filter(statusFilterPredicate)
|
||||||
return statuses.stream().filter(status->{
|
.collect(Collectors.toList());
|
||||||
for(Filter filter:filters){
|
|
||||||
if(filter.matches(status))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -286,6 +287,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_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.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.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
||||||
|
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getDisplayUsername())).setVisible(relationship.following);
|
||||||
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
||||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
||||||
if(relationship.following){
|
if(relationship.following){
|
||||||
@@ -372,6 +374,12 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
})
|
})
|
||||||
.wrapProgress(getActivity(), R.string.loading, false)
|
.wrapProgress(getActivity(), R.string.loading, false)
|
||||||
.exec(accountID);
|
.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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,11 +88,11 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
tabView.setId(switch(switchIndex){
|
tabView.setId(switch(switchIndex){
|
||||||
case 0 -> R.id.discover_local_timeline;
|
case 0 -> R.id.discover_local_timeline;
|
||||||
case 1 -> R.id.discover_federated_timeline;
|
case 1 -> R.id.discover_federated_timeline;
|
||||||
case 2 -> R.id.discover_hashtags;
|
case 2 -> R.id.discover_lists;
|
||||||
case 3 -> R.id.discover_posts;
|
case 3 -> R.id.discover_hashtags;
|
||||||
case 4 -> R.id.discover_news;
|
case 4 -> R.id.discover_posts;
|
||||||
case 5 -> R.id.discover_users;
|
case 5 -> R.id.discover_news;
|
||||||
case 6 -> R.id.discover_lists;
|
case 6 -> R.id.discover_users;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+switchIndex);
|
default -> throw new IllegalStateException("Unexpected value: "+switchIndex);
|
||||||
});
|
});
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
@@ -104,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));
|
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||||
|
|
||||||
pager.setOffscreenPageLimit(4);
|
pager.setOffscreenPageLimit(4);
|
||||||
|
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||||
pager.setAdapter(new DiscoverPagerAdapter());
|
pager.setAdapter(new DiscoverPagerAdapter());
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||||
@Override
|
@Override
|
||||||
@@ -165,11 +166,12 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.local_timeline;
|
case 0 -> R.string.local_timeline;
|
||||||
case 1 -> R.string.sk_federated_timeline;
|
case 1 -> R.string.sk_federated_timeline;
|
||||||
case 2 -> R.string.hashtags;
|
case 2 -> R.string.sk_list_timelines;
|
||||||
case 3 -> R.string.posts;
|
case 3 -> R.string.hashtags;
|
||||||
case 4 -> R.string.news;
|
case 4 -> R.string.posts;
|
||||||
case 5 -> R.string.for_you;
|
case 5 -> R.string.news;
|
||||||
case 6 -> R.string.sk_list_timelines;
|
case 6 -> R.string.for_you;
|
||||||
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
tab.view.textView.setAllCaps(true);
|
||||||
@@ -356,4 +358,10 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void selectSearch(){
|
||||||
|
searchEdit.requestFocus();
|
||||||
|
onSearchEditFocusChanged(searchEdit, true);
|
||||||
|
getActivity().getSystemService(InputMethodManager.class).showSoftInput(searchEdit, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
|||||||
return switch(s.type){
|
return switch(s.type){
|
||||||
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
||||||
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
||||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true);
|
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -12,19 +12,21 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||||
import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail;
|
import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail;
|
||||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
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.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.HomeFragment;
|
import org.joinmastodon.android.fragments.HomeFragment;
|
||||||
import org.joinmastodon.android.fragments.SettingsFragment;
|
import org.joinmastodon.android.fragments.SettingsFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -35,40 +37,50 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class AccountActivationFragment extends AppKitFragment{
|
public class AccountActivationFragment extends ToolbarFragment{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
|
||||||
private Button btn, backBtn;
|
private Button openEmailBtn, resendBtn;
|
||||||
private View buttonBar;
|
private View contentView;
|
||||||
private Handler uiHandler=new Handler(Looper.getMainLooper());
|
private Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||||
private Runnable pollRunnable=this::tryGetAccount;
|
private Runnable pollRunnable=this::tryGetAccount;
|
||||||
private APIRequest currentRequest;
|
private APIRequest currentRequest;
|
||||||
|
private Runnable resendTimer=this::updateResendTimer;
|
||||||
|
private long lastResendTime;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
|
setTitle(R.string.confirm_email_title);
|
||||||
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
lastResendTime=session.activationInfo!=null ? session.activationInfo.lastEmailConfirmationResend : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@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);
|
View view=inflater.inflate(R.layout.fragment_onboarding_activation, container, false);
|
||||||
|
|
||||||
btn=view.findViewById(R.id.btn_next);
|
openEmailBtn=view.findViewById(R.id.btn_next);
|
||||||
btn.setOnClickListener(v->onButtonClick());
|
openEmailBtn.setOnClickListener(this::onOpenEmailClick);
|
||||||
btn.setOnLongClickListener(v->{
|
openEmailBtn.setOnLongClickListener(v->{
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
resendBtn=view.findViewById(R.id.btn_resend);
|
||||||
view.findViewById(R.id.btn_back).setOnClickListener(v->onBackButtonClick());
|
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;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,14 +92,32 @@ public class AccountActivationFragment extends AppKitFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, 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
|
@Override
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=27){
|
if(Build.VERSION.SDK_INT>=27){
|
||||||
int inset=insets.getSystemWindowInsetBottom();
|
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));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||||
}else{
|
}else{
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
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{
|
try{
|
||||||
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||||
}catch(ActivityNotFoundException x){
|
}catch(ActivityNotFoundException x){
|
||||||
@@ -119,12 +149,21 @@ public class AccountActivationFragment extends AppKitFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBackButtonClick(){
|
private void onResendClick(View v){
|
||||||
new ResendConfirmationEmail(null)
|
new ResendConfirmationEmail(null)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Object result){
|
public void onSuccess(Object result){
|
||||||
Toast.makeText(getActivity(), R.string.resent_email, Toast.LENGTH_SHORT).show();
|
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
|
@Override
|
||||||
@@ -152,7 +191,7 @@ public class AccountActivationFragment extends AppKitFragment{
|
|||||||
AccountSessionManager mgr=AccountSessionManager.getInstance();
|
AccountSessionManager mgr=AccountSessionManager.getInstance();
|
||||||
AccountSession session=mgr.getAccount(accountID);
|
AccountSession session=mgr.getAccount(accountID);
|
||||||
mgr.removeAccount(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();
|
String newID=mgr.getLastActiveAccountID();
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", newID);
|
args.putString("account", newID);
|
||||||
@@ -189,4 +228,25 @@ public class AccountActivationFragment extends AppKitFragment{
|
|||||||
})
|
})
|
||||||
.exec(accountID);
|
.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,259 @@
|
|||||||
|
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 CustomLoginFragment extends InstanceCatalogFragment {
|
||||||
|
private View headerView;
|
||||||
|
|
||||||
|
public CustomLoginFragment() {
|
||||||
|
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);
|
||||||
|
((TextView) headerView.findViewById(R.id.username)).setText("@moshidon");
|
||||||
|
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
||||||
|
((TextView) headerView.findViewById(R.id.timestamp)).setText(R.string.time_now);
|
||||||
|
((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);
|
||||||
|
|
||||||
|
// itemView.setPadding(V.dp(16), V.dp(16), V.dp(16), V.dp(16));
|
||||||
|
// TypedValue value = new TypedValue();
|
||||||
|
// getActivity().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, value, true);
|
||||||
|
// itemView.setBackground(getActivity().getTheme().getDrawable(R.drawable.bg_search_field));
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
description=findViewById(R.id.description);
|
||||||
|
userCount=findViewById(R.id.user_count);
|
||||||
|
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;
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.graphics.Paint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -15,6 +16,7 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
@@ -33,6 +35,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
@@ -46,7 +49,7 @@ import okhttp3.Request;
|
|||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
||||||
private UsableRecyclerView list;
|
private UsableRecyclerView list;
|
||||||
private MergeRecyclerAdapter adapter;
|
private MergeRecyclerAdapter adapter;
|
||||||
private Button btn;
|
private Button btn;
|
||||||
@@ -60,6 +63,7 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
|||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
setTitle(R.string.privacy_policy_title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,37 +86,24 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||||
|
|
||||||
list=view.findViewById(R.id.list);
|
list=view.findViewById(R.id.list);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
||||||
TextView title=headerView.findViewById(R.id.title);
|
TextView text=headerView.findViewById(R.id.text);
|
||||||
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
text.setText(R.string.privacy_policy_subtitle);
|
||||||
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
|
|
||||||
title.setText(R.string.privacy_policy_title);
|
|
||||||
subtitle.setText(R.string.privacy_policy_subtitle);
|
|
||||||
|
|
||||||
adapter=new MergeRecyclerAdapter();
|
adapter=new MergeRecyclerAdapter();
|
||||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||||
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.setSelector(null);
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn=view.findViewById(R.id.btn_next);
|
btn=view.findViewById(R.id.btn_next);
|
||||||
btn.setOnClickListener(v->onButtonClick());
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -120,7 +111,15 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, 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(){
|
protected void onButtonClick(){
|
||||||
@@ -192,24 +191,17 @@ public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
||||||
private final TextView domain, title;
|
private final TextView title;
|
||||||
private final ImageView favicon;
|
|
||||||
|
|
||||||
public ItemViewHolder(){
|
public ItemViewHolder(){
|
||||||
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
||||||
domain=findViewById(R.id.domain);
|
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
favicon=findViewById(R.id.favicon);
|
title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(10));
|
|
||||||
itemView.setClipToOutline(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(Item item){
|
public void onBind(Item item){
|
||||||
domain.setText(item.domain);
|
|
||||||
title.setText(item.title);
|
title.setText(item.title);
|
||||||
|
|
||||||
ViewImageLoader.load(favicon, null, new UrlImageLoaderRequest(item.faviconUrl));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
isSignup=getArguments().getBoolean("signup");
|
isSignup=getArguments() != null && getArguments().getBoolean("signup");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void proceedWithAuthOrSignup(Instance instance);
|
protected abstract void proceedWithAuthOrSignup(Instance instance);
|
||||||
@@ -187,6 +187,8 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
||||||
|
if(TextUtils.isEmpty(_domain))
|
||||||
|
return;
|
||||||
String domain=normalizeInstanceDomain(_domain);
|
String domain=normalizeInstanceDomain(_domain);
|
||||||
Instance cachedInstance=instancesCache.get(domain);
|
Instance cachedInstance=instancesCache.get(domain);
|
||||||
if(cachedInstance!=null){
|
if(cachedInstance!=null){
|
||||||
@@ -222,7 +224,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
}
|
}
|
||||||
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
||||||
boolean found=false;
|
boolean found=false;
|
||||||
for(CatalogInstance ci : filteredData){
|
for(CatalogInstance ci:filteredData){
|
||||||
if(ci.domain.equals(domain) && ci!=fakeInstance){
|
if(ci.domain.equals(domain) && ci!=fakeInstance){
|
||||||
found=true;
|
found=true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,39 +1,52 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
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.Context;
|
||||||
import android.os.Build;
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.LocaleList;
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.view.Menu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.HorizontalScrollView;
|
||||||
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
||||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
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.Instance;
|
||||||
import org.joinmastodon.android.model.catalog.CatalogCategory;
|
import org.joinmastodon.android.model.catalog.CatalogCategory;
|
||||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.FilterChipView;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Locale;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -42,18 +55,36 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.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.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment implements OnBackPressedListener{
|
||||||
private View headerView;
|
|
||||||
private MastodonAPIRequest<?> getCategoriesRequest;
|
private MastodonAPIRequest<?> getCategoriesRequest;
|
||||||
private TabLayout categoriesList;
|
|
||||||
private String currentCategory="all";
|
private String currentCategory="all";
|
||||||
private List<CatalogCategory> categories=new ArrayList<>();
|
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(){
|
public InstanceCatalogSignupFragment(){
|
||||||
super(R.layout.fragment_onboarding_common, 10);
|
super(R.layout.fragment_onboarding_common, 10);
|
||||||
@@ -63,6 +94,12 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
public void onAttach(Context context){
|
public void onAttach(Context context){
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
setRefreshEnabled(false);
|
setRefreshEnabled(false);
|
||||||
|
setRetainInstance(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +112,25 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
if(getActivity()==null)
|
if(getActivity()==null)
|
||||||
return;
|
return;
|
||||||
onDataLoaded(sortInstances(result), false);
|
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();
|
updateFilteredList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +167,14 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateCategories(){
|
private void updateCategories(){
|
||||||
categoriesList.removeAllTabs();
|
// categoriesList.removeAllTabs();
|
||||||
for(CatalogCategory cat:categories){
|
// for(CatalogCategory cat:categories){
|
||||||
int titleRes=getTitleForCategory(cat.category);
|
// int titleRes=getTitleForCategory(cat.category);
|
||||||
TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_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);
|
// ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
|
||||||
emoji.setImageResource(getEmojiForCategory(cat.category));
|
// emoji.setImageResource(getEmojiForCategory(cat.category));
|
||||||
categoriesList.addTab(tab);
|
// categoriesList.addTab(tab);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -130,27 +186,77 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false);
|
View headerView=new View(getActivity());
|
||||||
searchEdit=headerView.findViewById(R.id.search_edit);
|
headerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
|
||||||
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
|
mergeAdapter=new MergeRecyclerAdapter();
|
||||||
public void onTabUnselected(TabLayout.Tab tab){
|
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||||
|
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||||
}
|
return mergeAdapter;
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public void onTabReselected(TabLayout.Tab tab){
|
|
||||||
|
|
||||||
|
@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.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
searchEdit.addTextChangedListener(new TextWatcher(){
|
||||||
@Override
|
@Override
|
||||||
@@ -166,42 +272,145 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s){
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mergeAdapter=new MergeRecyclerAdapter();
|
FilterChipView langFilter=new FilterChipView(getActivity());
|
||||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
|
||||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
langFilter.setText(R.string.server_filter_any_language);
|
||||||
return mergeAdapter;
|
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
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
protected void onNextClick(View v){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
if(chosenInstance==null){
|
||||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
String lang=Locale.getDefault().getLanguage();
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
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());
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
|
if(instances.isEmpty()){
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
||||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
}
|
||||||
|
if(instances.isEmpty()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chosenInstance=instances.get(new Random().nextInt(instances.size()));
|
||||||
|
}
|
||||||
|
super.onNextClick(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void proceedWithAuthOrSignup(Instance instance){
|
protected void proceedWithAuthOrSignup(Instance instance){
|
||||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||||
if(isSignup){
|
if(!instance.registrations){
|
||||||
if(!instance.registrations){
|
new M3AlertDialogBuilder(getActivity())
|
||||||
new M3AlertDialogBuilder(getActivity())
|
.setTitle(R.string.error)
|
||||||
.setTitle(R.string.error)
|
.setMessage(R.string.instance_signup_closed)
|
||||||
.setMessage(R.string.instance_signup_closed)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.show();
|
||||||
.show();
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putParcelable("instance", Parcels.wrap(instance));
|
|
||||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
|
||||||
}else{
|
|
||||||
}
|
}
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putParcelable("instance", Parcels.wrap(instance));
|
||||||
|
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// private String getEmojiForCategory(String category){
|
// private String getEmojiForCategory(String category){
|
||||||
@@ -265,11 +474,29 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
protected void updateFilteredList(){
|
protected void updateFilteredList(){
|
||||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||||
filteredData.clear();
|
filteredData.clear();
|
||||||
for(CatalogInstance instance:data){
|
if(searchQueryMode){
|
||||||
if(currentCategory.equals("all") || instance.categories.contains(currentCategory)){
|
if(!TextUtils.isEmpty(currentSearchQuery)){
|
||||||
if(TextUtils.isEmpty(currentSearchQuery) || instance.domain.contains(currentSearchQuery)){
|
for(CatalogInstance instance:data){
|
||||||
if(instance.domain.equals(currentSearchQuery) || !isSignup || !instance.approvalRequired)
|
if(instance.domain.contains(currentSearchQuery)){
|
||||||
filteredData.add(instance);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,8 +523,46 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
}).dispatchUpdatesTo(adapter);
|
}).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()));
|
||||||
|
}
|
||||||
|
|
||||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder>{
|
@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(){
|
public InstancesAdapter(){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
}
|
}
|
||||||
@@ -323,32 +588,53 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
public int getItemViewType(int position){
|
public int getItemViewType(int position){
|
||||||
return -1;
|
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.Clickable{
|
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable, ImageLoaderViewHolder{
|
||||||
private final TextView title, description, userCount, lang;
|
private final TextView title, description;
|
||||||
private final RadioButton radioButton;
|
private final RadioButton radioButton;
|
||||||
|
private final ImageView thumbnail;
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
public InstanceViewHolder(){
|
public InstanceViewHolder(){
|
||||||
super(getActivity(), R.layout.item_instance_catalog, list);
|
super(getActivity(), R.layout.item_instance_catalog, list);
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
description=findViewById(R.id.description);
|
description=findViewById(R.id.description);
|
||||||
userCount=findViewById(R.id.user_count);
|
|
||||||
lang=findViewById(R.id.lang);
|
|
||||||
radioButton=findViewById(R.id.radiobtn);
|
radioButton=findViewById(R.id.radiobtn);
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
thumbnail=findViewById(R.id.image);
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
|
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(CatalogInstance item){
|
public void onBind(CatalogInstance item){
|
||||||
title.setText(item.normalizedDomain);
|
title.setText(item.normalizedDomain);
|
||||||
description.setText(item.description);
|
|
||||||
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
|
|
||||||
lang.setText(item.language.toUpperCase());
|
|
||||||
radioButton.setChecked(chosenInstance==item);
|
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
|
@Override
|
||||||
@@ -358,10 +644,17 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
if(chosenInstance!=null){
|
if(chosenInstance!=null){
|
||||||
int idx=filteredData.indexOf(chosenInstance);
|
int idx=filteredData.indexOf(chosenInstance);
|
||||||
if(idx!=-1){
|
if(idx!=-1){
|
||||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
|
boolean found=false;
|
||||||
if(holder instanceof InstanceCatalogSignupFragment.InstanceViewHolder ivh){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
ivh.radioButton.setChecked(false);
|
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);
|
radioButton.setChecked(true);
|
||||||
@@ -370,5 +663,36 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment{
|
|||||||
chosenInstance=item;
|
chosenInstance=item;
|
||||||
loadInstanceInfo(chosenInstance.domain, false);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,13 +106,13 @@ public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
|
|||||||
.execNoAuth("");
|
.execNoAuth("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// @Override
|
||||||
protected void onUpdateToolbar(){
|
// protected void onUpdateToolbar(){
|
||||||
super.onUpdateToolbar();
|
// super.onUpdateToolbar();
|
||||||
Toolbar toolbar=getToolbar();
|
// Toolbar toolbar=getToolbar();
|
||||||
toolbar.setElevation(0);
|
// toolbar.setElevation(0);
|
||||||
toolbar.setBackground(null);
|
// toolbar.setBackground(null);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -22,14 +24,14 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class InstanceRulesFragment extends AppKitFragment{
|
public class InstanceRulesFragment extends ToolbarFragment{
|
||||||
private UsableRecyclerView list;
|
private UsableRecyclerView list;
|
||||||
private MergeRecyclerAdapter adapter;
|
private MergeRecyclerAdapter adapter;
|
||||||
private Button btn;
|
private Button btn;
|
||||||
@@ -47,31 +49,28 @@ public class InstanceRulesFragment extends AppKitFragment{
|
|||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||||
|
setTitle(R.string.instance_rules_title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||||
|
|
||||||
list=view.findViewById(R.id.list);
|
list=view.findViewById(R.id.list);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
||||||
TextView title=headerView.findViewById(R.id.title);
|
TextView text=headerView.findViewById(R.id.text);
|
||||||
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
text.setText(getString(R.string.instance_rules_subtitle, instance.uri));
|
||||||
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));
|
|
||||||
|
|
||||||
adapter=new MergeRecyclerAdapter();
|
adapter=new MergeRecyclerAdapter();
|
||||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||||
adapter.addAdapter(new ItemsAdapter());
|
adapter.addAdapter(new ItemsAdapter());
|
||||||
list.setAdapter(adapter);
|
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=view.findViewById(R.id.btn_next);
|
||||||
btn.setOnClickListener(v->onButtonClick());
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -79,7 +78,15 @@ public class InstanceRulesFragment extends AppKitFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, 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(){
|
protected void onButtonClick(){
|
||||||
@@ -119,23 +126,22 @@ public class InstanceRulesFragment extends AppKitFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ItemViewHolder extends BindableViewHolder<Instance.Rule>{
|
private class ItemViewHolder extends BindableViewHolder<Instance.Rule>{
|
||||||
private final TextView title, subtitle;
|
private final TextView text, number;
|
||||||
private final ImageView checkbox;
|
|
||||||
|
|
||||||
public ItemViewHolder(){
|
public ItemViewHolder(){
|
||||||
super(getActivity(), R.layout.item_report_choice, list);
|
super(getActivity(), R.layout.item_server_rule, list);
|
||||||
title=findViewById(R.id.title);
|
text=findViewById(R.id.text);
|
||||||
subtitle=findViewById(R.id.subtitle);
|
number=findViewById(R.id.number);
|
||||||
checkbox=findViewById(R.id.checkbox);
|
|
||||||
subtitle.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
@Override
|
@Override
|
||||||
public void onBind(Instance.Rule item){
|
public void onBind(Instance.Rule item){
|
||||||
if(item.parsedText==null){
|
if(item.parsedText==null){
|
||||||
item.parsedText=HtmlParser.parseLinks(item.text);
|
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.accounts.RegisterAccount;
|
||||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||||
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
|
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.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -49,30 +50,28 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.V;
|
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 int AVATAR_RESULT=198;
|
||||||
private static final String TAG="SignupFragment";
|
private static final String TAG="SignupFragment";
|
||||||
|
|
||||||
private Instance instance;
|
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 TextView reasonExplain;
|
||||||
private Button btn;
|
private Button btn;
|
||||||
private View buttonBar;
|
private View buttonBar;
|
||||||
private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState());
|
private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState());
|
||||||
private ImageView avatar;
|
|
||||||
private APIRequest currentBackgroundRequest;
|
private APIRequest currentBackgroundRequest;
|
||||||
private Application apiApplication;
|
private Application apiApplication;
|
||||||
private Token apiToken;
|
private Token apiToken;
|
||||||
private boolean submitAfterGettingToken;
|
private boolean submitAfterGettingToken;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
private Uri avatarUri;
|
|
||||||
private File avatarFile;
|
|
||||||
private HashSet<EditText> errorFields=new HashSet<>();
|
private HashSet<EditText> errorFields=new HashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,25 +80,30 @@ public class SignupFragment extends AppKitFragment{
|
|||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||||
createAppAndGetToken();
|
createAppAndGetToken();
|
||||||
|
setTitle(R.string.signup_title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@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);
|
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);
|
TextView domain=view.findViewById(R.id.domain);
|
||||||
displayName=view.findViewById(R.id.display_name);
|
displayName=view.findViewById(R.id.display_name);
|
||||||
username=view.findViewById(R.id.username);
|
username=view.findViewById(R.id.username);
|
||||||
email=view.findViewById(R.id.email);
|
email=view.findViewById(R.id.email);
|
||||||
password=view.findViewById(R.id.password);
|
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);
|
reason=view.findViewById(R.id.reason);
|
||||||
reasonExplain=view.findViewById(R.id.reason_explain);
|
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);
|
domain.setText('@'+instance.uri);
|
||||||
|
|
||||||
username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
@@ -114,23 +118,20 @@ public class SignupFragment extends AppKitFragment{
|
|||||||
btn=view.findViewById(R.id.btn_next);
|
btn=view.findViewById(R.id.btn_next);
|
||||||
btn.setOnClickListener(v->onButtonClick());
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
|
||||||
updateButtonState();
|
updateButtonState();
|
||||||
|
|
||||||
username.addTextChangedListener(buttonStateUpdater);
|
username.addTextChangedListener(buttonStateUpdater);
|
||||||
email.addTextChangedListener(buttonStateUpdater);
|
email.addTextChangedListener(buttonStateUpdater);
|
||||||
password.addTextChangedListener(buttonStateUpdater);
|
password.addTextChangedListener(buttonStateUpdater);
|
||||||
|
passwordConfirm.addTextChangedListener(buttonStateUpdater);
|
||||||
reason.addTextChangedListener(buttonStateUpdater);
|
reason.addTextChangedListener(buttonStateUpdater);
|
||||||
|
|
||||||
username.addTextChangedListener(new ErrorClearingListener(username));
|
username.addTextChangedListener(new ErrorClearingListener(username));
|
||||||
email.addTextChangedListener(new ErrorClearingListener(email));
|
email.addTextChangedListener(new ErrorClearingListener(email));
|
||||||
password.addTextChangedListener(new ErrorClearingListener(password));
|
password.addTextChangedListener(new ErrorClearingListener(password));
|
||||||
|
passwordConfirm.addTextChangedListener(new ErrorClearingListener(passwordConfirm));
|
||||||
reason.addTextChangedListener(new ErrorClearingListener(reason));
|
reason.addTextChangedListener(new ErrorClearingListener(reason));
|
||||||
|
|
||||||
avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22));
|
|
||||||
avaWrap.setClipToOutline(true);
|
|
||||||
avaWrap.setOnClickListener(v->onAvatarClick());
|
|
||||||
|
|
||||||
if(!instance.approvalRequired){
|
if(!instance.approvalRequired){
|
||||||
reason.setVisibility(View.GONE);
|
reason.setVisibility(View.GONE);
|
||||||
reasonExplain.setVisibility(View.GONE);
|
reasonExplain.setVisibility(View.GONE);
|
||||||
@@ -142,10 +143,23 @@ public class SignupFragment extends AppKitFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, 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(){
|
private void onButtonClick(){
|
||||||
|
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
|
||||||
|
passwordConfirm.setError(getString(R.string.signup_passwords_dont_match));
|
||||||
|
passwordConfirmWrap.setErrorState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
showProgressDialog();
|
showProgressDialog();
|
||||||
if(currentBackgroundRequest!=null){
|
if(currentBackgroundRequest!=null){
|
||||||
submitAfterGettingToken=true;
|
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(){
|
private void submit(){
|
||||||
if(avatarUri!=null && (avatarFile==null || !avatarFile.exists())){
|
actuallySubmit();
|
||||||
copyAvatar(this::actuallySubmit);
|
|
||||||
}else{
|
|
||||||
actuallySubmit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void actuallySubmit(){
|
private void actuallySubmit(){
|
||||||
@@ -204,9 +194,7 @@ public class SignupFragment extends AppKitFragment{
|
|||||||
fakeAccount.acct=fakeAccount.username=username;
|
fakeAccount.acct=fakeAccount.username=username;
|
||||||
fakeAccount.id="tmp"+System.currentTimeMillis();
|
fakeAccount.id="tmp"+System.currentTimeMillis();
|
||||||
fakeAccount.displayName=displayName.getText().toString();
|
fakeAccount.displayName=displayName.getText().toString();
|
||||||
if(avatarFile!=null)
|
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, new AccountActivationInfo(email, System.currentTimeMillis()));
|
||||||
fakeAccount.avatar=avatarFile.getAbsolutePath();
|
|
||||||
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, false);
|
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
|
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
|
||||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||||
@@ -225,6 +213,7 @@ public class SignupFragment extends AppKitFragment{
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
||||||
|
getFieldWrapByName(fieldName).setErrorState();
|
||||||
errorFields.add(field);
|
errorFields.add(field);
|
||||||
if(first){
|
if(first){
|
||||||
first=false;
|
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(){
|
private void showProgressDialog(){
|
||||||
if(progressDialog==null){
|
if(progressDialog==null){
|
||||||
progressDialog=new ProgressDialog(getActivity());
|
progressDialog=new ProgressDialog(getActivity());
|
||||||
@@ -262,7 +261,7 @@ public class SignupFragment extends AppKitFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateButtonState(){
|
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(){
|
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{
|
private class ErrorClearingListener implements TextWatcher{
|
||||||
public final EditText editText;
|
public final EditText editText;
|
||||||
|
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
||||||
for(StatusDisplayItem item:items){
|
for(StatusDisplayItem item:items){
|
||||||
if(item instanceof ImageStatusDisplayItem isdi){
|
if(item instanceof ImageStatusDisplayItem isdi){
|
||||||
isdi.horizontalInset=V.dp(40+32);
|
isdi.horizontalInset=V.dp(40+32);
|
||||||
|
|||||||
@@ -164,6 +164,10 @@ public class Account extends BaseModel{
|
|||||||
return '@'+acct;
|
return '@'+acct;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getShortUsername() {
|
||||||
|
return '@'+acct.split("@")[0];
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "Account{"+
|
return "Account{"+
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import com.google.gson.annotations.SerializedName;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
|
import org.parceler.Parcel;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Parcel
|
||||||
public class Filter extends BaseModel{
|
public class Filter extends BaseModel{
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public String id;
|
public String id;
|
||||||
@@ -21,6 +23,7 @@ public class Filter extends BaseModel{
|
|||||||
public Instant expiresAt;
|
public Instant expiresAt;
|
||||||
public boolean irreversible;
|
public boolean irreversible;
|
||||||
public boolean wholeWord;
|
public boolean wholeWord;
|
||||||
|
public FilterAction filterAction;
|
||||||
|
|
||||||
@SerializedName("context")
|
@SerializedName("context")
|
||||||
private List<FilterContext> _context;
|
private List<FilterContext> _context;
|
||||||
@@ -76,4 +79,11 @@ public class Filter extends BaseModel{
|
|||||||
@SerializedName("thread")
|
@SerializedName("thread")
|
||||||
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;
|
||||||
|
}
|
||||||
@@ -57,6 +57,11 @@ public class Poll extends BaseModel{
|
|||||||
public String title;
|
public String title;
|
||||||
public Integer votesCount;
|
public Integer votesCount;
|
||||||
|
|
||||||
|
public Option() {}
|
||||||
|
public Option(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "Option{"+
|
return "Option{"+
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
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.content = s.text = params.text;
|
||||||
|
s.spoilerText = params.spoilerText;
|
||||||
|
s.visibility = params.visibility;
|
||||||
|
s.language = params.language;
|
||||||
|
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;
|
public Instant createdAt;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public Account account;
|
public Account account;
|
||||||
// @RequiredField
|
// @RequiredField
|
||||||
public String content;
|
public String content;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public StatusPrivacy visibility;
|
public StatusPrivacy visibility;
|
||||||
@@ -40,6 +40,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
|||||||
public long favouritesCount;
|
public long favouritesCount;
|
||||||
public long repliesCount;
|
public long repliesCount;
|
||||||
public Instant editedAt;
|
public Instant editedAt;
|
||||||
|
public List<FilterResult> filtered;
|
||||||
|
|
||||||
public String url;
|
public String url;
|
||||||
public String inReplyToId;
|
public String inReplyToId;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
public class TranslatedStatus extends BaseModel {
|
public class TranslatedStatus extends BaseModel {
|
||||||
public String content;
|
public String content;
|
||||||
public String detectedSourceLanguage;
|
public String detectedSourceLanguage;
|
||||||
public String provider;
|
public String provider;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package org.joinmastodon.android.model.catalog;
|
package org.joinmastodon.android.model.catalog;
|
||||||
|
|
||||||
|
import android.graphics.Region;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.model.BaseModel;
|
import org.joinmastodon.android.model.BaseModel;
|
||||||
@@ -7,13 +12,17 @@ import org.joinmastodon.android.model.BaseModel;
|
|||||||
import java.net.IDN;
|
import java.net.IDN;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
@AllFieldsAreRequired
|
@AllFieldsAreRequired
|
||||||
public class CatalogInstance extends BaseModel{
|
public class CatalogInstance extends BaseModel{
|
||||||
public String domain;
|
public String domain;
|
||||||
public String version;
|
public String version;
|
||||||
public String description;
|
public String description;
|
||||||
public List<String> languages;
|
public List<String> languages;
|
||||||
public String region;
|
@SerializedName("region")
|
||||||
|
private String _region;
|
||||||
public List<String> categories;
|
public List<String> categories;
|
||||||
public String proxiedThumbnail;
|
public String proxiedThumbnail;
|
||||||
public int totalUsers;
|
public int totalUsers;
|
||||||
@@ -22,7 +31,9 @@ public class CatalogInstance extends BaseModel{
|
|||||||
public String language;
|
public String language;
|
||||||
public String category;
|
public String category;
|
||||||
|
|
||||||
|
public transient Region region;
|
||||||
public transient String normalizedDomain;
|
public transient String normalizedDomain;
|
||||||
|
public transient UrlImageLoaderRequest thumbnailRequest;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postprocess() throws ObjectValidationException{
|
public void postprocess() throws ObjectValidationException{
|
||||||
@@ -31,6 +42,14 @@ public class CatalogInstance extends BaseModel{
|
|||||||
normalizedDomain=IDN.toUnicode(domain);
|
normalizedDomain=IDN.toUnicode(domain);
|
||||||
else
|
else
|
||||||
normalizedDomain=domain;
|
normalizedDomain=domain;
|
||||||
|
if(!TextUtils.isEmpty(_region)){
|
||||||
|
try{
|
||||||
|
region=Region.valueOf(_region.toUpperCase());
|
||||||
|
}catch(IllegalArgumentException ignore){}
|
||||||
|
}
|
||||||
|
if(!TextUtils.isEmpty(proxiedThumbnail)){
|
||||||
|
thumbnailRequest=new UrlImageLoaderRequest(proxiedThumbnail, 0, V.dp(56));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -50,4 +69,13 @@ public class CatalogInstance extends BaseModel{
|
|||||||
", category='"+category+'\''+
|
", category='"+category+'\''+
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Region{
|
||||||
|
EUROPE,
|
||||||
|
NORTH_AMERICA,
|
||||||
|
SOUTH_AMERICA,
|
||||||
|
AFRICA,
|
||||||
|
ASIA,
|
||||||
|
OCEANIA
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ package org.joinmastodon.android.ui;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
@@ -25,8 +23,7 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.SplashFragment;
|
import org.joinmastodon.android.fragments.onboarding.CustomLoginFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -80,7 +77,7 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
|
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
|
||||||
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
|
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
|
||||||
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, ()->{
|
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, ()->{
|
||||||
Nav.go(activity, SplashFragment.class, null);
|
Nav.go(activity, CustomLoginFragment.class, null);
|
||||||
dismiss();
|
dismiss();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -243,7 +240,10 @@ public class AccountSwitcherSheet extends BottomSheet{
|
|||||||
|
|
||||||
public WrappedAccount(AccountSession session){
|
public WrappedAccount(AccountSession session){
|
||||||
this.session=session;
|
this.session=session;
|
||||||
req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50));
|
if(session.self.avatar!=null)
|
||||||
|
req=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? session.self.avatar : session.self.avatarStatic, V.dp(50), V.dp(50));
|
||||||
|
else
|
||||||
|
req=null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package org.joinmastodon.android.ui;
|
package org.joinmastodon.android.ui;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.recentEmojis;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
@@ -12,8 +13,13 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
||||||
@@ -21,13 +27,13 @@ import org.joinmastodon.android.model.Emoji;
|
|||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||||
@@ -40,6 +46,9 @@ import me.grishka.appkit.utils.V;
|
|||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
||||||
|
//determines how many emoji need to be clicked, before it disappears from the recent emojis
|
||||||
|
private static final int NEW_RECENT_VALUE=15;
|
||||||
|
|
||||||
private List<EmojiCategory> emojis;
|
private List<EmojiCategory> emojis;
|
||||||
private UsableRecyclerView list;
|
private UsableRecyclerView list;
|
||||||
private ListImageLoaderWrapper imgLoader;
|
private ListImageLoaderWrapper imgLoader;
|
||||||
@@ -82,6 +91,17 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
|||||||
list.setLayoutManager(lm);
|
list.setLayoutManager(lm);
|
||||||
imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null);
|
imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null);
|
||||||
|
|
||||||
|
// inject category with last used emojis
|
||||||
|
if (!recentEmojis.isEmpty()) {
|
||||||
|
List<Emoji> allAvailableEmojis = emojis.stream().flatMap(category -> category.emojis.stream()).collect(Collectors.toList());
|
||||||
|
List<Emoji> recentEmojiList = new ArrayList<>();
|
||||||
|
for (String emojiCode : recentEmojis.keySet().stream().sorted(Comparator.comparingInt(GlobalUserPreferences.recentEmojis::get).reversed()).collect(Collectors.toList())) {
|
||||||
|
Optional<Emoji> element = allAvailableEmojis.stream().filter(e -> e.shortcode.equals(emojiCode)).findFirst();
|
||||||
|
element.ifPresent(recentEmojiList::add);
|
||||||
|
}
|
||||||
|
emojis.add(0, new EmojiCategory(activity.getString(R.string.sk_emoji_recent), recentEmojiList));
|
||||||
|
}
|
||||||
|
|
||||||
for(EmojiCategory category:emojis)
|
for(EmojiCategory category:emojis)
|
||||||
adapter.addAdapter(new SingleCategoryAdapter(category));
|
adapter.addAdapter(new SingleCategoryAdapter(category));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
@@ -100,6 +120,11 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
|||||||
list.setBackgroundColor(UiUtils.getThemeColor(activity, android.R.attr.colorBackground));
|
list.setBackgroundColor(UiUtils.getThemeColor(activity, android.R.attr.colorBackground));
|
||||||
list.setSelector(null);
|
list.setSelector(null);
|
||||||
|
|
||||||
|
//remove recently used afterwards, it would get duplicated otherwise
|
||||||
|
if (!recentEmojis.isEmpty()) {
|
||||||
|
emojis.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,6 +132,19 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
|||||||
this.listener=listener;
|
this.listener=listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void increaseEmojiCount(Emoji emoji) {
|
||||||
|
Integer usageCount = recentEmojis.get(emoji.shortcode);
|
||||||
|
if (usageCount != null) {
|
||||||
|
recentEmojis.put(emoji.shortcode, usageCount + 1);
|
||||||
|
} else {
|
||||||
|
recentEmojis.put(emoji.shortcode, NEW_RECENT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
recentEmojis.entrySet().removeIf(e -> e.getValue() <= 0);
|
||||||
|
recentEmojis.replaceAll((k, v) -> v - 1);
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onEmojiUpdated(EmojiUpdatedEvent ev){
|
public void onEmojiUpdated(EmojiUpdatedEvent ev){
|
||||||
@@ -203,6 +241,7 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
|
increaseEmojiCount(item);
|
||||||
listener.accept(item);
|
listener.accept(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class M3AlertDialogBuilder extends AlertDialog.Builder{
|
|||||||
View title=alert.findViewById(titleID);
|
View title=alert.findViewById(titleID);
|
||||||
if(title!=null){
|
if(title!=null){
|
||||||
int pad=V.dp(24);
|
int pad=V.dp(24);
|
||||||
title.setPadding(pad, pad, pad, pad);
|
title.setPadding(pad, pad, pad, V.dp(18));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int titleDividerID=getContext().getResources().getIdentifier("titleDividerNoCustom", "id", "android");
|
int titleDividerID=getContext().getResources().getIdentifier("titleDividerNoCustom", "id", "android");
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import android.widget.ImageView;
|
|||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -23,7 +25,9 @@ import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
|||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
@@ -154,10 +158,18 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
private void onFollowRequestButtonClick(View v) {
|
private void onFollowRequestButtonClick(View v) {
|
||||||
itemView.setHasTransientState(true);
|
itemView.setHasTransientState(true);
|
||||||
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, item.parentFragment.getAccountID(), item.notification.id , v == acceptButton, relationship, rel -> {
|
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, item.parentFragment.getAccountID(), null, v == acceptButton, relationship, rel -> {
|
||||||
itemView.setHasTransientState(false);
|
itemView.setHasTransientState(false);
|
||||||
item.parentFragment.putRelationship(item.account.id, rel);
|
item.parentFragment.putRelationship(item.account.id, rel);
|
||||||
rebind();
|
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
||||||
|
if (!rel.requested && !rel.followedBy && adapter != null) {
|
||||||
|
int index = item.parentFragment.getDisplayItems().indexOf(item);
|
||||||
|
item.parentFragment.getDisplayItems().remove(index);
|
||||||
|
item.parentFragment.getDisplayItems().remove(index - 1);
|
||||||
|
adapter.notifyItemRangeRemoved(getLayoutPosition()-1, 2);
|
||||||
|
} else {
|
||||||
|
rebind();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
||||||
case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
|
case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
|
||||||
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
|
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
|
||||||
case DIRECT -> R.drawable.ic_at_symbol;
|
case DIRECT -> R.drawable.ic_fluent_mention_24_regular;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,44 @@
|
|||||||
package org.joinmastodon.android.ui.displayitems;
|
package org.joinmastodon.android.ui.displayitems;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
|
import android.view.animation.AlphaAnimation;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.AnimationSet;
|
||||||
|
import android.view.animation.BounceInterpolator;
|
||||||
|
import android.view.animation.RotateAnimation;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class FooterStatusDisplayItem extends StatusDisplayItem{
|
public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||||
@@ -46,6 +60,19 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
||||||
private final TextView reply, boost, favorite, bookmark;
|
private final TextView reply, boost, favorite, bookmark;
|
||||||
private final ImageView share;
|
private final ImageView share;
|
||||||
|
private static final Animation opacityOut, opacityIn;
|
||||||
|
private static AnimationSet animSet;
|
||||||
|
|
||||||
|
|
||||||
|
private View touchingView = null;
|
||||||
|
private boolean longClickPerformed = false;
|
||||||
|
private final Runnable longClickRunnable = () -> {
|
||||||
|
longClickPerformed = touchingView != null && touchingView.performLongClick();
|
||||||
|
if (longClickPerformed && touchingView != null) {
|
||||||
|
touchingView.startAnimation(opacityIn);
|
||||||
|
touchingView.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
|
private final View.AccessibilityDelegate buttonAccessibilityDelegate=new View.AccessibilityDelegate(){
|
||||||
@Override
|
@Override
|
||||||
@@ -56,6 +83,25 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static {
|
||||||
|
opacityOut = new AlphaAnimation(1, 0.55f);
|
||||||
|
opacityOut.setDuration(300);
|
||||||
|
opacityOut.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
opacityOut.setFillAfter(true);
|
||||||
|
opacityIn = new AlphaAnimation(0.55f, 1);
|
||||||
|
opacityIn.setDuration(400);
|
||||||
|
opacityIn.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
Animation spin = new RotateAnimation(0, 360,
|
||||||
|
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
|
||||||
|
0.5f);
|
||||||
|
|
||||||
|
animSet = new AnimationSet(true);
|
||||||
|
animSet.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
animSet.addAnimation(spin);
|
||||||
|
animSet.addAnimation(opacityIn);
|
||||||
|
animSet.setDuration(400);
|
||||||
|
}
|
||||||
|
|
||||||
public Holder(Activity activity, ViewGroup parent){
|
public Holder(Activity activity, ViewGroup parent){
|
||||||
super(activity, R.layout.display_item_footer, parent);
|
super(activity, R.layout.display_item_footer, parent);
|
||||||
reply=findViewById(R.id.reply);
|
reply=findViewById(R.id.reply);
|
||||||
@@ -74,15 +120,25 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
View favorite=findViewById(R.id.favorite_btn);
|
View favorite=findViewById(R.id.favorite_btn);
|
||||||
View share=findViewById(R.id.share_btn);
|
View share=findViewById(R.id.share_btn);
|
||||||
View bookmark=findViewById(R.id.bookmark_btn);
|
View bookmark=findViewById(R.id.bookmark_btn);
|
||||||
|
reply.setOnTouchListener(this::onButtonTouch);
|
||||||
reply.setOnClickListener(this::onReplyClick);
|
reply.setOnClickListener(this::onReplyClick);
|
||||||
|
reply.setOnLongClickListener(this::onReplyLongClick);
|
||||||
reply.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
reply.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||||
|
boost.setOnTouchListener(this::onButtonTouch);
|
||||||
boost.setOnClickListener(this::onBoostClick);
|
boost.setOnClickListener(this::onBoostClick);
|
||||||
|
boost.setOnLongClickListener(this::onBoostLongClick);
|
||||||
boost.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
boost.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||||
|
favorite.setOnTouchListener(this::onButtonTouch);
|
||||||
favorite.setOnClickListener(this::onFavoriteClick);
|
favorite.setOnClickListener(this::onFavoriteClick);
|
||||||
|
favorite.setOnLongClickListener(this::onFavoriteLongClick);
|
||||||
favorite.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
favorite.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||||
|
bookmark.setOnTouchListener(this::onButtonTouch);
|
||||||
bookmark.setOnClickListener(this::onBookmarkClick);
|
bookmark.setOnClickListener(this::onBookmarkClick);
|
||||||
|
bookmark.setOnLongClickListener(this::onBookmarkLongClick);
|
||||||
bookmark.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
bookmark.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||||
|
share.setOnTouchListener(this::onButtonTouch);
|
||||||
share.setOnClickListener(this::onShareClick);
|
share.setOnClickListener(this::onShareClick);
|
||||||
|
share.setOnLongClickListener(this::onShareLongClick);
|
||||||
share.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
share.setAccessibilityDelegate(buttonAccessibilityDelegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +147,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
bindButton(reply, item.status.repliesCount);
|
bindButton(reply, item.status.repliesCount);
|
||||||
bindButton(boost, item.status.reblogsCount);
|
bindButton(boost, item.status.reblogsCount);
|
||||||
bindButton(favorite, item.status.favouritesCount);
|
bindButton(favorite, item.status.favouritesCount);
|
||||||
|
reply.setSelected(item.status.repliesCount > 0);
|
||||||
boost.setSelected(item.status.reblogged);
|
boost.setSelected(item.status.reblogged);
|
||||||
favorite.setSelected(item.status.favourited);
|
favorite.setSelected(item.status.favourited);
|
||||||
bookmark.setSelected(item.status.bookmarked);
|
bookmark.setSelected(item.status.bookmarked);
|
||||||
@@ -108,37 +165,212 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean onButtonTouch(View v, MotionEvent event){
|
||||||
|
boolean disabled = !v.isEnabled() || (v instanceof FrameLayout parentFrame &&
|
||||||
|
parentFrame.getChildCount() > 0 && !parentFrame.getChildAt(0).isEnabled());
|
||||||
|
int action = event.getAction();
|
||||||
|
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||||
|
touchingView = null;
|
||||||
|
v.removeCallbacks(longClickRunnable);
|
||||||
|
if (!longClickPerformed) v.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
|
||||||
|
if (disabled) return true;
|
||||||
|
if (action == MotionEvent.ACTION_UP && !longClickPerformed) v.performClick();
|
||||||
|
else if (!longClickPerformed) v.startAnimation(opacityIn);
|
||||||
|
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||||
|
longClickPerformed = false;
|
||||||
|
touchingView = v;
|
||||||
|
// 20dp to center in middle of icon, because: (icon width = 24dp) / 2 + (paddingStart = 8dp)
|
||||||
|
v.setPivotX(V.dp(20));
|
||||||
|
v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
|
||||||
|
if (disabled) return true;
|
||||||
|
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
|
||||||
|
v.startAnimation(opacityOut);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void onReplyClick(View v){
|
private void onReplyClick(View v){
|
||||||
|
v.startAnimation(opacityIn);
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", item.accountID);
|
args.putString("account", item.accountID);
|
||||||
args.putParcelable("replyTo", Parcels.wrap(item.status));
|
args.putParcelable("replyTo", Parcels.wrap(item.status));
|
||||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean onReplyLongClick(View v) {
|
||||||
|
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
|
||||||
|
UiUtils.pickAccount(v.getContext(), item.accountID, R.string.sk_reply_as, R.drawable.ic_fluent_arrow_reply_28_regular, session -> {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
String accountID = session.getID();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
UiUtils.lookupStatus(v.getContext(), item.status, accountID, item.accountID, status -> {
|
||||||
|
args.putParcelable("replyTo", Parcels.wrap(status));
|
||||||
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||||
|
});
|
||||||
|
}, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void onBoostClick(View v){
|
private void onBoostClick(View v){
|
||||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged);
|
boost.setSelected(!item.status.reblogged);
|
||||||
boost.setSelected(item.status.reblogged);
|
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setReblogged(item.status, !item.status.reblogged, null, r->boostConsumer(v, r));
|
||||||
bindButton(boost, item.status.reblogsCount);
|
}
|
||||||
|
|
||||||
|
private void boostConsumer(View v, Status r) {
|
||||||
|
v.startAnimation(opacityIn);
|
||||||
|
bindButton(boost, r.reblogsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onBoostLongClick(View v){
|
||||||
|
Context ctx = itemView.getContext();
|
||||||
|
View menu = LayoutInflater.from(ctx).inflate(R.layout.item_boost_menu, null);
|
||||||
|
Dialog dialog = new M3AlertDialogBuilder(ctx).setView(menu).create();
|
||||||
|
AccountSession session = AccountSessionManager.getInstance().getAccount(item.accountID);
|
||||||
|
|
||||||
|
Consumer<StatusPrivacy> doReblog = (visibility) -> {
|
||||||
|
v.startAnimation(opacityOut);
|
||||||
|
session.getStatusInteractionController()
|
||||||
|
.setReblogged(item.status, !item.status.reblogged, visibility, r->boostConsumer(v, r));
|
||||||
|
dialog.dismiss();
|
||||||
|
};
|
||||||
|
|
||||||
|
View separator = menu.findViewById(R.id.separator);
|
||||||
|
TextView reblogHeader = menu.findViewById(R.id.reblog_header);
|
||||||
|
TextView undoReblog = menu.findViewById(R.id.delete_reblog);
|
||||||
|
TextView reblogAs = menu.findViewById(R.id.reblog_as);
|
||||||
|
TextView itemPublic = menu.findViewById(R.id.vis_public);
|
||||||
|
TextView itemUnlisted = menu.findViewById(R.id.vis_unlisted);
|
||||||
|
TextView itemFollowers = menu.findViewById(R.id.vis_followers);
|
||||||
|
|
||||||
|
undoReblog.setVisibility(item.status.reblogged ? View.VISIBLE : View.GONE);
|
||||||
|
separator.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
|
||||||
|
reblogHeader.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
|
||||||
|
reblogAs.setVisibility(AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1 ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
itemPublic.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.PUBLIC) ? View.GONE : View.VISIBLE);
|
||||||
|
itemUnlisted.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) ? View.GONE : View.VISIBLE);
|
||||||
|
itemFollowers.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.PRIVATE) ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
Drawable checkMark = ctx.getDrawable(R.drawable.ic_fluent_checkmark_circle_20_regular);
|
||||||
|
Drawable publicDrawable = ctx.getDrawable(R.drawable.ic_fluent_earth_24_regular);
|
||||||
|
Drawable unlistedDrawable = ctx.getDrawable(R.drawable.ic_fluent_people_community_24_regular);
|
||||||
|
Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_people_checkmark_24_regular);
|
||||||
|
|
||||||
|
StatusPrivacy defaultVisibility = session.preferences != null ? session.preferences.postingDefaultVisibility : null;
|
||||||
|
// e.g. post visibility is unlisted, but default is public
|
||||||
|
// in this case, we want to display the check mark on the most visible visibility
|
||||||
|
if (defaultVisibility != null && item.status.visibility.isLessVisibleThan(defaultVisibility)) {
|
||||||
|
for (StatusPrivacy vis : StatusPrivacy.values()) {
|
||||||
|
if (vis.equals(item.status.visibility)) {
|
||||||
|
defaultVisibility = vis;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemPublic.setCompoundDrawablesWithIntrinsicBounds(publicDrawable, null, StatusPrivacy.PUBLIC.equals(defaultVisibility) ? checkMark : null, null);
|
||||||
|
itemUnlisted.setCompoundDrawablesWithIntrinsicBounds(unlistedDrawable, null, StatusPrivacy.UNLISTED.equals(defaultVisibility) ? checkMark : null, null);
|
||||||
|
itemFollowers.setCompoundDrawablesWithIntrinsicBounds(followersDrawable, null, StatusPrivacy.PRIVATE.equals(defaultVisibility) ? checkMark : null, null);
|
||||||
|
|
||||||
|
undoReblog.setOnClickListener(c->doReblog.accept(null));
|
||||||
|
itemPublic.setOnClickListener(c->doReblog.accept(StatusPrivacy.PUBLIC));
|
||||||
|
itemUnlisted.setOnClickListener(c->doReblog.accept(StatusPrivacy.UNLISTED));
|
||||||
|
itemFollowers.setOnClickListener(c->doReblog.accept(StatusPrivacy.PRIVATE));
|
||||||
|
reblogAs.setOnClickListener(c->{
|
||||||
|
dialog.dismiss();
|
||||||
|
UiUtils.pickInteractAs(v.getContext(),
|
||||||
|
item.accountID, item.status,
|
||||||
|
s -> s.reblogged,
|
||||||
|
(ic, status, consumer) -> ic.setReblogged(status, true, null, consumer),
|
||||||
|
R.string.sk_reblog_as,
|
||||||
|
R.string.sk_reblogged_as,
|
||||||
|
R.string.sk_already_reblogged,
|
||||||
|
// TODO: replace once available: https://raw.githubusercontent.com/microsoft/fluentui-system-icons/main/android/library/src/main/res/drawable/ic_fluent_arrow_repeat_all_28_regular.xml
|
||||||
|
R.drawable.ic_fluent_arrow_repeat_all_24_regular
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.findViewById(R.id.quote).setOnClickListener(c->{
|
||||||
|
dialog.dismiss();
|
||||||
|
v.startAnimation(opacityIn);
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", item.accountID);
|
||||||
|
StringBuilder prefilledText = new StringBuilder().append("\n\n");
|
||||||
|
String ownID = AccountSessionManager.getInstance().getAccount(item.accountID).self.id;
|
||||||
|
if (!item.status.account.id.equals(ownID)) prefilledText.append('@').append(item.status.account.acct).append(' ');
|
||||||
|
prefilledText.append(item.status.url);
|
||||||
|
args.putString("prefilledText", prefilledText.toString());
|
||||||
|
args.putInt("selectionStart", 0);
|
||||||
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFavoriteClick(View v){
|
private void onFavoriteClick(View v){
|
||||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited);
|
favorite.setSelected(!item.status.favourited);
|
||||||
favorite.setSelected(item.status.favourited);
|
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
|
||||||
bindButton(favorite, item.status.favouritesCount);
|
if (item.status.favourited) {
|
||||||
|
if(GlobalUserPreferences.reduceMotion){
|
||||||
|
v.startAnimation(opacityIn);
|
||||||
|
}else{
|
||||||
|
v.startAnimation(animSet);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.startAnimation(opacityIn);
|
||||||
|
}
|
||||||
|
bindButton(favorite, r.favouritesCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onFavoriteLongClick(View v) {
|
||||||
|
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
|
||||||
|
UiUtils.pickInteractAs(v.getContext(),
|
||||||
|
item.accountID, item.status,
|
||||||
|
s -> s.favourited,
|
||||||
|
(ic, status, consumer) -> ic.setFavorited(status, true, consumer),
|
||||||
|
R.string.sk_favorite_as,
|
||||||
|
R.string.sk_favorited_as,
|
||||||
|
R.string.sk_already_favorited,
|
||||||
|
R.drawable.ic_fluent_star_28_regular
|
||||||
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onBookmarkClick(View v){
|
private void onBookmarkClick(View v){
|
||||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
|
bookmark.setSelected(!item.status.bookmarked);
|
||||||
bookmark.setSelected(item.status.bookmarked);
|
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked, r->{
|
||||||
|
v.startAnimation(opacityIn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onBookmarkLongClick(View v) {
|
||||||
|
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
|
||||||
|
UiUtils.pickInteractAs(v.getContext(),
|
||||||
|
item.accountID, item.status,
|
||||||
|
s -> s.bookmarked,
|
||||||
|
(ic, status, consumer) -> ic.setBookmarked(status, true, consumer),
|
||||||
|
R.string.sk_bookmark_as,
|
||||||
|
R.string.sk_bookmarked_as,
|
||||||
|
R.string.sk_already_bookmarked,
|
||||||
|
R.drawable.ic_fluent_bookmark_28_regular
|
||||||
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onShareClick(View v){
|
private void onShareClick(View v){
|
||||||
|
v.startAnimation(opacityIn);
|
||||||
Intent intent=new Intent(Intent.ACTION_SEND);
|
Intent intent=new Intent(Intent.ACTION_SEND);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, item.status.url);
|
intent.putExtra(Intent.EXTRA_TEXT, item.status.url);
|
||||||
v.getContext().startActivity(Intent.createChooser(intent, v.getContext().getString(R.string.share_toot_title)));
|
v.getContext().startActivity(Intent.createChooser(intent, v.getContext().getString(R.string.share_toot_title)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean onShareLongClick(View v){
|
||||||
|
UiUtils.copyText(v, item.status.url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private int descriptionForId(int id){
|
private int descriptionForId(int id){
|
||||||
if(id==R.id.reply_btn)
|
if(id==R.id.reply_btn)
|
||||||
return R.string.button_reply;
|
return R.string.button_reply;
|
||||||
@@ -153,4 +385,4 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,8 +9,10 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.SubMenu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
@@ -22,16 +24,21 @@ import android.widget.Toast;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusSourceText;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
|
import org.joinmastodon.android.fragments.NotificationsListFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||||
@@ -39,8 +46,12 @@ import org.joinmastodon.android.ui.utils.UiUtils;
|
|||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.FormatStyle;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
@@ -62,15 +73,20 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
private boolean hasVisibilityToggle;
|
private boolean hasVisibilityToggle;
|
||||||
boolean needBottomPadding;
|
boolean needBottomPadding;
|
||||||
private String extraText;
|
private String extraText;
|
||||||
|
private Notification notification;
|
||||||
|
private ScheduledStatus scheduledStatus;
|
||||||
|
|
||||||
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, String extraText){
|
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID, Status status, String extraText, Notification notification, ScheduledStatus scheduledStatus){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
|
user=scheduledStatus != null ? AccountSessionManager.getInstance().getAccount(accountID).self : user;
|
||||||
this.user=user;
|
this.user=user;
|
||||||
this.createdAt=createdAt;
|
this.createdAt=createdAt;
|
||||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic, V.dp(50), V.dp(50));
|
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic, V.dp(50), V.dp(50));
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
parsedName=new SpannableStringBuilder(user.displayName);
|
parsedName=new SpannableStringBuilder(user.displayName);
|
||||||
this.status=status;
|
this.status=status;
|
||||||
|
this.notification=notification;
|
||||||
|
this.scheduledStatus=scheduledStatus;
|
||||||
HtmlParser.parseCustomEmoji(parsedName, user.emojis);
|
HtmlParser.parseCustomEmoji(parsedName, user.emojis);
|
||||||
emojiHelper.setText(parsedName);
|
emojiHelper.setText(parsedName);
|
||||||
if(status!=null){
|
if(status!=null){
|
||||||
@@ -107,7 +123,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||||
private final TextView name, username, timestamp, extraText;
|
private final TextView name, username, timestamp, extraText;
|
||||||
private final ImageView avatar, more, visibility;
|
private final ImageView avatar, more, visibility, deleteNotification;
|
||||||
private final PopupMenu optionsMenu;
|
private final PopupMenu optionsMenu;
|
||||||
private Relationship relationship;
|
private Relationship relationship;
|
||||||
private APIRequest<?> currentRelationshipRequest;
|
private APIRequest<?> currentRelationshipRequest;
|
||||||
@@ -127,18 +143,25 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
avatar=findViewById(R.id.avatar);
|
avatar=findViewById(R.id.avatar);
|
||||||
more=findViewById(R.id.more);
|
more=findViewById(R.id.more);
|
||||||
visibility=findViewById(R.id.visibility);
|
visibility=findViewById(R.id.visibility);
|
||||||
|
deleteNotification=findViewById(R.id.delete_notification);
|
||||||
extraText=findViewById(R.id.extra_text);
|
extraText=findViewById(R.id.extra_text);
|
||||||
avatar.setOnClickListener(this::onAvaClick);
|
avatar.setOnClickListener(this::onAvaClick);
|
||||||
avatar.setOutlineProvider(roundCornersOutline);
|
avatar.setOutlineProvider(roundCornersOutline);
|
||||||
avatar.setClipToOutline(true);
|
avatar.setClipToOutline(true);
|
||||||
more.setOnClickListener(this::onMoreClick);
|
more.setOnClickListener(this::onMoreClick);
|
||||||
visibility.setOnClickListener(v->item.parentFragment.onVisibilityIconClick(this));
|
visibility.setOnClickListener(v->item.parentFragment.onVisibilityIconClick(this));
|
||||||
|
deleteNotification.setOnClickListener(v->UiUtils.confirmDeleteNotification(activity, item.parentFragment.getAccountID(), item.notification, ()->{
|
||||||
|
if (item.parentFragment instanceof NotificationsListFragment fragment) {
|
||||||
|
fragment.removeNotification(item.notification);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
optionsMenu=new PopupMenu(activity, more);
|
optionsMenu=new PopupMenu(activity, more);
|
||||||
optionsMenu.inflate(R.menu.post);
|
optionsMenu.inflate(R.menu.post);
|
||||||
optionsMenu.setOnMenuItemClickListener(menuItem->{
|
optionsMenu.setOnMenuItemClickListener(menuItem->{
|
||||||
Account account=item.user;
|
Account account=item.user;
|
||||||
int id=menuItem.getItemId();
|
int id=menuItem.getItemId();
|
||||||
|
|
||||||
if(id==R.id.edit || id==R.id.delete_and_redraft) {
|
if(id==R.id.edit || id==R.id.delete_and_redraft) {
|
||||||
final Bundle args=new Bundle();
|
final Bundle args=new Bundle();
|
||||||
args.putString("account", item.parentFragment.getAccountID());
|
args.putString("account", item.parentFragment.getAccountID());
|
||||||
@@ -153,6 +176,12 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
if(TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
|
if(TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
|
||||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||||
|
}else if(item.scheduledStatus!=null){
|
||||||
|
args.putString("sourceText", item.status.text);
|
||||||
|
args.putString("sourceSpoiler", item.status.spoilerText);
|
||||||
|
args.putBoolean("redraftStatus", true);
|
||||||
|
args.putParcelable("scheduledStatus", Parcels.wrap(item.scheduledStatus));
|
||||||
|
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||||
}else{
|
}else{
|
||||||
new GetStatusSourceText(item.status.id)
|
new GetStatusSourceText(item.status.id)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@@ -178,8 +207,12 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
.exec(item.parentFragment.getAccountID());
|
.exec(item.parentFragment.getAccountID());
|
||||||
}
|
}
|
||||||
}else if(id==R.id.delete){
|
}else if(id==R.id.delete){
|
||||||
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
if (item.scheduledStatus != null) {
|
||||||
}else if(id==R.id.pin || id==R.id.unpin){
|
UiUtils.confirmDeleteScheduledPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.scheduledStatus, ()->{});
|
||||||
|
} else {
|
||||||
|
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
|
||||||
|
}
|
||||||
|
}else if(id==R.id.pin || id==R.id.unpin) {
|
||||||
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
|
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
|
||||||
}else if(id==R.id.mute){
|
}else if(id==R.id.mute){
|
||||||
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
|
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
|
||||||
@@ -191,8 +224,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
args.putParcelable("status", Parcels.wrap(item.status));
|
args.putParcelable("status", Parcels.wrap(item.status));
|
||||||
args.putParcelable("reportAccount", Parcels.wrap(item.status.account));
|
args.putParcelable("reportAccount", Parcels.wrap(item.status.account));
|
||||||
Nav.go(item.parentFragment.getActivity(), ReportReasonChoiceFragment.class, args);
|
Nav.go(item.parentFragment.getActivity(), ReportReasonChoiceFragment.class, args);
|
||||||
}else if(id==R.id.open_in_browser){
|
}else if(id==R.id.open_in_browser) {
|
||||||
UiUtils.launchWebBrowser(activity, item.status.url);
|
UiUtils.launchWebBrowser(activity, item.status.url);
|
||||||
|
}else if(id==R.id.copy_link){
|
||||||
|
UiUtils.copyText(parent, item.status.url);
|
||||||
}else if(id==R.id.follow){
|
}else if(id==R.id.follow){
|
||||||
if(relationship==null)
|
if(relationship==null)
|
||||||
return true;
|
return true;
|
||||||
@@ -206,7 +241,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
}, rel->{
|
}, rel->{
|
||||||
relationship=rel;
|
relationship=rel;
|
||||||
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : R.string.unfollowed_user, account.getDisplayUsername()), Toast.LENGTH_SHORT).show();
|
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : R.string.unfollowed_user, account.getShortUsername()), Toast.LENGTH_SHORT).show();
|
||||||
});
|
});
|
||||||
}else if(id==R.id.block_domain){
|
}else if(id==R.id.block_domain){
|
||||||
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
|
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
|
||||||
@@ -215,17 +250,40 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
UiUtils.enablePopupMenuIcons(activity, optionsMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateAccountsMenu(Menu menu) {
|
||||||
|
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
||||||
|
sessions.stream().filter(s -> !s.getID().equals(item.accountID)).forEach(s -> {
|
||||||
|
String username = "@"+s.self.username+"@"+s.domain;
|
||||||
|
menu.add(username).setOnMenuItemClickListener(c->{
|
||||||
|
UiUtils.openURL(item.parentFragment.getActivity(), s.getID(), item.status.url, false);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(HeaderStatusDisplayItem item){
|
public void onBind(HeaderStatusDisplayItem item){
|
||||||
name.setText(item.parsedName);
|
name.setText(item.parsedName);
|
||||||
username.setText('@'+item.user.acct);
|
username.setText('@'+item.user.acct);
|
||||||
if(item.status==null || item.status.editedAt==null)
|
|
||||||
|
username.setCompoundDrawablesWithIntrinsicBounds(item.user.bot ? R.drawable.ic_fluent_bot_24_filled : 0, 0, 0, 0);
|
||||||
|
|
||||||
|
if (item.scheduledStatus!=null)
|
||||||
|
if (item.scheduledStatus.scheduledAt.isAfter(CreateStatus.DRAFTS_AFTER_INSTANT)) {
|
||||||
|
timestamp.setText(R.string.sk_draft);
|
||||||
|
} else {
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
|
||||||
|
timestamp.setText(item.scheduledStatus.scheduledAt.atZone(ZoneId.systemDefault()).format(formatter));
|
||||||
|
}
|
||||||
|
else if(item.status==null || item.status.editedAt==null)
|
||||||
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
|
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
|
||||||
else
|
else
|
||||||
timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt)));
|
timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt)));
|
||||||
visibility.setVisibility(item.hasVisibilityToggle && !item.inset ? View.VISIBLE : View.GONE);
|
visibility.setVisibility(item.hasVisibilityToggle && !item.inset ? View.VISIBLE : View.GONE);
|
||||||
|
deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification!=null && !item.inset ? View.VISIBLE : View.GONE);
|
||||||
if(item.hasVisibilityToggle){
|
if(item.hasVisibilityToggle){
|
||||||
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
|
visibility.setImageResource(item.status.spoilerRevealed ? R.drawable.ic_visibility_off : R.drawable.ic_visibility);
|
||||||
visibility.setContentDescription(item.parentFragment.getString(item.status.spoilerRevealed ? R.string.hide_content : R.string.reveal_content));
|
visibility.setContentDescription(item.parentFragment.getString(item.status.spoilerRevealed ? R.string.hide_content : R.string.reveal_content));
|
||||||
@@ -298,15 +356,30 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateOptionsMenu(){
|
private void updateOptionsMenu(){
|
||||||
Account account=item.user;
|
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
|
||||||
Menu menu=optionsMenu.getMenu();
|
Menu menu=optionsMenu.getMenu();
|
||||||
|
|
||||||
|
MenuItem openWithAccounts = menu.findItem(R.id.open_with_account);
|
||||||
|
SubMenu accountsMenu = openWithAccounts != null ? openWithAccounts.getSubMenu() : null;
|
||||||
|
if (hasMultipleAccounts && accountsMenu != null) {
|
||||||
|
openWithAccounts.setVisible(true);
|
||||||
|
accountsMenu.clear();
|
||||||
|
populateAccountsMenu(accountsMenu);
|
||||||
|
} else if (openWithAccounts != null) {
|
||||||
|
openWithAccounts.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Account account=item.user;
|
||||||
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
||||||
|
boolean isPostScheduled=item.scheduledStatus!=null;
|
||||||
|
menu.findItem(R.id.open_with_account).setVisible(!isPostScheduled && hasMultipleAccounts);
|
||||||
menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost);
|
menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost);
|
||||||
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
||||||
menu.findItem(R.id.delete_and_redraft).setVisible(item.status!=null && isOwnPost);
|
menu.findItem(R.id.delete_and_redraft).setVisible(!isPostScheduled && item.status!=null && isOwnPost);
|
||||||
menu.findItem(R.id.pin).setVisible(item.status!=null && isOwnPost && !item.status.pinned);
|
menu.findItem(R.id.pin).setVisible(!isPostScheduled && item.status!=null && isOwnPost && !item.status.pinned);
|
||||||
menu.findItem(R.id.unpin).setVisible(item.status!=null && isOwnPost && item.status.pinned);
|
menu.findItem(R.id.unpin).setVisible(!isPostScheduled && item.status!=null && isOwnPost && item.status.pinned);
|
||||||
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);
|
menu.findItem(R.id.open_in_browser).setVisible(!isPostScheduled && item.status!=null);
|
||||||
|
menu.findItem(R.id.copy_link).setVisible(!isPostScheduled && item.status!=null);
|
||||||
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||||
MenuItem mute=menu.findItem(R.id.mute);
|
MenuItem mute=menu.findItem(R.id.mute);
|
||||||
MenuItem block=menu.findItem(R.id.block);
|
MenuItem block=menu.findItem(R.id.block);
|
||||||
@@ -322,7 +395,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
bookmark.setVisible(false);
|
bookmark.setVisible(false);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
if(isOwnPost){
|
if(isPostScheduled || isOwnPost){
|
||||||
mute.setVisible(false);
|
mute.setVisible(false);
|
||||||
block.setVisible(false);
|
block.setVisible(false);
|
||||||
report.setVisible(false);
|
report.setVisible(false);
|
||||||
@@ -333,16 +406,22 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||||||
block.setVisible(true);
|
block.setVisible(true);
|
||||||
report.setVisible(true);
|
report.setVisible(true);
|
||||||
follow.setVisible(relationship==null || relationship.following || (!relationship.blocking && !relationship.blockedBy && !relationship.domainBlocking && !relationship.muting));
|
follow.setVisible(relationship==null || relationship.following || (!relationship.blocking && !relationship.blockedBy && !relationship.domainBlocking && !relationship.muting));
|
||||||
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||||
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
mute.setIcon(relationship!=null && relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||||
report.setTitle(item.parentFragment.getString(R.string.report_user, account.getDisplayUsername()));
|
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), mute);
|
||||||
if(!account.isLocal()){
|
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
||||||
blockDomain.setVisible(true);
|
report.setTitle(item.parentFragment.getString(R.string.report_user, account.getShortUsername()));
|
||||||
blockDomain.setTitle(item.parentFragment.getString(relationship!=null && relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
// disabled in megalodon. domain blocks from a post clutters the context menu and looks out of place
|
||||||
}else{
|
// if(!account.isLocal()){
|
||||||
|
// blockDomain.setVisible(true);
|
||||||
|
// blockDomain.setTitle(item.parentFragment.getString(relationship!=null && relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||||
|
// }else{
|
||||||
blockDomain.setVisible(false);
|
blockDomain.setVisible(false);
|
||||||
}
|
// }
|
||||||
follow.setTitle(item.parentFragment.getString(relationship!=null && relationship.following ? R.string.unfollow_user : R.string.follow_user, account.getDisplayUsername()));
|
boolean following = relationship!=null && relationship.following;
|
||||||
|
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, account.getShortUsername()));
|
||||||
|
follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular);
|
||||||
|
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onClick(View v){
|
private void onClick(View v){
|
||||||
UiUtils.launchWebBrowser(itemView.getContext(), item.status.card.url);
|
UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), item.status.card.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,10 +89,13 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
|||||||
button.setBackground(progressBg);
|
button.setBackground(progressBg);
|
||||||
itemView.setSelected(item.isMostVoted);
|
itemView.setSelected(item.isMostVoted);
|
||||||
icon.setSelected(item.poll.ownVotes.contains(item.poll.options.indexOf(item.option)));
|
icon.setSelected(item.poll.ownVotes.contains(item.poll.options.indexOf(item.option)));
|
||||||
|
icon.setVisibility(item.poll.voted && item.poll.ownVotes.isEmpty() ? View.GONE : View.VISIBLE);
|
||||||
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
|
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
|
||||||
}else{
|
}else{
|
||||||
itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
|
itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
|
||||||
button.setBackgroundResource(R.drawable.bg_poll_option_clickable);
|
button.setBackgroundResource(R.drawable.bg_poll_option_clickable);
|
||||||
|
icon.setSelected(itemView.isSelected());
|
||||||
|
icon.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.ui.displayitems;
|
|||||||
import static org.joinmastodon.android.MastodonApp.context;
|
import static org.joinmastodon.android.MastodonApp.context;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
@@ -14,6 +15,7 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
@@ -30,10 +32,13 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
private CharSequence text;
|
private CharSequence text;
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
private int icon;
|
private int icon;
|
||||||
|
private StatusPrivacy visibility;
|
||||||
|
@DrawableRes
|
||||||
|
private int iconEnd;
|
||||||
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||||
private View.OnClickListener handleClick;
|
private View.OnClickListener handleClick;
|
||||||
|
|
||||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, @Nullable View.OnClickListener handleClick){
|
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
||||||
HtmlParser.parseCustomEmoji(ssb, emojis);
|
HtmlParser.parseCustomEmoji(ssb, emojis);
|
||||||
@@ -43,6 +48,17 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
this.handleClick=handleClick;
|
this.handleClick=handleClick;
|
||||||
TypedValue outValue = new TypedValue();
|
TypedValue outValue = new TypedValue();
|
||||||
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
||||||
|
updateVisibility(visibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateVisibility(StatusPrivacy visibility) {
|
||||||
|
this.visibility = visibility;
|
||||||
|
this.iconEnd = visibility != null ? switch (visibility) {
|
||||||
|
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
||||||
|
case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
|
||||||
|
case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
|
||||||
|
default -> 0;
|
||||||
|
} : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,10 +86,18 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
|||||||
@Override
|
@Override
|
||||||
public void onBind(ReblogOrReplyLineStatusDisplayItem item){
|
public void onBind(ReblogOrReplyLineStatusDisplayItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0);
|
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
|
||||||
if(item.handleClick!=null) text.setOnClickListener(item.handleClick);
|
if(item.handleClick!=null) text.setOnClickListener(item.handleClick);
|
||||||
text.setEnabled(!item.inset);
|
text.setEnabled(!item.inset);
|
||||||
text.setClickable(!item.inset);
|
text.setClickable(!item.inset);
|
||||||
|
Context ctx = itemView.getContext();
|
||||||
|
int visibilityText = item.visibility != null ? switch (item.visibility) {
|
||||||
|
case PUBLIC -> R.string.visibility_public;
|
||||||
|
case UNLISTED -> R.string.sk_visibility_unlisted;
|
||||||
|
case PRIVATE -> R.string.visibility_followers_only;
|
||||||
|
default -> 0;
|
||||||
|
} : 0;
|
||||||
|
if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")");
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
|
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.Poll;
|
import org.joinmastodon.android.model.Poll;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
@@ -73,26 +76,29 @@ public abstract class StatusDisplayItem{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter){
|
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification){
|
||||||
String parentID=parentObject.getID();
|
String parentID=parentObject.getID();
|
||||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||||
Status statusForContent=status.getContentStatus();
|
Status statusForContent=status.getContentStatus();
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
|
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null;
|
||||||
|
|
||||||
if(status.reblog!=null){
|
if(status.reblog!=null){
|
||||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, i->{
|
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
|
||||||
|
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
||||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||||
}));
|
}));
|
||||||
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
|
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
|
||||||
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
|
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
|
||||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, i->{
|
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, null, i->{
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(account));
|
args.putParcelable("profileAccount", Parcels.wrap(account));
|
||||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
HeaderStatusDisplayItem header;
|
HeaderStatusDisplayItem header;
|
||||||
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
|
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus));
|
||||||
if(!TextUtils.isEmpty(statusForContent.content))
|
if(!TextUtils.isEmpty(statusForContent.content))
|
||||||
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent));
|
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent));
|
||||||
else
|
else
|
||||||
@@ -170,7 +176,7 @@ public abstract class StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Holder(Context context, int layout, ViewGroup parent){
|
public Holder(Context context, int layout, ViewGroup parent){
|
||||||
super(context, layout, parent);
|
super(context, layout, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getItemID(){
|
public String getItemID(){
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.MovieDrawable;
|
import me.grishka.appkit.imageloader.MovieDrawable;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class TextStatusDisplayItem extends StatusDisplayItem{
|
public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||||
@@ -41,10 +42,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public final Status status;
|
public final Status status;
|
||||||
public boolean translated = false;
|
public boolean translated = false;
|
||||||
public TranslatedStatus translation = null;
|
public TranslatedStatus translation = null;
|
||||||
|
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
private Instance instanceInfo;
|
|
||||||
private boolean translateEnabled;
|
|
||||||
|
|
||||||
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status){
|
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
@@ -57,8 +55,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
spoilerEmojiHelper.setText(parsedSpoilerText);
|
spoilerEmojiHelper.setText(parsedSpoilerText);
|
||||||
}
|
}
|
||||||
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
|
||||||
instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
|
||||||
translateEnabled = instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null && instanceInfo.v2.configuration.translation.enabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -146,8 +142,12 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
itemView.setClickable(false);
|
itemView.setClickable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
translateWrap.setVisibility(item.textSelectable && item.translateEnabled &&
|
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(item.session.domain);
|
||||||
|
boolean translateEnabled = instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null && instanceInfo.v2.configuration.translation.enabled;
|
||||||
|
|
||||||
|
translateWrap.setVisibility(translateEnabled &&
|
||||||
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
|
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
|
||||||
|
item.status.language != null &&
|
||||||
(item.session.preferences == null || !item.status.language.equalsIgnoreCase(item.session.preferences.postingDefaultLanguage))
|
(item.session.preferences == null || !item.status.language.equalsIgnoreCase(item.session.preferences.postingDefaultLanguage))
|
||||||
? View.VISIBLE : View.GONE);
|
? View.VISIBLE : View.GONE);
|
||||||
translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post);
|
translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post);
|
||||||
@@ -156,6 +156,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
if (item.translation == null) {
|
if (item.translation == null) {
|
||||||
translateProgress.setVisibility(View.VISIBLE);
|
translateProgress.setVisibility(View.VISIBLE);
|
||||||
translateButton.setClickable(false);
|
translateButton.setClickable(false);
|
||||||
|
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
|
||||||
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
|
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(TranslatedStatus translatedStatus) {
|
public void onSuccess(TranslatedStatus translatedStatus) {
|
||||||
@@ -163,6 +164,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
item.translated = true;
|
item.translated = true;
|
||||||
translateProgress.setVisibility(View.GONE);
|
translateProgress.setVisibility(View.GONE);
|
||||||
translateButton.setClickable(true);
|
translateButton.setClickable(true);
|
||||||
|
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
|
||||||
rebind();
|
rebind();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +172,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
public void onError(ErrorResponse error) {
|
public void onError(ErrorResponse error) {
|
||||||
translateProgress.setVisibility(View.GONE);
|
translateProgress.setVisibility(View.GONE);
|
||||||
translateButton.setClickable(true);
|
translateButton.setClickable(true);
|
||||||
|
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
|
||||||
error.showToast(itemView.getContext());
|
error.showToast(itemView.getContext());
|
||||||
}
|
}
|
||||||
}).exec(item.parentFragment.getAccountID());
|
}).exec(item.parentFragment.getAccountID());
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import android.text.Layout;
|
|||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.SoundEffectConstants;
|
import android.view.SoundEffectConstants;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
@@ -20,7 +22,12 @@ public class ClickableLinksDelegate {
|
|||||||
private Path hlPath;
|
private Path hlPath;
|
||||||
private LinkSpan selectedSpan;
|
private LinkSpan selectedSpan;
|
||||||
private TextView view;
|
private TextView view;
|
||||||
|
|
||||||
|
private final Runnable longClickRunnable = () -> {
|
||||||
|
if (selectedSpan != null) selectedSpan.onLongClick(view);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
public ClickableLinksDelegate(TextView view) {
|
public ClickableLinksDelegate(TextView view) {
|
||||||
this.view=view;
|
this.view=view;
|
||||||
hlPaint=new Paint();
|
hlPaint=new Paint();
|
||||||
@@ -30,6 +37,7 @@ public class ClickableLinksDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean onTouch(MotionEvent event) {
|
public boolean onTouch(MotionEvent event) {
|
||||||
|
long eventDuration = event.getEventTime() - event.getDownTime();
|
||||||
if(event.getAction()==MotionEvent.ACTION_DOWN){
|
if(event.getAction()==MotionEvent.ACTION_DOWN){
|
||||||
int line=-1;
|
int line=-1;
|
||||||
Rect rect=new Rect();
|
Rect rect=new Rect();
|
||||||
@@ -63,6 +71,7 @@ public class ClickableLinksDelegate {
|
|||||||
}
|
}
|
||||||
hlPath=new Path();
|
hlPath=new Path();
|
||||||
selectedSpan=span;
|
selectedSpan=span;
|
||||||
|
view.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
|
||||||
hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000);
|
hlPaint.setColor((span.getColor() & 0x00FFFFFF) | 0x33000000);
|
||||||
//l.getSelectionPath(start, end, hlPath);
|
//l.getSelectionPath(start, end, hlPath);
|
||||||
for(int j=lstart;j<=lend;j++){
|
for(int j=lstart;j<=lend;j++){
|
||||||
@@ -90,8 +99,11 @@ public class ClickableLinksDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(event.getAction()==MotionEvent.ACTION_UP && selectedSpan!=null){
|
if(event.getAction()==MotionEvent.ACTION_UP && selectedSpan!=null){
|
||||||
view.playSoundEffect(SoundEffectConstants.CLICK);
|
if (eventDuration <= ViewConfiguration.getLongPressTimeout()) {
|
||||||
selectedSpan.onClick(view.getContext());
|
view.playSoundEffect(SoundEffectConstants.CLICK);
|
||||||
|
selectedSpan.onClick(view.getContext());
|
||||||
|
}
|
||||||
|
view.removeCallbacks(longClickRunnable);
|
||||||
hlPath=null;
|
hlPath=null;
|
||||||
selectedSpan=null;
|
selectedSpan=null;
|
||||||
view.invalidate();
|
view.invalidate();
|
||||||
@@ -100,6 +112,7 @@ public class ClickableLinksDelegate {
|
|||||||
if(event.getAction()==MotionEvent.ACTION_CANCEL){
|
if(event.getAction()==MotionEvent.ACTION_CANCEL){
|
||||||
hlPath=null;
|
hlPath=null;
|
||||||
selectedSpan=null;
|
selectedSpan=null;
|
||||||
|
view.removeCallbacks(longClickRunnable);
|
||||||
view.invalidate();
|
view.invalidate();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ public class HtmlParser{
|
|||||||
case "a" -> {
|
case "a" -> {
|
||||||
String href=el.attr("href");
|
String href=el.attr("href");
|
||||||
LinkSpan.Type linkType;
|
LinkSpan.Type linkType;
|
||||||
|
String text=el.text();
|
||||||
if(el.hasClass("hashtag")){
|
if(el.hasClass("hashtag")){
|
||||||
String text=el.text();
|
|
||||||
if(text.startsWith("#")){
|
if(text.startsWith("#")){
|
||||||
linkType=LinkSpan.Type.HASHTAG;
|
linkType=LinkSpan.Type.HASHTAG;
|
||||||
href=text.substring(1);
|
href=text.substring(1);
|
||||||
@@ -136,7 +136,7 @@ public class HtmlParser{
|
|||||||
}else{
|
}else{
|
||||||
linkType=LinkSpan.Type.URL;
|
linkType=LinkSpan.Type.URL;
|
||||||
}
|
}
|
||||||
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID), ssb.length(), el));
|
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID, text), ssb.length(), el));
|
||||||
}
|
}
|
||||||
case "br" -> ssb.append('\n');
|
case "br" -> ssb.append('\n');
|
||||||
case "span" -> {
|
case "span" -> {
|
||||||
@@ -182,7 +182,7 @@ public class HtmlParser{
|
|||||||
ssb.append("…", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
ssb.append("…", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}else if(blockElements.contains(el.nodeName()) && node.nextSibling()!=null){
|
}else if(blockElements.contains(el.nodeName()) && node.nextSibling()!=null){
|
||||||
ssb.append("\n"); // line end
|
ssb.append("\n"); // line end
|
||||||
ssb.append("\n", new RelativeSizeSpan(0.75f), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // margin after block
|
ssb.append("\n", new RelativeSizeSpan(0.65f), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // margin after block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +260,7 @@ public class HtmlParser{
|
|||||||
String url=matcher.group(3);
|
String url=matcher.group(3);
|
||||||
if(TextUtils.isEmpty(matcher.group(4)))
|
if(TextUtils.isEmpty(matcher.group(4)))
|
||||||
url="http://"+url;
|
url="http://"+url;
|
||||||
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0);
|
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null, url), matcher.start(3), matcher.end(3), 0);
|
||||||
}while(matcher.find()); // Find more URLs
|
}while(matcher.find()); // Find more URLs
|
||||||
return ssb;
|
return ssb;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package org.joinmastodon.android.ui.text;
|
package org.joinmastodon.android.ui.text;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.text.style.CharacterStyle;
|
import android.text.style.CharacterStyle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
@@ -13,12 +16,14 @@ public class LinkSpan extends CharacterStyle {
|
|||||||
private String link;
|
private String link;
|
||||||
private Type type;
|
private Type type;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
private String text;
|
||||||
|
|
||||||
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID){
|
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, String text){
|
||||||
this.listener=listener;
|
this.listener=listener;
|
||||||
this.link=link;
|
this.link=link;
|
||||||
this.type=type;
|
this.type=type;
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
|
this.text=text;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getColor(){
|
public int getColor(){
|
||||||
@@ -29,7 +34,7 @@ public class LinkSpan extends CharacterStyle {
|
|||||||
public void updateDrawState(TextPaint tp) {
|
public void updateDrawState(TextPaint tp) {
|
||||||
tp.setColor(color=tp.linkColor);
|
tp.setColor(color=tp.linkColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick(Context context){
|
public void onClick(Context context){
|
||||||
switch(getType()){
|
switch(getType()){
|
||||||
case URL -> UiUtils.openURL(context, accountID, link);
|
case URL -> UiUtils.openURL(context, accountID, link);
|
||||||
@@ -38,6 +43,17 @@ public class LinkSpan extends CharacterStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onLongClick(View view) {
|
||||||
|
if (getType() == Type.URL) {
|
||||||
|
Intent shareIntent = new Intent(Intent.ACTION_SEND)
|
||||||
|
.setType("text/plain")
|
||||||
|
.putExtra(Intent.EXTRA_TEXT, link);
|
||||||
|
view.getContext().startActivity(Intent.createChooser(shareIntent, null));
|
||||||
|
} else {
|
||||||
|
UiUtils.copyText(view, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getLink(){
|
public String getLink(){
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.joinmastodon.android.ui.utils;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.ThemePreference;
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.theme;
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
|
||||||
|
import androidx.annotation.StyleRes;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ColorPalette {
|
||||||
|
public static final Map<GlobalUserPreferences.ColorPreference, ColorPalette> palettes = Map.of(
|
||||||
|
ColorPreference.MATERIAL3, new ColorPalette(R.style.ColorPalette_Material3)
|
||||||
|
.dark(R.style.ColorPalette_Material3_Dark, R.style.ColorPalette_Material3_AutoLightDark),
|
||||||
|
ColorPreference.PINK, new ColorPalette(R.style.ColorPalette_Pink),
|
||||||
|
ColorPreference.PURPLE, new ColorPalette(R.style.ColorPalette_Purple),
|
||||||
|
ColorPreference.GREEN, new ColorPalette(R.style.ColorPalette_Green),
|
||||||
|
ColorPreference.BLUE, new ColorPalette(R.style.ColorPalette_Blue),
|
||||||
|
ColorPreference.BROWN, new ColorPalette(R.style.ColorPalette_Brown),
|
||||||
|
ColorPreference.RED, new ColorPalette(R.style.ColorPalette_Red),
|
||||||
|
ColorPreference.YELLOW, new ColorPalette(R.style.ColorPalette_Yellow),
|
||||||
|
ColorPreference.NORD, new ColorPalette(R.style.ColorPalette_Nord)
|
||||||
|
);
|
||||||
|
|
||||||
|
private @StyleRes int base;
|
||||||
|
private @StyleRes int autoDark;
|
||||||
|
private @StyleRes int light;
|
||||||
|
private @StyleRes int dark;
|
||||||
|
private @StyleRes int black;
|
||||||
|
private @StyleRes int autoBlack;
|
||||||
|
|
||||||
|
public ColorPalette(@StyleRes int baseRes) { base = baseRes; }
|
||||||
|
|
||||||
|
public ColorPalette(@StyleRes int lightRes, @StyleRes int darkRes, @StyleRes int autoDarkRes, @StyleRes int blackRes, @StyleRes int autoBlackRes) {
|
||||||
|
light = lightRes;
|
||||||
|
dark = darkRes;
|
||||||
|
autoDark = autoDarkRes;
|
||||||
|
black = blackRes;
|
||||||
|
autoBlack = autoBlackRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ColorPalette light(@StyleRes int res) { light = res; return this; }
|
||||||
|
public ColorPalette dark(@StyleRes int res, @StyleRes int auto) { dark = res; autoDark = auto; return this; }
|
||||||
|
public ColorPalette black(@StyleRes int res, @StyleRes int auto) { dark = res; autoBlack = auto; return this; }
|
||||||
|
|
||||||
|
public void apply(Context context) {
|
||||||
|
if (!((dark != 0 && autoDark != 0) || (black != 0 && autoBlack != 0) || light != 0 || base != 0)) {
|
||||||
|
throw new IllegalStateException("Invalid color scheme definition");
|
||||||
|
}
|
||||||
|
|
||||||
|
Resources.Theme t = context.getTheme();
|
||||||
|
if (base != 0) t.applyStyle(base, true);
|
||||||
|
if (light != 0 && theme.equals(ThemePreference.LIGHT)) t.applyStyle(light, true);
|
||||||
|
else if (theme.equals(ThemePreference.DARK)) {
|
||||||
|
if (dark != 0 && !trueBlackTheme) t.applyStyle(dark, true);
|
||||||
|
else if (black != 0 && trueBlackTheme) t.applyStyle(black, true);
|
||||||
|
} else if (theme.equals(ThemePreference.AUTO)) {
|
||||||
|
if (autoDark != 0 && !trueBlackTheme) t.applyStyle(autoDark, true);
|
||||||
|
else if (autoBlack != 0 && trueBlackTheme) t.applyStyle(autoBlack, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
package org.joinmastodon.android.ui.utils;
|
package org.joinmastodon.android.ui.utils;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.theme;
|
||||||
|
import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
@@ -20,9 +28,10 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import android.provider.Settings;
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -36,23 +45,28 @@ import org.joinmastodon.android.E;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.StatusInteractionController;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
|
import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked;
|
||||||
import org.joinmastodon.android.api.requests.accounts.AuthorizeFollowRequest;
|
import org.joinmastodon.android.api.requests.accounts.AuthorizeFollowRequest;
|
||||||
import org.joinmastodon.android.api.requests.accounts.RejectFollowRequest;
|
import org.joinmastodon.android.api.requests.accounts.RejectFollowRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.notifications.DismissNotification;
|
||||||
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||||
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||||
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||||
import org.joinmastodon.android.fragments.ListTimelineFragment;
|
import org.joinmastodon.android.fragments.ListTimelineFragment;
|
||||||
@@ -60,16 +74,21 @@ import org.joinmastodon.android.fragments.ProfileFragment;
|
|||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
|
import org.joinmastodon.android.model.SearchResults;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||||
import org.joinmastodon.android.ui.text.SpacerSpan;
|
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
@@ -80,9 +99,12 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.AttrRes;
|
import androidx.annotation.AttrRes;
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.browser.customtabs.CustomTabsIntent;
|
import androidx.browser.customtabs.CustomTabsIntent;
|
||||||
@@ -326,22 +348,29 @@ public class UiUtils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){
|
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){
|
||||||
showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), onConfirmed);
|
showConfirmationAlert(context, title, message, confirmButton, 0, onConfirmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void showConfirmationAlert(Context context, CharSequence title, CharSequence message, CharSequence confirmButton, Runnable onConfirmed){
|
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, @DrawableRes int icon, Runnable onConfirmed){
|
||||||
|
showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), icon, onConfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showConfirmationAlert(Context context, CharSequence title, CharSequence message, CharSequence confirmButton, int icon, Runnable onConfirmed){
|
||||||
new M3AlertDialogBuilder(context)
|
new M3AlertDialogBuilder(context)
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setPositiveButton(confirmButton, (dlg, i)->onConfirmed.run())
|
.setPositiveButton(confirmButton, (dlg, i)->onConfirmed.run())
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.setIcon(icon)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback){
|
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback){
|
||||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
|
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
|
||||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
|
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
|
||||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), ()->{
|
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
|
||||||
|
currentlyBlocked ? R.drawable.ic_fluent_person_28_regular : R.drawable.ic_fluent_person_prohibited_28_regular,
|
||||||
|
()->{
|
||||||
new SetAccountBlocked(account.id, !currentlyBlocked)
|
new SetAccountBlocked(account.id, !currentlyBlocked)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
@@ -365,7 +394,9 @@ public class UiUtils{
|
|||||||
public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback){
|
public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback){
|
||||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title),
|
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title),
|
||||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain),
|
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain),
|
||||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block), ()->{
|
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
|
||||||
|
R.drawable.ic_fluent_shield_28_regular,
|
||||||
|
()->{
|
||||||
new SetDomainBlocked(domain, !currentlyBlocked)
|
new SetDomainBlocked(domain, !currentlyBlocked)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
@@ -386,7 +417,9 @@ public class UiUtils{
|
|||||||
public static void confirmToggleMuteUser(Activity activity, String accountID, Account account, boolean currentlyMuted, Consumer<Relationship> resultCallback){
|
public static void confirmToggleMuteUser(Activity activity, String accountID, Account account, boolean currentlyMuted, Consumer<Relationship> resultCallback){
|
||||||
showConfirmationAlert(activity, activity.getString(currentlyMuted ? R.string.confirm_unmute_title : R.string.confirm_mute_title),
|
showConfirmationAlert(activity, activity.getString(currentlyMuted ? R.string.confirm_unmute_title : R.string.confirm_mute_title),
|
||||||
activity.getString(currentlyMuted ? R.string.confirm_unmute : R.string.confirm_mute, account.displayName),
|
activity.getString(currentlyMuted ? R.string.confirm_unmute : R.string.confirm_mute, account.displayName),
|
||||||
activity.getString(currentlyMuted ? R.string.do_unmute : R.string.do_mute), ()->{
|
activity.getString(currentlyMuted ? R.string.do_unmute : R.string.do_mute),
|
||||||
|
currentlyMuted ? R.drawable.ic_fluent_speaker_0_28_regular : R.drawable.ic_fluent_speaker_off_28_regular,
|
||||||
|
()->{
|
||||||
new SetAccountMuted(account.id, !currentlyMuted)
|
new SetAccountMuted(account.id, !currentlyMuted)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
@@ -411,24 +444,53 @@ public class UiUtils{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback, boolean forRedraft){
|
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback, boolean forRedraft){
|
||||||
showConfirmationAlert(activity, forRedraft ? R.string.sk_confirm_delete_and_redraft_title : R.string.confirm_delete_title, forRedraft ? R.string.sk_confirm_delete_and_redraft : R.string.confirm_delete, forRedraft ? R.string.sk_delete_and_redraft : R.string.delete, ()->{
|
showConfirmationAlert(activity,
|
||||||
new DeleteStatus(status.id)
|
forRedraft ? R.string.sk_confirm_delete_and_redraft_title : R.string.confirm_delete_title,
|
||||||
.setCallback(new Callback<>(){
|
forRedraft ? R.string.sk_confirm_delete_and_redraft : R.string.confirm_delete,
|
||||||
@Override
|
forRedraft ? R.string.sk_delete_and_redraft : R.string.delete,
|
||||||
public void onSuccess(Status result){
|
forRedraft ? R.drawable.ic_fluent_arrow_clockwise_28_regular : R.drawable.ic_fluent_delete_28_regular,
|
||||||
resultCallback.accept(result);
|
() -> new DeleteStatus(status.id)
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().deleteStatus(status.id);
|
.setCallback(new Callback<>(){
|
||||||
E.post(new StatusDeletedEvent(status.id, accountID));
|
@Override
|
||||||
}
|
public void onSuccess(Status result){
|
||||||
|
resultCallback.accept(result);
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().deleteStatus(status.id);
|
||||||
|
E.post(new StatusDeletedEvent(status.id, accountID));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
error.showToast(activity);
|
error.showToast(activity);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.wrapProgress(activity, R.string.deleting, false)
|
.wrapProgress(activity, R.string.deleting, false)
|
||||||
.exec(accountID);
|
.exec(accountID)
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void confirmDeleteScheduledPost(Activity activity, String accountID, ScheduledStatus status, Runnable resultCallback){
|
||||||
|
boolean isDraft = status.scheduledAt.isAfter(CreateStatus.DRAFTS_AFTER_INSTANT);
|
||||||
|
showConfirmationAlert(activity,
|
||||||
|
isDraft ? R.string.sk_confirm_delete_draft_title : R.string.sk_confirm_delete_scheduled_post_title,
|
||||||
|
isDraft ? R.string.sk_confirm_delete_draft : R.string.sk_confirm_delete_scheduled_post,
|
||||||
|
R.string.delete,
|
||||||
|
R.drawable.ic_fluent_delete_28_regular,
|
||||||
|
() -> new DeleteStatus.Scheduled(status.id)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object nothing){
|
||||||
|
resultCallback.run();
|
||||||
|
E.post(new ScheduledStatusDeletedEvent(status.id, accountID));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
error.showToast(activity);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrapProgress(activity, R.string.deleting, false)
|
||||||
|
.exec(accountID)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void confirmPinPost(Activity activity, String accountID, Status status, boolean pinned, Consumer<Status> resultCallback){
|
public static void confirmPinPost(Activity activity, String accountID, Status status, boolean pinned, Consumer<Status> resultCallback){
|
||||||
@@ -436,6 +498,7 @@ public class UiUtils{
|
|||||||
pinned ? R.string.sk_confirm_pin_post_title : R.string.sk_confirm_unpin_post_title,
|
pinned ? R.string.sk_confirm_pin_post_title : R.string.sk_confirm_unpin_post_title,
|
||||||
pinned ? R.string.sk_confirm_pin_post : R.string.sk_confirm_unpin_post,
|
pinned ? R.string.sk_confirm_pin_post : R.string.sk_confirm_unpin_post,
|
||||||
pinned ? R.string.sk_pin_post : R.string.sk_unpin_post,
|
pinned ? R.string.sk_pin_post : R.string.sk_unpin_post,
|
||||||
|
pinned ? R.drawable.ic_fluent_pin_off_28_regular : R.drawable.ic_fluent_pin_28_regular,
|
||||||
()->{
|
()->{
|
||||||
new SetStatusPinned(status.id, pinned)
|
new SetStatusPinned(status.id, pinned)
|
||||||
.setCallback(new Callback<>() {
|
.setCallback(new Callback<>() {
|
||||||
@@ -458,6 +521,26 @@ public class UiUtils{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void confirmDeleteNotification(Activity activity, String accountID, Notification notification, Runnable callback) {
|
||||||
|
showConfirmationAlert(activity,
|
||||||
|
notification == null ? R.string.sk_clear_all_notifications : R.string.sk_delete_notification,
|
||||||
|
notification == null ? R.string.sk_clear_all_notifications_confirm : R.string.sk_delete_notification_confirm,
|
||||||
|
notification == null ? R.string.sk_clear_all_notifications_confirm_action : R.string.sk_delete_notification_confirm_action,
|
||||||
|
notification == null ? R.drawable.ic_fluent_mail_inbox_dismiss_28_regular : R.drawable.ic_fluent_delete_28_regular,
|
||||||
|
() -> new DismissNotification(notification != null ? notification.id : null).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object o) {
|
||||||
|
callback.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(activity);
|
||||||
|
}
|
||||||
|
}).exec(accountID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setRelationshipToActionButton(Relationship relationship, Button button){
|
public static void setRelationshipToActionButton(Relationship relationship, Button button){
|
||||||
setRelationshipToActionButton(relationship, button, false);
|
setRelationshipToActionButton(relationship, button, false);
|
||||||
}
|
}
|
||||||
@@ -625,6 +708,42 @@ public class UiUtils{
|
|||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void insetPopupMenuIcon(Context context, MenuItem item) {
|
||||||
|
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||||
|
insetPopupMenuIcon(item, iconTint);
|
||||||
|
}
|
||||||
|
public static void insetPopupMenuIcon(MenuItem item, ColorStateList iconTint) {
|
||||||
|
Drawable icon=item.getIcon().mutate();
|
||||||
|
if(Build.VERSION.SDK_INT>=26) item.setIconTintList(iconTint);
|
||||||
|
else icon.setTintList(iconTint);
|
||||||
|
icon=new InsetDrawable(icon, V.dp(8), 0, V.dp(8), 0);
|
||||||
|
item.setIcon(icon);
|
||||||
|
SpannableStringBuilder ssb=new SpannableStringBuilder(item.getTitle());
|
||||||
|
item.setTitle(ssb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void enableOptionsMenuIcons(Context context, Menu menu, @IdRes int... asAction) {
|
||||||
|
if(menu.getClass().getSimpleName().equals("MenuBuilder")){
|
||||||
|
try {
|
||||||
|
Method m = menu.getClass().getDeclaredMethod(
|
||||||
|
"setOptionalIconsVisible", Boolean.TYPE);
|
||||||
|
m.setAccessible(true);
|
||||||
|
m.invoke(menu, true);
|
||||||
|
enableMenuIcons(context, menu, asAction);
|
||||||
|
}
|
||||||
|
catch(Exception ignored){}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void enableMenuIcons(Context context, Menu m, @IdRes int... exclude) {
|
||||||
|
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
||||||
|
for(int i=0;i<m.size();i++){
|
||||||
|
MenuItem item=m.getItem(i);
|
||||||
|
if (item.getIcon() == null || Arrays.stream(exclude).anyMatch(id -> id == item.getItemId())) continue;
|
||||||
|
insetPopupMenuIcon(item, iconTint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void enablePopupMenuIcons(Context context, PopupMenu menu){
|
public static void enablePopupMenuIcons(Context context, PopupMenu menu){
|
||||||
Menu m=menu.getMenu();
|
Menu m=menu.getMenu();
|
||||||
if(Build.VERSION.SDK_INT>=29){
|
if(Build.VERSION.SDK_INT>=29){
|
||||||
@@ -636,103 +755,158 @@ public class UiUtils{
|
|||||||
setOptionalIconsVisible.invoke(m, true);
|
setOptionalIconsVisible.invoke(m, true);
|
||||||
}catch(Exception ignore){}
|
}catch(Exception ignore){}
|
||||||
}
|
}
|
||||||
ColorStateList iconTint=ColorStateList.valueOf(UiUtils.getThemeColor(context, android.R.attr.textColorSecondary));
|
enableMenuIcons(context, m);
|
||||||
for(int i=0;i<m.size();i++){
|
|
||||||
MenuItem item=m.getItem(i);
|
|
||||||
Drawable icon=item.getIcon().mutate();
|
|
||||||
if(Build.VERSION.SDK_INT>=26){
|
|
||||||
item.setIconTintList(iconTint);
|
|
||||||
}else{
|
|
||||||
icon.setTintList(iconTint);
|
|
||||||
}
|
|
||||||
icon=new InsetDrawable(icon, V.dp(8), 0, 0, 0);
|
|
||||||
item.setIcon(icon);
|
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(item.getTitle());
|
|
||||||
ssb.insert(0, " ");
|
|
||||||
ssb.setSpan(new SpacerSpan(V.dp(24), 1), 0, 1, 0);
|
|
||||||
ssb.append(" ", new SpacerSpan(V.dp(8), 1), 0);
|
|
||||||
item.setTitle(ssb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setUserPreferredTheme(Context context){
|
public static void setUserPreferredTheme(Context context){
|
||||||
// boolean isDarkTheme = isDarkTheme();
|
context.setTheme(switch (theme) {
|
||||||
switch(GlobalUserPreferences.color){
|
case LIGHT -> R.style.Theme_Mastodon_Light;
|
||||||
case PINK:
|
case DARK -> trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack : R.style.Theme_Mastodon_Dark;
|
||||||
context.setTheme(switch(GlobalUserPreferences.theme){
|
default -> trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack : R.style.Theme_Mastodon_AutoLightDark;
|
||||||
case AUTO ->
|
});
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack : R.style.Theme_Mastodon_AutoLightDark;
|
|
||||||
case LIGHT ->
|
|
||||||
R.style.Theme_Mastodon_Light;
|
|
||||||
case DARK ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack : R.style.Theme_Mastodon_Dark;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case PURPLE:
|
|
||||||
context.setTheme(switch(GlobalUserPreferences.theme){
|
|
||||||
case AUTO ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack_Original : R.style.Theme_Mastodon_AutoLightDark_Original;
|
|
||||||
case LIGHT ->
|
|
||||||
R.style.Theme_Mastodon_Light_Original;
|
|
||||||
case DARK ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Original : R.style.Theme_Mastodon_Dark_Original;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case GREEN:
|
|
||||||
context.setTheme(switch(GlobalUserPreferences.theme){
|
|
||||||
case AUTO ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack_Green : R.style.Theme_Mastodon_AutoLightDark_Green;
|
|
||||||
case LIGHT ->
|
|
||||||
R.style.Theme_Mastodon_Light_Green;
|
|
||||||
case DARK ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Green : R.style.Theme_Mastodon_Dark_Green;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case BLUE:
|
|
||||||
context.setTheme(switch(GlobalUserPreferences.theme){
|
|
||||||
case AUTO ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack_Blue : R.style.Theme_Mastodon_AutoLightDark_Blue;
|
|
||||||
case LIGHT ->
|
|
||||||
R.style.Theme_Mastodon_Light_Blue;
|
|
||||||
case DARK ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Blue : R.style.Theme_Mastodon_Dark_Blue;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case BROWN:
|
|
||||||
context.setTheme(switch(GlobalUserPreferences.theme){
|
|
||||||
case AUTO ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack_Brown : R.style.Theme_Mastodon_AutoLightDark_Brown;
|
|
||||||
case LIGHT ->
|
|
||||||
R.style.Theme_Mastodon_Light_Brown;
|
|
||||||
case DARK ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Brown : R.style.Theme_Mastodon_Dark_Brown;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case YELLOW:
|
|
||||||
context.setTheme(switch(GlobalUserPreferences.theme){
|
|
||||||
case AUTO ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_AutoLightDark_TrueBlack_Yellow : R.style.Theme_Mastodon_AutoLightDark_Yellow;
|
|
||||||
case LIGHT ->
|
|
||||||
R.style.Theme_Mastodon_Light_Yellow;
|
|
||||||
case DARK ->
|
|
||||||
GlobalUserPreferences.trueBlackTheme ? R.style.Theme_Mastodon_Dark_TrueBlack_Yellow : R.style.Theme_Mastodon_Dark_Yellow;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
|
||||||
|
if (palette != null) palette.apply(context);
|
||||||
}
|
}
|
||||||
public static boolean isDarkTheme(){
|
public static boolean isDarkTheme(){
|
||||||
if(GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO)
|
if(theme==GlobalUserPreferences.ThemePreference.AUTO)
|
||||||
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)==Configuration.UI_MODE_NIGHT_YES;
|
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)==Configuration.UI_MODE_NIGHT_YES;
|
||||||
return GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK;
|
return theme==GlobalUserPreferences.ThemePreference.DARK;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void openURL(Context context, @Nullable String accountID, String url){
|
// https://mastodon.foo.bar/@User
|
||||||
|
// https://mastodon.foo.bar/@User/43456787654678
|
||||||
|
// https://pleroma.foo.bar/users/User
|
||||||
|
// https://pleroma.foo.bar/users/9qTHT2ANWUdXzENqC0
|
||||||
|
// https://pleroma.foo.bar/notice/9sBHWIlwwGZi5QGlHc
|
||||||
|
// https://pleroma.foo.bar/objects/d4643c42-3ae0-4b73-b8b0-c725f5819207
|
||||||
|
// https://friendica.foo.bar/profile/user
|
||||||
|
// https://friendica.foo.bar/display/d4643c42-3ae0-4b73-b8b0-c725f5819207
|
||||||
|
// https://misskey.foo.bar/notes/83w6r388br (always lowercase)
|
||||||
|
// https://pixelfed.social/p/connyduck/391263492998670833
|
||||||
|
// https://pixelfed.social/connyduck
|
||||||
|
// https://gts.foo.bar/@goblin/statuses/01GH9XANCJ0TA8Y95VE9H3Y0Q2
|
||||||
|
// https://gts.foo.bar/@goblin
|
||||||
|
// https://foo.microblog.pub/o/5b64045effd24f48a27d7059f6cb38f5
|
||||||
|
//
|
||||||
|
// COPIED FROM https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt
|
||||||
|
public static boolean looksLikeMastodonUrl(String urlString) {
|
||||||
|
URI uri;
|
||||||
|
try {
|
||||||
|
uri = new URI(urlString);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null) return false;
|
||||||
|
|
||||||
|
String it = uri.getPath();
|
||||||
|
return it.matches("^/@[^/]+$") ||
|
||||||
|
it.matches("^/@[^/]+/\\d+$") ||
|
||||||
|
it.matches("^/users/\\w+$") ||
|
||||||
|
it.matches("^/notice/[a-zA-Z0-9]+$") ||
|
||||||
|
it.matches("^/objects/[-a-f0-9]+$") ||
|
||||||
|
it.matches("^/notes/[a-z0-9]+$") ||
|
||||||
|
it.matches("^/display/[-a-f0-9]+$") ||
|
||||||
|
it.matches("^/profile/\\w+$") ||
|
||||||
|
it.matches("^/p/\\w+/\\d+$") ||
|
||||||
|
it.matches("^/\\w+$") ||
|
||||||
|
it.matches("^/@[^/]+/statuses/[a-zA-Z0-9]+$") ||
|
||||||
|
it.matches("^/users/[^/]+/statuses/[a-zA-Z0-9]+$") ||
|
||||||
|
it.matches("^/o/[a-f0-9]+$");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getInstanceName(String accountID) {
|
||||||
|
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
|
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||||
|
return instance != null && !instance.title.isBlank() ? instance.title : session.domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void pickAccount(Context context, String exceptFor, @StringRes int titleRes, @DrawableRes int iconRes, Consumer<AccountSession> sessionConsumer, Consumer<AlertDialog.Builder> transformDialog) {
|
||||||
|
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts()
|
||||||
|
.stream().filter(s->!s.getID().equals(exceptFor)).collect(Collectors.toList());
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new M3AlertDialogBuilder(context)
|
||||||
|
.setItems(
|
||||||
|
sessions.stream().map(AccountSession::getFullUsername).toArray(String[]::new),
|
||||||
|
(dialog, which) -> sessionConsumer.accept(sessions.get(which))
|
||||||
|
)
|
||||||
|
.setTitle(titleRes == 0 ? R.string.choose_account : titleRes)
|
||||||
|
.setIcon(iconRes);
|
||||||
|
if (transformDialog != null) transformDialog.accept(builder);
|
||||||
|
builder.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface InteractionPerformer {
|
||||||
|
void interact(StatusInteractionController ic, Status status, Consumer<Status> resultConsumer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void pickInteractAs(Context context, String accountID, Status sourceStatus, Predicate<Status> checkInteracted, InteractionPerformer interactionPerformer, @StringRes int interactAsRes, @StringRes int interactedAsAccountRes, @StringRes int alreadyInteractedRes, @DrawableRes int iconRes) {
|
||||||
|
pickAccount(context, accountID, interactAsRes, iconRes, session -> {
|
||||||
|
lookupStatus(context, sourceStatus, session.getID(), accountID, status -> {
|
||||||
|
if (checkInteracted.test(status)) {
|
||||||
|
Toast.makeText(context, alreadyInteractedRes, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusInteractionController ic = AccountSessionManager.getInstance().getAccount(session.getID()).getRemoteStatusInteractionController();
|
||||||
|
interactionPerformer.interact(ic, status, s -> {
|
||||||
|
if (checkInteracted.test(s)) {
|
||||||
|
Toast.makeText(context, context.getString(interactedAsAccountRes, session.getFullUsername()), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void lookupStatus(Context context, Status queryStatus, String targetAccountID, @Nullable String sourceAccountID, Consumer<Status> statusConsumer) {
|
||||||
|
if (sourceAccountID != null && targetAccountID.startsWith(sourceAccountID.substring(0, sourceAccountID.indexOf('_')))) {
|
||||||
|
statusConsumer.accept(queryStatus);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new GetSearchResults(queryStatus.url, GetSearchResults.Type.STATUSES, true).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SearchResults results) {
|
||||||
|
if (!results.statuses.isEmpty()) statusConsumer.accept(results.statuses.get(0));
|
||||||
|
else Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(context);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrapProgress((Activity)context, R.string.loading, true,
|
||||||
|
d -> transformDialogForLookup(context, targetAccountID, null, d))
|
||||||
|
.exec(targetAccountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openURL(Context context, String accountID, String url) {
|
||||||
|
openURL(context, accountID, url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void transformDialogForLookup(Context context, String accountID, @Nullable String url, ProgressDialog dialog) {
|
||||||
|
if (accountID != null) {
|
||||||
|
dialog.setTitle(context.getString(R.string.sk_loading_resource_on_instance_title, getInstanceName(accountID)));
|
||||||
|
} else {
|
||||||
|
dialog.setTitle(R.string.sk_loading_fediverse_resource_title);
|
||||||
|
}
|
||||||
|
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel), (d, which) -> d.cancel());
|
||||||
|
if (url != null) {
|
||||||
|
dialog.setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.open_in_browser), (d, which) -> {
|
||||||
|
d.cancel();
|
||||||
|
launchWebBrowser(context, url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openURL(Context context, String accountID, String url, boolean launchBrowser){
|
||||||
Uri uri=Uri.parse(url);
|
Uri uri=Uri.parse(url);
|
||||||
if(accountID!=null && "https".equals(uri.getScheme()) && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){
|
List<String> path=uri.getPathSegments();
|
||||||
List<String> path=uri.getPathSegments();
|
if(accountID!=null && "https".equals(uri.getScheme())){
|
||||||
// Match URLs like https://mastodon.social/@Gargron/108132679274083591
|
if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){
|
||||||
if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$")){
|
|
||||||
new GetStatusByID(path.get(1))
|
new GetStatusByID(path.get(1))
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
@@ -746,14 +920,88 @@ public class UiUtils{
|
|||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
error.showToast(context);
|
error.showToast(context);
|
||||||
launchWebBrowser(context, url);
|
if (launchBrowser) launchWebBrowser(context, url);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.wrapProgress((Activity)context, R.string.loading, true)
|
.wrapProgress((Activity)context, R.string.loading, true,
|
||||||
|
d -> transformDialogForLookup(context, accountID, url, d))
|
||||||
|
.exec(accountID);
|
||||||
|
return;
|
||||||
|
} else if (looksLikeMastodonUrl(url)) {
|
||||||
|
new GetSearchResults(url, null, true)
|
||||||
|
.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SearchResults results) {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
if (!results.statuses.isEmpty()) {
|
||||||
|
args.putParcelable("status", Parcels.wrap(results.statuses.get(0)));
|
||||||
|
Nav.go((Activity) context, ThreadFragment.class, args);
|
||||||
|
} else if (!results.accounts.isEmpty()) {
|
||||||
|
args.putParcelable("profileAccount", Parcels.wrap(results.accounts.get(0)));
|
||||||
|
Nav.go((Activity) context, ProfileFragment.class, args);
|
||||||
|
} else {
|
||||||
|
if (launchBrowser) launchWebBrowser(context, url);
|
||||||
|
else Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(context);
|
||||||
|
if (launchBrowser) launchWebBrowser(context, url);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.wrapProgress((Activity)context, R.string.loading, true,
|
||||||
|
d -> transformDialogForLookup(context, accountID, url, d))
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
launchWebBrowser(context, url);
|
launchWebBrowser(context, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void copyText(View v, String text) {
|
||||||
|
Context context = v.getContext();
|
||||||
|
context.getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, text));
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU || UiUtils.isMIUI()){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||||
|
Toast.makeText(context, R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
v.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSystemProperty(String key){
|
||||||
|
try{
|
||||||
|
Class<?> props=Class.forName("android.os.SystemProperties");
|
||||||
|
Method get=props.getMethod("get", String.class);
|
||||||
|
return (String)get.invoke(null, key);
|
||||||
|
}catch(Exception ignore){}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMIUI(){
|
||||||
|
return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.code"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean pickAccountForCompose(Activity activity, String accountID, String prefilledText){
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
if (prefilledText != null) args.putString("prefilledText", prefilledText);
|
||||||
|
return pickAccountForCompose(activity, accountID, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean pickAccountForCompose(Activity activity, String accountID){
|
||||||
|
return pickAccountForCompose(activity, accountID, (String) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean pickAccountForCompose(Activity activity, String accountID, Bundle args){
|
||||||
|
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1) {
|
||||||
|
UiUtils.pickAccount(activity, accountID, 0, 0, session -> {
|
||||||
|
args.putString("account", session.getID());
|
||||||
|
Nav.go(activity, ComposeFragment.class, args);
|
||||||
|
}, null);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.joinmastodon.android.ui.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
public class FilterChipView extends Button{
|
||||||
|
|
||||||
|
private boolean currentlySelected;
|
||||||
|
|
||||||
|
public FilterChipView(Context context){
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterChipView(Context context, AttributeSet attrs){
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FilterChipView(Context context, AttributeSet attrs, int defStyle){
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
setCompoundDrawablePadding(V.dp(8));
|
||||||
|
setBackgroundResource(R.drawable.bg_filter_chip);
|
||||||
|
setTextAppearance(R.style.m3_label_large);
|
||||||
|
setTextColor(getResources().getColorStateList(R.color.filter_chip_text, context.getTheme()));
|
||||||
|
setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurface)));
|
||||||
|
updatePadding();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawableStateChanged(){
|
||||||
|
super.drawableStateChanged();
|
||||||
|
if(currentlySelected==isSelected())
|
||||||
|
return;
|
||||||
|
currentlySelected=isSelected();
|
||||||
|
Drawable start=currentlySelected ? getResources().getDrawable(R.drawable.ic_baseline_check_18, getContext().getTheme()) : null;
|
||||||
|
Drawable end=getCompoundDrawablesRelative()[2];
|
||||||
|
setCompoundDrawablesRelativeWithIntrinsicBounds(start, null, end, null);
|
||||||
|
updatePadding();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePadding(){
|
||||||
|
int vertical=V.dp(6);
|
||||||
|
Drawable[] drawables=getCompoundDrawablesRelative();
|
||||||
|
setPaddingRelative(V.dp(drawables[0]==null ? 16 : 8), vertical, V.dp(drawables[2]==null ? 16 : 8), vertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawableEnd(@DrawableRes int drawable){
|
||||||
|
setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, drawable, 0);
|
||||||
|
updatePadding();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,19 +5,34 @@ import android.animation.AnimatorListenerAdapter;
|
|||||||
import android.animation.AnimatorSet;
|
import android.animation.AnimatorSet;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Region;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.InsetDrawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
@@ -28,6 +43,10 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
|||||||
private int offsetY;
|
private int offsetY;
|
||||||
private boolean hintVisible;
|
private boolean hintVisible;
|
||||||
private Animator currentAnim;
|
private Animator currentAnim;
|
||||||
|
private float animProgress;
|
||||||
|
private RectF tmpRect=new RectF();
|
||||||
|
private ColorStateList labelColors, origHintColors;
|
||||||
|
private boolean errorState;
|
||||||
|
|
||||||
public FloatingHintEditTextLayout(Context context){
|
public FloatingHintEditTextLayout(Context context){
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -44,7 +63,9 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
|||||||
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FloatingHintEditTextLayout);
|
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FloatingHintEditTextLayout);
|
||||||
labelTextSize=ta.getDimensionPixelSize(R.styleable.FloatingHintEditTextLayout_android_labelTextSize, V.dp(12));
|
labelTextSize=ta.getDimensionPixelSize(R.styleable.FloatingHintEditTextLayout_android_labelTextSize, V.dp(12));
|
||||||
offsetY=ta.getDimensionPixelOffset(R.styleable.FloatingHintEditTextLayout_editTextOffsetY, 0);
|
offsetY=ta.getDimensionPixelOffset(R.styleable.FloatingHintEditTextLayout_editTextOffsetY, 0);
|
||||||
|
labelColors=ta.getColorStateList(R.styleable.FloatingHintEditTextLayout_labelTextColor);
|
||||||
ta.recycle();
|
ta.recycle();
|
||||||
|
setAddStatesFromChildren(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,13 +79,15 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
|||||||
|
|
||||||
label=new TextView(getContext());
|
label=new TextView(getContext());
|
||||||
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
|
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
|
||||||
label.setTextColor(edit.getHintTextColors());
|
// label.setTextColor(labelColors==null ? edit.getHintTextColors() : labelColors);
|
||||||
|
origHintColors=edit.getHintTextColors();
|
||||||
label.setText(edit.getHint());
|
label.setText(edit.getHint());
|
||||||
label.setSingleLine();
|
label.setSingleLine();
|
||||||
label.setPivotX(0f);
|
label.setPivotX(0f);
|
||||||
label.setPivotY(0f);
|
label.setPivotY(0f);
|
||||||
|
label.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||||
LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP);
|
LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP);
|
||||||
lp.setMarginStart(edit.getPaddingStart());
|
lp.setMarginStart(edit.getPaddingStart()+((LayoutParams)edit.getLayoutParams()).getMarginStart());
|
||||||
addView(label, lp);
|
addView(label, lp);
|
||||||
|
|
||||||
hintVisible=edit.getText().length()==0;
|
hintVisible=edit.getText().length()==0;
|
||||||
@@ -75,6 +98,11 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onTextChanged(Editable text){
|
private void onTextChanged(Editable text){
|
||||||
|
if(errorState){
|
||||||
|
errorState=false;
|
||||||
|
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field));
|
||||||
|
refreshDrawableState();
|
||||||
|
}
|
||||||
boolean newHintVisible=text.length()==0;
|
boolean newHintVisible=text.length()==0;
|
||||||
if(newHintVisible==hintVisible)
|
if(newHintVisible==hintVisible)
|
||||||
return;
|
return;
|
||||||
@@ -83,42 +111,210 @@ public class FloatingHintEditTextLayout extends FrameLayout{
|
|||||||
hintVisible=newHintVisible;
|
hintVisible=newHintVisible;
|
||||||
|
|
||||||
label.setAlpha(1);
|
label.setAlpha(1);
|
||||||
float scale=edit.getLineHeight()/(float)label.getLineHeight();
|
edit.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
|
|
||||||
|
|
||||||
AnimatorSet anim=new AnimatorSet();
|
|
||||||
if(hintVisible){
|
|
||||||
anim.playTogether(
|
|
||||||
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0),
|
|
||||||
ObjectAnimator.ofFloat(label, SCALE_X, scale),
|
|
||||||
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
|
|
||||||
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY)
|
|
||||||
);
|
|
||||||
edit.setHintTextColor(0);
|
|
||||||
}else{
|
|
||||||
label.setScaleX(scale);
|
|
||||||
label.setScaleY(scale);
|
|
||||||
label.setTranslationY(transY);
|
|
||||||
anim.playTogether(
|
|
||||||
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
|
|
||||||
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
|
|
||||||
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
|
|
||||||
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
anim.setDuration(150);
|
|
||||||
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
anim.start();
|
|
||||||
anim.addListener(new AnimatorListenerAdapter(){
|
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationEnd(Animator animation){
|
public boolean onPreDraw(){
|
||||||
currentAnim=null;
|
edit.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
|
||||||
|
float scale=edit.getLineHeight()/(float)label.getLineHeight();
|
||||||
|
float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
|
||||||
|
|
||||||
|
AnimatorSet anim=new AnimatorSet();
|
||||||
if(hintVisible){
|
if(hintVisible){
|
||||||
label.setAlpha(0);
|
anim.playTogether(
|
||||||
edit.setHintTextColor(label.getTextColors());
|
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0),
|
||||||
|
ObjectAnimator.ofFloat(label, SCALE_X, scale),
|
||||||
|
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
|
||||||
|
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY),
|
||||||
|
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 0f)
|
||||||
|
);
|
||||||
|
edit.setHintTextColor(0);
|
||||||
|
}else{
|
||||||
|
label.setScaleX(scale);
|
||||||
|
label.setScaleY(scale);
|
||||||
|
label.setTranslationY(transY);
|
||||||
|
anim.playTogether(
|
||||||
|
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
|
||||||
|
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
|
||||||
|
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
|
||||||
|
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f),
|
||||||
|
ObjectAnimator.ofFloat(FloatingHintEditTextLayout.this, "animProgress", 1f)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
anim.setDuration(150);
|
||||||
|
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
anim.start();
|
||||||
|
anim.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
currentAnim=null;
|
||||||
|
if(hintVisible){
|
||||||
|
label.setAlpha(0);
|
||||||
|
edit.setHintTextColor(origHintColors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentAnim=anim;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
currentAnim=anim;
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public void setAnimProgress(float progress){
|
||||||
|
animProgress=progress;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public float getAnimProgress(){
|
||||||
|
return animProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawForeground(Canvas canvas){
|
||||||
|
if(getForeground()!=null && animProgress>0){
|
||||||
|
canvas.save();
|
||||||
|
float width=(label.getWidth()+V.dp(8))*animProgress;
|
||||||
|
float centerX=label.getLeft()+label.getWidth()/2f;
|
||||||
|
tmpRect.set(centerX-width/2f, label.getTop(), centerX+width/2f, label.getBottom());
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O)
|
||||||
|
canvas.clipOutRect(tmpRect);
|
||||||
|
else
|
||||||
|
canvas.clipRect(tmpRect, Region.Op.DIFFERENCE);
|
||||||
|
super.onDrawForeground(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
}else{
|
||||||
|
super.onDrawForeground(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setForeground(Drawable foreground){
|
||||||
|
super.setForeground(new PaddedForegroundDrawable(foreground));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Drawable getForeground(){
|
||||||
|
if(super.getForeground() instanceof PaddedForegroundDrawable pfd){
|
||||||
|
return pfd.wrapped;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawableStateChanged(){
|
||||||
|
super.drawableStateChanged();
|
||||||
|
if(label==null || errorState)
|
||||||
|
return;
|
||||||
|
ColorStateList color=labelColors==null ? origHintColors : labelColors;
|
||||||
|
label.setTextColor(color.getColorForState(getDrawableState(), 0xff00ff00));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorState(){
|
||||||
|
if(errorState)
|
||||||
|
return;
|
||||||
|
errorState=true;
|
||||||
|
setForeground(getResources().getDrawable(R.drawable.bg_m3_outlined_text_field_error, getContext().getTheme()));
|
||||||
|
label.setTextColor(UiUtils.getThemeColor(getContext(), R.attr.colorM3Error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PaddedForegroundDrawable extends Drawable{
|
||||||
|
private final Drawable wrapped;
|
||||||
|
|
||||||
|
private PaddedForegroundDrawable(Drawable wrapped){
|
||||||
|
this.wrapped=wrapped;
|
||||||
|
wrapped.setCallback(new Callback(){
|
||||||
|
@Override
|
||||||
|
public void invalidateDrawable(@NonNull Drawable who){
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when){
|
||||||
|
scheduleSelf(what, when);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what){
|
||||||
|
unscheduleSelf(what);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas){
|
||||||
|
wrapped.draw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha){
|
||||||
|
wrapped.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(@Nullable ColorFilter colorFilter){
|
||||||
|
wrapped.setColorFilter(colorFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity(){
|
||||||
|
return wrapped.getOpacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setState(@NonNull int[] stateSet){
|
||||||
|
return wrapped.setState(stateSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLayoutDirection(){
|
||||||
|
return wrapped.getLayoutDirection();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAlpha(){
|
||||||
|
return wrapped.getAlpha();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ColorFilter getColorFilter(){
|
||||||
|
return wrapped.getColorFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStateful(){
|
||||||
|
return wrapped.isStateful();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public int[] getState(){
|
||||||
|
return wrapped.getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Drawable getCurrent(){
|
||||||
|
return wrapped.getCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyTheme(@NonNull Resources.Theme t){
|
||||||
|
wrapped.applyTheme(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canApplyTheme(){
|
||||||
|
return wrapped.canApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBoundsChange(@NonNull Rect bounds){
|
||||||
|
super.onBoundsChange(bounds);
|
||||||
|
LayoutParams lp=(LayoutParams) edit.getLayoutParams();
|
||||||
|
wrapped.setBounds(bounds.left+lp.leftMargin-V.dp(12), bounds.top, bounds.right-lp.rightMargin+V.dp(12), bounds.bottom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public abstract class GithubSelfUpdater{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class UpdateInfo{
|
public static class UpdateInfo{
|
||||||
|
public String changelog;
|
||||||
public String version;
|
public String version;
|
||||||
public long size;
|
public long size;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user