Compare commits
796 Commits
1.2.0+fork
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd749e9c9c | ||
|
|
1d25d80186 | ||
|
|
cdeeb24eac | ||
|
|
5a5181fde8 | ||
|
|
69420b2399 | ||
|
|
aaa80f80f1 | ||
|
|
9b11fbbbfe | ||
|
|
57f513048a | ||
|
|
c5e35e550c | ||
|
|
2751b804fe | ||
|
|
d310673f92 | ||
|
|
afca57501f | ||
|
|
34443726e2 | ||
|
|
fc9ffc9aef | ||
|
|
44b1bc70af | ||
|
|
45796000c4 | ||
|
|
22febe019b | ||
|
|
e0f4f87086 | ||
|
|
7917e34568 | ||
|
|
d7287441ca | ||
|
|
5b391b44d3 | ||
|
|
fe20fe4254 | ||
|
|
63e536c66c | ||
|
|
b5a08b1b98 | ||
|
|
226e2a7cdc | ||
|
|
4d7c4aed4c | ||
|
|
c9bcd000c3 | ||
|
|
de42145f30 | ||
|
|
7bcdd6070a | ||
|
|
8a215e90d0 | ||
|
|
b736fa18bb | ||
|
|
43c19e4942 | ||
|
|
ffc18029bb | ||
|
|
b88b3d15f8 | ||
|
|
c817886a2d | ||
|
|
aae239494e | ||
|
|
b0b2daa5d5 | ||
|
|
eea2e38f1b | ||
|
|
f894ecd25b | ||
|
|
e0b6ed7103 | ||
|
|
a78e75747a | ||
|
|
3b25e367bb | ||
|
|
08b29dff3d | ||
|
|
2f2e053d26 | ||
|
|
191d582c30 | ||
|
|
8d3380ff6e | ||
|
|
ba85d18574 | ||
|
|
0f53b17515 | ||
|
|
cb9c869712 | ||
|
|
aa3d9e7b8f | ||
|
|
b3a9b5824d | ||
|
|
218503a739 | ||
|
|
f2c13ed379 | ||
|
|
fd21b9e568 | ||
|
|
13480ce575 | ||
|
|
581ee53f12 | ||
|
|
02545f3dd2 | ||
|
|
37fe0a1145 | ||
|
|
e753eea23f | ||
|
|
9d8cf2dc02 | ||
|
|
de589d1fdf | ||
|
|
573872291c | ||
|
|
e4232fb3de | ||
|
|
04f805e846 | ||
|
|
ae2769a1b9 | ||
|
|
d8d6cd8258 | ||
|
|
8bc395cb16 | ||
|
|
6d06c4d740 | ||
|
|
fc8784393b | ||
|
|
ce92ffe6dd | ||
|
|
1953cab173 | ||
|
|
2525714bc9 | ||
|
|
debdda5fed | ||
|
|
c4c17e3aea | ||
|
|
04699c3684 | ||
|
|
d78fce92c6 | ||
|
|
73a9073ee4 | ||
|
|
ef541058c4 | ||
|
|
bf4c905674 | ||
|
|
d256c759ee | ||
|
|
6b8418845e | ||
|
|
47a3834716 | ||
|
|
b540729f6b | ||
|
|
98e24a5285 | ||
|
|
ec1e850549 | ||
|
|
b6186a349f | ||
|
|
730aa28979 | ||
|
|
d15d8a0169 | ||
|
|
6077bf4b55 | ||
|
|
b568dac138 | ||
|
|
2c7df11e84 | ||
|
|
29777c2513 | ||
|
|
f70a9cbe3f | ||
|
|
7bc49aa21c | ||
|
|
4d0790dcec | ||
|
|
bb7b18e148 | ||
|
|
b7b646f03a | ||
|
|
7c59b700da | ||
|
|
cf64e75e2f | ||
|
|
e121fccfc1 | ||
|
|
5a1a2ac9f5 | ||
|
|
00d6f29285 | ||
|
|
a649ea0a00 | ||
|
|
180cf3c902 | ||
|
|
ba997903b6 | ||
|
|
32da4f8e09 | ||
|
|
1098c855c4 | ||
|
|
ee8ca09e0e | ||
|
|
ed75a62228 | ||
|
|
c3c76126a3 | ||
|
|
4da6016e06 | ||
|
|
e4f4ca5392 | ||
|
|
9d690e7670 | ||
|
|
e1acfef1bf | ||
|
|
29267dacb4 | ||
|
|
7c8698521d | ||
|
|
fb02689c30 | ||
|
|
8a4231686b | ||
|
|
c1252638c6 | ||
|
|
5515b12fd9 | ||
|
|
9fe7ebf3bf | ||
|
|
09733f3343 | ||
|
|
249dd5c0e5 | ||
|
|
181fa1383b | ||
|
|
ee1a4edbe1 | ||
|
|
f3081f3f6c | ||
|
|
6a12708905 | ||
|
|
04a8e33385 | ||
|
|
d79c183685 | ||
|
|
0c6efac46a | ||
|
|
6a5a506abf | ||
|
|
746717a875 | ||
|
|
e55dd0eaca | ||
|
|
6af22b1f0e | ||
|
|
7d840502d4 | ||
|
|
665a0176b9 | ||
|
|
ef4422828b | ||
|
|
002fc0897c | ||
|
|
0a21c90efa | ||
|
|
3df62d2516 | ||
|
|
ee18236ec8 | ||
|
|
15fcb0e25d | ||
|
|
2dae662333 | ||
|
|
3ad46926f1 | ||
|
|
2385d102ae | ||
|
|
74f2bb4707 | ||
|
|
76a85af0d7 | ||
|
|
5d7363585a | ||
|
|
46398f7bad | ||
|
|
7c6087c1f5 | ||
|
|
ed7bcdb761 | ||
|
|
50f9ebe4f7 | ||
|
|
2daf94f91e | ||
|
|
d3b7a60f18 | ||
|
|
1d5bf452ab | ||
|
|
51063b65ae | ||
|
|
198dfc067e | ||
|
|
ab0eed88c4 | ||
|
|
7a036a52ae | ||
|
|
aa5404842b | ||
|
|
a33009a297 | ||
|
|
8b27e6de33 | ||
|
|
0a17ceb984 | ||
|
|
4ef18f1f4a | ||
|
|
4c698cf217 | ||
|
|
05217b7712 | ||
|
|
64e681c227 | ||
|
|
0de227ab9c | ||
|
|
19cb8703a6 | ||
|
|
e18567dd82 | ||
|
|
bfb3bcdbfb | ||
|
|
565cd14d88 | ||
|
|
ebc37eac75 | ||
|
|
c3702db577 | ||
|
|
e15c4fa342 | ||
|
|
8330b9f1c5 | ||
|
|
f759150982 | ||
|
|
6af177b596 | ||
|
|
657bb94975 | ||
|
|
3c946212b1 | ||
|
|
b4e80f7fca | ||
|
|
c9efc2cb2b | ||
|
|
0d62e33dc7 | ||
|
|
ac88b9e19c | ||
|
|
871dfda79e | ||
|
|
e0c2c208ae | ||
|
|
22ac112bdb | ||
|
|
afd0cca176 | ||
|
|
c083c8bce5 | ||
|
|
63bde032b3 | ||
|
|
49492c0788 | ||
|
|
b439c64add | ||
|
|
1868bfe8e3 | ||
|
|
f240a3d996 | ||
|
|
788e5bd12e | ||
|
|
a55fed4502 | ||
|
|
a8fdaf1a47 | ||
|
|
4a758bd488 | ||
|
|
2c9731ec2a | ||
|
|
eef33266fc | ||
|
|
58d2c3e5a6 | ||
|
|
a4515ea360 | ||
|
|
2cea6a9df5 | ||
|
|
d6d6155f2c | ||
|
|
88f498409f | ||
|
|
f39690b7e5 | ||
|
|
9e6a355db0 | ||
|
|
0d10e09fd6 | ||
|
|
f85bb995ba | ||
|
|
268e5639f6 | ||
|
|
0a1c42f90b | ||
|
|
a59587eb62 | ||
|
|
7615723d4f | ||
|
|
804129271b | ||
|
|
ef1b1e5d1f | ||
|
|
51809df8ca | ||
|
|
94fb676b0c | ||
|
|
a47106594b | ||
|
|
fe21a95766 | ||
|
|
6f3cb14dc9 | ||
|
|
1698a32d75 | ||
|
|
1e54b21842 | ||
|
|
99ca3b7acb | ||
|
|
828617f7d7 | ||
|
|
d93d66f702 | ||
|
|
b2f9f7ae54 | ||
|
|
de7b908c78 | ||
|
|
75d3c2fdce | ||
|
|
ea1b6c5835 | ||
|
|
c4d8baf8a2 | ||
|
|
118a073961 | ||
|
|
4b39011497 | ||
|
|
470a693a1f | ||
|
|
cb7887da41 | ||
|
|
1f4970c0d2 | ||
|
|
e9833baaa4 | ||
|
|
325d962bbb | ||
|
|
0aa432bf6e | ||
|
|
a80313ee6b | ||
|
|
1ed891ced8 | ||
|
|
2ebf52d156 | ||
|
|
9d45fc1c84 | ||
|
|
a436573f25 | ||
|
|
76980244ec | ||
|
|
0e6a3ccd72 | ||
|
|
b8869e6460 | ||
|
|
e1a821bc43 | ||
|
|
924ea2d03a | ||
|
|
55270fe654 | ||
|
|
a125fab57b | ||
|
|
395ee0aa99 | ||
|
|
0f50fa6ba1 | ||
|
|
adb7df3c71 | ||
|
|
5d7bcb629b | ||
|
|
a00f1417d2 | ||
|
|
8efd7e8ebf | ||
|
|
b016d277e0 | ||
|
|
fdb39617d1 | ||
|
|
89f83fbf62 | ||
|
|
ecee9e01a6 | ||
|
|
20dc9bb8b9 | ||
|
|
2c47d0e9ed | ||
|
|
8e13d52e51 | ||
|
|
cc40198c9e | ||
|
|
290897ea41 | ||
|
|
b9e1c84304 | ||
|
|
3c44c80e2e | ||
|
|
dffa4e4594 | ||
|
|
fa2d9fec58 | ||
|
|
09c1a2cfa0 | ||
|
|
d1f90eb231 | ||
|
|
1f7d97134b | ||
|
|
79be91784d | ||
|
|
de2654def3 | ||
|
|
56343dacff | ||
|
|
0e677f8ce7 | ||
|
|
aa911896d6 | ||
|
|
c62a8635b9 | ||
|
|
4e900247c5 | ||
|
|
b3bd62bc6c | ||
|
|
8e5fd48ecd | ||
|
|
b2bca9dd2c | ||
|
|
b8c0dc3181 | ||
|
|
cccdc5292e | ||
|
|
76d77a0e7a | ||
|
|
e737f4bf9a | ||
|
|
391db2f1c9 | ||
|
|
359d61183c | ||
|
|
46fd05d88e | ||
|
|
cde22a0945 | ||
|
|
111b7e25c5 | ||
|
|
4f8d8f0c8d | ||
|
|
915b0603d0 | ||
|
|
6ec43a6f86 | ||
|
|
df93a1a845 | ||
|
|
41a70a353c | ||
|
|
8d69bcfd4b | ||
|
|
0ef30f82a7 | ||
|
|
be60e78ea6 | ||
|
|
5434325fa8 | ||
|
|
0a04c9357c | ||
|
|
075aab8074 | ||
|
|
21c4cef397 | ||
|
|
4b2fcd760a | ||
|
|
6ebe4c86af | ||
|
|
39ea51c317 | ||
|
|
cd668e1bba | ||
|
|
916bd68a16 | ||
|
|
4aa7ff6f9c | ||
|
|
38fb05a38b | ||
|
|
aa919867c8 | ||
|
|
0925c8c582 | ||
|
|
9824b5fb56 | ||
|
|
78fcf31e34 | ||
|
|
eadb62d3a8 | ||
|
|
f6279fcc0c | ||
|
|
a683fdce62 | ||
|
|
b958299446 | ||
|
|
7a350c2685 | ||
|
|
7da2c06670 | ||
|
|
dd4bad706b | ||
|
|
3f80be8377 | ||
|
|
ced0accde5 | ||
|
|
b454ff5ec7 | ||
|
|
45af198f32 | ||
|
|
ff374f8899 | ||
|
|
faecb3bc4b | ||
|
|
6b893fadef | ||
|
|
c328467a41 | ||
|
|
182325470b | ||
|
|
f330ad71ac | ||
|
|
5d253a39b2 | ||
|
|
e84a92fedc | ||
|
|
6bc6aca17e | ||
|
|
ba0c064f36 | ||
|
|
a7261bec47 | ||
|
|
6f52e5de76 | ||
|
|
f329f7a241 | ||
|
|
3eaff4cb4c | ||
|
|
9bcae3f893 | ||
|
|
6d094af6d5 | ||
|
|
cf0b1cf035 | ||
|
|
7c11a12e9a | ||
|
|
e244f63949 | ||
|
|
c4a390820d | ||
|
|
1c0b62ac30 | ||
|
|
eacaf214e8 | ||
|
|
1369756e1e | ||
|
|
20803df255 | ||
|
|
80931b8948 | ||
|
|
167597bc9f | ||
|
|
93dcc12f8d | ||
|
|
1287dff879 | ||
|
|
6bf6e38384 | ||
|
|
a1f8cdd76b | ||
|
|
de48c48b49 | ||
|
|
27f0af4f0b | ||
|
|
4226f776c1 | ||
|
|
f1e4eae858 | ||
|
|
4647fd6ee7 | ||
|
|
3a965500e8 | ||
|
|
7a9bb807f0 | ||
|
|
694f0044cc | ||
|
|
27696e4f0f | ||
|
|
7ec3d4d50b | ||
|
|
803b1cd21a | ||
|
|
125d39b019 | ||
|
|
63d1d9f49a | ||
|
|
6832cc8de2 | ||
|
|
f97effb042 | ||
|
|
3e233d1f72 | ||
|
|
f07f1bf0e2 | ||
|
|
8eb66a049e | ||
|
|
9578b139a9 | ||
|
|
4299e15ad2 | ||
|
|
1dceb02e09 | ||
|
|
32bfdd231a | ||
|
|
05b2d603f1 | ||
|
|
e7967eadc5 | ||
|
|
001873998a | ||
|
|
3d834a0c44 | ||
|
|
35dcc44fe8 | ||
|
|
81f4934282 | ||
|
|
f2335ff2d3 | ||
|
|
75161fc694 | ||
|
|
14775aeb67 | ||
|
|
5979341fb9 | ||
|
|
d86509233f | ||
|
|
d6d45cd9a5 | ||
|
|
21e940c94d | ||
|
|
f3ad600c94 | ||
|
|
72e0a9c32e | ||
|
|
c791833fbe | ||
|
|
bcac023d33 | ||
|
|
7459181fa9 | ||
|
|
ef19a1ad09 | ||
|
|
41446cde38 | ||
|
|
8d7aaee5b9 | ||
|
|
44509bb274 | ||
|
|
54d9c315e5 | ||
|
|
9f8f7adb26 | ||
|
|
c2341070f5 | ||
|
|
20af9eb6f4 | ||
|
|
36bb1ea8af | ||
|
|
7d43105925 | ||
|
|
ebb3f3c5ae | ||
|
|
271815fa53 | ||
|
|
8570bdb4c0 | ||
|
|
092a089580 | ||
|
|
f2af2cd9af | ||
|
|
2a8e4231b4 | ||
|
|
c3cb082de8 | ||
|
|
20b6d15c2e | ||
|
|
5ec762ac0d | ||
|
|
7f44c48c12 | ||
|
|
2a071bc5de | ||
|
|
68cba2de63 | ||
|
|
5a914f9c0e | ||
|
|
b0e6805a20 | ||
|
|
21e7e44c01 | ||
|
|
ded9b70483 | ||
|
|
f7df4abdae | ||
|
|
0019f2183a | ||
|
|
68f77a6c8a | ||
|
|
7674ceefe9 | ||
|
|
a4d07d4307 | ||
|
|
4be575c534 | ||
|
|
903e844aad | ||
|
|
a6b299b029 | ||
|
|
05f9ae8d76 | ||
|
|
314afed9de | ||
|
|
dd0f0a7d5a | ||
|
|
759b44c224 | ||
|
|
ebdd1a0b03 | ||
|
|
6c44575f7a | ||
|
|
8be832239a | ||
|
|
c7be202430 | ||
|
|
244c5dc6b4 | ||
|
|
6bbb9a638e | ||
|
|
44041b136a | ||
|
|
7e17c30ce2 | ||
|
|
c4d3d1b409 | ||
|
|
03de63754b | ||
|
|
0e506f0b1a | ||
|
|
a2ab752870 | ||
|
|
eaa032828a | ||
|
|
cceb0b4c6c | ||
|
|
a58640a718 | ||
|
|
920384b26c | ||
|
|
880c34c3f0 | ||
|
|
6d7bf34f51 | ||
|
|
bc10a9fcf5 | ||
|
|
193a2c4f70 | ||
|
|
793dec98b2 | ||
|
|
11ddf8015d | ||
|
|
b6d969bc7b | ||
|
|
b799b54c9e | ||
|
|
5389f05ea8 | ||
|
|
b4bf4a5bf5 | ||
|
|
6d9c87c9bc | ||
|
|
1753bdbd8b | ||
|
|
d6e563486b | ||
|
|
e8abc0bbd2 | ||
|
|
0112bfa9c4 | ||
|
|
5951611fb0 | ||
|
|
0589612df9 | ||
|
|
af2d951b50 | ||
|
|
132b672441 | ||
|
|
ffb7cc5c18 | ||
|
|
5a9a3eb140 | ||
|
|
5395855775 | ||
|
|
9d60924512 | ||
|
|
1680c635dc | ||
|
|
898fbcc52b | ||
|
|
d0e4578af0 | ||
|
|
2d31b726ac | ||
|
|
d2f295ef88 | ||
|
|
d1b53554ce | ||
|
|
ec5db122d0 | ||
|
|
0216e22fcc | ||
|
|
5734b19d8c | ||
|
|
cc8c818e13 | ||
|
|
e58aeec097 | ||
|
|
58b000927a | ||
|
|
797642b972 | ||
|
|
2e1f273d78 | ||
|
|
35d6800877 | ||
|
|
781856b822 | ||
|
|
ff272179e7 | ||
|
|
bec47f40f7 | ||
|
|
f9607a434a | ||
|
|
b650ca85bc | ||
|
|
4ceacae954 | ||
|
|
56e5877040 | ||
|
|
91e154bbee | ||
|
|
f4365ed163 | ||
|
|
0c02b0cb68 | ||
|
|
b0aaa58fa7 | ||
|
|
054ab774d5 | ||
|
|
8ca33b552d | ||
|
|
6734c2b9f7 | ||
|
|
c484477d6a | ||
|
|
d9b5223749 | ||
|
|
55856450b3 | ||
|
|
bb28a3bf83 | ||
|
|
c0484de506 | ||
|
|
addb1b27bb | ||
|
|
96ecdcd33c | ||
|
|
e3f445cbed | ||
|
|
8ccb505165 | ||
|
|
98c193ac51 | ||
|
|
b966d5626b | ||
|
|
9c01f7a522 | ||
|
|
f3c9f52d37 | ||
|
|
1df61780a3 | ||
|
|
2be3bca70e | ||
|
|
18ec63b9b4 | ||
|
|
6781fb6e25 | ||
|
|
852bc6ba24 | ||
|
|
223aa7043b | ||
|
|
3178b5f41c | ||
|
|
14665c9a24 | ||
|
|
ea823ef0cf | ||
|
|
62d5c7a492 | ||
|
|
287e75e54a | ||
|
|
02af385853 | ||
|
|
ede2a3e0b5 | ||
|
|
66dab9194a | ||
|
|
6e5fa34be6 | ||
|
|
8ce782b578 | ||
|
|
05168b9826 | ||
|
|
5208195f72 | ||
|
|
5e4e56bd2c | ||
|
|
82fac1d4e7 | ||
|
|
1dc90f930a | ||
|
|
aa56d160e6 | ||
|
|
452eac6fd7 | ||
|
|
d37a2b9db7 | ||
|
|
14e2747599 | ||
|
|
bf9afba590 | ||
|
|
b667afc7cd | ||
|
|
21b0736842 | ||
|
|
8bd3c7cc28 | ||
|
|
4db90b87f4 | ||
|
|
46fd0be0eb | ||
|
|
05a966869b | ||
|
|
6fef51fcbb | ||
|
|
ae7b2f3fc7 | ||
|
|
d5b6c02b22 | ||
|
|
6cd722dbef | ||
|
|
8dfc1ecae8 | ||
|
|
fdca799a5f | ||
|
|
def79e591b | ||
|
|
093a27970d | ||
|
|
9a1e33a776 | ||
|
|
a4a206eae6 | ||
|
|
21ee5fc2bf | ||
|
|
7df72e6bc6 | ||
|
|
9ceea8de99 | ||
|
|
79d48a12a9 | ||
|
|
473d3a5196 | ||
|
|
43717634f6 | ||
|
|
6ba8555adf | ||
|
|
722ad8c53e | ||
|
|
b0b497ed46 | ||
|
|
6c881eccd2 | ||
|
|
94b92bd7c1 | ||
|
|
676b195aee | ||
|
|
4591731cdc | ||
|
|
fc5eeae9e7 | ||
|
|
aaa88c45f7 | ||
|
|
5d91e8030c | ||
|
|
c9be188d19 | ||
|
|
ecc8daa8f1 | ||
|
|
5bbe11bf45 | ||
|
|
e52170abea | ||
|
|
0f87fc7924 | ||
|
|
cc7c4fc95f | ||
|
|
4a2dcdf701 | ||
|
|
ede47af962 | ||
|
|
70e4cb2286 | ||
|
|
f4007dba90 | ||
|
|
c5e8460516 | ||
|
|
f852dac1e5 | ||
|
|
374626cb32 | ||
|
|
aefa0b89ae | ||
|
|
9cc8b2668c | ||
|
|
94b862a3ff | ||
|
|
f71bb6b78c | ||
|
|
499ac8f727 | ||
|
|
938ae97cac | ||
|
|
e2193e8e3c | ||
|
|
d687abcdc1 | ||
|
|
16f907c91f | ||
|
|
584700225c | ||
|
|
bad72985cb | ||
|
|
56b24420d1 | ||
|
|
92beac8dff | ||
|
|
ed1fdba9a5 | ||
|
|
5e194e3079 | ||
|
|
27c2791d6c | ||
|
|
d6bcc9c156 | ||
|
|
4c85fd4387 | ||
|
|
80d529d503 | ||
|
|
c5a19a2334 | ||
|
|
16857bebd9 | ||
|
|
1c340b7c66 | ||
|
|
7d9d8f0aae | ||
|
|
21fc35230c | ||
|
|
fc67c82040 | ||
|
|
a9b828001c | ||
|
|
be050abf7e | ||
|
|
2d071ca252 | ||
|
|
abf94e1e70 | ||
|
|
2991c7421c | ||
|
|
834f84f995 | ||
|
|
6bf713c96c | ||
|
|
72b16be297 | ||
|
|
0c7f27b3ac | ||
|
|
6fa930aa9f | ||
|
|
736ec4dbf9 | ||
|
|
04ed9e76f2 | ||
|
|
811304c0c7 | ||
|
|
ca101748eb | ||
|
|
3584c123d6 | ||
|
|
9a991b8f33 | ||
|
|
a2fd4be339 | ||
|
|
656364db60 | ||
|
|
884347da12 | ||
|
|
675c49a922 | ||
|
|
4d04741fe0 | ||
|
|
5c7fe9dcb5 | ||
|
|
a3146d6cdd | ||
|
|
d769f757ed | ||
|
|
b28b70c6b8 | ||
|
|
c2c5b08659 | ||
|
|
7460b24fb0 | ||
|
|
2531015094 | ||
|
|
2efdf09333 | ||
|
|
30582bf8e3 | ||
|
|
654e3eb36b | ||
|
|
67e3244655 | ||
|
|
f793bbc711 | ||
|
|
94634781c4 | ||
|
|
0fbbdb6621 | ||
|
|
d3f2049c7d | ||
|
|
134b1edb19 | ||
|
|
2489b57e42 | ||
|
|
ee56ad07e9 | ||
|
|
e1853cb1f0 | ||
|
|
ee79916104 | ||
|
|
f62ba685d1 | ||
|
|
c3aa3af650 | ||
|
|
fb44015402 | ||
|
|
8cc5678b38 | ||
|
|
592eb2d589 | ||
|
|
84713eb3b7 | ||
|
|
d95b395cfc | ||
|
|
b41b2c8f3c | ||
|
|
289dc4bf86 | ||
|
|
c34e1e5f32 | ||
|
|
729680df8c | ||
|
|
b85b578ddd | ||
|
|
8a7d2df7b8 | ||
|
|
3ff3eb819a | ||
|
|
1ea24b5ff6 | ||
|
|
79d0ab311b | ||
|
|
79f7df089e | ||
|
|
32464f8552 | ||
|
|
7ce96a2311 | ||
|
|
a009ea212f | ||
|
|
8848b75325 | ||
|
|
2eb04a3835 | ||
|
|
b61ce8399e | ||
|
|
52392c9ed9 | ||
|
|
4b35ac6ad8 | ||
|
|
a56b603c5c | ||
|
|
48bd277769 | ||
|
|
04101eb31b | ||
|
|
34c6a76a53 | ||
|
|
3f20c6104a | ||
|
|
b5f82261b0 | ||
|
|
b9a5db7ea4 | ||
|
|
64cb6e52a8 | ||
|
|
6e96e8929f | ||
|
|
bdd48f0001 | ||
|
|
4bf19adc8e | ||
|
|
bb9ff3c33f | ||
|
|
ae11e78d78 | ||
|
|
222c3a2526 | ||
|
|
08aa64a666 | ||
|
|
f10c157598 | ||
|
|
5059d8fc4f | ||
|
|
4152179dea | ||
|
|
3619be71ab | ||
|
|
0bf31de1f1 | ||
|
|
7fd32cab3d | ||
|
|
4a695b2a83 | ||
|
|
a8ba50e762 | ||
|
|
f79fc66578 | ||
|
|
810e9eeb11 | ||
|
|
25cff94665 | ||
|
|
0c63b99e6c | ||
|
|
5881f5fa05 | ||
|
|
0e08209f4c | ||
|
|
db0b4fb615 | ||
|
|
672cfe58d6 | ||
|
|
338e164143 | ||
|
|
e964b76302 | ||
|
|
8dedc77ff8 | ||
|
|
5e8ede6ab8 | ||
|
|
1124bab96b | ||
|
|
cbcdf09bfe | ||
|
|
3b762c14a1 | ||
|
|
59941fc867 | ||
|
|
50601853f5 | ||
|
|
3137f3c1e4 | ||
|
|
575ca6251c | ||
|
|
bfaa732544 | ||
|
|
9c83a5aaeb | ||
|
|
bc3e46eae1 | ||
|
|
1b7a257a48 | ||
|
|
4d40fad10d | ||
|
|
49015f3e3e | ||
|
|
77b5819c65 | ||
|
|
ac5615497b | ||
|
|
4ecd525f13 | ||
|
|
6756b36e87 | ||
|
|
36808e4e8e | ||
|
|
1c38570609 | ||
|
|
2dc6deb93a | ||
|
|
0b58d19811 | ||
|
|
50362d630b | ||
|
|
bfcd67cbaf | ||
|
|
ea190c0597 | ||
|
|
e68160800d | ||
|
|
73481f4a1f | ||
|
|
0303e59fc1 | ||
|
|
0c0d36da62 | ||
|
|
e7e0b8841c | ||
|
|
b38a460a02 | ||
|
|
bddf024ba0 | ||
|
|
be7a7acadd | ||
|
|
d05c90ca7f | ||
|
|
79d9abe7f7 | ||
|
|
684fbc0050 | ||
|
|
03973d41be | ||
|
|
06c533bf5a | ||
|
|
cc64a20e53 | ||
|
|
4187da9168 | ||
|
|
a051e636e6 | ||
|
|
4144639b75 | ||
|
|
360cd7b5df | ||
|
|
55d2ca4a93 | ||
|
|
c7f61291ed | ||
|
|
f404895b5c | ||
|
|
94ed0c3f49 | ||
|
|
00ffe9c41e | ||
|
|
d633b6f1d4 | ||
|
|
7b68baef0d | ||
|
|
fb5ef921f7 | ||
|
|
da44e50679 | ||
|
|
93d89f93b2 | ||
|
|
cce64f9a76 | ||
|
|
afb396acd8 | ||
|
|
0943908173 | ||
|
|
b018788cd1 | ||
|
|
4a315e73eb | ||
|
|
d0a9ba041d | ||
|
|
b01ef6d5a7 | ||
|
|
604e581139 | ||
|
|
082f697b40 | ||
|
|
0a8d73dc0b | ||
|
|
fd99f3caa1 | ||
|
|
794c4e5227 | ||
|
|
f5df8225d1 | ||
|
|
42c6446125 | ||
|
|
e3486ebf7c | ||
|
|
c0115f068c | ||
|
|
41682d1147 | ||
|
|
8c61660cfc | ||
|
|
d6933be3cd | ||
|
|
ca6cfd2b91 | ||
|
|
20960bdd57 | ||
|
|
c97a7e5158 | ||
|
|
32e2d24b15 | ||
|
|
c102aae819 | ||
|
|
bed72cb5ed | ||
|
|
f0c1046fe9 | ||
|
|
d88104d105 | ||
|
|
f3e21e5a82 | ||
|
|
a77bee8664 | ||
|
|
b5d0aed59e | ||
|
|
2440cc6af5 | ||
|
|
90114dfbe0 | ||
|
|
64b8cdf7dc |
48
.github/workflows/nightly-builds.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Nightly builds
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'corretto'
|
||||
cache: gradle
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Decode Keystore
|
||||
id: decode_keystore
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: 'nightly_keystore.jks'
|
||||
fileDir: './mastodon/keystore/'
|
||||
encodedString: ${{ secrets.KEYSTORE }}
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew assembleNightly
|
||||
env:
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
CURRENT_DATE: ${{ steps.date.outputs.date }}
|
||||
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: moshidon-nightly.apk
|
||||
path: ./mastodon/build/outputs/apk/nightly/moshidon-nightly.apk
|
||||
11
.github/workflows/validate-gradle-wrapper.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: Validate Gradle Wrapper
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
2
.gitignore
vendored
@@ -9,3 +9,5 @@
|
||||
.cxx
|
||||
local.properties
|
||||
*.jks
|
||||
*.keystore
|
||||
/mastodon/keystore/nightly_keystore.keystore
|
||||
|
||||
29
README.md
@@ -5,18 +5,35 @@
|
||||
> A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly won’t ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
|
||||
|
||||
|
||||
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
||||
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
||||
|
||||
[](https://github.com/LucasGGamerM/moshidon-nightly/releases/latest/download/moshidon-nightly.apk)
|
||||
|
||||
|
||||
[](https://translate.codeberg.org/engage/moshidon/)
|
||||
|
||||
[](https://github.com/LucasGGamerM/moshidon/actions/workflows/nightly-builds.yml)
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.moshinda"><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.moshinda"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
||||
|
||||
---
|
||||
|
||||
## F.A.Q
|
||||
|
||||
### Q: What are the main differences between Moshidon and Megalodon?
|
||||
|
||||
### A: There are many, but the most outstanding differences are: the ability to have other server's local timeline inside the app. It can be acessed in the "Add community" option in the top right corner of the Edit timelines screen. Other outstanding features that Moshidon has are some quality of life improvements, such as notification actions and allowing for unlisted replies by default. Most other features are pretty minor, such as profile notes directly available in the person's profile. Other features are quite minor usability and visibility improvements. All of which can be found in the settings page.
|
||||
|
||||
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
|
||||
alt="Get it on IzzyOnDroid"
|
||||
height="80">](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda)
|
||||
---
|
||||
|
||||
|
||||
## Key features
|
||||
|
||||
### **The ability to add new custom local timelines!**
|
||||
|
||||
#### It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts!
|
||||
|
||||
### **Material you theme support on Android 12+ devices!**
|
||||
|
||||
### **Show posts filtered with a warning!**
|
||||
@@ -30,7 +47,7 @@ Before | After
|
||||
|
||||
### **Color themes**
|
||||
|
||||
**Allows you to change theme within the app. Supports Purple, pink, green, blue, orange and yellow!**
|
||||
**Allows you to change theme within the app. Supports Purple, pink, green, blue, red, orange, yellow and Nord!**
|
||||
|
||||
### **Unlisted posting**
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Moshidon</title>
|
||||
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
||||
<link rel="me" href="https://floss.social/@mastodon">
|
||||
<link rel="me" href="https://floss.social/@megalodon">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
||||
</head>
|
||||
<body class="markdown-body">
|
||||
|
||||
@@ -5,7 +5,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath 'com.android.tools.build:gradle:8.0.0'
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
files:
|
||||
- source: /mastodon/src/main/res/values/strings.xml
|
||||
translation: /mastodon/src/main/res/values-%android_code%/strings.xml
|
||||
- source: /fastlane/metadata/android/en-US/*.txt
|
||||
translation: /fastlane/metadata/android/%locale%/%original_file_name%
|
||||
@@ -16,4 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=false
|
||||
android.enableJetifier=false
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=false
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
7
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
||||
#Thu Jan 13 11:33:43 MSK 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
288
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,67 +17,98 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@@ -87,9 +118,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -98,7 +129,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -106,80 +137,109 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
15
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
@@ -2,33 +2,78 @@ plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 33
|
||||
defaultConfig {
|
||||
manifestPlaceholders = [oAuthScheme:"moshidon-android-auth"]
|
||||
archivesBaseName = "moshidon"
|
||||
applicationId "org.joinmastodon.android.moshinda"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 98
|
||||
versionName "1.2.0+fork.98.moshinda"
|
||||
versionCode 99
|
||||
versionName "1.2.0+fork.99.moshinda"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
nightly{
|
||||
storeFile = file("keystore/nightly_keystore.jks")
|
||||
storePassword System.getenv("SIGNING_STORE_PASSWORD")
|
||||
if (storePassword == null) {
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
storePassword = properties.getProperty('SIGNING_STORE_PASSWORD')
|
||||
}
|
||||
keyAlias System.getenv("SIGNING_KEY_ALIAS")
|
||||
if (keyAlias == null) {
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
keyAlias = properties.getProperty('SIGNING_KEY_ALIAS')
|
||||
}
|
||||
keyPassword System.getenv("SIGNING_KEY_PASSWORD")
|
||||
if (keyPassword == null) {
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
keyPassword = properties.getProperty('SIGNING_KEY_PASSWORD')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// minifyEnabled true
|
||||
// shrinkResources true
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug{
|
||||
debuggable true
|
||||
versionNameSuffix '-debug'
|
||||
applicationIdSuffix '.debug'
|
||||
manifestPlaceholders = [oAuthScheme:"moshidon-android-debug-auth"]
|
||||
}
|
||||
githubRelease{
|
||||
initWith release
|
||||
}
|
||||
nightly{
|
||||
initWith release
|
||||
if(System.getenv("CURRENT_DATE") != null){
|
||||
versionNameSuffix '-nightly+@' + System.getenv("CURRENT_DATE")
|
||||
} else {
|
||||
Properties properties = new Properties()
|
||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
versionNameSuffix '-nightly+@' + properties.getProperty('CURRENT_DATE')
|
||||
}
|
||||
applicationIdSuffix '.nightly'
|
||||
signingConfig signingConfigs.nightly
|
||||
manifestPlaceholders = [oAuthScheme:"moshidon-android-nightly-auth"]
|
||||
}
|
||||
playRelease{
|
||||
initWith release
|
||||
minifyEnabled true
|
||||
@@ -49,14 +94,19 @@ android {
|
||||
setRoot "src/github"
|
||||
}
|
||||
}
|
||||
lintOptions{
|
||||
checkReleaseBuilds false
|
||||
namespace 'org.joinmastodon.android'
|
||||
lint {
|
||||
abortOnError false
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api 'androidx.annotation:annotation:1.3.0'
|
||||
api 'androidx.annotation:annotation:1.6.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
|
||||
implementation 'me.grishka.litex:recyclerview:1.2.1.1'
|
||||
implementation 'me.grishka.litex:swiperefreshlayout:1.1.0.1'
|
||||
@@ -65,7 +115,7 @@ dependencies {
|
||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||
implementation 'me.grishka.appkit:appkit:1.2.7'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
implementation 'de.psdev:async-otto:1.0.3'
|
||||
|
||||
7
mastodon/proguard-rules.pro
vendored
@@ -42,6 +42,13 @@
|
||||
|
||||
-keepattributes LineNumberTable
|
||||
|
||||
-keepattributes *
|
||||
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
|
||||
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
|
||||
|
||||
#-keep class javax.** { *; }
|
||||
-keep class org.joinmastodon.android.** { *; }
|
||||
|
||||
# Parceler library
|
||||
-keep interface org.parceler.Parcel
|
||||
-keep @org.parceler.Parcel class * { *; }
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.joinmastodon.android">
|
||||
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||
|
||||
<application>
|
||||
|
||||
<application
|
||||
tools:replace="android:label"
|
||||
android:label="@string/mo_app_name_debug">
|
||||
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$InstallerStatusReceiver" android:exported="false"/>-->
|
||||
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$AfterUpdateRestartReceiver" android:exported="true" android:enabled="false">-->
|
||||
<!-- <intent-filter>-->
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M54,90L54,90c-19.9,0 -36,-16.1 -36,-36v0c0,-19.9 16.1,-36 36,-36h0c19.9,0 36,16.1 36,36v0C90,73.9 73.9,90 54,90z"
|
||||
android:strokeAlpha="0"
|
||||
android:fillAlpha="0"/>
|
||||
<path
|
||||
android:pathData="M52.5,41.6c-2.4,0 -4.3,0.9 -5.5,2.8l-1.2,2l-1.2,-2c-1.2,-1.9 -3.1,-2.8 -5.5,-2.8c-2.1,0 -3.8,0.8 -5.1,2.2c-1.2,1.4 -1.9,3.4 -1.9,5.9v12h4.7V50c0,-2.4 1.1,-3.7 3.1,-3.7c2.3,0 3.4,1.4 3.4,4.4v6.4h4.7v-6.4c0,-2.9 1.1,-4.4 3.4,-4.4c2.1,0 3.1,1.2 3.1,3.7v11.7h4.7v-12c0,-2.4 -0.6,-4.4 -1.9,-5.9C56.2,42.3 54.6,41.6 52.5,41.6z"
|
||||
android:fillColor="#33D17A"/>
|
||||
<path
|
||||
android:pathData="M65.9,58.1h0.8c0,0 0,0 -0.1,0c-0.6,-0.3 -1.1,-0.8 -1.4,-1.4c-0.3,-0.6 -0.5,-1.4 -0.5,-2.1c0,-0.8 0.2,-1.5 0.5,-2.1c0.4,-0.6 0.8,-1.1 1.4,-1.4c0.6,-0.3 1.2,-0.5 1.9,-0.5c0.7,0 1.3,0.2 1.9,0.5s1.1,0.8 1.4,1.4s0.5,1.3 0.5,2.1c0,0.2 0,0.4 0,0.6l0.7,0.7c0.4,0 0.8,0 1.1,0l1.5,-1.5l0.2,-0.2c-0.1,-1.2 -0.4,-2.3 -0.9,-3.4c-0.6,-1.1 -1.4,-2 -2.6,-2.6c-1.1,-0.6 -2.4,-1 -3.7,-1c-1.4,0 -2.7,0.3 -3.8,1c-1.1,0.6 -2,1.5 -2.6,2.6c-0.6,1.1 -0.9,2.4 -0.9,3.7s0.3,2.7 0.9,3.7c0.6,1.1 1.5,2 2.6,2.6c0.4,0.2 0.8,0.4 1.1,0.5v-1.8V58.1z"
|
||||
android:fillColor="#33D17A"/>
|
||||
<path
|
||||
android:pathData="M76,58.3l1.2,-1.2L76.2,56l-1.7,1.7c-0.4,-0.1 -0.7,-0.2 -1.1,-0.2s-0.8,0.1 -1.1,0.2L70.7,56l-1,1.1l1.2,1.2c-0.5,0.4 -1.1,0.9 -1.4,1.5h-2.1v1.5H69c0,0.2 -0.1,0.5 -0.1,0.8v0.8h-1.5v1.5h1.5v0.8c0,0.2 0,0.5 0.1,0.8h-1.6v1.5h2.1c0.8,1.4 2.3,2.3 4,2.3s3.1,-0.9 4,-2.3h2.1v-1.5H78c0,-0.2 0.1,-0.5 0.1,-0.8v-0.8h1.5v-1.5h-1.5v-0.8c0,-0.2 0,-0.5 -0.1,-0.8h1.6v-1.5h-2.1C77.1,59.2 76.6,58.8 76,58.3zM75,65.9H72v-1.5H75V65.9zM75,62.9H72v-1.5H75V62.9z"
|
||||
android:fillColor="#33D17A"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M54,90L54,90c-19.9,0 -36,-16.1 -36,-36v0c0,-19.9 16.1,-36 36,-36h0c19.9,0 36,16.1 36,36v0C90,73.9 73.9,90 54,90z"
|
||||
android:strokeAlpha="0"
|
||||
android:fillAlpha="0"/>
|
||||
<path
|
||||
android:pathData="M52.5,41.6c-2.4,0 -4.3,0.9 -5.5,2.8l-1.2,2l-1.2,-2c-1.2,-1.9 -3.1,-2.8 -5.5,-2.8c-2.1,0 -3.8,0.8 -5.1,2.2c-1.2,1.4 -1.9,3.4 -1.9,5.9v12h4.7V50c0,-2.4 1.1,-3.7 3.1,-3.7c2.3,0 3.4,1.4 3.4,4.4v6.4h4.7v-6.4c0,-2.9 1.1,-4.4 3.4,-4.4c2.1,0 3.1,1.2 3.1,3.7v11.7h4.7v-12c0,-2.4 -0.6,-4.4 -1.9,-5.9C56.2,42.3 54.6,41.6 52.5,41.6z"
|
||||
android:fillColor="#33D17A"/>
|
||||
<path
|
||||
android:pathData="M65.9,58.1h0.8c0,0 0,0 -0.1,0c-0.6,-0.3 -1.1,-0.8 -1.4,-1.4c-0.3,-0.6 -0.5,-1.4 -0.5,-2.1c0,-0.8 0.2,-1.5 0.5,-2.1c0.4,-0.6 0.8,-1.1 1.4,-1.4c0.6,-0.3 1.2,-0.5 1.9,-0.5c0.7,0 1.3,0.2 1.9,0.5s1.1,0.8 1.4,1.4s0.5,1.3 0.5,2.1c0,0.2 0,0.4 0,0.6l0.7,0.7c0.4,0 0.8,0 1.1,0l1.5,-1.5l0.2,-0.2c-0.1,-1.2 -0.4,-2.3 -0.9,-3.4c-0.6,-1.1 -1.4,-2 -2.6,-2.6c-1.1,-0.6 -2.4,-1 -3.7,-1c-1.4,0 -2.7,0.3 -3.8,1c-1.1,0.6 -2,1.5 -2.6,2.6c-0.6,1.1 -0.9,2.4 -0.9,3.7s0.3,2.7 0.9,3.7c0.6,1.1 1.5,2 2.6,2.6c0.4,0.2 0.8,0.4 1.1,0.5v-1.8V58.1z"
|
||||
android:fillColor="#33D17A"/>
|
||||
<path
|
||||
android:pathData="M76,58.3l1.2,-1.2L76.2,56l-1.7,1.7c-0.4,-0.1 -0.7,-0.2 -1.1,-0.2s-0.8,0.1 -1.1,0.2L70.7,56l-1,1.1l1.2,1.2c-0.5,0.4 -1.1,0.9 -1.4,1.5h-2.1v1.5H69c0,0.2 -0.1,0.5 -0.1,0.8v0.8h-1.5v1.5h1.5v0.8c0,0.2 0,0.5 0.1,0.8h-1.6v1.5h2.1c0.8,1.4 2.3,2.3 4,2.3s3.1,-0.9 4,-2.3h2.1v-1.5H78c0,-0.2 0.1,-0.5 0.1,-0.8v-0.8h1.5v-1.5h-1.5v-0.8c0,-0.2 0,-0.5 -0.1,-0.8h1.6v-1.5h-2.1C77.1,59.2 76.6,58.8 76,58.3zM75,65.9H72v-1.5H75V65.9zM75,62.9H72v-1.5H75V62.9z"
|
||||
android:fillColor="#33D17A"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground_debug"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground_monochrome_debug"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground_debug"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground_monochrome_debug"/>
|
||||
</adaptive-icon>
|
||||
BIN
mastodon/src/github/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
mastodon/src/github/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
mastodon/src/github/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 988 B |
BIN
mastodon/src/github/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
mastodon/src/github/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
mastodon/src/github/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
mastodon/src/github/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
mastodon/src/github/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
mastodon/src/github/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
mastodon/src/github/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#000000</color>
|
||||
</resources>
|
||||
@@ -17,6 +17,9 @@
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.TRANSLATE" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
@@ -28,6 +31,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:largeHeap="true">
|
||||
|
||||
<activity android:name=".MainActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize" android:launchMode="singleTask">
|
||||
@@ -41,10 +45,11 @@
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="moshidon-android-auth" android:host="callback"/>
|
||||
<data android:scheme="${oAuthScheme}" android:host="callback"/>
|
||||
</intent-filter>
|
||||
</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"
|
||||
android:theme="@style/TransparentDialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
|
||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Application;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiCategory;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
public class DomainManager {
|
||||
private static final String TAG="DomainManager";
|
||||
|
||||
private static final DomainManager instance=new DomainManager();
|
||||
|
||||
private String currentDomain = "";
|
||||
|
||||
|
||||
public static DomainManager getInstance(){
|
||||
return instance;
|
||||
}
|
||||
|
||||
private DomainManager(){
|
||||
|
||||
}
|
||||
|
||||
public String getCurrentDomain() {
|
||||
return currentDomain;
|
||||
}
|
||||
|
||||
public void setCurrentDomain(String domain) {
|
||||
this.currentDomain = domain;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.ClipData;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -12,7 +12,9 @@ import android.widget.Toast;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.jsoup.internal.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -27,18 +29,23 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
UiUtils.setUserPreferredTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
if(savedInstanceState==null){
|
||||
|
||||
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||
boolean isMastodonURL = UiUtils.looksLikeMastodonUrl(text);
|
||||
|
||||
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
||||
if(sessions.isEmpty()){
|
||||
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}else if(sessions.size()==1){
|
||||
}else if(sessions.size()==1 && !isMastodonURL){
|
||||
openComposeFragment(sessions.get(0).getID());
|
||||
}else{
|
||||
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
||||
UiUtils.pickAccount(this, null, R.string.choose_account, 0,
|
||||
session -> openComposeFragment(session.getID()),
|
||||
b -> b.setOnCancelListener(d -> finish())
|
||||
);
|
||||
new AccountSwitcherSheet(this, false, false, isMastodonURL, accountSession -> {
|
||||
if(accountSession!=null)
|
||||
openComposeFragment(accountSession.getID());
|
||||
else
|
||||
UiUtils.openURL(this, AccountSessionManager.getInstance().getLastActiveAccountID(), text);
|
||||
}).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,9 +58,15 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
String subject = "";
|
||||
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
|
||||
subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
||||
if (!subject.isBlank()) builder.append(subject).append("\n\n");
|
||||
if (!StringUtil.isBlank(subject)) builder.append(subject).append("\n\n");
|
||||
}
|
||||
if (intent.hasExtra(Intent.EXTRA_TEXT)) {
|
||||
String extra = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
if (!StringUtil.isBlank(extra)) {
|
||||
if (extra.startsWith(subject)) extra = extra.substring(subject.length()).trim();
|
||||
builder.append(extra).append("\n\n");
|
||||
}
|
||||
}
|
||||
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
|
||||
String text=builder.toString();
|
||||
List<Uri> mediaUris;
|
||||
if(Intent.ACTION_SEND.equals(intent.getAction())){
|
||||
@@ -80,8 +93,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
args.putString("account", accountID);
|
||||
if(!TextUtils.isEmpty(text))
|
||||
args.putString("prefilledText", text);
|
||||
if(!subject.isBlank())
|
||||
args.putInt("selectionEnd", subject.length());
|
||||
args.putInt("selectionStart", StringUtil.isBlank(subject) ? 0 : subject.length());
|
||||
if(mediaUris!=null && !mediaUris.isEmpty())
|
||||
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
|
||||
Fragment fragment=new ComposeFragment();
|
||||
@@ -96,4 +108,11 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
return null;
|
||||
return new ArrayList<>(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent outContent) {
|
||||
super.onProvideAssistContent(outContent);
|
||||
|
||||
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,14 @@ public class GlobalUserPreferences{
|
||||
public static boolean collapseLongPosts;
|
||||
public static boolean spectatorMode;
|
||||
public static boolean autoHideFab;
|
||||
public static boolean defaultToUnlistedReplies;
|
||||
public static boolean disableDoubleTapToSwipe;
|
||||
public static boolean compactReblogReplyLine;
|
||||
public static boolean confirmBeforeReblog;
|
||||
public static boolean replyLineAboveHeader;
|
||||
public static boolean swapBookmarkWithBoostAction;
|
||||
public static boolean loadRemoteAccountFollowers;
|
||||
public static boolean mentionRebloggerAutomatically;
|
||||
public static String publishButtonText;
|
||||
public static ThemePreference theme;
|
||||
public static ColorPreference color;
|
||||
@@ -61,7 +69,13 @@ public class GlobalUserPreferences{
|
||||
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||
public static Map<String, Integer> recentEmojis;
|
||||
|
||||
private static SharedPreferences getPrefs(){
|
||||
/**
|
||||
* Pleroma
|
||||
*/
|
||||
public static String replyVisibility;
|
||||
|
||||
|
||||
public static SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@@ -80,7 +94,7 @@ public class GlobalUserPreferences{
|
||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
|
||||
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
|
||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", true);
|
||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
||||
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
||||
@@ -101,6 +115,15 @@ public class GlobalUserPreferences{
|
||||
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
||||
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
||||
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
||||
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
||||
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
|
||||
disableDoubleTapToSwipe=prefs.getBoolean("disableDoubleTapToSwipe", false);
|
||||
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
|
||||
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
||||
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
|
||||
swapBookmarkWithBoostAction=prefs.getBoolean("swapBookmarkWithBoostAction", false);
|
||||
loadRemoteAccountFollowers=prefs.getBoolean("loadRemoteAccountFollowers", true);
|
||||
mentionRebloggerAutomatically=prefs.getBoolean("mentionRebloggerAutomatically", false);
|
||||
publishButtonText=prefs.getString("publishButtonText", "");
|
||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
|
||||
@@ -109,6 +132,7 @@ public class GlobalUserPreferences{
|
||||
pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
|
||||
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
|
||||
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
|
||||
replyVisibility=prefs.getString("replyVisibility", null);
|
||||
|
||||
try {
|
||||
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
|
||||
@@ -152,6 +176,14 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("autoHideFab", autoHideFab)
|
||||
.putString("publishButtonText", publishButtonText)
|
||||
.putBoolean("bottomEncoding", bottomEncoding)
|
||||
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
|
||||
.putBoolean("disableDoubleTapToSwipe", disableDoubleTapToSwipe)
|
||||
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
||||
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
|
||||
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
|
||||
.putBoolean("swapBookmarkWithBoostAction", swapBookmarkWithBoostAction)
|
||||
.putBoolean("loadRemoteAccountFollowers", loadRemoteAccountFollowers)
|
||||
.putBoolean("mentionRebloggerAutomatically", mentionRebloggerAutomatically)
|
||||
.putInt("theme", theme.ordinal())
|
||||
.putString("color", color.name())
|
||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
||||
@@ -159,6 +191,7 @@ public class GlobalUserPreferences{
|
||||
.putString("recentEmojis", gson.toJson(recentEmojis))
|
||||
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
|
||||
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
|
||||
.putString("replyVisibility", replyVisibility)
|
||||
.apply();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ package org.joinmastodon.android;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@@ -17,7 +19,6 @@ import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
@@ -27,6 +28,7 @@ import androidx.annotation.Nullable;
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
|
||||
public class MainActivity extends FragmentStackActivity{
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||
UiUtils.setUserPreferredTheme(this);
|
||||
@@ -81,6 +83,7 @@ public class MainActivity extends FragmentStackActivity{
|
||||
AccountSession accountSession;
|
||||
try{
|
||||
accountSession=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
DomainManager.getInstance().setCurrentDomain(accountSession.domain);
|
||||
}catch(IllegalStateException x){
|
||||
return;
|
||||
}
|
||||
@@ -168,4 +171,11 @@ public class MainActivity extends FragmentStackActivity{
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent outContent) {
|
||||
super.onProvideAssistContent(outContent);
|
||||
|
||||
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.joinmastodon.android;
|
||||
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.getPrefs;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationChannelGroup;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteInput;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.opengl.Visibility;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -16,15 +20,27 @@ import android.util.Log;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotificationByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.NotificationAction;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -37,11 +53,14 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
private static final String TAG="PushNotificationReceive";
|
||||
|
||||
public static final int NOTIFICATION_ID=178;
|
||||
private static final String ACTION_KEY_TEXT_REPLY = "ACTION_KEY_TEXT_REPLY";
|
||||
|
||||
private static final int SUMMARY_ID = 791;
|
||||
private static int notificationId = 0;
|
||||
private static int notificationId;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent){
|
||||
UiUtils.setUserPreferredTheme(context);
|
||||
if(BuildConfig.DEBUG){
|
||||
Log.e(TAG, "received: "+intent);
|
||||
Bundle extras=intent.getExtras();
|
||||
@@ -71,6 +90,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
}
|
||||
String accountID=account.getID();
|
||||
PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s);
|
||||
E.post(new NotificationReceivedEvent(accountID, pn.notificationId+""));
|
||||
new GetNotificationByID(pn.notificationId+"")
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
@@ -92,10 +112,40 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
Log.w(TAG, "onReceive: invalid push notification format");
|
||||
}
|
||||
}
|
||||
if(intent.getBooleanExtra("fromNotificationAction", false)){
|
||||
String accountID=intent.getStringExtra("accountID");
|
||||
int notificationId=intent.getIntExtra("notificationId", -1);
|
||||
|
||||
if (notificationId >= 0){
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.cancel(accountID, notificationId);
|
||||
}
|
||||
|
||||
if(intent.hasExtra("notification")){
|
||||
org.joinmastodon.android.model.Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
||||
String statusID=notification.status.id;
|
||||
if (statusID != null) {
|
||||
AccountSessionManager accountSessionManager = AccountSessionManager.getInstance();
|
||||
Preferences preferences = accountSessionManager.getAccount(accountID).preferences;
|
||||
|
||||
switch (NotificationAction.values()[intent.getIntExtra("notificationAction", 0)]) {
|
||||
case FAVORITE -> new SetStatusFavorited(statusID, true).exec(accountID);
|
||||
case BOOKMARK -> new SetStatusBookmarked(statusID, true).exec(accountID);
|
||||
case BOOST -> new SetStatusReblogged(notification.status.id, true, preferences.postingDefaultVisibility).exec(accountID);
|
||||
case UNBOOST -> new SetStatusReblogged(notification.status.id, false, preferences.postingDefaultVisibility).exec(accountID);
|
||||
case REPLY -> handleReplyAction(context, accountID, intent, notification, notificationId, preferences);
|
||||
default -> Log.w(TAG, "onReceive: Failed to get NotificationAction");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
Log.e(TAG, "onReceive: Failed to load notification");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
|
||||
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
||||
notificationId=getPrefs().getInt("latestNotificationId", 0);
|
||||
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||
Notification.Builder builder;
|
||||
@@ -165,6 +215,97 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().size()>1){
|
||||
builder.setSubText(accountName);
|
||||
}
|
||||
nm.notify(accountID, GlobalUserPreferences.keepOnlyLatestNotification ? NOTIFICATION_ID : notificationId++, builder.build());
|
||||
|
||||
int id = GlobalUserPreferences.keepOnlyLatestNotification ? NOTIFICATION_ID : notificationId++;
|
||||
getPrefs().edit().putInt("latestNotificationId", notificationId).apply();
|
||||
|
||||
if (notification != null){
|
||||
switch (pn.notificationType){
|
||||
case MENTION, STATUS -> {
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||
builder.addAction(buildReplyAction(context, id, accountID, notification));
|
||||
}
|
||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.button_favorite), NotificationAction.FAVORITE));
|
||||
if(GlobalUserPreferences.swapBookmarkWithBoostAction){
|
||||
if(notification.status.visibility != StatusPrivacy.DIRECT) {
|
||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.button_reblog), NotificationAction.BOOST));
|
||||
}else{
|
||||
// This is just so there is a bookmark action if you cannot reblog the toot
|
||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.add_bookmark), NotificationAction.BOOKMARK));
|
||||
}
|
||||
} else {
|
||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.add_bookmark), NotificationAction.BOOKMARK));
|
||||
}
|
||||
}
|
||||
case UPDATE -> {
|
||||
if(notification.status.reblogged)
|
||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.sk_undo_reblog), NotificationAction.UNBOOST));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nm.notify(accountID, id, builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private Notification.Action buildNotificationAction(Context context, int notificationId, String accountID, org.joinmastodon.android.model.Notification notification, String title, NotificationAction action){
|
||||
Intent notificationIntent=new Intent(context, PushNotificationReceiver.class);
|
||||
notificationIntent.putExtra("notificationId", notificationId);
|
||||
notificationIntent.putExtra("fromNotificationAction", true);
|
||||
notificationIntent.putExtra("accountID", accountID);
|
||||
notificationIntent.putExtra("notificationAction", action.ordinal());
|
||||
notificationIntent.putExtra("notification", Parcels.wrap(notification));
|
||||
PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, new Random().nextInt(), notificationIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
return new Notification.Action.Builder(null, title, actionPendingIntent).build();
|
||||
}
|
||||
|
||||
private Notification.Action buildReplyAction(Context context, int notificationId, String accountID, org.joinmastodon.android.model.Notification notification){
|
||||
String replyLabel = context.getResources().getString(R.string.button_reply);
|
||||
RemoteInput remoteInput = new RemoteInput.Builder(ACTION_KEY_TEXT_REPLY)
|
||||
.setLabel(replyLabel)
|
||||
.build();
|
||||
|
||||
Intent notificationIntent=new Intent(context, PushNotificationReceiver.class);
|
||||
notificationIntent.putExtra("notificationId", notificationId);
|
||||
notificationIntent.putExtra("fromNotificationAction", true);
|
||||
notificationIntent.putExtra("accountID", accountID);
|
||||
notificationIntent.putExtra("notificationAction", NotificationAction.REPLY.ordinal());
|
||||
notificationIntent.putExtra("notification", Parcels.wrap(notification));
|
||||
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT : PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(context, new Random().nextInt(), notificationIntent,flags);
|
||||
return new Notification.Action.Builder(null, replyLabel, replyPendingIntent).addRemoteInput(remoteInput).build();
|
||||
}
|
||||
|
||||
private void handleReplyAction(Context context, String accountID, Intent intent, org.joinmastodon.android.model.Notification notification, int notificationId, Preferences preferences) {
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
if (remoteInput == null) {
|
||||
Log.e(TAG, "handleReplyAction: Could not get reply input");
|
||||
return;
|
||||
}
|
||||
CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY);
|
||||
|
||||
CreateStatus.Request req=new CreateStatus.Request();
|
||||
req.status = input.toString() + "\n\n" + "@" + notification.status.account.acct;
|
||||
req.language = notification.status.language;
|
||||
req.visibility = (notification.status.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : notification.status.visibility);
|
||||
req.inReplyToId = notification.status.id;
|
||||
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
|
||||
req.spoilerText = "re: " + notification.status.spoilerText;
|
||||
}
|
||||
|
||||
new CreateStatus(req, UUID.randomUUID().toString()).exec(accountID);
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
|
||||
new Notification.Builder(context, accountID+"_"+notification.type) :
|
||||
new Notification.Builder(context)
|
||||
.setPriority(Notification.PRIORITY_DEFAULT)
|
||||
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
|
||||
|
||||
Notification repliedNotification = builder.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||
.setContentText(context.getString(R.string.mo_notification_action_replied, notification.status.account.getDisplayUsername()))
|
||||
.build();
|
||||
notificationManager.notify(accountID, notificationId, repliedNotification);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public class ApiUtils{
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> List<String> enumSetToStrings(EnumSet<E> e, Class<E> cls){
|
||||
public static <E extends Enum<E>> List<String> enumSetToStrings(EnumSet<E> e, Class<E> cls){
|
||||
return e.stream().map(ev->{
|
||||
try{
|
||||
SerializedName annotation=cls.getField(ev.name()).getAnnotation(SerializedName.class);
|
||||
|
||||
@@ -13,9 +13,11 @@ import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
@@ -126,11 +128,12 @@ public class CacheController{
|
||||
});
|
||||
}
|
||||
|
||||
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<PaginatedResponse<List<Notification>>> callback){
|
||||
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<CacheablePaginatedResponse<List<Notification>>> callback){
|
||||
cancelDelayedClose();
|
||||
databaseThread.postRunnable(()->{
|
||||
try{
|
||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.NOTIFICATIONS)).collect(Collectors.toList());
|
||||
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
List<Filter> filters=accountSession.wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.NOTIFICATIONS)).collect(Collectors.toList());
|
||||
if(!forceReload){
|
||||
SQLiteDatabase db=getOrOpenDatabase();
|
||||
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||
@@ -153,18 +156,19 @@ public class CacheController{
|
||||
result.add(ntf);
|
||||
}while(cursor.moveToNext());
|
||||
String _newMaxID=newMaxID;
|
||||
uiHandler.post(()->callback.onSuccess(new PaginatedResponse<>(result, _newMaxID)));
|
||||
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
||||
return;
|
||||
}
|
||||
}catch(IOException x){
|
||||
Log.w(TAG, "getNotifications: corrupted notification object in database", x);
|
||||
}
|
||||
}
|
||||
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class))
|
||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
|
||||
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.pleroma != null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Notification> result){
|
||||
callback.onSuccess(new PaginatedResponse<>(result.stream().filter(ntf->{
|
||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
|
||||
if(ntf.status!=null){
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(ntf.status)){
|
||||
@@ -173,7 +177,7 @@ public class CacheController{
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id));
|
||||
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||
putNotifications(result, onlyMentions, onlyPosts, maxID==null);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -40,12 +41,15 @@ import okhttp3.ResponseBody;
|
||||
|
||||
public class MastodonAPIController{
|
||||
private static final String TAG="MastodonAPIController";
|
||||
public static final Gson gson=new GsonBuilder()
|
||||
public static final Gson gsonWithoutDeserializer = new GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapter(Instant.class, new IsoInstantTypeAdapter())
|
||||
.registerTypeAdapter(LocalDate.class, new IsoLocalDateTypeAdapter())
|
||||
.create();
|
||||
public static final Gson gson = gsonWithoutDeserializer.newBuilder()
|
||||
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
|
||||
.create();
|
||||
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
||||
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
||||
|
||||
@@ -81,7 +85,7 @@ public class MastodonAPIController{
|
||||
final boolean isBad = host == null || badDomains.stream().anyMatch(h -> h.equalsIgnoreCase(host) || host.toLowerCase().endsWith("." + h));
|
||||
thread.postRunnable(()->{
|
||||
try{
|
||||
if (isBad) throw new IllegalArgumentException();
|
||||
// if (isBad) throw new IllegalArgumentException();
|
||||
if(req.canceled)
|
||||
return;
|
||||
Request.Builder builder=new Request.Builder()
|
||||
|
||||
@@ -5,8 +5,6 @@ import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
public class MastodonErrorResponse extends ErrorResponse{
|
||||
@@ -22,7 +20,7 @@ public class MastodonErrorResponse extends ErrorResponse{
|
||||
|
||||
@Override
|
||||
public void bindErrorView(View view){
|
||||
TextView text=view.findViewById(R.id.error_text);
|
||||
TextView text=view.findViewById(me.grishka.appkit.R.id.error_text);
|
||||
text.setText(error);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,15 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
|
||||
public class SetAccountMuted extends MastodonAPIRequest<Relationship>{
|
||||
public SetAccountMuted(String id, boolean muted){
|
||||
public SetAccountMuted(String id, boolean muted, long duration){
|
||||
super(HttpMethod.POST, "/accounts/"+id+"/"+(muted ? "mute" : "unmute"), Relationship.class);
|
||||
setRequestBody(new Object());
|
||||
setRequestBody(muted ? new Request(duration): new Object());
|
||||
}
|
||||
|
||||
private static class Request{
|
||||
public long duration;
|
||||
public Request(long duration){
|
||||
this.duration=duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.joinmastodon.android.api.requests.markers;
|
||||
|
||||
import org.joinmastodon.android.api.ApiUtils;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Marker;
|
||||
import org.joinmastodon.android.model.Markers;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class GetMarkers extends MastodonAPIRequest<Markers> {
|
||||
public GetMarkers(EnumSet<Marker.Type> timelines) {
|
||||
super(HttpMethod.GET, "/markers", Markers.class);
|
||||
for (String type : ApiUtils.enumSetToStrings(timelines, Marker.Type.class)){
|
||||
addQueryParameter("timeline[]", type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.api.ApiUtils;
|
||||
@@ -11,18 +10,24 @@ import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
public class GetNotifications extends MastodonAPIRequest<List<Notification>>{
|
||||
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> includeTypes){
|
||||
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> includeTypes, boolean isPleromaInstance){
|
||||
super(HttpMethod.GET, "/notifications", new TypeToken<>(){});
|
||||
if(maxID!=null)
|
||||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(includeTypes!=null){
|
||||
for(String type:ApiUtils.enumSetToStrings(includeTypes, Notification.Type.class)){
|
||||
addQueryParameter("types[]", type);
|
||||
}
|
||||
for(String type:ApiUtils.enumSetToStrings(EnumSet.complementOf(includeTypes), Notification.Type.class)){
|
||||
addQueryParameter("exclude_types[]", type);
|
||||
if(!isPleromaInstance) {
|
||||
for(String type:ApiUtils.enumSetToStrings(includeTypes, Notification.Type.class)){
|
||||
addQueryParameter("types[]", type);
|
||||
}
|
||||
for(String type:ApiUtils.enumSetToStrings(EnumSet.complementOf(includeTypes), Notification.Type.class)){
|
||||
addQueryParameter("exclude_types[]", type);
|
||||
}
|
||||
}else{
|
||||
for(String type:ApiUtils.enumSetToStrings(includeTypes, Notification.Type.class)){
|
||||
addQueryParameter("include_types[]", type);
|
||||
}
|
||||
}
|
||||
}
|
||||
removeUnsupportedItems=true;
|
||||
|
||||
@@ -45,6 +45,8 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||
public Instant scheduledAt;
|
||||
public String language;
|
||||
|
||||
public String quoteId;
|
||||
|
||||
public static class Poll{
|
||||
public ArrayList<String> options=new ArrayList<>();
|
||||
public int expiresIn;
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.api.requests.timelines;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
|
||||
@@ -18,5 +19,7 @@ public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(GlobalUserPreferences.replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.joinmastodon.android.api.StatusInteractionController;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Application;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Markers;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
@@ -31,6 +32,7 @@ public class AccountSession{
|
||||
public String pushAccountID;
|
||||
public Preferences preferences;
|
||||
public AccountActivationInfo activationInfo;
|
||||
public Markers markers;
|
||||
private transient MastodonAPIController apiController;
|
||||
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
||||
private transient CacheController cacheController;
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
|
||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.api.requests.markers.GetMarkers;
|
||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||
import org.joinmastodon.android.events.EmojiUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
@@ -33,6 +34,8 @@ import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiCategory;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Marker;
|
||||
import org.joinmastodon.android.model.Markers;
|
||||
import org.joinmastodon.android.model.Preferences;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
|
||||
@@ -46,6 +49,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -61,7 +65,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
public class AccountSessionManager{
|
||||
private static final String TAG="AccountSessionManager";
|
||||
public static final String SCOPE="read write follow push";
|
||||
public static final String REDIRECT_URI="moshidon-android-auth://callback";
|
||||
public static final String REDIRECT_URI = getRedirectURI();
|
||||
|
||||
private static final AccountSessionManager instance=new AccountSessionManager();
|
||||
|
||||
@@ -80,6 +84,17 @@ public class AccountSessionManager{
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static String getRedirectURI() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("moshidon-android-");
|
||||
if (BuildConfig.BUILD_TYPE.equals("debug") || BuildConfig.BUILD_TYPE.equals("nightly")) {
|
||||
builder.append(BuildConfig.BUILD_TYPE);
|
||||
builder.append('-');
|
||||
}
|
||||
builder.append("auth://callback");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private AccountSessionManager(){
|
||||
prefs=MastodonApp.context.getSharedPreferences("account_manager", Context.MODE_PRIVATE);
|
||||
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
||||
@@ -211,7 +226,7 @@ public class AccountSessionManager{
|
||||
.path("/oauth/authorize")
|
||||
.appendQueryParameter("response_type", "code")
|
||||
.appendQueryParameter("client_id", result.clientId)
|
||||
.appendQueryParameter("redirect_uri", "moshidon-android-auth://callback")
|
||||
.appendQueryParameter("redirect_uri", REDIRECT_URI)
|
||||
.appendQueryParameter("scope", SCOPE)
|
||||
.build();
|
||||
|
||||
@@ -255,6 +270,7 @@ public class AccountSessionManager{
|
||||
// if(now-session.filtersLastUpdated>3600_000L){
|
||||
updateSessionWordFilters(session);
|
||||
// }
|
||||
updateSessionMarkers(session);
|
||||
}
|
||||
if(loadedInstances){
|
||||
maybeUpdateCustomEmojis(domains);
|
||||
@@ -271,6 +287,15 @@ public class AccountSessionManager{
|
||||
}
|
||||
}
|
||||
|
||||
private void preferencesFromSource(AccountSession session, Account account) {
|
||||
if (account != null && account.source != null && session.preferences != null) {
|
||||
if (account.source.privacy != null)
|
||||
session.preferences.postingDefaultVisibility = account.source.privacy;
|
||||
if (account.source.language != null)
|
||||
session.preferences.postingDefaultLanguage = account.source.language;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSessionLocalInfo(AccountSession session){
|
||||
new GetOwnAccount()
|
||||
.setCallback(new Callback<>(){
|
||||
@@ -278,13 +303,12 @@ public class AccountSessionManager{
|
||||
public void onSuccess(Account result){
|
||||
session.self=result;
|
||||
session.infoLastUpdated=System.currentTimeMillis();
|
||||
preferencesFromSource(session, result);
|
||||
writeAccountsFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
|
||||
}
|
||||
public void onError(ErrorResponse error){}
|
||||
})
|
||||
.exec(session.getID());
|
||||
}
|
||||
@@ -294,10 +318,14 @@ public class AccountSessionManager{
|
||||
@Override
|
||||
public void onSuccess(Preferences preferences) {
|
||||
session.preferences=preferences;
|
||||
preferencesFromSource(session, session.self);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {}
|
||||
public void onError(ErrorResponse error) {
|
||||
session.preferences = new Preferences();
|
||||
preferencesFromSource(session, session.self);
|
||||
}
|
||||
}).exec(session.getID());
|
||||
}
|
||||
|
||||
@@ -319,6 +347,21 @@ public class AccountSessionManager{
|
||||
.exec(session.getID());
|
||||
}
|
||||
|
||||
private void updateSessionMarkers(AccountSession session) {
|
||||
new GetMarkers(EnumSet.allOf(Marker.Type.class)).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Markers markers) {
|
||||
session.markers = markers;
|
||||
writeAccountsFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
|
||||
}
|
||||
}).exec(session.getID());
|
||||
}
|
||||
|
||||
public void updateInstanceInfo(String domain){
|
||||
new GetInstance()
|
||||
.setCallback(new Callback<>(){
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
public class AllNotificationsSeenEvent {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.joinmastodon.android.events;
|
||||
|
||||
public class NotificationReceivedEvent {
|
||||
public String account, id;
|
||||
public NotificationReceivedEvent(String account, String id) {
|
||||
this.account = account;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
@@ -20,11 +21,14 @@ import android.view.animation.TranslateAnimation;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||
@@ -32,12 +36,10 @@ import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.TileGridLayoutManager;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
@@ -45,8 +47,10 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout;
|
||||
import org.joinmastodon.android.ui.views.MediaGridLayout;
|
||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -57,7 +61,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
@@ -71,7 +74,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop{
|
||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, DomainDisplay{
|
||||
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
||||
protected DisplayItemsAdapter adapter;
|
||||
protected String accountID;
|
||||
@@ -81,6 +84,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
protected HashMap<String, Account> knownAccounts=new HashMap<>();
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected Rect tmpRect=new Rect();
|
||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||
|
||||
private final int THRESHOLD = 800;
|
||||
|
||||
@@ -96,6 +100,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
UiUtils.loadMaxWidth(getContext());
|
||||
if(GlobalUserPreferences.disableMarquee){
|
||||
setTitleMarqueeEnabled(false);
|
||||
setSubtitleMarqueeEnabled(false);
|
||||
@@ -192,21 +197,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex){
|
||||
final Status status=_status.reblog!=null ? _status.reblog : _status;
|
||||
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
|
||||
final Status status=_status.getContentStatus();
|
||||
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
|
||||
private ImageStatusDisplayItem.Holder<?> transitioningHolder;
|
||||
private MediaAttachmentViewController transitioningHolder;
|
||||
|
||||
@Override
|
||||
public void setPhotoViewVisibility(int index, boolean visible){
|
||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
||||
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||
if(holder!=null)
|
||||
holder.photo.setAlpha(visible ? 1f : 0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
||||
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||
if(holder!=null){
|
||||
transitioningHolder=holder;
|
||||
View view=transitioningHolder.photo;
|
||||
@@ -214,7 +219,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
view.getLocationOnScreen(pos);
|
||||
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
|
||||
list.setClipChildren(false);
|
||||
transitioningHolder.itemView.setElevation(1f);
|
||||
gridHolder.setClipChildren(false);
|
||||
transitioningHolder.view.setElevation(1f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -241,15 +247,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
view.setTranslationY(0f);
|
||||
view.setScaleX(1f);
|
||||
view.setScaleY(1f);
|
||||
transitioningHolder.itemView.setElevation(0f);
|
||||
transitioningHolder.view.setElevation(0f);
|
||||
if(list!=null)
|
||||
list.setClipChildren(true);
|
||||
gridHolder.setClipChildren(true);
|
||||
transitioningHolder=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable getPhotoViewCurrentDrawable(int index){
|
||||
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
|
||||
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||
if(holder!=null)
|
||||
return holder.photo.getDrawable();
|
||||
return null;
|
||||
@@ -265,23 +272,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST);
|
||||
}
|
||||
|
||||
private ImageStatusDisplayItem.Holder<?> findPhotoViewHolder(int index){
|
||||
if(list==null)
|
||||
return null;
|
||||
int offset=0;
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
if(item.parentID.equals(parentID)){
|
||||
if(item instanceof ImageStatusDisplayItem){
|
||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(getMainAdapterOffset()+offset+index);
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
||||
return imgHolder;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return null;
|
||||
private MediaAttachmentViewController findPhotoViewHolder(int index){
|
||||
return gridHolder.getViewController(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -380,31 +372,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView.LayoutManager onCreateLayoutManager(){
|
||||
GridLayoutManager lm=new TileGridLayoutManager(getActivity(), 1000);
|
||||
lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup(){
|
||||
@Override
|
||||
public int getSpanSize(int position){
|
||||
position-=getMainAdapterOffset();
|
||||
if(position>=0 && position<displayItems.size()){
|
||||
StatusDisplayItem item=displayItems.get(position);
|
||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=imgItem.tiledLayout;
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgItem.thisTile;
|
||||
int spans=0;
|
||||
for(int i=0;i<tile.colSpan;i++){
|
||||
spans+=layout.columnSizes[tile.startCol+i];
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
});
|
||||
return lm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig){
|
||||
super.onConfigurationChanged(newConfig);
|
||||
@@ -453,7 +420,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
|
||||
int prevSize=pollItems.size();
|
||||
pollItems.clear();
|
||||
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems);
|
||||
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems, status);
|
||||
if(prevSize!=pollItems.size()){
|
||||
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
|
||||
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
|
||||
@@ -490,12 +457,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(holder.getItem().status.reloadWhenClicked){
|
||||
Status queryStatus = holder.getItem().status;
|
||||
UiUtils.lookupStatus(getContext(), queryStatus, accountID, null, status -> {
|
||||
submitPollVote(holder.getItemID(), status.poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
|
||||
});
|
||||
return;
|
||||
}
|
||||
submitPollVote(holder.getItemID(), poll.id, Collections.singletonList(poll.options.indexOf(option)));
|
||||
}
|
||||
}
|
||||
|
||||
public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){
|
||||
Poll poll=holder.getItem().poll;
|
||||
if(holder.getItem().status.reloadWhenClicked){
|
||||
Status queryStatus = holder.getItem().status;
|
||||
UiUtils.lookupStatus(getContext(), queryStatus, accountID, null, status -> {
|
||||
submitPollVote(holder.getItemID(), status.poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
|
||||
});
|
||||
return;
|
||||
}
|
||||
submitPollVote(holder.getItemID(), poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@@ -523,7 +504,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
revealSpoiler(status, holder.getItemID());
|
||||
}
|
||||
|
||||
public void onRevealSpoilerClick(ImageStatusDisplayItem.Holder<?> holder){
|
||||
public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){
|
||||
Status status=holder.getItem().status;
|
||||
revealSpoiler(status, holder.getItemID());
|
||||
}
|
||||
@@ -557,7 +538,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
holder.getItem().status.textExpandable = expandable;
|
||||
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
||||
if (header != null) header.rebind();
|
||||
holder.rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,13 +551,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
|
||||
protected void updateImagesSpoilerState(Status status, String itemID){
|
||||
ArrayList<Integer> updatedPositions=new ArrayList<>();
|
||||
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
|
||||
photo.setRevealed(status.spoilerRevealed);
|
||||
updatedPositions.add(photo.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
|
||||
if(mediaGrid!=null){
|
||||
mediaGrid.setRevealed(status.spoilerRevealed);
|
||||
updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||
}
|
||||
int i=0;
|
||||
for(StatusDisplayItem item:displayItems){
|
||||
if(itemID.equals(item.parentID) && item instanceof ImageStatusDisplayItem && !updatedPositions.contains(i)){
|
||||
if(itemID.equals(item.parentID) && item instanceof MediaGridStatusDisplayItem && !updatedPositions.contains(i)){
|
||||
adapter.notifyItemChanged(i);
|
||||
}
|
||||
i++;
|
||||
@@ -664,6 +645,11 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0) == null || list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
protected int getListWidthForMediaLayout(){
|
||||
return list.getWidth();
|
||||
}
|
||||
@@ -720,6 +706,15 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
||||
}
|
||||
|
||||
private MediaAttachmentViewController makeNewMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){
|
||||
return new MediaAttachmentViewController(getActivity(), type);
|
||||
}
|
||||
|
||||
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> getAttachmentViewsPool(){
|
||||
return attachmentViewsPool;
|
||||
}
|
||||
|
||||
|
||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public DisplayItemsAdapter(){
|
||||
@@ -757,16 +752,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
||||
return displayItems.get(position).getImageRequest(image);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void onViewDetachedFromWindow(@NonNull BindableViewHolder<StatusDisplayItem> holder){
|
||||
// if(holder instanceof ImageLoaderViewHolder){
|
||||
// int count=holder.getItem().getImageCount();
|
||||
// for(int i=0;i<count;i++){
|
||||
// ((ImageLoaderViewHolder) holder).clearImage(i);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
|
||||
@@ -800,25 +785,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
||||
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
||||
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||
hiddenMediaPaint.setColor(0x80000000);
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
||||
float hGap=tile.startCol>0 ? V.dp(1) : 0;
|
||||
float vGap=tile.startRow>0 ? V.dp(1) : 0;
|
||||
c.drawRect(child.getX()-hGap, child.getY()-vGap, child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
|
||||
c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
for(int i=0;i<parent.getChildCount();i++){
|
||||
View child=parent.getChildAt(i);
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
||||
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
|
||||
if(!imgHolder.getItem().status.spoilerRevealed){
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
||||
if(tile.startCol==0 && tile.startRow==0 && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
|
||||
int listWidth=getListWidthForMediaLayout();
|
||||
int width=Math.min(listWidth, V.dp(ImageAttachmentFrameLayout.MAX_WIDTH));
|
||||
int width=Math.min(listWidth, UiUtils.MAX_WIDTH);
|
||||
if(currentMediaHiddenLayoutsWidth!=width)
|
||||
rebuildMediaHiddenLayouts(width-V.dp(32));
|
||||
c.save();
|
||||
@@ -843,47 +824,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder){
|
||||
int listWidth=getListWidthForMediaLayout();
|
||||
int width=Math.min(listWidth, V.dp(ImageAttachmentFrameLayout.MAX_WIDTH));
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=((ImageStatusDisplayItem.Holder<?>) holder).getItem().tiledLayout;
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=((ImageStatusDisplayItem.Holder<?>) holder).getItem().thisTile;
|
||||
if(tile.startCol+tile.colSpan<layout.columnSizes.length){
|
||||
outRect.right=V.dp(1);
|
||||
}
|
||||
if(tile.startRow+tile.rowSpan<layout.rowSizes.length){
|
||||
outRect.bottom=V.dp(1);
|
||||
}
|
||||
|
||||
// For a view that spans rows, compensate its additional height so the row it's in stays the right height
|
||||
if(tile.rowSpan>1){
|
||||
outRect.bottom=-(Math.round(tile.height/1000f*width)-Math.round(layout.rowSizes[tile.startRow]/1000f*width));
|
||||
}
|
||||
// ...and for its siblings, offset those on rows below first to the right where they belong
|
||||
if(tile.startCol>0 && layout.tiles[0].rowSpan>1 && tile.startRow>layout.tiles[0].startRow){
|
||||
int xOffset=Math.round(layout.tiles[0].width/1000f*listWidth);
|
||||
outRect.left=xOffset;
|
||||
outRect.right=-xOffset;
|
||||
}
|
||||
|
||||
// If the width of the media block is smaller than that of the RecyclerView, offset the views horizontally to center them
|
||||
if(listWidth>width){
|
||||
outRect.left+=(listWidth-V.dp(ImageAttachmentFrameLayout.MAX_WIDTH))/2;
|
||||
if(tile.startCol>0){
|
||||
int spanOffset=0;
|
||||
for(int i=0;i<tile.startCol;i++){
|
||||
spanOffset+=layout.columnSizes[i];
|
||||
}
|
||||
outRect.left-=Math.round(spanOffset/1000f*listWidth);
|
||||
outRect.left+=Math.round(spanOffset/1000f*width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildMediaHiddenLayouts(int width){
|
||||
currentMediaHiddenLayoutsWidth=width;
|
||||
String title=getString(R.string.sensitive_content);
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.graphics.drawable.LayerDrawable;
|
||||
import android.icu.text.BreakIterator;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.net.Uri;
|
||||
import android.opengl.Visibility;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
@@ -111,7 +112,7 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
|
||||
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||
import org.joinmastodon.android.ui.utils.TransferSpeedTracker;
|
||||
import org.joinmastodon.android.utils.TransferSpeedTracker;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ComposeEditText;
|
||||
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
|
||||
@@ -206,6 +207,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
private List<EmojiCategory> customEmojis;
|
||||
private CustomEmojiPopupKeyboard emojiKeyboard;
|
||||
private Status replyTo;
|
||||
private Status quote;
|
||||
private String initialText;
|
||||
private String uuid;
|
||||
private int pollDuration=24*3600;
|
||||
@@ -258,6 +260,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
|
||||
if(getArguments().containsKey("replyTo"))
|
||||
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
|
||||
if(getArguments().containsKey("quote"))
|
||||
quote=Parcels.unwrap(getArguments().getParcelable("quote"));
|
||||
if(instance==null){
|
||||
Nav.finish(this);
|
||||
return;
|
||||
@@ -355,7 +359,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
scheduleTimeBtn=view.findViewById(R.id.scheduled_time_btn);
|
||||
sensitiveIcon=view.findViewById(R.id.sensitive_icon);
|
||||
sensitiveItem=view.findViewById(R.id.sensitive_item);
|
||||
replyText=view.findViewById(R.id.reply_text);
|
||||
replyText=view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text : R.id.reply_text_below);
|
||||
view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text_below : R.id.reply_text)
|
||||
.setVisibility(View.GONE);
|
||||
|
||||
if (isPhotoPickerAvailable()) {
|
||||
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
|
||||
@@ -627,7 +633,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
});
|
||||
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
|
||||
if(replyTo!=null){
|
||||
if(replyTo!=null || quote!=null){
|
||||
Status status = quote!=null ? quote : replyTo;
|
||||
View replyWrap = view.findViewById(R.id.reply_wrap);
|
||||
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||
int scrollHeight = scrollView.getHeight();
|
||||
@@ -653,13 +660,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
originalPost.setOnClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("status", Parcels.wrap(replyTo));
|
||||
args.putParcelable("status", Parcels.wrap(status));
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||
});
|
||||
|
||||
ImageView avatar = view.findViewById(R.id.avatar);
|
||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(replyTo.account.avatar));
|
||||
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(status.account.avatar));
|
||||
ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline){
|
||||
@@ -671,15 +678,15 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
avatar.setOnClickListener(v->{
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(replyTo.account));
|
||||
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
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());
|
||||
((TextView) view.findViewById(R.id.name)).setText(status.account.displayName);
|
||||
((TextView) view.findViewById(R.id.username)).setText(status.account.getDisplayUsername());
|
||||
view.findViewById(R.id.visibility).setVisibility(View.GONE);
|
||||
Drawable visibilityIcon = getActivity().getDrawable(switch(replyTo.visibility){
|
||||
Drawable visibilityIcon = getActivity().getDrawable(switch(status.visibility){
|
||||
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
|
||||
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular;
|
||||
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled;
|
||||
@@ -690,27 +697,28 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
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()) {
|
||||
if (status.editedAt!=null) timestamp.setText(getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), status.editedAt)));
|
||||
else if (status.createdAt!=null) timestamp.setText(UiUtils.formatRelativeTimestamp(getContext(), status.createdAt));
|
||||
else timestamp.setText("");
|
||||
if (status.spoilerText != null && !status.spoilerText.isBlank()) {
|
||||
view.findViewById(R.id.spoiler_header).setVisibility(View.VISIBLE);
|
||||
((TextView) view.findViewById(R.id.spoiler_title_inline)).setText(replyTo.spoilerText);
|
||||
((TextView) view.findViewById(R.id.spoiler_title_inline)).setText(status.spoilerText);
|
||||
}
|
||||
|
||||
SpannableStringBuilder content = HtmlParser.parse(replyTo.content, replyTo.emojis, replyTo.mentions, replyTo.tags, accountID);
|
||||
SpannableStringBuilder content = HtmlParser.parse(status.content, status.emojis, status.mentions, status.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) {
|
||||
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.displayName));
|
||||
int visibilityNameRes = switch (status.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;
|
||||
case LOCAL -> R.string.sk_local_only;
|
||||
};
|
||||
replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
|
||||
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
|
||||
replyText.setOnClickListener(v->{
|
||||
scrollView.smoothScrollTo(0, 0);
|
||||
});
|
||||
@@ -721,9 +729,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
|
||||
ArrayList<String> mentions=new ArrayList<>();
|
||||
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
||||
if(!replyTo.account.id.equals(ownID))
|
||||
mentions.add('@'+replyTo.account.acct);
|
||||
for(Mention mention:replyTo.mentions){
|
||||
if(!status.account.id.equals(ownID))
|
||||
mentions.add('@'+status.account.acct);
|
||||
if(status.rebloggedBy != null && GlobalUserPreferences.mentionRebloggerAutomatically)
|
||||
mentions.add('@'+status.rebloggedBy.acct);
|
||||
for(Mention mention:status.mentions){
|
||||
if(mention.id.equals(ownID))
|
||||
continue;
|
||||
String m='@'+mention.acct;
|
||||
@@ -736,17 +746,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
ignoreSelectionChanges=true;
|
||||
mainEditText.setSelection(mainEditText.length());
|
||||
ignoreSelectionChanges=false;
|
||||
if(!TextUtils.isEmpty(replyTo.spoilerText)){
|
||||
if(!TextUtils.isEmpty(status.spoilerText)){
|
||||
hasSpoiler=true;
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
if(GlobalUserPreferences.prefixRepliesWithRe && !replyTo.spoilerText.startsWith("re: ")){
|
||||
spoilerEdit.setText("re: " + replyTo.spoilerText);
|
||||
if(GlobalUserPreferences.prefixRepliesWithRe && !status.spoilerText.startsWith("re: ")){
|
||||
spoilerEdit.setText("re: " + status.spoilerText);
|
||||
}else{
|
||||
spoilerEdit.setText(replyTo.spoilerText);
|
||||
spoilerEdit.setText(status.spoilerText);
|
||||
}
|
||||
spoilerBtn.setSelected(true);
|
||||
}
|
||||
if (replyTo.language != null && !replyTo.language.isEmpty()) updateLanguage(replyTo.language);
|
||||
if (status.language != null && !status.language.isEmpty()) updateLanguage(status.language);
|
||||
}
|
||||
}else if (editingStatus==null || editingStatus.inReplyToId==null){
|
||||
// TODO: remove workaround after https://github.com/mastodon/mastodon-android/issues/341 gets fixed
|
||||
@@ -1012,9 +1022,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
private void onCustomEmojiClick(Emoji emoji){
|
||||
int start=mainEditText.getSelectionStart();
|
||||
String prefix=start>0 && !Character.isWhitespace(mainEditText.getText().charAt(start-1)) ? " :" : ":";
|
||||
mainEditText.getText().replace(start, mainEditText.getSelectionEnd(), prefix+emoji.shortcode+':');
|
||||
if(getActivity().getCurrentFocus() instanceof EditText edit){
|
||||
int start=edit.getSelectionStart();
|
||||
String prefix=start>0 && !Character.isWhitespace(edit.getText().charAt(start-1)) ? " :" : ":";
|
||||
edit.getText().replace(start, edit.getSelectionEnd(), prefix+emoji.shortcode+':');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1137,6 +1149,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(hasSpoiler && spoilerEdit.length()>0){
|
||||
req.spoilerText=spoilerEdit.getText().toString();
|
||||
}
|
||||
if(quote != null){
|
||||
req.quoteId=quote.id;
|
||||
}
|
||||
if(uuid==null)
|
||||
uuid=UUID.randomUUID().toString();
|
||||
|
||||
@@ -1169,7 +1184,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}else{
|
||||
E.post(new StatusUpdatedEvent(result));
|
||||
}
|
||||
Nav.finish(ComposeFragment.this);
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
|
||||
Nav.finish(ComposeFragment.this);
|
||||
}
|
||||
if (getArguments().getBoolean("navigateToStatus", false)) {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -1370,6 +1387,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private boolean addMediaAttachment(Uri uri, String description){
|
||||
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){
|
||||
showMediaAttachmentError(getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS));
|
||||
@@ -1586,7 +1604,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(att.isUploadingOrProcessing())
|
||||
att.cancelUpload();
|
||||
attachments.remove(att);
|
||||
uploadNextQueuedAttachment();
|
||||
if(!areThereAnyUploadingAttachments())
|
||||
uploadNextQueuedAttachment();
|
||||
attachmentsView.removeView(att.view);
|
||||
if(getMediaAttachmentsCount()==0)
|
||||
attachmentsView.setVisibility(View.GONE);
|
||||
@@ -1945,7 +1964,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
|
||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
||||
if(replyTo != null) statusVisibility = replyTo.visibility;
|
||||
if(replyTo != null) {
|
||||
statusVisibility = (replyTo.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : replyTo.visibility);
|
||||
}
|
||||
|
||||
// A saved privacy setting from a previous compose session wins over the reply visibility
|
||||
if(savedInstanceState !=null){
|
||||
|
||||
@@ -1,27 +1,17 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByHandle;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class CustomLocalTimelineFragment extends StatusListFragment {
|
||||
@@ -48,6 +38,11 @@ public class CustomLocalTimelineFragment extends StatusListFragment {
|
||||
setTitle(this.domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
|
||||
@@ -60,6 +55,7 @@ public class CustomLocalTimelineFragment extends StatusListFragment {
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||
result.stream().forEach(status -> {
|
||||
status.account.acct += "@"+domain;
|
||||
status.mentions.forEach(mention -> mention.id = null);
|
||||
status.reloadWhenClicked = true;
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
|
||||
public interface DomainDisplay {
|
||||
|
||||
default String getDomain(){
|
||||
AccountSession session = AccountSessionManager.getInstance().getLastActiveAccount();
|
||||
if (session != null)
|
||||
return session.domain;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -50,12 +50,11 @@ import java.util.Map;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
||||
public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
||||
private String accountID;
|
||||
private TimelinesAdapter adapter;
|
||||
private final ItemTouchHelper itemTouchHelper;
|
||||
@@ -151,7 +150,7 @@ public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefiniti
|
||||
FrameLayout inputWrap = new FrameLayout(getContext());
|
||||
EditText input = new EditText(getContext());
|
||||
input.setHint(R.string.sk_example_domain);
|
||||
input.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
|
||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
|
||||
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);
|
||||
@@ -237,6 +236,11 @@ public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefiniti
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
@@ -38,7 +38,6 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
@@ -47,7 +46,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
|
||||
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
@@ -149,6 +148,11 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public AccountsAdapter(){
|
||||
|
||||
@@ -16,11 +16,10 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop {
|
||||
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop {
|
||||
private String nextMaxID;
|
||||
private String accountId;
|
||||
|
||||
@@ -76,6 +75,11 @@ public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.tags.GetHashtag;
|
||||
@@ -43,12 +44,19 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/tags/" + hashtag;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
updateTitle(getArguments().getString("hashtag"));
|
||||
following=getArguments().getBoolean("following", false);
|
||||
setHasOptionsMenu(true);
|
||||
DomainManager.getInstance().setCurrentDomain(getDomain());
|
||||
}
|
||||
|
||||
private void updateTitle(String hashtagName) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Outline;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -16,21 +17,44 @@ import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
||||
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
||||
import org.joinmastodon.android.fragments.discover.DiscoverAccountsFragment;
|
||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestionsFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.TabBar;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import me.grishka.appkit.FragmentStackActivity;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.LoaderFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
@@ -44,14 +68,13 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
|
||||
private HomeTabFragment homeTabFragment;
|
||||
|
||||
// private HomeTimelineFragment homeTimelineFragment;
|
||||
|
||||
private NotificationsFragment notificationsFragment;
|
||||
private DiscoverFragment searchFragment;
|
||||
private ProfileFragment profileFragment;
|
||||
private TabBar tabBar;
|
||||
private View tabBarWrap;
|
||||
private ImageView tabBarAvatar;
|
||||
private ImageView notificationTabIcon;
|
||||
@IdRes
|
||||
private int currentTab=R.id.tab_home;
|
||||
|
||||
@@ -60,6 +83,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
E.register(this);
|
||||
accountID=getArguments().getString("account");
|
||||
setTitle(R.string.mo_app_name);
|
||||
|
||||
@@ -73,9 +97,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
homeTabFragment=new HomeTabFragment();
|
||||
homeTabFragment.setArguments(args);
|
||||
|
||||
// homeTimelineFragment=new HomeTimelineFragment();
|
||||
// homeTimelineFragment.setArguments(args);
|
||||
|
||||
args=new Bundle(args);
|
||||
args.putBoolean("noAutoLoad", true);
|
||||
searchFragment=new DiscoverFragment();
|
||||
@@ -98,7 +119,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
content.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
FrameLayout fragmentContainer=new FrameLayout(getActivity());
|
||||
fragmentContainer.setId(R.id.fragment_wrap);
|
||||
fragmentContainer.setId(me.grishka.appkit.R.id.fragment_wrap);
|
||||
content.addView(fragmentContainer, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||
|
||||
inflater.inflate(R.layout.tab_bar, content);
|
||||
@@ -117,20 +138,17 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||
ViewImageLoader.load(tabBarAvatar, null, new UrlImageLoaderRequest(self.avatar, V.dp(28), V.dp(28)));
|
||||
|
||||
notificationTabIcon=content.findViewById(R.id.tab_notifications);
|
||||
updateNotificationBadge();
|
||||
|
||||
if(savedInstanceState==null){
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.add(R.id.fragment_wrap, homeTabFragment)
|
||||
.add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||
.add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||
.add(me.grishka.appkit.R.id.fragment_wrap, homeTabFragment)
|
||||
.add(me.grishka.appkit.R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||
.add(me.grishka.appkit.R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||
.commit();
|
||||
|
||||
// getChildFragmentManager().beginTransaction()
|
||||
// .add(R.id.fragment_wrap, homeTimelineFragment)
|
||||
// .add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||
// .add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||
// .add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||
// .commit();
|
||||
|
||||
String defaultTab=getArguments().getString("tab");
|
||||
if("notifications".equals(defaultTab)){
|
||||
@@ -155,12 +173,9 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
|
||||
if(savedInstanceState==null) return;
|
||||
|
||||
// if(savedInstanceState==null || homeTimelineFragment!=null)
|
||||
// return;
|
||||
|
||||
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
|
||||
|
||||
// homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment");
|
||||
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
||||
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
||||
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
||||
@@ -175,19 +190,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
.show(current)
|
||||
.commit();
|
||||
|
||||
// getChildFragmentManager().beginTransaction()
|
||||
// .hide(homeTimelineFragment)
|
||||
// .hide(searchFragment)
|
||||
// .hide(notificationsFragment)
|
||||
// .hide(profileFragment)
|
||||
// .show(current)
|
||||
// .commit();
|
||||
maybeTriggerLoading(current);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHiddenChanged(boolean hidden){
|
||||
super.onHiddenChanged(hidden);
|
||||
if (!hidden && fragmentForTab(currentTab) instanceof DomainDisplay display)
|
||||
DomainManager.getInstance().setCurrentDomain(display.getDomain());
|
||||
fragmentForTab(currentTab).onHiddenChanged(hidden);
|
||||
}
|
||||
|
||||
@@ -214,8 +224,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
|
||||
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
|
||||
// homeTimelineFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
|
||||
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
||||
@@ -224,9 +232,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
private Fragment fragmentForTab(@IdRes int tab){
|
||||
if(tab==R.id.tab_home){
|
||||
return homeTabFragment;
|
||||
|
||||
// if(tab==R.id.tab_home){
|
||||
// return homeTimelineFragment;
|
||||
}else if(tab==R.id.tab_search){
|
||||
return searchFragment;
|
||||
}else if(tab==R.id.tab_notifications){
|
||||
@@ -255,6 +260,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
scrollable.scrollToTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (newFragment instanceof DomainDisplay display) {
|
||||
DomainManager.getInstance().setCurrentDomain(display.getDomain());
|
||||
}
|
||||
|
||||
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
||||
maybeTriggerLoading(newFragment);
|
||||
currentTab=tab;
|
||||
@@ -285,7 +295,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
||||
}
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
new AccountSwitcherSheet(getActivity(), true, true, false, accountSession -> {
|
||||
getActivity().finish();
|
||||
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
||||
}).show();
|
||||
return true;
|
||||
}
|
||||
if(tab==R.id.tab_search){
|
||||
@@ -294,6 +307,11 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
searchFragment.selectSearch();
|
||||
return true;
|
||||
}
|
||||
if(tab==R.id.tab_home){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -321,10 +339,50 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
||||
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||
if (profileFragment.isAdded()) getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||
}
|
||||
|
||||
// getChildFragmentManager().putFragment(outState, "homeTimelineFragment", homeTimelineFragment);
|
||||
// getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||
// getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||
// getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||
public void updateNotificationBadge() {
|
||||
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||
if (instance == null) return;
|
||||
|
||||
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.pleroma != null)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(List<Notification> notifications) {
|
||||
if (notifications.size() > 0) {
|
||||
try {
|
||||
long newestId = Long.parseLong(notifications.get(0).id);
|
||||
long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId);
|
||||
System.out.println("NEWEST: " + newestId);
|
||||
System.out.println("LAST SEEN: " + lastSeenId);
|
||||
|
||||
setNotificationBadge(newestId > lastSeenId);
|
||||
} catch (Exception ignored) {
|
||||
setNotificationBadge(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
setNotificationBadge(false);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
public void setNotificationBadge(boolean badge) {
|
||||
notificationTabIcon.setImageResource(badge
|
||||
? R.drawable.ic_fluent_alert_28_selector_badged
|
||||
: R.drawable.ic_fluent_alert_28_selector);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onNotificationReceived(NotificationReceivedEvent notificationReceivedEvent) {
|
||||
if (notificationReceivedEvent.account.equals(accountID)) setNotificationBadge(true);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
|
||||
setNotificationBadge(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -45,6 +46,8 @@ import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsFragment;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
||||
import org.joinmastodon.android.model.Announcement;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
@@ -70,7 +73,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener {
|
||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay {
|
||||
private static final int ANNOUNCEMENTS_RESULT = 654;
|
||||
|
||||
private String accountID;
|
||||
@@ -111,6 +114,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
fragments = new Fragment[count];
|
||||
tabViews = new FrameLayout[count];
|
||||
timelines = new TimelineDefinition[count];
|
||||
if(GlobalUserPreferences.disableMarquee){
|
||||
setTitleMarqueeEnabled(false);
|
||||
setSubtitleMarqueeEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -196,6 +203,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
if (fragments[position] instanceof BaseRecyclerFragment<?> page){
|
||||
if(!page.loaded && !page.isDataLoading()) page.loadData();
|
||||
}
|
||||
|
||||
//update recent app list url
|
||||
if (fragments[position] instanceof DomainDisplay page)
|
||||
DomainManager.getInstance().setCurrentDomain(page.getDomain());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -280,6 +291,14 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
if (fragments[pager.getCurrentItem()] instanceof DomainDisplay page) {
|
||||
return page.getDomain();
|
||||
}
|
||||
return DomainDisplay.super.getDomain();
|
||||
}
|
||||
|
||||
private void addListsToOverflowMenu() {
|
||||
Context ctx = getContext();
|
||||
listsMenu.clear();
|
||||
@@ -358,7 +377,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
addListsToOverflowMenu();
|
||||
addHashtagsToOverflowMenu();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
|
||||
m.setGroupDividerEnabled(true);
|
||||
}
|
||||
}
|
||||
@@ -444,7 +463,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
getToolbar().post(() -> overflowPopup.show());
|
||||
return true;
|
||||
} else if (id == R.id.settings || id == R.id.settings_action) {
|
||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||
Nav.go(getActivity(), SettingsMainFragment.class, args);
|
||||
} else if (id == R.id.announcements || id == R.id.announcements_action) {
|
||||
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
||||
} else if (id == R.id.edit_timelines) {
|
||||
@@ -464,9 +483,20 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
|
||||
@Override
|
||||
public void scrollToTop(){
|
||||
if (((ScrollableToTop) fragments[pager.getCurrentItem()]).isScrolledToTop() &&
|
||||
!GlobalUserPreferences.disableDoubleTapToSwipe && !newPostsBtnShown) {
|
||||
int nextPage = (pager.getCurrentItem() + 1) % count;
|
||||
navigateTo(nextPage);
|
||||
return;
|
||||
}
|
||||
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return ((ScrollableToTop) fragments[pager.getCurrentItem()]).isScrolledToTop();
|
||||
}
|
||||
|
||||
public void hideNewPostsButton(){
|
||||
if(!newPostsBtnShown)
|
||||
return;
|
||||
@@ -537,8 +567,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
||||
|
||||
private void onNewPostsBtnClick(View view) {
|
||||
if(newPostsBtnShown){
|
||||
hideNewPostsButton();
|
||||
scrollToTop();
|
||||
hideNewPostsButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,11 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/home";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
@@ -167,6 +172,10 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
|
||||
if (parent.getParentFragment() instanceof HomeFragment homeFragment) {
|
||||
homeFragment.updateNotificationBadge();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,11 +37,10 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||
public class ListTimelinesFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||
private String accountId;
|
||||
private String profileAccountId;
|
||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||
@@ -200,6 +199,11 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
||||
@NonNull
|
||||
@Override
|
||||
|
||||
@@ -37,7 +37,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop{
|
||||
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, DomainDisplay{
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 pager;
|
||||
@@ -48,6 +48,11 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
|
||||
private String accountID;
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return DomainDisplay.super.getDomain() + "/notifications";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -140,17 +145,20 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("__is_tab", true);
|
||||
args.putBoolean("noAutoLoad", true);
|
||||
|
||||
allNotificationsFragment=new NotificationsListFragment();
|
||||
allNotificationsFragment.setArguments(args);
|
||||
|
||||
args=new Bundle(args);
|
||||
args.putBoolean("onlyMentions", true);
|
||||
args.putBoolean("noAutoLoad", true);
|
||||
mentionsFragment=new NotificationsListFragment();
|
||||
mentionsFragment.setArguments(args);
|
||||
|
||||
args=new Bundle(args);
|
||||
args.putBoolean("onlyPosts", true);
|
||||
args.putBoolean("noAutoLoad", true);
|
||||
postsFragment=new NotificationsListFragment();
|
||||
postsFragment.setArguments(args);
|
||||
|
||||
@@ -197,9 +205,19 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
|
||||
@Override
|
||||
public void scrollToTop(){
|
||||
if (getFragmentForPage(pager.getCurrentItem()).isScrolledToTop() && !GlobalUserPreferences.disableDoubleTapToSwipe) {
|
||||
int nextPage = (pager.getCurrentItem() + 1) % tabViews.length;
|
||||
pager.setCurrentItem(nextPage, true);
|
||||
return;
|
||||
}
|
||||
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return getFragmentForPage(pager.getCurrentItem()).isScrolledToTop();
|
||||
}
|
||||
|
||||
public void loadData(){
|
||||
refreshFollowRequestsBadge();
|
||||
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
|
||||
@@ -226,7 +244,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
||||
@Override
|
||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
FrameLayout view=tabViews[viewType];
|
||||
((ViewGroup)view.getParent()).removeView(view);
|
||||
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
return new SimpleViewHolder(view);
|
||||
|
||||
@@ -11,19 +11,21 @@ import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -40,7 +42,6 @@ import java.util.stream.Stream;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
|
||||
private boolean onlyMentions;
|
||||
@@ -53,6 +54,11 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/notifications";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -84,6 +90,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||
Account reportTarget = n.report == null ? null : n.report.targetAccount == null ? null :
|
||||
n.report.targetAccount;
|
||||
Emoji emoji = new Emoji();
|
||||
if(n.emojiUrl!=null){
|
||||
emoji.shortcode=n.emoji.substring(1,n.emoji.length()-1);
|
||||
emoji.url=n.emojiUrl;
|
||||
emoji.staticUrl=n.emojiUrl;
|
||||
emoji.visibleInPicker=false;
|
||||
}
|
||||
String extraText=switch(n.type){
|
||||
case FOLLOW -> getString(R.string.user_followed_you);
|
||||
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request);
|
||||
@@ -94,17 +107,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
case UPDATE -> getString(R.string.sk_post_edited);
|
||||
case SIGN_UP -> getString(R.string.sk_signed_up);
|
||||
case REPORT -> getString(R.string.sk_reported);
|
||||
case EMOJI_REACTION -> getString(R.string.sk_reacted, n.emoji);
|
||||
};
|
||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null;
|
||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, n.emojiUrl!=null ? HtmlParser.parseCustomEmoji(extraText, Collections.singletonList(emoji)) : extraText, n, null) : null;
|
||||
if(n.status!=null){
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS, titleItem);
|
||||
if(titleItem!=null){
|
||||
for(StatusDisplayItem item:items){
|
||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
||||
imgItem.horizontalInset=V.dp(32);
|
||||
}
|
||||
}
|
||||
}
|
||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS);
|
||||
if(titleItem!=null)
|
||||
items.add(0, titleItem);
|
||||
return items;
|
||||
}else if(titleItem!=null){
|
||||
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
|
||||
@@ -125,6 +134,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
knownAccounts.put(s.account.id, s.account);
|
||||
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
|
||||
knownAccounts.put(s.status.account.id, s.status.account);
|
||||
if(s.status!=null && s.status.reblog!=null && !knownAccounts.containsKey(s.status.reblog.account.id))
|
||||
knownAccounts.put(s.status.reblog.account.id, s.status.reblog.account);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,7 +144,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
.getAccount(accountID).getCacheController()
|
||||
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing, new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||
public void onSuccess(CacheablePaginatedResponse<List<Notification>> result){
|
||||
if (getActivity() == null) return;
|
||||
if(refreshing)
|
||||
relationships.clear();
|
||||
@@ -145,8 +156,12 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
loadRelationships(needRelationships);
|
||||
maxID=result.maxID;
|
||||
|
||||
if(offset==0 && !result.items.isEmpty()){
|
||||
if(offset==0 && !result.items.isEmpty() && !result.isFromCache()){
|
||||
E.post(new AllNotificationsSeenEvent());
|
||||
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
||||
AccountSessionManager.getInstance().getAccount(accountID).markers
|
||||
.notifications.lastReadId = result.items.get(0).id;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -166,8 +181,11 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
// if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||
// loadData();
|
||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading){
|
||||
refreshing=true;
|
||||
loadData();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.joinmastodon.android.model.TimelineDefinition;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class PinnableStatusListFragment extends StatusListFragment {
|
||||
public abstract class PinnableStatusListFragment extends StatusListFragment implements DomainDisplay {
|
||||
protected boolean pinnedUpdated;
|
||||
protected List<TimelineDefinition> pinnedTimelines;
|
||||
|
||||
|
||||
@@ -402,7 +402,7 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
|
||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
||||
super.onSelectedChanged(viewHolder, actionState);
|
||||
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
|
||||
viewHolder.itemView.setTag(R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
||||
viewHolder.itemView.setTag(me.grishka.appkit.R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
||||
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
draggedViewHolder=viewHolder;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,11 @@ import android.os.Bundle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -50,7 +48,9 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
@@ -92,13 +92,10 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
@@ -209,6 +206,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHiddenChanged(boolean hidden) {
|
||||
super.onHiddenChanged(hidden);
|
||||
if (!hidden) {
|
||||
DomainManager.getInstance().setCurrentDomain(account.url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
View content=inflater.inflate(R.layout.fragment_profile, container, false);
|
||||
@@ -520,6 +525,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
toolbarTitleView.setTranslationY(titleTransY);
|
||||
toolbarSubtitleView.setTranslationY(titleTransY);
|
||||
}
|
||||
RecyclerFragment.setRefreshLayoutColors(refreshLayout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -647,10 +653,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
|
||||
fields.clear();
|
||||
|
||||
AccountField joined=new AccountField();
|
||||
joined.parsedName=joined.name=getString(R.string.profile_joined);
|
||||
joined.parsedValue=joined.value=DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(LocalDateTime.ofInstant(account.createdAt, ZoneId.systemDefault()));
|
||||
fields.add(joined);
|
||||
if (account.createdAt != null) {
|
||||
AccountField joined=new AccountField();
|
||||
joined.parsedName=joined.name=getString(R.string.profile_joined);
|
||||
joined.parsedValue=joined.value=DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(LocalDateTime.ofInstant(account.createdAt, ZoneId.systemDefault()));
|
||||
fields.add(joined);
|
||||
}
|
||||
|
||||
for(AccountField field:account.fields){
|
||||
field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||
@@ -857,6 +865,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
// aboutFragment.setNote(relationship.note, accountID, profileAccountID);
|
||||
}
|
||||
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
||||
DomainManager.getInstance().setCurrentDomain(account.url);
|
||||
}
|
||||
|
||||
public ImageButton getFab() {
|
||||
@@ -1223,6 +1232,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
scrollView.smoothScrollTo(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
private void onFollowersOrFollowingClick(View v){
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -1242,7 +1256,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
@Override
|
||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
FrameLayout view=tabViews[viewType];
|
||||
((ViewGroup)view.getParent()).removeView(view);
|
||||
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
return new SimpleViewHolder(view);
|
||||
@@ -1498,7 +1512,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
||||
super.onSelectedChanged(viewHolder, actionState);
|
||||
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
|
||||
viewHolder.itemView.setTag(R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
||||
viewHolder.itemView.setTag(me.grishka.appkit.R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
|
||||
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
||||
draggedViewHolder=viewHolder;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
|
||||
|
||||
public abstract class RecyclerFragment<T> extends BaseRecyclerFragment<T> {
|
||||
public RecyclerFragment(int perPage) {
|
||||
super(perPage);
|
||||
}
|
||||
|
||||
public RecyclerFragment(int layout, int perPage) {
|
||||
super(layout, perPage);
|
||||
}
|
||||
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (refreshLayout != null) setRefreshLayoutColors(refreshLayout);
|
||||
}
|
||||
|
||||
public static void setRefreshLayoutColors(SwipeRefreshLayout l) {
|
||||
List<Integer> colors = new ArrayList<>(Arrays.asList(
|
||||
R.color.primary_600,
|
||||
R.color.red_primary_600,
|
||||
R.color.green_primary_600,
|
||||
R.color.blue_primary_600,
|
||||
R.color.purple_600
|
||||
));
|
||||
int primary = UiUtils.getThemeColorRes(l.getContext(), R.attr.colorPrimary600);
|
||||
if (!colors.contains(primary)) colors.add(0, primary);
|
||||
int offset = colors.indexOf(primary);
|
||||
int[] sorted = new int[colors.size()];
|
||||
for (int i = 0; i < colors.size(); i++) {
|
||||
sorted[i] = colors.get((i + offset) % colors.size());
|
||||
}
|
||||
l.setColorSchemeResources(sorted);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public interface ScrollableToTop{
|
||||
boolean isScrolledToTop();
|
||||
|
||||
void scrollToTop();
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
public class SplashFragment extends AppKitFragment{
|
||||
|
||||
private static final String DEFAULT_SERVER="mastodon.social";
|
||||
|
||||
private SizeListenerFrameLayout contentView;
|
||||
private View artContainer, blueFill, greenFill;
|
||||
private ViewPager2 pager;
|
||||
private ViewGroup pagerDots;
|
||||
private InterpolatingMotionEffect motionEffect;
|
||||
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -46,44 +50,26 @@ public class SplashFragment extends AppKitFragment{
|
||||
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
||||
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
||||
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
||||
Button joinDefault=contentView.findViewById(R.id.btn_join_default_server);
|
||||
joinDefault.setText(getString(R.string.join_default_server, DEFAULT_SERVER));
|
||||
joinDefault.setOnClickListener(this::onJoinDefaultServerClick);
|
||||
contentView.findViewById(R.id.btn_learn_more).setOnClickListener(this::onLearnMoreClick);
|
||||
|
||||
artClouds=contentView.findViewById(R.id.art_clouds);
|
||||
artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant);
|
||||
artRightHill=contentView.findViewById(R.id.art_right_hill);
|
||||
artLeftHill=contentView.findViewById(R.id.art_left_hill);
|
||||
artCenterHill=contentView.findViewById(R.id.art_center_hill);
|
||||
pager=contentView.findViewById(R.id.pager);
|
||||
pagerDots=contentView.findViewById(R.id.pager_dots);
|
||||
pager.setAdapter(new PagerAdapter());
|
||||
pager.setOffscreenPageLimit(3);
|
||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
|
||||
for(int i=0;i<pagerDots.getChildCount();i++){
|
||||
float alpha;
|
||||
if(i==position){
|
||||
alpha=0.3f+0.7f*(1f-positionOffset);
|
||||
}else if(i==position+1){
|
||||
alpha=0.3f+0.7f*positionOffset;
|
||||
}else{
|
||||
alpha=0.3f;
|
||||
}
|
||||
pagerDots.getChildAt(i).setAlpha(alpha);
|
||||
}
|
||||
|
||||
float parallaxProgress=(position+positionOffset)/2f;
|
||||
artClouds.setTranslationX(V.dp(-27)*(position>=1 ? 1f : positionOffset));
|
||||
artPlaneElephant.setTranslationX(V.dp(101.55f)*parallaxProgress);
|
||||
artLeftHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
||||
artLeftHill.setTranslationY(V.dp(24)*parallaxProgress);
|
||||
artRightHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
||||
artRightHill.setTranslationY(V.dp(-24)*parallaxProgress);
|
||||
artCenterHill.setTranslationX(V.dp(-40)*parallaxProgress);
|
||||
}
|
||||
});
|
||||
|
||||
artContainer=contentView.findViewById(R.id.art_container);
|
||||
blueFill=contentView.findViewById(R.id.blue_fill);
|
||||
greenFill=contentView.findViewById(R.id.green_fill);
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artClouds, V.dp(-5), V.dp(5), V.dp(-5), V.dp(5)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artRightHill, V.dp(-15), V.dp(25), V.dp(-10), V.dp(10)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artLeftHill, V.dp(-25), V.dp(15), V.dp(-15), V.dp(15)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artCenterHill, V.dp(-14), V.dp(14), V.dp(-5), V.dp(25)));
|
||||
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(artPlaneElephant, V.dp(-20), V.dp(12), V.dp(-20), V.dp(12)));
|
||||
artContainer.setOnTouchListener(motionEffect);
|
||||
|
||||
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
|
||||
@Override
|
||||
@@ -109,6 +95,38 @@ public class SplashFragment extends AppKitFragment{
|
||||
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
|
||||
}
|
||||
|
||||
private void onJoinDefaultServerClick(View v){
|
||||
new GetInstance()
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Instance result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(result));
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
error.showToast(getActivity());
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading_instance, true)
|
||||
.execNoAuth(DEFAULT_SERVER);
|
||||
}
|
||||
|
||||
private void onLearnMoreClick(View v){
|
||||
View sheetView=getActivity().getLayoutInflater().inflate(R.layout.intro_bottom_sheet, null);
|
||||
BottomSheet sheet=new BottomSheet(getActivity());
|
||||
sheet.setContentView(sheetView);
|
||||
sheet.setNavigationBarBackground(new ColorDrawable(UiUtils.alphaBlendColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Surface),
|
||||
UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary), 0.05f)), !UiUtils.isDarkTheme());
|
||||
sheet.show();
|
||||
}
|
||||
|
||||
private void updateArtSize(int w, int h){
|
||||
float scale=w/(float)V.dp(360);
|
||||
artContainer.setScaleX(scale);
|
||||
@@ -139,60 +157,15 @@ public class SplashFragment extends AppKitFragment{
|
||||
return true;
|
||||
}
|
||||
|
||||
private class PagerAdapter extends RecyclerView.Adapter<PagerViewHolder>{
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
return new PagerViewHolder(viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PagerViewHolder holder, int position){}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return position;
|
||||
}
|
||||
@Override
|
||||
protected void onShown(){
|
||||
super.onShown();
|
||||
motionEffect.activate();
|
||||
}
|
||||
|
||||
private class PagerViewHolder extends RecyclerView.ViewHolder{
|
||||
public PagerViewHolder(int page){
|
||||
super(new LinearLayout(getActivity()));
|
||||
LinearLayout ll=(LinearLayout) itemView;
|
||||
ll.setOrientation(LinearLayout.VERTICAL);
|
||||
int pad=V.dp(16);
|
||||
ll.setPadding(pad, pad, pad, pad);
|
||||
ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
TextView title=new TextView(getActivity());
|
||||
title.setTextAppearance(R.style.m3_headline_medium);
|
||||
title.setText(switch(page){
|
||||
case 0 -> getString(R.string.welcome_page1_title);
|
||||
case 1 -> getString(R.string.welcome_page2_title);
|
||||
case 2 -> getString(R.string.welcome_page3_title);
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
});
|
||||
title.setTextColor(0xFF17063B);
|
||||
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(page==0 ? 46 : 36));
|
||||
lp.bottomMargin=V.dp(page==0 ? 4 : 14);
|
||||
ll.addView(title, lp);
|
||||
|
||||
TextView text=new TextView(getActivity());
|
||||
text.setTextAppearance(R.style.m3_body_medium);
|
||||
text.setText(switch(page){
|
||||
case 0 -> R.string.welcome_page1_text;
|
||||
case 1 -> R.string.welcome_page2_text;
|
||||
case 2 -> R.string.welcome_page3_text;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
});
|
||||
text.setTextColor(0xFF17063B);
|
||||
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
}
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
motionEffect.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
@@ -29,7 +30,7 @@ import java.util.stream.Stream;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.Nav;
|
||||
|
||||
public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
public abstract class StatusListFragment extends BaseStatusListFragment<Status> implements DomainDisplay{
|
||||
protected EventListener eventListener=new EventListener();
|
||||
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
@@ -42,6 +43,8 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
protected void addAccountToKnown(Status s){
|
||||
if(!knownAccounts.containsKey(s.account.id))
|
||||
knownAccounts.put(s.account.id, s.account);
|
||||
if(s.reblog!=null && !knownAccounts.containsKey(s.reblog.account.id))
|
||||
knownAccounts.put(s.reblog.account.id, s.reblog.account);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,7 +165,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
||||
|
||||
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||
List<Status> toRemove=Stream.concat(data.stream(), preloadedData.stream())
|
||||
.filter(s->s.account.id.equals(ev.postsByAccountID) || (s.reblog!=null && s.reblog.account.id.equals(ev.postsByAccountID)))
|
||||
.filter(s->s.account.id.equals(ev.postsByAccountID) || (!ev.isUnfollow && s.reblog!=null && s.reblog.account.id.equals(ev.postsByAccountID)))
|
||||
.collect(Collectors.toList());
|
||||
for(Status s:toRemove){
|
||||
removeStatus(s);
|
||||
|
||||
@@ -3,11 +3,15 @@ package org.joinmastodon.android.fragments;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusContext;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
@@ -19,15 +23,21 @@ import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class ThreadFragment extends StatusListFragment{
|
||||
public class ThreadFragment extends StatusListFragment implements DomainDisplay{
|
||||
protected Status mainStatus;
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return mainStatus.url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -38,6 +48,8 @@ public class ThreadFragment extends StatusListFragment{
|
||||
data.add(mainStatus);
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
|
||||
|
||||
DomainManager.getInstance().setCurrentDomain(getDomain());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,6 +80,30 @@ public class ThreadFragment extends StatusListFragment{
|
||||
data.add(mainStatus);
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
}
|
||||
AccountSession account=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
|
||||
if(instance.pleroma != null){
|
||||
List<String> threadIds=new ArrayList<>();
|
||||
threadIds.add(mainStatus.id);
|
||||
for(Status s:result.descendants){
|
||||
if(threadIds.contains(s.inReplyToId)){
|
||||
threadIds.add(s.id);
|
||||
}
|
||||
}
|
||||
threadIds.add(mainStatus.inReplyToId);
|
||||
for(int i=result.ancestors.size()-1; i >= 0; i--){
|
||||
Status s=result.ancestors.get(i);
|
||||
if(s.inReplyToId != null && threadIds.contains(s.id)){
|
||||
threadIds.add(s.inReplyToId);
|
||||
}
|
||||
}
|
||||
|
||||
result.ancestors=result.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
|
||||
result.descendants=getDescendantsOrdered(mainStatus.id,
|
||||
result.descendants.stream()
|
||||
.filter(s -> threadIds.contains(s.id))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
result.descendants=filterStatuses(result.descendants);
|
||||
result.ancestors=filterStatuses(result.ancestors);
|
||||
if(footerProgress!=null)
|
||||
@@ -90,6 +126,24 @@ public class ThreadFragment extends StatusListFragment{
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private List<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
||||
List<Status> out=new ArrayList<>();
|
||||
for(Status s:getDirectDescendants(id, statuses)){
|
||||
out.add(s);
|
||||
getDirectDescendants(s.id, statuses).forEach(d ->{
|
||||
out.add(d);
|
||||
out.addAll(getDescendantsOrdered(d.id, statuses));
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||
return statuses.stream()
|
||||
.filter(s -> s.inReplyToId.equals(id))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<Status> filterStatuses(List<Status> statuses){
|
||||
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
|
||||
return statuses.stream()
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
@@ -48,7 +49,6 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.APIRequest;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
@@ -57,7 +57,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseAccountListFragment.AccountItem>{
|
||||
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> {
|
||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||
protected String accountID;
|
||||
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
||||
@@ -74,10 +74,15 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
|
||||
@Override
|
||||
protected void onDataLoaded(List<AccountItem> d, boolean more){
|
||||
if (getActivity() == null)
|
||||
return;
|
||||
if(refreshing){
|
||||
relationships.clear();
|
||||
}
|
||||
loadRelationships(d);
|
||||
if(!d.isEmpty() && !d.get(0).account.reloadWhenClicked){
|
||||
loadRelationships(d);
|
||||
}
|
||||
|
||||
super.onDataLoaded(d, more);
|
||||
}
|
||||
|
||||
@@ -240,7 +245,12 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
public void bindRelationship(){
|
||||
Relationship rel=relationships.get(item.account.id);
|
||||
if(rel==null || AccountSessionManager.getInstance().isSelf(accountID, item.account)){
|
||||
button.setVisibility(View.GONE);
|
||||
if(item.account.reloadWhenClicked){
|
||||
button.setVisibility(View.VISIBLE);
|
||||
button.setText(R.string.button_follow);
|
||||
} else {
|
||||
button.setVisibility(View.GONE);
|
||||
}
|
||||
}else{
|
||||
button.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButton(rel, button);
|
||||
@@ -267,6 +277,15 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
if(item.account.reloadWhenClicked){
|
||||
UiUtils.lookupAccount(getContext(), item.account, accountID, null, account -> {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(account));
|
||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||
});
|
||||
return;
|
||||
}
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||
@@ -295,7 +314,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
|
||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername())).setVisible(relationship.following);
|
||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
||||
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
||||
@@ -309,7 +327,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
||||
manageUserLists.setVisible(true);
|
||||
}else{
|
||||
hideBoosts.setVisible(false);
|
||||
manageUserLists.setVisible(true);
|
||||
manageUserLists.setVisible(false);
|
||||
}
|
||||
menu.findItem(R.id.block_domain).setVisible(false);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
targetAccount = account;
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
|
||||
}
|
||||
|
||||
@@ -19,4 +20,9 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetAccountFollowers(account.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count){
|
||||
return new GetAccountFollowers(id, maxID, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
targetAccount = account;
|
||||
setSubtitle(getResources().getQuantityString(R.plurals.x_following, (int)(account.followingCount%1000), account.followingCount));
|
||||
}
|
||||
|
||||
@@ -19,4 +20,9 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetAccountFollowing(account.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count){
|
||||
return new GetAccountFollowing(id, maxID, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,59 @@
|
||||
package org.joinmastodon.android.fragments.account_list;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public abstract class PaginatedAccountListFragment extends BaseAccountListFragment{
|
||||
private String nextMaxID;
|
||||
|
||||
protected Account targetAccount;
|
||||
|
||||
protected Account remoteAccount;
|
||||
|
||||
public abstract HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count);
|
||||
|
||||
public abstract HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count);
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if (shouldLoadRemote()) {
|
||||
if(remoteAccount == null){
|
||||
UiUtils.lookupRemoteAccount(getContext(), targetAccount, accountID, null, account -> {
|
||||
remoteAccount = account;
|
||||
if(remoteAccount != null){
|
||||
loadRemoteFollower(offset, count, remoteAccount);
|
||||
} else {
|
||||
loadFollower(offset, count);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
loadRemoteFollower(offset, count, remoteAccount);
|
||||
}
|
||||
} else {
|
||||
loadFollower(offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldLoadRemote() {
|
||||
if (!GlobalUserPreferences.loadRemoteAccountFollowers && (this instanceof FollowingListFragment || this instanceof FollowerListFragment)) {
|
||||
return false;
|
||||
}
|
||||
return targetAccount != null && targetAccount.getDomain() != null;
|
||||
}
|
||||
|
||||
void loadFollower(int offset, int count) {
|
||||
currentRequest=onCreateRequest(offset==0 ? null : nextMaxID, count)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
@@ -23,13 +62,46 @@ public abstract class PaginatedAccountListFragment extends BaseAccountListFragme
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
if (getActivity() == null) return;
|
||||
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void loadRemoteFollower(int offset, int count, Account account) {
|
||||
String ownDomain = AccountSessionManager.getInstance().getLastActiveAccount().domain;
|
||||
currentRequest=onCreateRemoteRequest(account.id, offset==0 ? null : nextMaxID, count)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Account> result){
|
||||
if(result.nextPageUri!=null)
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
result.stream().forEach(remoteAccount -> {
|
||||
remoteAccount.reloadWhenClicked = true;
|
||||
if (remoteAccount.getDomain() == null) {
|
||||
remoteAccount.acct += "@" + Uri.parse(remoteAccount.url).getHost();
|
||||
} else if (remoteAccount.getDomain().equals(ownDomain)) {
|
||||
remoteAccount.acct = remoteAccount.username;
|
||||
}
|
||||
});
|
||||
if(!result.isEmpty()){
|
||||
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
|
||||
} else {
|
||||
loadFollower(offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(getContext());
|
||||
loadFollower(offset, count);
|
||||
}
|
||||
})
|
||||
.execNoAuth(targetAccount.getDomain());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
|
||||
@@ -18,4 +18,9 @@ public class StatusFavoritesListFragment extends StatusRelatedAccountListFragmen
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetStatusFavorites(status.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,9 @@ public class StatusReblogsListFragment extends StatusRelatedAccountListFragment{
|
||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||
return new GetStatusReblogs(status.id, maxID, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ import android.widget.TextView;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||
import org.joinmastodon.android.fragments.DomainDisplay;
|
||||
import org.joinmastodon.android.fragments.IsOnTop;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
@@ -49,7 +51,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop {
|
||||
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop, DomainDisplay {
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
@@ -58,6 +60,11 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
||||
super(20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return DomainDisplay.super.getDomain() + "/explore/suggestions";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -140,6 +147,11 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnTop() {
|
||||
return isRecyclerViewOnTop(list);
|
||||
@@ -211,11 +223,11 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
||||
actionProgress=findViewById(R.id.action_progress);
|
||||
actionWrap=findViewById(R.id.action_btn_wrap);
|
||||
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(6));
|
||||
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
itemView.setClipToOutline(true);
|
||||
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
avatar.setClipToOutline(true);
|
||||
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
|
||||
cover.setOutlineProvider(OutlineProviders.roundedRect(12));
|
||||
cover.setClipToOutline(true);
|
||||
actionButton.setOnClickListener(this::onActionButtonClick);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,10 @@ import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.DomainDisplay;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||
@@ -38,7 +40,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener{
|
||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay {
|
||||
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager2 pager;
|
||||
@@ -64,6 +66,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
|
||||
// private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
if (searchActive) {
|
||||
return searchFragment.getDomain();
|
||||
}
|
||||
if (tabViews[tabLayout.getSelectedTabPosition()] instanceof DomainDisplay page) {
|
||||
return page.getDomain();
|
||||
}
|
||||
return DomainDisplay.super.getDomain() + "/explore";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -127,6 +140,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
if(!page.loaded && !page.isDataLoading())
|
||||
page.loadData();
|
||||
}
|
||||
|
||||
if (_page instanceof DomainDisplay display)
|
||||
DomainManager.getInstance().setCurrentDomain(display.getDomain());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -208,7 +224,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabLayoutMediator.attach();
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab){}
|
||||
public void onTabSelected(TabLayout.Tab tab){
|
||||
DomainManager.getInstance().setCurrentDomain(getDomain());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab){}
|
||||
@@ -284,6 +302,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
if(!searchActive){
|
||||
return ((ScrollableToTop)getFragmentForPage(pager.getCurrentItem())).isScrolledToTop();
|
||||
}else{
|
||||
return searchFragment.isScrolledToTop();
|
||||
}
|
||||
}
|
||||
|
||||
public void loadData(){
|
||||
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
|
||||
hashtagsFragment.loadData();
|
||||
|
||||
@@ -10,7 +10,9 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||
import org.joinmastodon.android.fragments.DomainDisplay;
|
||||
import org.joinmastodon.android.fragments.IsOnTop;
|
||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Card;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
@@ -35,7 +37,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop {
|
||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop, DomainDisplay {
|
||||
private String accountID;
|
||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||
@@ -44,6 +46,11 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
super(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return DomainDisplay.super.getDomain() + "/explore/links";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -83,6 +90,11 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnTop() {
|
||||
return isRecyclerViewOnTop(list);
|
||||
|
||||
@@ -19,6 +19,11 @@ import me.grishka.appkit.api.SimpleCallback;
|
||||
public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop {
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/explore/posts";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
currentRequest=new GetTrendingStatuses(offset, count)
|
||||
|
||||
@@ -25,6 +25,11 @@ public class FederatedTimelineFragment extends StatusListFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/public";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
|
||||
@@ -24,6 +24,10 @@ public class LocalTimelineFragment extends StatusListFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/public/local";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
|
||||
@@ -57,6 +57,11 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
setLayout(R.layout.fragment_search);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/search";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -248,7 +253,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||
}
|
||||
|
||||
public void setQuery(String q){
|
||||
if(Objects.equals(q, currentQuery))
|
||||
if(Objects.equals(q, currentQuery) || q.isBlank())
|
||||
return;
|
||||
if(currentRequest!=null){
|
||||
currentRequest.cancel();
|
||||
|
||||
@@ -7,7 +7,9 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
||||
import org.joinmastodon.android.fragments.DomainDisplay;
|
||||
import org.joinmastodon.android.fragments.IsOnTop;
|
||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||
@@ -24,7 +26,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop {
|
||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop, DomainDisplay {
|
||||
private String accountID;
|
||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
||||
|
||||
@@ -32,6 +34,11 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
||||
super(10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return DomainDisplay.super.getDomain() + "/explore/tags";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -68,6 +75,11 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
||||
smoothScrollRecyclerViewToTop(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isScrolledToTop() {
|
||||
return list.getChildAt(0).getTop() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnTop() {
|
||||
return isRecyclerViewOnTop(list);
|
||||
|
||||
@@ -23,8 +23,7 @@ import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.SettingsFragment;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
@@ -110,7 +109,10 @@ public class AccountActivationFragment extends ToolbarFragment{
|
||||
|
||||
@Override
|
||||
public void onToolbarNavigationClick(){
|
||||
new AccountSwitcherSheet(getActivity()).show();
|
||||
new AccountSwitcherSheet(getActivity(), true, true, false, accountSession -> {
|
||||
getActivity().finish();
|
||||
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
||||
}).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
@@ -44,7 +45,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||
import me.grishka.appkit.utils.V;
|
||||
@@ -52,7 +52,7 @@ import okhttp3.Call;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstance>{
|
||||
abstract class InstanceCatalogFragment extends RecyclerFragment<CatalogInstance> {
|
||||
protected RecyclerView.Adapter adapter;
|
||||
protected MergeRecyclerAdapter mergeAdapter;
|
||||
protected CatalogInstance chosenInstance;
|
||||
@@ -75,7 +75,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
||||
private static final double DUNBAR=Math.log(800);
|
||||
|
||||
public InstanceCatalogFragment(int layout, int perPage){
|
||||
super(layout, perPage);
|
||||
super(layout, perPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||
import org.joinmastodon.android.fragments.HomeFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||
import org.joinmastodon.android.model.FollowSuggestion;
|
||||
import org.joinmastodon.android.model.ParsedAccount;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
@@ -43,7 +44,6 @@ import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
@@ -52,7 +52,7 @@ import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<ParsedAccount>{
|
||||
public class OnboardingFollowSuggestionsFragment extends RecyclerFragment<ParsedAccount> {
|
||||
private String accountID;
|
||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||
private GetAccountRelationships relationshipsRequest;
|
||||
@@ -77,12 +77,13 @@ public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<Pa
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
buttonBar=view.findViewById(R.id.button_bar);
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||
getToolbar().setBackgroundResource(UiUtils.getThemeColorRes(getToolbarContext(), R.attr.colorBackgroundLight));
|
||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorWindowBackground));
|
||||
// list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||
|
||||
view.findViewById(R.id.btn_next).setOnClickListener(UiUtils.rateLimitedClickListener(this::onFollowAllClick));
|
||||
view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
|
||||
// view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -266,7 +267,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<Pa
|
||||
private Relationship relationship;
|
||||
|
||||
public SuggestionViewHolder(){
|
||||
super(getActivity(), R.layout.item_user_row_m3, list);
|
||||
super(getActivity(), R.layout.item_user_row, list);
|
||||
name=findViewById(R.id.name);
|
||||
username=findViewById(R.id.username);
|
||||
bio=findViewById(R.id.bio);
|
||||
@@ -296,7 +297,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<Pa
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
}else{
|
||||
actionWrap.setVisibility(View.VISIBLE);
|
||||
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||
UiUtils.setRelationshipToActionButton(relationship, actionButton);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,8 @@ import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
@@ -133,22 +131,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
if(holder.getAbsoluteAdapterPosition()==0)
|
||||
return;
|
||||
outRect.left=V.dp(40);
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
||||
PhotoLayoutHelper.TiledLayoutResult layout=imgHolder.getItem().tiledLayout;
|
||||
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
|
||||
String siblingID;
|
||||
if(holder.getAbsoluteAdapterPosition()<parent.getAdapter().getItemCount()-1){
|
||||
siblingID=displayItems.get(holder.getAbsoluteAdapterPosition()-getMainAdapterOffset()+1).parentID;
|
||||
}else{
|
||||
siblingID=null;
|
||||
}
|
||||
if(tile.startCol>0)
|
||||
outRect.left=0;
|
||||
outRect.left+=V.dp(16);
|
||||
outRect.right=V.dp(16);
|
||||
if(!imgHolder.getItemID().equals(siblingID) || tile.startRow+tile.rowSpan==layout.rowSizes.length)
|
||||
outRect.bottom=V.dp(16);
|
||||
}else if(holder instanceof AudioStatusDisplayItem.Holder){
|
||||
if(holder instanceof AudioStatusDisplayItem.Holder){
|
||||
outRect.bottom=V.dp(16);
|
||||
}else if(holder instanceof LinkCardStatusDisplayItem.Holder){
|
||||
outRect.bottom=V.dp(16);
|
||||
@@ -167,10 +150,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||
String id=sdiHolder.getItemID();
|
||||
int height=tmpRect.height();
|
||||
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
|
||||
if(imgHolder.getItem().thisTile.startCol+imgHolder.getItem().thisTile.colSpan<imgHolder.getItem().tiledLayout.columnSizes.length)
|
||||
height=0;
|
||||
}
|
||||
if(!(holder instanceof HeaderStatusDisplayItem.Holder) && !(holder instanceof ReblogOrReplyLineStatusDisplayItem.Holder))
|
||||
postsWithKnownNonHeaderHeights.add(id);
|
||||
knownDisplayItemHeights.put(holder.getAbsoluteAdapterPosition(), height);
|
||||
@@ -237,17 +216,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
||||
return adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, Filter.FilterContext.HOME);
|
||||
for(StatusDisplayItem item:items){
|
||||
if(item instanceof ImageStatusDisplayItem isdi){
|
||||
isdi.horizontalInset=V.dp(40+32);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
|
||||
parent.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||
tmpRect.offset(0, Math.round(child.getTranslationY()));
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.LruCache;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
|
||||
public class AboutFragment extends SettingsBaseFragment{
|
||||
|
||||
private TextItem checkForUpdateItem, clearImageCacheItem;
|
||||
private ImageCache imageCache;
|
||||
@Override
|
||||
public void addItems(ArrayList<Item> items) {
|
||||
items.add(new HeaderItem(R.string.sk_settings_about));
|
||||
|
||||
items.add(new TextItem(R.string.mo_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
|
||||
|
||||
if (GithubSelfUpdater.needSelfUpdating()) {
|
||||
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||
items.add(checkForUpdateItem);
|
||||
items.add(new SwitchItem(R.string.sk_updater_enable_pre_releases, 0, GlobalUserPreferences.enablePreReleases, i->{
|
||||
GlobalUserPreferences.enablePreReleases=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
}
|
||||
|
||||
LruCache<?, ?> cache = imageCache == null ? null : imageCache.getLruCache();
|
||||
clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), cache != null ? cache.size() : 0, true), this::clearImageCache, 0);
|
||||
items.add(clearImageCacheItem);
|
||||
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
|
||||
GlobalUserPreferences.recentLanguages.remove(accountID);
|
||||
GlobalUserPreferences.save();
|
||||
})));
|
||||
|
||||
items.add(new TextItem(R.string.mo_clear_recent_emoji, ()-> {
|
||||
GlobalUserPreferences.recentEmojis.clear();
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
if(BuildConfig.DEBUG){
|
||||
items.add(new RedHeaderItem("Debug options"));
|
||||
|
||||
items.add(new TextItem("Test E-Mail confirmation flow", ()->{
|
||||
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
sess.activated=false;
|
||||
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putBoolean("debug", true);
|
||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||
}));
|
||||
|
||||
items.add(new TextItem("Copy preferences", ()->{
|
||||
StringBuilder prefBuilder = new StringBuilder();
|
||||
GlobalUserPreferences.load();
|
||||
GlobalUserPreferences.getPrefs().getAll().forEach((key, value) -> prefBuilder.append(key).append(": ").append(value).append('\n'));
|
||||
UiUtils.copyText(view, prefBuilder.toString());
|
||||
}));
|
||||
|
||||
items.add(new TextItem("Reset preferences", ()->{
|
||||
GlobalUserPreferences.load();
|
||||
GlobalUserPreferences.getPrefs().edit().clear().commit();
|
||||
UiUtils.restartApp();
|
||||
}, R.drawable.ic_fluent_warning_24_regular));
|
||||
|
||||
items.add(new TextItem("Open App Info", () ->
|
||||
getContext().startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
.setData(Uri.fromParts("package", getContext().getPackageName(), null))),
|
||||
R.drawable.ic_fluent_open_24_regular
|
||||
)
|
||||
);
|
||||
|
||||
items.add(new TextItem("Open developer settings",
|
||||
()-> getContext().startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)),
|
||||
R.drawable.ic_fluent_open_24_regular)
|
||||
);
|
||||
}
|
||||
|
||||
String version = getContext().getString(R.string.mo_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE);
|
||||
items.add(new FooterItem(version, () -> UiUtils.copyText(view, version)));
|
||||
}
|
||||
|
||||
private void clearImageCache(){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
Activity activity=getActivity();
|
||||
imageCache.clear();
|
||||
Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(clearImageCacheItem)) instanceof TextViewHolder tvh) {
|
||||
clearImageCacheItem.secondaryText = UiUtils.formatFileSize(getContext(), 0, true);
|
||||
tvh.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class AccountFragment extends SettingsBaseFragment{
|
||||
|
||||
|
||||
private SwitchItem glitchModeItem;
|
||||
@Override
|
||||
public void addItems(ArrayList<Item> items) {
|
||||
items.add(new HeaderItem(R.string.settings_account));
|
||||
items.add(new TextItem(R.string.sk_settings_profile, ()-> UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_posting, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/preferences/other"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_filters, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/filters"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
|
||||
|
||||
items.add(new HeaderItem(getInstanceName()));
|
||||
items.add(new TextItem(R.string.sk_settings_rules, ()->{
|
||||
Bundle args=new Bundle();
|
||||
args.putParcelable("instance", Parcels.wrap(getInstance()));
|
||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||
}, R.drawable.ic_fluent_task_list_ltr_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_about_instance , ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_info_24_regular));
|
||||
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
|
||||
if (!TextUtils.isEmpty(getInstance().version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, getInstance().version)));
|
||||
|
||||
items.add(new HeaderItem(R.string.sk_instance_features));
|
||||
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
|
||||
glitchModeItem.enabled = i.checked;
|
||||
if (i.checked) {
|
||||
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
||||
if (getInstance().pleroma == null) GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||
} else {
|
||||
GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
|
||||
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
|
||||
}
|
||||
glitchModeItem.checked = GlobalUserPreferences.accountsInGlitchMode.contains(accountID);
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(glitchModeItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SmallTextItem(getString(R.string.sk_settings_local_only_explanation)));
|
||||
items.add(glitchModeItem = new SwitchItem(R.string.sk_settings_glitch_instance, 0, GlobalUserPreferences.accountsInGlitchMode.contains(accountID), i->{
|
||||
if (i.checked) {
|
||||
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||
} else {
|
||||
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
|
||||
}
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
glitchModeItem.enabled = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||
items.add(new SmallTextItem(getString(R.string.sk_settings_glitch_mode_explanation)));
|
||||
|
||||
|
||||
boolean translationAvailable = getInstance().v2 != null && getInstance().v2.configuration.translation != null && getInstance().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, getInstance().title)));
|
||||
|
||||
}
|
||||
|
||||
private void confirmLogOut(){
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.log_out)
|
||||
.setMessage(R.string.confirm_log_out)
|
||||
.setPositiveButton(R.string.log_out, (dialog, which) -> logOut())
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void logOut(){
|
||||
AccountSession session= AccountSessionManager.getInstance().getAccount(accountID);
|
||||
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
onLoggedOut();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
onLoggedOut();
|
||||
}
|
||||
})
|
||||
.wrapProgress(getActivity(), R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
private void onLoggedOut(){
|
||||
if (getActivity() == null) return;
|
||||
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||
getActivity().finish();
|
||||
Intent intent=new Intent(getActivity(), MainActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class BehaviourFragment extends SettingsBaseFragment{
|
||||
@Override
|
||||
public void addItems(ArrayList<Item> items) {
|
||||
items.add(new HeaderItem(R.string.settings_behavior));
|
||||
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||
GlobalUserPreferences.playGifs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.settings_custom_tabs, R.drawable.ic_fluent_link_24_regular, GlobalUserPreferences.useCustomTabs, i->{
|
||||
GlobalUserPreferences.useCustomTabs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_hide_compose_button_while_scrolling_setting, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.enableFabAutoHide, i->{
|
||||
GlobalUserPreferences.enableFabAutoHide =i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_load_remote_followers, R.drawable.ic_fluent_people_24_regular, GlobalUserPreferences.loadRemoteAccountFollowers, i -> {
|
||||
GlobalUserPreferences.loadRemoteAccountFollowers=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
|
||||
GlobalUserPreferences.showInteractionCounts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
||||
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
// items.add(new SwitchItem(R.string.sk_settings_show_differentiated_notification_icons, R.drawable.ic_ntf_logo, GlobalUserPreferences.showUniformPushNoticationIcons, this::onNotificationStyleChanged));
|
||||
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.mo_disable_double_tap_to_swipe_between_tabs, R.drawable.ic_fluent_double_tap_swipe_right_24_regular, GlobalUserPreferences.disableDoubleTapToSwipe, i->{
|
||||
GlobalUserPreferences.disableDoubleTapToSwipe=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
|
||||
GlobalUserPreferences.confirmBeforeReblog=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_swap_bookmark_with_reblog, R.drawable.ic_boost, GlobalUserPreferences.swapBookmarkWithBoostAction, i -> {
|
||||
GlobalUserPreferences.swapBookmarkWithBoostAction=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
items.add(new HeaderItem(R.string.mo_composer_behavior));
|
||||
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
|
||||
updatePublishText(b);
|
||||
b.setOnClickListener(l -> {
|
||||
if(!GlobalUserPreferences.relocatePublishButton) {
|
||||
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();
|
||||
|
||||
} else {
|
||||
Toast.makeText(getActivity(), R.string.mo_disable_relocate_publish_button_to_enable_customization,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
|
||||
GlobalUserPreferences.relocatePublishButton=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_change_default_reply_visibility_to_unlisted, R.drawable.ic_fluent_lock_open_24_regular, GlobalUserPreferences.defaultToUnlistedReplies, i->{
|
||||
GlobalUserPreferences.defaultToUnlistedReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
// TODO find a good icon for this setting
|
||||
items.add(new SwitchItem(R.string.mo_mention_reblogger_automatically, R.drawable.ic_fluent_balloon_24_regular, GlobalUserPreferences.mentionRebloggerAutomatically, i -> {
|
||||
GlobalUserPreferences.mentionRebloggerAutomatically=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_disable_reminder_to_add_alt_text, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, i->{
|
||||
GlobalUserPreferences.disableAltTextReminder=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
|
||||
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
}
|
||||
|
||||
private void updatePublishText(Button btn) {
|
||||
if (GlobalUserPreferences.publishButtonText.isBlank()) btn.setText(R.string.publish);
|
||||
else btn.setText(GlobalUserPreferences.publishButtonText);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class NotificationsFragment extends SettingsBaseFragment {
|
||||
@Override
|
||||
public void addItems(ArrayList<Item> items) {
|
||||
items.add(notificationPolicyItem = new NotificationPolicyItem());
|
||||
PushSubscription pushSubscription = getPushSubscription();
|
||||
boolean switchEnabled = pushSubscription.policy != PushSubscription.Policy.NONE;
|
||||
|
||||
items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i -> onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked), switchEnabled));
|
||||
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i -> onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked), switchEnabled));
|
||||
items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i -> onNotificationsChanged(PushNotification.Type.REBLOG, i.checked), switchEnabled));
|
||||
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i -> onNotificationsChanged(PushNotification.Type.MENTION, i.checked), switchEnabled));
|
||||
items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_chat_24_regular, pushSubscription.alerts.status, i -> onNotificationsChanged(PushNotification.Type.STATUS, i.checked), switchEnabled));
|
||||
items.add(new SwitchItem(R.string.sk_notify_update, R.drawable.ic_fluent_history_24_regular, pushSubscription.alerts.update, i -> onNotificationsChanged(PushNotification.Type.UPDATE, i.checked), switchEnabled));
|
||||
items.add(new SwitchItem(R.string.sk_notify_poll_results, R.drawable.ic_fluent_poll_24_regular, pushSubscription.alerts.poll, i -> onNotificationsChanged(PushNotification.Type.POLL, i.checked), switchEnabled));
|
||||
|
||||
items.add(new HeaderItem(R.string.mo_miscellaneous_settings));
|
||||
items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
||||
GlobalUserPreferences.enableDeleteNotifications=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
|
||||
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Build;
|
||||
import android.view.Gravity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SettingsAppearanceFragment extends SettingsBaseFragment {
|
||||
@Override
|
||||
public void addItems(ArrayList<Item> items) {
|
||||
items.add(themeItem = new SettingsBaseFragment.ThemeItem());
|
||||
items.add(new SettingsBaseFragment.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(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.mo_color_palette_nord;
|
||||
});
|
||||
}));
|
||||
items.add(new SettingsBaseFragment.SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
||||
items.add(new SettingsBaseFragment.SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i -> {
|
||||
GlobalUserPreferences.disableMarquee = i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart = true;
|
||||
}));
|
||||
items.add(new SettingsBaseFragment.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 SettingsBaseFragment.SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i -> {
|
||||
GlobalUserPreferences.reduceMotion = i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart = true;
|
||||
}));
|
||||
}
|
||||
|
||||
protected boolean onColorPreferenceClick(MenuItem item){
|
||||
GlobalUserPreferences.ColorPreference pref = null;
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == R.id.m3_color) pref = GlobalUserPreferences.ColorPreference.MATERIAL3;
|
||||
else if (id == R.id.pink_color) pref = GlobalUserPreferences.ColorPreference.PINK;
|
||||
else if (id == R.id.purple_color) pref = GlobalUserPreferences.ColorPreference.PURPLE;
|
||||
else if (id == R.id.green_color) pref = GlobalUserPreferences.ColorPreference.GREEN;
|
||||
else if (id == R.id.blue_color) pref = GlobalUserPreferences.ColorPreference.BLUE;
|
||||
else if (id == R.id.brown_color) pref = GlobalUserPreferences.ColorPreference.BROWN;
|
||||
else if (id == R.id.red_color) pref = GlobalUserPreferences.ColorPreference.RED;
|
||||
else if (id == R.id.yellow_color) pref = GlobalUserPreferences.ColorPreference.YELLOW;
|
||||
else if (id == R.id.nord_color) pref = GlobalUserPreferences.ColorPreference.NORD;
|
||||
|
||||
if (pref == null) return false;
|
||||
|
||||
GlobalUserPreferences.color=pref;
|
||||
GlobalUserPreferences.save();
|
||||
restartActivityToApplyNewTheme();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,863 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.DomainDisplay;
|
||||
import org.joinmastodon.android.fragments.MastodonToolbarFragment;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.ui.OutlineProviders;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public abstract class SettingsBaseFragment extends MastodonToolbarFragment implements DomainDisplay {
|
||||
protected View view;
|
||||
protected UsableRecyclerView list;
|
||||
|
||||
protected ImageView themeTransitionWindowView;
|
||||
|
||||
protected ThemeItem themeItem;
|
||||
|
||||
protected boolean needAppRestart;
|
||||
|
||||
protected SettingsBaseFragment.NotificationPolicyItem notificationPolicyItem;
|
||||
|
||||
protected PushSubscription pushSubscription;
|
||||
protected ArrayList<Item> items=new ArrayList<>();
|
||||
protected String accountID;
|
||||
|
||||
protected boolean needUpdateNotificationSettings;
|
||||
|
||||
public abstract void addItems(ArrayList<Item> items);
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||
setRetainInstance(true);
|
||||
setTitle(R.string.settings);
|
||||
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||
DomainManager.getInstance().setCurrentDomain(session.domain + "/settings");
|
||||
|
||||
addItems(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||
list=new UsableRecyclerView(getActivity());
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(new SettingsAdapter());
|
||||
list.setBackgroundColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorBackground));
|
||||
list.setPadding(0, V.dp(16), 0, V.dp(12));
|
||||
list.setClipToPadding(false);
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
// Add 32dp gaps between sections
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if((holder instanceof HeaderViewHolder || holder instanceof FooterViewHolder) && holder.getAbsoluteAdapterPosition()>1)
|
||||
outRect.top=V.dp(32);
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplyWindowInsets(WindowInsets insets){
|
||||
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
|
||||
list.setPadding(0, V.dp(16), 0, V.dp(12)+insets.getSystemWindowInsetBottom());
|
||||
insets=insets.inset(0, 0, 0, insets.getSystemWindowInsetBottom());
|
||||
}
|
||||
super.onApplyWindowInsets(insets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
|
||||
static abstract class Item{
|
||||
public abstract int getViewType();
|
||||
}
|
||||
|
||||
protected class HeaderItem extends Item{
|
||||
private String text;
|
||||
|
||||
public HeaderItem(@StringRes int text){
|
||||
this.text=getString(text);
|
||||
}
|
||||
|
||||
public HeaderItem(String text){
|
||||
this.text=text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return Type.HEADER.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
protected class SwitchItem extends Item{
|
||||
private String text;
|
||||
private int icon;
|
||||
boolean checked;
|
||||
private Consumer<SwitchItem> onChanged;
|
||||
private boolean enabled=true;
|
||||
|
||||
public SwitchItem(@StringRes int text, @DrawableRes int icon, boolean checked, Consumer<SwitchItem> onChanged){
|
||||
this.text=getString(text);
|
||||
this.icon=icon;
|
||||
this.checked=checked;
|
||||
this.onChanged=onChanged;
|
||||
}
|
||||
|
||||
public SwitchItem(@StringRes int text, @DrawableRes int icon, boolean checked, Consumer<SwitchItem> onChanged, boolean enabled){
|
||||
this.text=getString(text);
|
||||
this.icon=icon;
|
||||
this.checked=checked;
|
||||
this.onChanged=onChanged;
|
||||
this.enabled=enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return Type.SWITCH.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
protected class UpdateItem extends SettingsBaseFragment.Item {
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return Type.UPDATER.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ThemeItem extends SettingsBaseFragment.Item {
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return Type.THEME.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
protected static class NotificationPolicyItem extends SettingsBaseFragment.Item {
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return Type.NOTIFICATION_POLICY.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected 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 Type.BUTTON.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
protected class SmallTextItem extends Item {
|
||||
private String text;
|
||||
|
||||
public SmallTextItem(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType() {
|
||||
return Type.SMALL_TEXT.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
protected class TextItem extends Item{
|
||||
private String text;
|
||||
private String secondaryText;
|
||||
private Runnable onClick;
|
||||
private boolean loading;
|
||||
private int icon;
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick) {
|
||||
this(text, null, onClick, false, 0);
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
|
||||
this(text, null, onClick, false, icon);
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, @DrawableRes int icon) {
|
||||
this(text, secondaryText, onClick, false, icon);
|
||||
}
|
||||
|
||||
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, boolean loading, @DrawableRes int icon){
|
||||
this.text=getString(text);
|
||||
this.onClick=onClick;
|
||||
this.loading=loading;
|
||||
this.icon=icon;
|
||||
this.secondaryText = secondaryText;
|
||||
}
|
||||
|
||||
public TextItem(String text, Runnable onClick){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
public TextItem(String text, Runnable onClick, @DrawableRes int icon){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
this.icon=icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return Type.TEXT.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
protected class SettingsCategoryItem extends Item{
|
||||
private String text;
|
||||
private String secondaryText;
|
||||
private Runnable onClick;
|
||||
private boolean loading;
|
||||
private int icon;
|
||||
|
||||
public SettingsCategoryItem(@StringRes int text, Runnable onClick) {
|
||||
this(text, null, onClick, false, 0);
|
||||
}
|
||||
|
||||
public SettingsCategoryItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
|
||||
this(text, null, onClick, false, icon);
|
||||
}
|
||||
|
||||
public SettingsCategoryItem(@StringRes int text, String secondaryText, Runnable onClick, @DrawableRes int icon) {
|
||||
this(text, secondaryText, onClick, false, icon);
|
||||
}
|
||||
|
||||
public SettingsCategoryItem(@StringRes int text, String secondaryText, Runnable onClick, boolean loading, @DrawableRes int icon){
|
||||
this.text=getString(text);
|
||||
this.onClick=onClick;
|
||||
this.loading=loading;
|
||||
this.icon=icon;
|
||||
this.secondaryText = secondaryText;
|
||||
}
|
||||
|
||||
public SettingsCategoryItem(String text, Runnable onClick){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
public SettingsCategoryItem(String text, Runnable onClick, @DrawableRes int icon){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
this.icon=icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return Type.SETTINGS_CATEGORY.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected class FooterItem extends Item{
|
||||
private String text;
|
||||
private Runnable onClick;
|
||||
|
||||
public FooterItem(String text, Runnable onClick){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return Type.FOOTER.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
public enum Type{
|
||||
HEADER,
|
||||
SWITCH,
|
||||
THEME,
|
||||
TEXT,
|
||||
NOTIFICATION_POLICY,
|
||||
FOOTER,
|
||||
BUTTON,
|
||||
SMALL_TEXT,
|
||||
UPDATER,
|
||||
SETTINGS_CATEGORY
|
||||
}
|
||||
|
||||
|
||||
private class SettingsAdapter extends RecyclerView.Adapter<BindableViewHolder<Item>>{
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BindableViewHolder<Item> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||
//noinspection unchecked
|
||||
return (BindableViewHolder<Item>) switch(Type.values()[viewType]){
|
||||
case HEADER -> new HeaderViewHolder();
|
||||
case SWITCH -> new SwitchViewHolder();
|
||||
case THEME -> new ThemeViewHolder();
|
||||
case TEXT -> new TextViewHolder();
|
||||
case NOTIFICATION_POLICY -> new NotificationPolicyViewHolder();
|
||||
case FOOTER -> new FooterViewHolder();
|
||||
case BUTTON -> new ButtonViewHolder();
|
||||
case SMALL_TEXT -> new SmallTextViewHolder();
|
||||
case UPDATER -> new UpdateViewHolder();
|
||||
case SETTINGS_CATEGORY -> new SettingsCategoryViewHolder();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull BindableViewHolder<Item> holder, int position){
|
||||
holder.bind(items.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(){
|
||||
return items.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position){
|
||||
return items.get(position).getViewType();
|
||||
}
|
||||
}
|
||||
|
||||
private class HeaderViewHolder extends BindableViewHolder<HeaderItem>{
|
||||
private final TextView text;
|
||||
public HeaderViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_header, list);
|
||||
text=(TextView) itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(HeaderItem item){
|
||||
text.setText(item.text);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean onColorPreferenceClick(MenuItem item){
|
||||
GlobalUserPreferences.ColorPreference pref = null;
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == R.id.m3_color) pref = GlobalUserPreferences.ColorPreference.MATERIAL3;
|
||||
else if (id == R.id.pink_color) pref = GlobalUserPreferences.ColorPreference.PINK;
|
||||
else if (id == R.id.purple_color) pref = GlobalUserPreferences.ColorPreference.PURPLE;
|
||||
else if (id == R.id.green_color) pref = GlobalUserPreferences.ColorPreference.GREEN;
|
||||
else if (id == R.id.blue_color) pref = GlobalUserPreferences.ColorPreference.BLUE;
|
||||
else if (id == R.id.brown_color) pref = GlobalUserPreferences.ColorPreference.BROWN;
|
||||
else if (id == R.id.red_color) pref = GlobalUserPreferences.ColorPreference.RED;
|
||||
else if (id == R.id.yellow_color) pref = GlobalUserPreferences.ColorPreference.YELLOW;
|
||||
else if (id == R.id.nord_color) pref = GlobalUserPreferences.ColorPreference.NORD;
|
||||
|
||||
if (pref == null) return false;
|
||||
|
||||
GlobalUserPreferences.color=pref;
|
||||
GlobalUserPreferences.save();
|
||||
restartActivityToApplyNewTheme();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void onThemePreferenceClick(GlobalUserPreferences.ThemePreference theme){
|
||||
GlobalUserPreferences.theme=theme;
|
||||
GlobalUserPreferences.save();
|
||||
restartActivityToApplyNewTheme();
|
||||
}
|
||||
|
||||
protected void restartActivityToApplyNewTheme(){
|
||||
// Calling activity.recreate() causes a black screen for like half a second.
|
||||
// So, let's take a screenshot and overlay it on top to create the illusion of a smoother transition.
|
||||
// As a bonus, we can fade it out to make it even smoother.
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||
View activityDecorView=getActivity().getWindow().getDecorView();
|
||||
Bitmap bitmap=Bitmap.createBitmap(activityDecorView.getWidth(), activityDecorView.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
activityDecorView.draw(new Canvas(bitmap));
|
||||
themeTransitionWindowView=new ImageView(MastodonApp.context);
|
||||
themeTransitionWindowView.setImageBitmap(bitmap);
|
||||
WindowManager.LayoutParams lp=new WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION);
|
||||
lp.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
|
||||
lp.systemUiVisibility=View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
|
||||
lp.systemUiVisibility|=(activityDecorView.getWindowSystemUiVisibility() & (View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR));
|
||||
lp.width=lp.height=WindowManager.LayoutParams.MATCH_PARENT;
|
||||
lp.token=getActivity().getWindow().getAttributes().token;
|
||||
lp.windowAnimations=R.style.window_fade_out;
|
||||
MastodonApp.context.getSystemService(WindowManager.class).addView(themeTransitionWindowView, lp);
|
||||
}
|
||||
getActivity().recreate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy(){
|
||||
super.onDestroy();
|
||||
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
||||
}
|
||||
if(needAppRestart) UiUtils.restartApp();
|
||||
}
|
||||
|
||||
|
||||
protected void onTrueBlackThemeChanged(SettingsBaseFragment.SwitchItem item){
|
||||
GlobalUserPreferences.trueBlackTheme=item.checked;
|
||||
GlobalUserPreferences.save();
|
||||
|
||||
RecyclerView.ViewHolder themeHolder=list.findViewHolderForAdapterPosition(items.indexOf(themeItem));
|
||||
if(themeHolder!=null){
|
||||
((SettingsBaseFragment.ThemeViewHolder)themeHolder).bindSubitems();
|
||||
}else{
|
||||
list.getAdapter().notifyItemChanged(items.indexOf(themeItem));
|
||||
}
|
||||
|
||||
if(UiUtils.isDarkTheme()){
|
||||
restartActivityToApplyNewTheme();
|
||||
}
|
||||
}
|
||||
|
||||
private class NotificationPolicyViewHolder extends BindableViewHolder<NotificationPolicyItem>{
|
||||
private final Button button;
|
||||
private final PopupMenu popupMenu;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public NotificationPolicyViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_notification_policy, list);
|
||||
button=findViewById(R.id.button);
|
||||
popupMenu=new PopupMenu(getActivity(), button, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.notification_policy);
|
||||
popupMenu.setOnMenuItemClickListener(item->{
|
||||
PushSubscription.Policy policy;
|
||||
int id=item.getItemId();
|
||||
if(id==R.id.notify_anyone)
|
||||
policy=PushSubscription.Policy.ALL;
|
||||
else if(id==R.id.notify_followed)
|
||||
policy=PushSubscription.Policy.FOLLOWED;
|
||||
else if(id==R.id.notify_follower)
|
||||
policy=PushSubscription.Policy.FOLLOWER;
|
||||
else if(id==R.id.notify_none)
|
||||
policy=PushSubscription.Policy.NONE;
|
||||
else
|
||||
return false;
|
||||
onNotificationsPolicyChanged(policy);
|
||||
return true;
|
||||
});
|
||||
UiUtils.enablePopupMenuIcons(getActivity(), popupMenu);
|
||||
button.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
button.setOnClickListener(v->popupMenu.show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(SettingsBaseFragment.NotificationPolicyItem item){
|
||||
button.setText(switch(getPushSubscription().policy){
|
||||
case ALL -> R.string.notify_anyone;
|
||||
case FOLLOWED -> R.string.notify_followed;
|
||||
case FOLLOWER -> R.string.notify_follower;
|
||||
case NONE -> R.string.notify_none;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
|
||||
PushSubscription subscription=getPushSubscription();
|
||||
PushSubscription.Policy prevPolicy=subscription.policy;
|
||||
if(prevPolicy==policy)
|
||||
return;
|
||||
subscription.policy=policy;
|
||||
int index=items.indexOf(notificationPolicyItem);
|
||||
RecyclerView.ViewHolder policyHolder=list.findViewHolderForAdapterPosition(index);
|
||||
if(policyHolder!=null){
|
||||
((SettingsBaseFragment.NotificationPolicyViewHolder)policyHolder).rebind();
|
||||
}else{
|
||||
list.getAdapter().notifyItemChanged(index);
|
||||
}
|
||||
if((prevPolicy==PushSubscription.Policy.NONE)!=(policy==PushSubscription.Policy.NONE)){
|
||||
boolean newState=policy!=PushSubscription.Policy.NONE;
|
||||
for(PushNotification.Type value : PushNotification.Type.values()){
|
||||
onNotificationsChanged(value, newState);
|
||||
}
|
||||
index++;
|
||||
while(items.get(index) instanceof SettingsBaseFragment.SwitchItem si){
|
||||
si.enabled=si.checked=newState;
|
||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index);
|
||||
if(holder!=null)
|
||||
((BindableViewHolder<?>)holder).rebind();
|
||||
else
|
||||
list.getAdapter().notifyItemChanged(index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
needUpdateNotificationSettings=true;
|
||||
}
|
||||
|
||||
protected void onNotificationsChanged(PushNotification.Type type, boolean enabled){
|
||||
PushSubscription subscription=getPushSubscription();
|
||||
switch(type){
|
||||
case FAVORITE -> subscription.alerts.favourite=enabled;
|
||||
case FOLLOW -> subscription.alerts.follow=enabled;
|
||||
case REBLOG -> subscription.alerts.reblog=enabled;
|
||||
case MENTION -> subscription.alerts.mention=enabled;
|
||||
case POLL -> subscription.alerts.poll=enabled;
|
||||
case STATUS -> subscription.alerts.status=enabled;
|
||||
case UPDATE -> subscription.alerts.update=enabled;
|
||||
}
|
||||
needUpdateNotificationSettings=true;
|
||||
}
|
||||
|
||||
protected PushSubscription getPushSubscription(){
|
||||
if(pushSubscription!=null)
|
||||
return pushSubscription;
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
if(session.pushSubscription==null){
|
||||
pushSubscription=new PushSubscription();
|
||||
pushSubscription.alerts=PushSubscription.Alerts.ofAll();
|
||||
}else{
|
||||
pushSubscription=session.pushSubscription.clone();
|
||||
}
|
||||
return pushSubscription;
|
||||
}
|
||||
|
||||
private class SwitchViewHolder extends BindableViewHolder<SwitchItem> implements UsableRecyclerView.DisableableClickable{
|
||||
private final TextView text;
|
||||
private final ImageView icon;
|
||||
private final Switch checkbox;
|
||||
|
||||
public SwitchViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_switch, list);
|
||||
text=findViewById(R.id.text);
|
||||
icon=findViewById(R.id.icon);
|
||||
checkbox=findViewById(R.id.checkbox);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(SwitchItem item){
|
||||
text.setText(item.text);
|
||||
if (item.icon == 0) {
|
||||
icon.setVisibility(View.GONE);
|
||||
} else {
|
||||
icon.setVisibility(View.VISIBLE);
|
||||
icon.setImageResource(item.icon);
|
||||
}
|
||||
checkbox.setChecked(item.checked && item.enabled);
|
||||
checkbox.setEnabled(item.enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
item.checked=!item.checked;
|
||||
checkbox.setChecked(item.checked);
|
||||
item.onChanged.accept(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return item.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
class ThemeViewHolder extends BindableViewHolder<ThemeItem>{
|
||||
private ThemeViewHolder.SubitemHolder autoHolder, lightHolder, darkHolder;
|
||||
|
||||
public ThemeViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_theme, list);
|
||||
autoHolder=new ThemeViewHolder.SubitemHolder(findViewById(R.id.theme_auto));
|
||||
lightHolder=new ThemeViewHolder.SubitemHolder(findViewById(R.id.theme_light));
|
||||
darkHolder=new ThemeViewHolder.SubitemHolder(findViewById(R.id.theme_dark));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(SettingsBaseFragment.ThemeItem item){
|
||||
bindSubitems();
|
||||
}
|
||||
|
||||
public void bindSubitems(){
|
||||
autoHolder.bind(R.string.theme_auto, GlobalUserPreferences.trueBlackTheme ? R.drawable.theme_auto_trueblack : R.drawable.theme_auto, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO);
|
||||
lightHolder.bind(R.string.theme_light, R.drawable.theme_light, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.LIGHT);
|
||||
darkHolder.bind(R.string.theme_dark, GlobalUserPreferences.trueBlackTheme ? R.drawable.theme_dark_trueblack : R.drawable.theme_dark, GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK);
|
||||
}
|
||||
|
||||
private void onSubitemClick(View v){
|
||||
GlobalUserPreferences.ThemePreference pref;
|
||||
if(v.getId()==R.id.theme_auto)
|
||||
pref=GlobalUserPreferences.ThemePreference.AUTO;
|
||||
else if(v.getId()==R.id.theme_light)
|
||||
pref=GlobalUserPreferences.ThemePreference.LIGHT;
|
||||
else if(v.getId()==R.id.theme_dark)
|
||||
pref=GlobalUserPreferences.ThemePreference.DARK;
|
||||
else
|
||||
return;
|
||||
onThemePreferenceClick(pref);
|
||||
}
|
||||
|
||||
private class SubitemHolder{
|
||||
public TextView text;
|
||||
public ImageView icon;
|
||||
public RadioButton checkbox;
|
||||
|
||||
public SubitemHolder(View view){
|
||||
text=view.findViewById(R.id.text);
|
||||
icon=view.findViewById(R.id.icon);
|
||||
checkbox=view.findViewById(R.id.checkbox);
|
||||
view.setOnClickListener(ThemeViewHolder.this::onSubitemClick);
|
||||
|
||||
icon.setClipToOutline(true);
|
||||
icon.setOutlineProvider(OutlineProviders.roundedRect(4));
|
||||
}
|
||||
|
||||
public void bind(int text, int icon, boolean checked){
|
||||
this.text.setText(text);
|
||||
this.icon.setImageResource(icon);
|
||||
checkbox.setChecked(checked);
|
||||
}
|
||||
|
||||
public void setChecked(boolean checked){
|
||||
checkbox.setChecked(checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SettingsCategoryViewHolder extends BindableViewHolder<SettingsCategoryItem> implements UsableRecyclerView.DisableableClickable{
|
||||
private final ImageView icon;
|
||||
private final TextView text;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public SettingsCategoryViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_category, list);
|
||||
text=findViewById(R.id.text);
|
||||
icon=findViewById(R.id.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(SettingsCategoryItem item){
|
||||
text.setText(item.text);
|
||||
icon.setImageResource(item.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
item.onClick.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
|
||||
private final Button button;
|
||||
private final ImageView icon;
|
||||
private final TextView text;
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public ButtonViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_button, list);
|
||||
text=findViewById(R.id.text);
|
||||
icon=findViewById(R.id.icon);
|
||||
button=findViewById(R.id.button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ButtonItem item){
|
||||
text.setText(item.text);
|
||||
icon.setImageResource(item.icon);
|
||||
item.buttonConsumer.accept(button);
|
||||
}
|
||||
}
|
||||
|
||||
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
||||
private final TextView text, secondaryText;
|
||||
private final ProgressBar progress;
|
||||
private final ImageView icon;
|
||||
|
||||
public TextViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_text, list);
|
||||
text = itemView.findViewById(R.id.text);
|
||||
secondaryText = itemView.findViewById(R.id.secondary_text);
|
||||
progress = itemView.findViewById(R.id.progress);
|
||||
icon = itemView.findViewById(R.id.icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(TextItem item){
|
||||
icon.setVisibility(item.icon != 0 ? View.VISIBLE : View.GONE);
|
||||
secondaryText.setVisibility(item.secondaryText != null ? View.VISIBLE : View.GONE);
|
||||
|
||||
text.setText(item.text);
|
||||
progress.animate().alpha(item.loading ? 1 : 0);
|
||||
icon.setImageResource(item.icon);
|
||||
secondaryText.setText(item.secondaryText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
item.onClick.run();
|
||||
}
|
||||
}
|
||||
|
||||
private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> {
|
||||
private final TextView text;
|
||||
|
||||
public SmallTextViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_text, list);
|
||||
text = itemView.findViewById(R.id.text);
|
||||
text.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary));
|
||||
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||
text.setPaddingRelative(text.getPaddingStart(), 0, text.getPaddingEnd(), text.getPaddingBottom());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(SmallTextItem item){
|
||||
text.setText(item.text);
|
||||
}
|
||||
}
|
||||
|
||||
private class FooterViewHolder extends BindableViewHolder<FooterItem> implements UsableRecyclerView.Clickable{
|
||||
private final TextView text;
|
||||
public FooterViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_footer, list);
|
||||
text=(TextView) itemView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(FooterItem item){
|
||||
text.setText(item.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
item.onClick.run();
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||
|
||||
private final TextView text, changelog;
|
||||
private final Button button;
|
||||
private final ImageButton cancelBtn;
|
||||
private final ProgressBar progress;
|
||||
|
||||
private ObjectAnimator rotationAnimator;
|
||||
private Runnable progressUpdater=this::updateProgress;
|
||||
|
||||
public UpdateViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_update, list);
|
||||
text=findViewById(R.id.text);
|
||||
changelog=findViewById(R.id.changelog);
|
||||
button=findViewById(R.id.button);
|
||||
cancelBtn=findViewById(R.id.cancel_btn);
|
||||
progress=findViewById(R.id.progress);
|
||||
button.setOnClickListener(v->{
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
switch(updater.getState()){
|
||||
case UPDATE_AVAILABLE -> updater.downloadUpdate();
|
||||
case DOWNLOADED -> updater.installUpdate(getActivity());
|
||||
}
|
||||
});
|
||||
cancelBtn.setOnClickListener(v->GithubSelfUpdater.getInstance().cancelDownload());
|
||||
rotationAnimator=ObjectAnimator.ofFloat(progress, View.ROTATION, 0f, 360f);
|
||||
rotationAnimator.setInterpolator(new LinearInterpolator());
|
||||
rotationAnimator.setDuration(1500);
|
||||
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(SettingsBaseFragment.UpdateItem item){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
GithubSelfUpdater.UpdateState state=updater.getState();
|
||||
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
||||
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
||||
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
||||
text.setText(getString(R.string.mo_update_available, info.version));
|
||||
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
||||
}else{
|
||||
text.setText(getString(R.string.mo_update_ready, info.version));
|
||||
button.setText(R.string.install_update);
|
||||
}
|
||||
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
||||
rotationAnimator.start();
|
||||
button.setVisibility(View.INVISIBLE);
|
||||
cancelBtn.setVisibility(View.VISIBLE);
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
updateProgress();
|
||||
}else{
|
||||
rotationAnimator.cancel();
|
||||
button.setVisibility(View.VISIBLE);
|
||||
cancelBtn.setVisibility(View.GONE);
|
||||
progress.setVisibility(View.GONE);
|
||||
progress.removeCallbacks(progressUpdater);
|
||||
}
|
||||
changelog.setText(info.changelog);
|
||||
// changelog.setText(getString(R.string.sk_changelog, info.changelog));
|
||||
}
|
||||
|
||||
private void updateProgress(){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
if(updater.getState()!=GithubSelfUpdater.UpdateState.DOWNLOADING)
|
||||
return;
|
||||
int value=Math.round(progress.getMax()*updater.getDownloadProgress());
|
||||
if(Build.VERSION.SDK_INT>=24)
|
||||
progress.setProgress(value, true);
|
||||
else
|
||||
progress.setProgress(value);
|
||||
progress.postDelayed(progressUpdater, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -7,8 +7,10 @@ import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.LruCache;
|
||||
import android.util.TypedValue;
|
||||
@@ -36,6 +38,7 @@ import android.widget.Toast;
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.DomainManager;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||
@@ -49,6 +52,7 @@ import org.joinmastodon.android.api.session.AccountActivationInfo;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||
import org.joinmastodon.android.fragments.MastodonToolbarFragment;
|
||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||
@@ -76,12 +80,13 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.UsableRecyclerView;
|
||||
|
||||
public class SettingsFragment extends MastodonToolbarFragment{
|
||||
public class SettingsFragment extends MastodonToolbarFragment {
|
||||
private View view;
|
||||
private UsableRecyclerView list;
|
||||
private ArrayList<Item> items=new ArrayList<>();
|
||||
private ThemeItem themeItem;
|
||||
private NotificationPolicyItem notificationPolicyItem;
|
||||
private SwitchItem showNewPostsButtonItem, glitchModeItem;
|
||||
private SwitchItem showNewPostsButtonItem, glitchModeItem, compactReblogReplyLineItem;
|
||||
private String accountID;
|
||||
private boolean needUpdateNotificationSettings;
|
||||
private boolean needAppRestart;
|
||||
@@ -103,32 +108,19 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||
String instanceName = UiUtils.getInstanceName(accountID);
|
||||
|
||||
DomainManager.getInstance().setCurrentDomain(session.domain + "/settings");
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||
GithubSelfUpdater.UpdateState state=updater.getState();
|
||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING){
|
||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING && updater.getUpdateInfo() != null){
|
||||
items.add(new UpdateItem());
|
||||
}
|
||||
}
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_theme));
|
||||
|
||||
items.add(themeItem=new ThemeItem());
|
||||
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
||||
items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
|
||||
GlobalUserPreferences.disableMarquee=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
|
||||
GlobalUserPreferences.uniformNotificationIcon=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
|
||||
GlobalUserPreferences.reduceMotion=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
|
||||
|
||||
items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.color_palettes);
|
||||
@@ -148,15 +140,76 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
case NORD -> R.string.mo_color_palette_nord;
|
||||
});
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
||||
items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
|
||||
GlobalUserPreferences.disableMarquee=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
|
||||
GlobalUserPreferences.uniformNotificationIcon=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
|
||||
GlobalUserPreferences.reduceMotion=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_behavior));
|
||||
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||
GlobalUserPreferences.playGifs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.settings_custom_tabs, R.drawable.ic_fluent_link_24_regular, GlobalUserPreferences.useCustomTabs, i->{
|
||||
GlobalUserPreferences.useCustomTabs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_hide_compose_button_while_scrolling_setting, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.enableFabAutoHide, i->{
|
||||
GlobalUserPreferences.enableFabAutoHide =i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_load_remote_followers, R.drawable.ic_fluent_people_24_regular, GlobalUserPreferences.loadRemoteAccountFollowers, i -> {
|
||||
GlobalUserPreferences.loadRemoteAccountFollowers=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
|
||||
GlobalUserPreferences.showInteractionCounts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
||||
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
// items.add(new SwitchItem(R.string.sk_settings_show_differentiated_notification_icons, R.drawable.ic_ntf_logo, GlobalUserPreferences.showUniformPushNoticationIcons, this::onNotificationStyleChanged));
|
||||
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.mo_disable_double_tap_to_swipe_between_tabs, R.drawable.ic_fluent_double_tap_swipe_right_24_regular, GlobalUserPreferences.disableDoubleTapToSwipe, i->{
|
||||
GlobalUserPreferences.disableDoubleTapToSwipe=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
|
||||
GlobalUserPreferences.confirmBeforeReblog=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_swap_bookmark_with_reblog, R.drawable.ic_boost, GlobalUserPreferences.swapBookmarkWithBoostAction, i -> {
|
||||
GlobalUserPreferences.swapBookmarkWithBoostAction=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
|
||||
items.add(new HeaderItem(R.string.mo_composer_behavior));
|
||||
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.mo_disable_relocate_publish_button_to_enable_customization,
|
||||
Toast.LENGTH_LONG).show();
|
||||
});
|
||||
} else {
|
||||
b.setOnClickListener(l -> {
|
||||
b.setOnClickListener(l -> {
|
||||
if(!GlobalUserPreferences.relocatePublishButton) {
|
||||
FrameLayout inputWrap = new FrameLayout(getContext());
|
||||
EditText input = new EditText(getContext());
|
||||
input.setHint(R.string.publish);
|
||||
@@ -179,72 +232,57 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
.setNegativeButton(R.string.cancel, (d, which) -> {
|
||||
})
|
||||
.show();
|
||||
});}
|
||||
}));
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_behavior));
|
||||
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||
GlobalUserPreferences.playGifs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.settings_custom_tabs, R.drawable.ic_fluent_link_24_regular, GlobalUserPreferences.useCustomTabs, i->{
|
||||
GlobalUserPreferences.useCustomTabs=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
|
||||
GlobalUserPreferences.showInteractionCounts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
||||
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_tabs_disable_swipe, R.drawable.ic_fluent_swipe_right_24_regular, GlobalUserPreferences.disableSwipe, i->{
|
||||
GlobalUserPreferences.disableSwipe=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
||||
GlobalUserPreferences.enableDeleteNotifications=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.mo_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{
|
||||
GlobalUserPreferences.disableDividers=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_hide_compose_button_while_scrolling_setting, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.enableFabAutoHide, i->{
|
||||
GlobalUserPreferences.enableFabAutoHide =i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
} else {
|
||||
Toast.makeText(getActivity(), R.string.mo_disable_relocate_publish_button_to_enable_customization,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
|
||||
GlobalUserPreferences.relocatePublishButton=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
|
||||
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
||||
items.add(new SwitchItem(R.string.mo_change_default_reply_visibility_to_unlisted, R.drawable.ic_fluent_lock_open_24_regular, GlobalUserPreferences.defaultToUnlistedReplies, i->{
|
||||
GlobalUserPreferences.defaultToUnlistedReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
// TODO find a good icon for this setting
|
||||
items.add(new SwitchItem(R.string.mo_mention_reblogger_automatically, R.drawable.ic_fluent_balloon_24_regular, GlobalUserPreferences.mentionRebloggerAutomatically, i -> {
|
||||
GlobalUserPreferences.mentionRebloggerAutomatically=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_disable_reminder_to_add_alt_text, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, i->{
|
||||
GlobalUserPreferences.disableAltTextReminder=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
|
||||
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_disable_reminder_to_add_alt_text, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, i->{
|
||||
GlobalUserPreferences.showNoAltIndicator=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
|
||||
|
||||
|
||||
items.add(new HeaderItem(R.string.sk_timelines));
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||
GlobalUserPreferences.showReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
if (instance.pleroma != null) {
|
||||
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.reply_visibility);
|
||||
popupMenu.setOnMenuItemClickListener(item -> this.onReplyVisibilityChanged(item, b));
|
||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
b.setOnClickListener(v->popupMenu.show());
|
||||
b.setText(GlobalUserPreferences.replyVisibility == null ?
|
||||
R.string.sk_settings_reply_visibility_all :
|
||||
switch(GlobalUserPreferences.replyVisibility){
|
||||
case "following" -> R.string.sk_settings_reply_visibility_following;
|
||||
case "self" -> R.string.sk_settings_reply_visibility_self;
|
||||
default -> R.string.sk_settings_reply_visibility_all;
|
||||
});
|
||||
}));
|
||||
}
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
|
||||
GlobalUserPreferences.showBoosts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -278,6 +316,26 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
GlobalUserPreferences.collapseLongPosts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_hide_fab, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.autoHideFab, i->{
|
||||
GlobalUserPreferences.autoHideFab=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_reply_line_above_avatar, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.replyLineAboveHeader, i->{
|
||||
GlobalUserPreferences.replyLineAboveHeader=i.checked;
|
||||
GlobalUserPreferences.compactReblogReplyLine=i.checked;
|
||||
compactReblogReplyLineItem.enabled=i.checked;
|
||||
compactReblogReplyLineItem.checked= GlobalUserPreferences.replyLineAboveHeader;
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(compactReblogReplyLineItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(compactReblogReplyLineItem=new SwitchItem(R.string.sk_compact_reblog_reply_line, R.drawable.ic_fluent_re_order_24_regular, GlobalUserPreferences.compactReblogReplyLine, i->{
|
||||
GlobalUserPreferences.compactReblogReplyLine=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
compactReblogReplyLineItem.enabled=GlobalUserPreferences.replyLineAboveHeader;
|
||||
items.add(new SwitchItem(R.string.sk_settings_hide_interaction, R.drawable.ic_fluent_eye_24_regular, GlobalUserPreferences.spectatorMode, i->{
|
||||
GlobalUserPreferences.spectatorMode=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -289,7 +347,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
// needAppRestart=true;
|
||||
// }));
|
||||
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_notifications));
|
||||
items.add(notificationPolicyItem=new NotificationPolicyItem());
|
||||
PushSubscription pushSubscription=getPushSubscription();
|
||||
@@ -303,6 +360,24 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
items.add(new SwitchItem(R.string.sk_notify_update, R.drawable.ic_fluent_history_24_regular, pushSubscription.alerts.update, i->onNotificationsChanged(PushNotification.Type.UPDATE, i.checked), switchEnabled));
|
||||
items.add(new SwitchItem(R.string.sk_notify_poll_results, R.drawable.ic_fluent_poll_24_regular, pushSubscription.alerts.poll, i->onNotificationsChanged(PushNotification.Type.POLL, i.checked), switchEnabled));
|
||||
|
||||
items.add(new HeaderItem(R.string.mo_miscellaneous_settings));
|
||||
items.add(new SwitchItem(R.string.mo_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{
|
||||
GlobalUserPreferences.disableDividers=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
||||
GlobalUserPreferences.enableDeleteNotifications=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
|
||||
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
|
||||
|
||||
items.add(new HeaderItem(R.string.settings_account));
|
||||
items.add(new TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_posting, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/preferences/other"), R.drawable.ic_fluent_open_24_regular));
|
||||
@@ -356,6 +431,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
|
||||
items.add(new HeaderItem(R.string.sk_settings_about));
|
||||
|
||||
// if(BuildConfig.BUILD_TYPE.equals("nightly")){
|
||||
// items.add(new TextItem(R.string.mo_download_latest_nightly_release, ()->UiUtils.launchWebBrowser(getActivity(), "https://nightly.link/LucasGGamerM/moshidon/workflows/nightly-builds/master/moshidon-nightly.apk.zip"), R.drawable.ic_fluent_open_24_regular));
|
||||
// }
|
||||
|
||||
items.add(new TextItem(R.string.mo_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
|
||||
|
||||
if (GithubSelfUpdater.needSelfUpdating()) {
|
||||
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||
items.add(checkForUpdateItem);
|
||||
@@ -365,9 +447,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}));
|
||||
}
|
||||
|
||||
items.add(new TextItem(R.string.mo_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
|
||||
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
|
||||
|
||||
LruCache<?, ?> cache = imageCache == null ? null : imageCache.getLruCache();
|
||||
clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), cache != null ? cache.size() : 0, true), this::clearImageCache, 0);
|
||||
items.add(clearImageCacheItem);
|
||||
@@ -380,11 +459,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
GlobalUserPreferences.recentEmojis.clear();
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
// items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||
|
||||
if(BuildConfig.DEBUG){
|
||||
items.add(new RedHeaderItem("Debug options"));
|
||||
items.add(new TextItem("Test e-mail confirmation flow", ()->{
|
||||
|
||||
items.add(new TextItem("Test E-Mail confirmation flow", ()->{
|
||||
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
sess.activated=false;
|
||||
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
||||
@@ -393,9 +472,35 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
args.putBoolean("debug", true);
|
||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||
}));
|
||||
|
||||
items.add(new TextItem("Copy preferences", ()->{
|
||||
StringBuilder prefBuilder = new StringBuilder();
|
||||
GlobalUserPreferences.load();
|
||||
GlobalUserPreferences.getPrefs().getAll().forEach((key, value) -> prefBuilder.append(key).append(": ").append(value).append('\n'));
|
||||
UiUtils.copyText(view, prefBuilder.toString());
|
||||
}));
|
||||
|
||||
items.add(new TextItem("Reset preferences", ()->{
|
||||
GlobalUserPreferences.load();
|
||||
GlobalUserPreferences.getPrefs().edit().clear().commit();
|
||||
UiUtils.restartApp();
|
||||
}, R.drawable.ic_fluent_warning_24_regular));
|
||||
|
||||
items.add(new TextItem("Open App Info", () ->
|
||||
getContext().startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
.setData(Uri.fromParts("package", getContext().getPackageName(), null))),
|
||||
R.drawable.ic_fluent_open_24_regular
|
||||
)
|
||||
);
|
||||
|
||||
items.add(new TextItem("Open developer settings",
|
||||
()-> getContext().startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)),
|
||||
R.drawable.ic_fluent_open_24_regular)
|
||||
);
|
||||
}
|
||||
|
||||
items.add(new FooterItem(getString(R.string.mo_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||
String version = getContext().getString(R.string.mo_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE);
|
||||
items.add(new FooterItem(version, () -> UiUtils.copyText(view, version)));
|
||||
}
|
||||
|
||||
private void updatePublishText(Button btn) {
|
||||
@@ -454,6 +559,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
this.view = view;
|
||||
if(GithubSelfUpdater.needSelfUpdating())
|
||||
E.register(this);
|
||||
}
|
||||
@@ -510,6 +616,25 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
|
||||
String pref = null;
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == R.id.reply_visibility_following) pref = "following";
|
||||
else if (id == R.id.reply_visibility_self) pref = "self";
|
||||
|
||||
GlobalUserPreferences.replyVisibility=pref;
|
||||
GlobalUserPreferences.save();
|
||||
btn.setText(GlobalUserPreferences.replyVisibility == null ?
|
||||
R.string.sk_settings_reply_visibility_all :
|
||||
switch(GlobalUserPreferences.replyVisibility){
|
||||
case "following" -> R.string.sk_settings_reply_visibility_following;
|
||||
case "self" -> R.string.sk_settings_reply_visibility_self;
|
||||
default -> R.string.sk_settings_reply_visibility_all;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void restartActivityToApplyNewTheme(){
|
||||
// Calling activity.recreate() causes a black screen for like half a second.
|
||||
// So, let's take a screenshot and overlay it on top to create the illusion of a smoother transition.
|
||||
@@ -803,6 +928,12 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
public TextItem(String text, Runnable onClick, @DrawableRes int icon){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
this.icon=icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewType(){
|
||||
return 4;
|
||||
@@ -827,9 +958,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
|
||||
private class FooterItem extends Item{
|
||||
private String text;
|
||||
private Runnable onClick;
|
||||
|
||||
public FooterItem(String text){
|
||||
public FooterItem(String text, Runnable onClick){
|
||||
this.text=text;
|
||||
this.onClick=onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1108,7 +1241,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
}
|
||||
}
|
||||
|
||||
private class FooterViewHolder extends BindableViewHolder<FooterItem>{
|
||||
private class FooterViewHolder extends BindableViewHolder<FooterItem> implements UsableRecyclerView.Clickable{
|
||||
private final TextView text;
|
||||
public FooterViewHolder(){
|
||||
super(getActivity(), R.layout.item_settings_footer, list);
|
||||
@@ -1119,6 +1252,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
||||
public void onBind(FooterItem item){
|
||||
text.setText(item.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
item.onClick.run();
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
|
||||
public class SettingsMainFragment extends SettingsBaseFragment{
|
||||
@Override
|
||||
public void addItems(ArrayList<SettingsBaseFragment.Item> items) {
|
||||
items.add(new SettingsBaseFragment.SettingsCategoryItem(R.string.settings_theme, () -> {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("account", accountID);
|
||||
Nav.go(getActivity(), SettingsAppearanceFragment.class, args);
|
||||
}, R.drawable.ic_fluent_color_24_regular));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.joinmastodon.android.fragments.settings;
|
||||
|
||||
import android.view.Gravity;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class TimeLineFragment extends SettingsBaseFragment{
|
||||
|
||||
private SwitchItem showNewPostsButtonItem, compactReblogReplyLineItem;
|
||||
@Override
|
||||
public void addItems(ArrayList<Item> items) {
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||
GlobalUserPreferences.showReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
if (getInstance().pleroma != null) {
|
||||
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.reply_visibility);
|
||||
popupMenu.setOnMenuItemClickListener(item -> this.onReplyVisibilityChanged(item, b));
|
||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
b.setOnClickListener(v->popupMenu.show());
|
||||
b.setText(GlobalUserPreferences.replyVisibility == null ?
|
||||
R.string.sk_settings_reply_visibility_all :
|
||||
switch(GlobalUserPreferences.replyVisibility){
|
||||
case "following" -> R.string.sk_settings_reply_visibility_following;
|
||||
case "self" -> R.string.sk_settings_reply_visibility_self;
|
||||
default -> R.string.sk_settings_reply_visibility_all;
|
||||
});
|
||||
}));
|
||||
}
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
|
||||
GlobalUserPreferences.showBoosts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
||||
GlobalUserPreferences.loadNewPosts=i.checked;
|
||||
showNewPostsButtonItem.enabled = i.checked;
|
||||
if (!i.checked) {
|
||||
GlobalUserPreferences.showNewPostsButton = false;
|
||||
showNewPostsButtonItem.checked = false;
|
||||
}
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsButtonItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(showNewPostsButtonItem = new SwitchItem(R.string.sk_settings_show_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{
|
||||
GlobalUserPreferences.showNewPostsButton=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_alt_indicator, R.drawable.ic_fluent_scan_text_24_regular, GlobalUserPreferences.showAltIndicator, i->{
|
||||
GlobalUserPreferences.showAltIndicator=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_show_no_alt_indicator, R.drawable.ic_fluent_important_24_regular, GlobalUserPreferences.showNoAltIndicator, i->{
|
||||
GlobalUserPreferences.showNoAltIndicator=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_regular, GlobalUserPreferences.collapseLongPosts, i->{
|
||||
GlobalUserPreferences.collapseLongPosts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_hide_fab, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.autoHideFab, i->{
|
||||
GlobalUserPreferences.autoHideFab=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_reply_line_above_avatar, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.replyLineAboveHeader, i->{
|
||||
GlobalUserPreferences.replyLineAboveHeader=i.checked;
|
||||
GlobalUserPreferences.compactReblogReplyLine=i.checked;
|
||||
compactReblogReplyLineItem.enabled=i.checked;
|
||||
compactReblogReplyLineItem.checked= GlobalUserPreferences.replyLineAboveHeader;
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(compactReblogReplyLineItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(compactReblogReplyLineItem=new SwitchItem(R.string.sk_compact_reblog_reply_line, R.drawable.ic_fluent_re_order_24_regular, GlobalUserPreferences.compactReblogReplyLine, i->{
|
||||
GlobalUserPreferences.compactReblogReplyLine=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
compactReblogReplyLineItem.enabled=GlobalUserPreferences.replyLineAboveHeader;
|
||||
items.add(new SwitchItem(R.string.sk_settings_hide_interaction, R.drawable.ic_fluent_eye_24_regular, GlobalUserPreferences.spectatorMode, i->{
|
||||
GlobalUserPreferences.spectatorMode=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.mo_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{
|
||||
GlobalUserPreferences.disableDividers=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
}));
|
||||
}
|
||||
|
||||
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
|
||||
String pref = null;
|
||||
int id = item.getItemId();
|
||||
|
||||
if (id == R.id.reply_visibility_following) pref = "following";
|
||||
else if (id == R.id.reply_visibility_self) pref = "self";
|
||||
|
||||
GlobalUserPreferences.replyVisibility=pref;
|
||||
GlobalUserPreferences.save();
|
||||
btn.setText(GlobalUserPreferences.replyVisibility == null ?
|
||||
R.string.sk_settings_reply_visibility_all :
|
||||
switch(GlobalUserPreferences.replyVisibility){
|
||||
case "following" -> R.string.sk_settings_reply_visibility_following;
|
||||
case "self" -> R.string.sk_settings_reply_visibility_self;
|
||||
default -> R.string.sk_settings_reply_visibility_all;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class Account extends BaseModel implements Searchable{
|
||||
/**
|
||||
* The profile's display name.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public String displayName;
|
||||
/**
|
||||
* The profile's bio / description.
|
||||
@@ -86,7 +86,7 @@ public class Account extends BaseModel implements Searchable{
|
||||
/**
|
||||
* When the account was created.
|
||||
*/
|
||||
@RequiredField
|
||||
// @RequiredField
|
||||
public Instant createdAt;
|
||||
/**
|
||||
* When the most recent status was posted.
|
||||
@@ -134,8 +134,9 @@ public class Account extends BaseModel implements Searchable{
|
||||
public Instant muteExpiresAt;
|
||||
|
||||
public List<Role> roles;
|
||||
public boolean reloadWhenClicked;
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public String getQuery() {
|
||||
return url;
|
||||
}
|
||||
|
||||