Compare commits
545 Commits
1.3.0+fork
...
feature/ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1cb75d45f | ||
|
|
1675c8ab79 | ||
|
|
986b9731a1 | ||
|
|
dc66b27c8a | ||
|
|
3924c65e8c | ||
|
|
df7da2d7f6 | ||
|
|
697f95f3e3 | ||
|
|
f0d7da7194 | ||
|
|
b641e83470 | ||
|
|
f22f9c2dcc | ||
|
|
9ef2c278f6 | ||
|
|
4a7cb4f129 | ||
|
|
3a47e8eea3 | ||
|
|
4defd9c420 | ||
|
|
3bbff120f0 | ||
|
|
bf07e9f079 | ||
|
|
72fdff96c8 | ||
|
|
4e21b60087 | ||
|
|
7c786e55a5 | ||
|
|
51294bbb14 | ||
|
|
5dcda4f37a | ||
|
|
33d725acb4 | ||
|
|
fb78ac1243 | ||
|
|
ccfa614dc7 | ||
|
|
bc7f614573 | ||
|
|
7700288dbe | ||
|
|
e2c62aa76b | ||
|
|
35bf858a83 | ||
|
|
870bfaf08c | ||
|
|
c4238fb19b | ||
|
|
ba7aeb358b | ||
|
|
6f3fd4d454 | ||
|
|
97547f334f | ||
|
|
1ab953d819 | ||
|
|
161c19b628 | ||
|
|
307d483a56 | ||
|
|
9612248695 | ||
|
|
1f63401e5b | ||
|
|
d35ec18a88 | ||
|
|
b93b1847c3 | ||
|
|
cd46ed565f | ||
|
|
4a0e4edef8 | ||
|
|
2ea7333daa | ||
|
|
fa7a66809d | ||
|
|
71884ab760 | ||
|
|
f31205c670 | ||
|
|
0091ae87ce | ||
|
|
c196dc563f | ||
|
|
2baf51616c | ||
|
|
b75e2ce26f | ||
|
|
15976a991b | ||
|
|
16ee7371e7 | ||
|
|
02f0c794c7 | ||
|
|
ad13b1e927 | ||
|
|
a354ea80ab | ||
|
|
9f65b8112a | ||
|
|
6ac5d957fe | ||
|
|
4258c55b88 | ||
|
|
969f29e2e9 | ||
|
|
68921d0f0b | ||
|
|
c4ac4ee173 | ||
|
|
659b4e2fcd | ||
|
|
24e5bda8d3 | ||
|
|
02b1ad8d7a | ||
|
|
47eeb01b75 | ||
|
|
4288814138 | ||
|
|
ac4458e106 | ||
|
|
3d24b2de10 | ||
|
|
ed994b23e9 | ||
|
|
8c4678aba5 | ||
|
|
3d5fb2dfea | ||
|
|
ef6238b593 | ||
|
|
bc9bec3d66 | ||
|
|
d16e199dd1 | ||
|
|
a9c2df2e83 | ||
|
|
4673a4b9f7 | ||
|
|
d4a5286895 | ||
|
|
1b4579346b | ||
|
|
0665b8dd3b | ||
|
|
4f367d3e88 | ||
|
|
853124e2ce | ||
|
|
5dcd6e5a0d | ||
|
|
6f25c8be0f | ||
|
|
1db4b1319e | ||
|
|
76a97fcb47 | ||
|
|
4baaa39f35 | ||
|
|
52f025ae5a | ||
|
|
14b805e883 | ||
|
|
433a7b15fe | ||
|
|
6c8cbbc34a | ||
|
|
d4fbb298c1 | ||
|
|
9d78bb508a | ||
|
|
2aeb5f03d6 | ||
|
|
6522403c37 | ||
|
|
f090ca7f75 | ||
|
|
ac8b950893 | ||
|
|
2f02a238df | ||
|
|
22f9e941aa | ||
|
|
0d5fa97800 | ||
|
|
b102deaee1 | ||
|
|
3a73dbf304 | ||
|
|
faabc068ca | ||
|
|
443a69b10d | ||
|
|
6881651ce7 | ||
|
|
04aadf48f2 | ||
|
|
fc7ff07f40 | ||
|
|
968b2ee460 | ||
|
|
890340de94 | ||
|
|
c0589da549 | ||
|
|
4ca1a7b29e | ||
|
|
5432f2590c | ||
|
|
60ccf5cf0a | ||
|
|
bc717f5b10 | ||
|
|
3aead05ad4 | ||
|
|
9ef43cc6d3 | ||
|
|
24df7d49d5 | ||
|
|
486eef21dd | ||
|
|
44a4d02815 | ||
|
|
336a8194bd | ||
|
|
ac7c1c8497 | ||
|
|
d72f66b9bf | ||
|
|
1d445b8b27 | ||
|
|
14175a9140 | ||
|
|
7859f4cd05 | ||
|
|
37622ba9ce | ||
|
|
7a6af89375 | ||
|
|
056bfaacfe | ||
|
|
6684311ec5 | ||
|
|
11943571ad | ||
|
|
f696fcd412 | ||
|
|
2919e109ca | ||
|
|
995f478708 | ||
|
|
04fdea0296 | ||
|
|
b112c3a0b2 | ||
|
|
47149dd394 | ||
|
|
d1e0c1ebad | ||
|
|
2ef9d87250 | ||
|
|
fb8764bcd7 | ||
|
|
d7f73e02c5 | ||
|
|
e897b3af57 | ||
|
|
e04fd8a004 | ||
|
|
ada70ae1b5 | ||
|
|
5fdec0900e | ||
|
|
56a93288c4 | ||
|
|
02e3421f98 | ||
|
|
df7b53e10f | ||
|
|
fdbf331432 | ||
|
|
aed86ac6f0 | ||
|
|
3a13d4d6c0 | ||
|
|
f5336564d0 | ||
|
|
1ce49c68fe | ||
|
|
d37e880993 | ||
|
|
6fdb81a01f | ||
|
|
f9d6827572 | ||
|
|
10bf72b9ff | ||
|
|
800f929a15 | ||
|
|
bfcff1e19f | ||
|
|
f373e7df3e | ||
|
|
3985de5b14 | ||
|
|
4d75621384 | ||
|
|
e175a721d4 | ||
|
|
d9784ebc31 | ||
|
|
f241092277 | ||
|
|
0702703d78 | ||
|
|
2c4504bad3 | ||
|
|
07ca5a8b77 | ||
|
|
798a43906f | ||
|
|
41cb0f2e09 | ||
|
|
e12c0fb81f | ||
|
|
ac39f119e2 | ||
|
|
016faf3df0 | ||
|
|
b2d6879282 | ||
|
|
6926a212f4 | ||
|
|
addb6e06bf | ||
|
|
8ea752fbf7 | ||
|
|
89afc05d5c | ||
|
|
936f39161b | ||
|
|
ee20ee0722 | ||
|
|
02f9f8c8ea | ||
|
|
de3a252884 | ||
|
|
5e7a00de3e | ||
|
|
2858aeb55e | ||
|
|
357104efa9 | ||
|
|
bb8027c7ef | ||
|
|
f9dd787009 | ||
|
|
9478258caa | ||
|
|
9432bf9f38 | ||
|
|
c935a00763 | ||
|
|
404c4a3fdd | ||
|
|
4fa8f6deb5 | ||
|
|
f55b05012d | ||
|
|
ae81fee449 | ||
|
|
ec8dda4501 | ||
|
|
269b0b36b0 | ||
|
|
c8ed7c364b | ||
|
|
356833e248 | ||
|
|
1aa80270b4 | ||
|
|
1fccecdbf6 | ||
|
|
8be396e801 | ||
|
|
6755ff836e | ||
|
|
a3fc9710f7 | ||
|
|
e005731ba6 | ||
|
|
18ae3f4f61 | ||
|
|
10dfe0327e | ||
|
|
1d1e921137 | ||
|
|
0985a4c968 | ||
|
|
8df589c103 | ||
|
|
3ac9c09338 | ||
|
|
71b6b2f451 | ||
|
|
d85940ded8 | ||
|
|
e9e491c0b0 | ||
|
|
c73562fb75 | ||
|
|
3feacb59c8 | ||
|
|
a033d711c1 | ||
|
|
ba31afda27 | ||
|
|
3029c37755 | ||
|
|
728d55ffa0 | ||
|
|
265e9a9f56 | ||
|
|
7fbe205902 | ||
|
|
9b84333868 | ||
|
|
9525893bf7 | ||
|
|
daf15dcc62 | ||
|
|
b3e2b69b70 | ||
|
|
32081b71f5 | ||
|
|
7849c34d1f | ||
|
|
24977ec613 | ||
|
|
786bbab0d5 | ||
|
|
1facb07c28 | ||
|
|
bba5aba22d | ||
|
|
d7b85d6eba | ||
|
|
6832bfb95c | ||
|
|
4c379b67a3 | ||
|
|
3a2ae1ce71 | ||
|
|
c80afaf9c0 | ||
|
|
31d22bac47 | ||
|
|
b5f6687925 | ||
|
|
b3f25af923 | ||
|
|
78c141e946 | ||
|
|
83d36ce736 | ||
|
|
b928357ff1 | ||
|
|
c074bc57bc | ||
|
|
0e80c88b7d | ||
|
|
5ffa5b01fc | ||
|
|
61d9929485 | ||
|
|
231f19d113 | ||
|
|
bb41f62db5 | ||
|
|
47edc3180b | ||
|
|
9939d99c4b | ||
|
|
8053e8bb05 | ||
|
|
b7e9380bc4 | ||
|
|
83600087e1 | ||
|
|
b3dd5a2279 | ||
|
|
f9a8e10a85 | ||
|
|
d982331e21 | ||
|
|
68dfab9a44 | ||
|
|
c4f736a3fb | ||
|
|
b5abc86428 | ||
|
|
6b356c4dfd | ||
|
|
9e6723be41 | ||
|
|
fe84dc4823 | ||
|
|
08023a104c | ||
|
|
fd97cc6e87 | ||
|
|
78e5f0f011 | ||
|
|
73944675fa | ||
|
|
c6ded3d505 | ||
|
|
45b97de615 | ||
|
|
ec11a5e8e3 | ||
|
|
d71d17e5af | ||
|
|
e49e485ba3 | ||
|
|
2d4fc2166a | ||
|
|
84ba4ea999 | ||
|
|
c38eb545b1 | ||
|
|
b2d4a5aab0 | ||
|
|
201995849c | ||
|
|
1fc2f81dab | ||
|
|
69ddc95c2c | ||
|
|
a6ac68499c | ||
|
|
c10d7cfee4 | ||
|
|
f933bdbc53 | ||
|
|
274bca84d9 | ||
|
|
1e286dbc7a | ||
|
|
724d872491 | ||
|
|
64d5f9190a | ||
|
|
f1ab6833d3 | ||
|
|
140395f3cd | ||
|
|
f922e028a7 | ||
|
|
9aac0c007e | ||
|
|
e682cae7e7 | ||
|
|
f14977ba24 | ||
|
|
e8290e2f78 | ||
|
|
197110cfaf | ||
|
|
f320ac066c | ||
|
|
78acb5e7ea | ||
|
|
a14e864731 | ||
|
|
a87da87aad | ||
|
|
8befbb5a62 | ||
|
|
28893bc50b | ||
|
|
b73ef28f12 | ||
|
|
a71dc7f481 | ||
|
|
de4e7c1822 | ||
|
|
62090475f9 | ||
|
|
3c8715a7c4 | ||
|
|
4e188503a2 | ||
|
|
6fb7e97f13 | ||
|
|
80c9c591fc | ||
|
|
992fb5fefe | ||
|
|
f7c76f7503 | ||
|
|
e94364ecf6 | ||
|
|
1fab3d3743 | ||
|
|
35477055a9 | ||
|
|
e585f1df29 | ||
|
|
6311d18751 | ||
|
|
d29eef51ca | ||
|
|
c9692ef27b | ||
|
|
c36d3e9011 | ||
|
|
9409bbb9b2 | ||
|
|
3497747015 | ||
|
|
6343da1410 | ||
|
|
78a0c60600 | ||
|
|
ea152234a9 | ||
|
|
03612fcf64 | ||
|
|
a6a67512f4 | ||
|
|
dc9e977d40 | ||
|
|
5657df8db2 | ||
|
|
6abfe6ddd7 | ||
|
|
ab7489a049 | ||
|
|
a6fd6ae135 | ||
|
|
b30d4a025f | ||
|
|
5b747bfc74 | ||
|
|
a410d19114 | ||
|
|
a8589cc5b0 | ||
|
|
b057c9f7a8 | ||
|
|
96e4a4933c | ||
|
|
630064500d | ||
|
|
9543294996 | ||
|
|
56e9cc3406 | ||
|
|
be569cbe72 | ||
|
|
99f0817bdb | ||
|
|
220cd35d82 | ||
|
|
07f4ef1697 | ||
|
|
f20732ddc2 | ||
|
|
b1e0dc5843 | ||
|
|
285eb25706 | ||
|
|
ec556511e6 | ||
|
|
85c3d9f65f | ||
|
|
a7ebadf269 | ||
|
|
94c09d46c2 | ||
|
|
889fbc688d | ||
|
|
85d971242e | ||
|
|
23f82197c6 | ||
|
|
b4b16e2f37 | ||
|
|
2d838a8a23 | ||
|
|
7dab63cfe8 | ||
|
|
6548751bb0 | ||
|
|
65c158391f | ||
|
|
556fc4d31e | ||
|
|
c673f08aec | ||
|
|
818024d8dd | ||
|
|
cb3b893f72 | ||
|
|
f482b4bfe9 | ||
|
|
333c47339d | ||
|
|
a9283bfec8 | ||
|
|
f6f08d176c | ||
|
|
4c9f29e949 | ||
|
|
4ff5136652 | ||
|
|
56835b4f2d | ||
|
|
c43f734101 | ||
|
|
d4e1850d8c | ||
|
|
0b08072dfc | ||
|
|
66cdd63496 | ||
|
|
8b502b605c | ||
|
|
936a86acd7 | ||
|
|
64fc052c55 | ||
|
|
9897b8bfcd | ||
|
|
bcb50a9dd1 | ||
|
|
0b0775a86e | ||
|
|
73253f5f64 | ||
|
|
591b1cd9bf | ||
|
|
47a5d18ee0 | ||
|
|
b3f43804b0 | ||
|
|
139ec08d9d | ||
|
|
bc8846c351 | ||
|
|
85066f8ae6 | ||
|
|
1aec319e1c | ||
|
|
9848a94853 | ||
|
|
18ed3b44b4 | ||
|
|
a4043cc0e5 | ||
|
|
2b94d0c147 | ||
|
|
7e6395192b | ||
|
|
31db1cae7c | ||
|
|
f2c80a92c4 | ||
|
|
2cd107b400 | ||
|
|
eef4f42203 | ||
|
|
7eba69f574 | ||
|
|
cd226125cd | ||
|
|
1f543b4aa5 | ||
|
|
bd752824d9 | ||
|
|
a67dcbbf66 | ||
|
|
e0c77d3399 | ||
|
|
caf3ab5ce1 | ||
|
|
e549636645 | ||
|
|
3e2c3f40c7 | ||
|
|
40e20ead44 | ||
|
|
dfeba71abe | ||
|
|
2c0ec28803 | ||
|
|
2e1795dc6f | ||
|
|
74b06a4997 | ||
|
|
346610dd04 | ||
|
|
37cdc7116d | ||
|
|
d403f4ef01 | ||
|
|
cd1be782fa | ||
|
|
7f7eed1dec | ||
|
|
4843d574ca | ||
|
|
67059f3d71 | ||
|
|
0f0291074e | ||
|
|
4e143abfb9 | ||
|
|
558c5fba56 | ||
|
|
e50df3ea6d | ||
|
|
4383b11947 | ||
|
|
22209efc37 | ||
|
|
c3b5bb409b | ||
|
|
15f4d3326b | ||
|
|
a9ab9cb249 | ||
|
|
ac2e7cde41 | ||
|
|
3b7e11ba96 | ||
|
|
889fff5103 | ||
|
|
84ae1aad56 | ||
|
|
e9a2fae600 | ||
|
|
d32f03a067 | ||
|
|
7020018bb7 | ||
|
|
0cd0b9d580 | ||
|
|
3fda9e9eab | ||
|
|
e65404a466 | ||
|
|
3d47d1b4db | ||
|
|
d1749ab610 | ||
|
|
806c264686 | ||
|
|
34a9cb5a74 | ||
|
|
64fad2e871 | ||
|
|
961c69b525 | ||
|
|
c70f393559 | ||
|
|
9abdc174f4 | ||
|
|
2e5bfa1d9c | ||
|
|
9c89c26097 | ||
|
|
e3b6a5d389 | ||
|
|
0fb54efde5 | ||
|
|
a4a3f32dba | ||
|
|
03a1e29e0c | ||
|
|
eda9ff272b | ||
|
|
b3728e06ac | ||
|
|
33cbd85e19 | ||
|
|
8cb1f3f387 | ||
|
|
3f0c6fcec5 | ||
|
|
697262bb47 | ||
|
|
797cf893da | ||
|
|
a3564b70e1 | ||
|
|
43004307b8 | ||
|
|
5102fa2699 | ||
|
|
acd1e4ced3 | ||
|
|
6717070f93 | ||
|
|
65907742a6 | ||
|
|
6ef55f8b31 | ||
|
|
313448c42a | ||
|
|
4883f24fc7 | ||
|
|
be5ed6e224 | ||
|
|
3b32fe3663 | ||
|
|
e15b752bad | ||
|
|
5928cf675a | ||
|
|
7655a5a3ae | ||
|
|
387499ae49 | ||
|
|
8ab140c55d | ||
|
|
914abb95dd | ||
|
|
b5e0fca9c0 | ||
|
|
bac6f58115 | ||
|
|
5360c0f0f7 | ||
|
|
243d803b51 | ||
|
|
b343fe3835 | ||
|
|
3c42c1120f | ||
|
|
ad840dcef6 | ||
|
|
e2a20f9599 | ||
|
|
900823e0dd | ||
|
|
918bda54d6 | ||
|
|
cc1d1180e8 | ||
|
|
006c0d00f2 | ||
|
|
741a55110a | ||
|
|
f7dcb754ed | ||
|
|
f5e50bf668 | ||
|
|
4ad895ab71 | ||
|
|
0fccd0ab37 | ||
|
|
f73072d95e | ||
|
|
95cb9b5079 | ||
|
|
c6684d3c9b | ||
|
|
5c5989d8c0 | ||
|
|
60e92d30b0 | ||
|
|
8bf8e3f86b | ||
|
|
4fc6a8a2a5 | ||
|
|
891ee2d06b | ||
|
|
b450bc7ae8 | ||
|
|
4ca1e0d5db | ||
|
|
859213dd9e | ||
|
|
ad2857791d | ||
|
|
497827f2e2 | ||
|
|
967e333022 | ||
|
|
8df1406006 | ||
|
|
4af42fafdc | ||
|
|
a9e6a452c1 | ||
|
|
a4a4632397 | ||
|
|
421f39e414 | ||
|
|
f8121e2dc4 | ||
|
|
b1784fc51c | ||
|
|
96db0d7de7 | ||
|
|
3837ed9cb1 | ||
|
|
2be789a43c | ||
|
|
fd8d96169a | ||
|
|
397d768f3a | ||
|
|
1562dc32c1 | ||
|
|
f76eba894a | ||
|
|
42ae74f1a7 | ||
|
|
9d09a904ab | ||
|
|
38f377ca09 | ||
|
|
cc28bba884 | ||
|
|
beb3081918 | ||
|
|
1b3c9106b5 | ||
|
|
385b91761b | ||
|
|
43600756c0 | ||
|
|
3c3e0633ad | ||
|
|
f819ad6917 | ||
|
|
e7ad396fc6 | ||
|
|
b1cb4d4257 | ||
|
|
100bd4b062 | ||
|
|
7da09d9b37 | ||
|
|
f46eb07228 | ||
|
|
7627b5eb25 | ||
|
|
c710448c6b | ||
|
|
1ad270b1d6 | ||
|
|
099e253b2b | ||
|
|
66de4a5b91 | ||
|
|
41437d91d5 | ||
|
|
d33d5a6efa | ||
|
|
4f9248d040 | ||
|
|
f40c0e41f3 | ||
|
|
deeb03ff2b | ||
|
|
5c2a09e243 | ||
|
|
2473c999db | ||
|
|
ea2cc265e3 | ||
|
|
a0cd2d42cf |
1
.github/FUNDING.yml
vendored
@@ -1,6 +1,7 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: LucasGGamerM
|
github: LucasGGamerM
|
||||||
|
custom: ["https://liberapay.com/LucasGGamerM/donate", liberapay.com]
|
||||||
patreon: # mastodon
|
patreon: # mastodon
|
||||||
open_collective: # Replace with a single Open Collective username e.g., user1
|
open_collective: # Replace with a single Open Collective username e.g., user1
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
|||||||
22
.github/workflows/nightly-builds.yml
vendored
@@ -10,6 +10,28 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout Appkit Repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: grishka/appkit
|
||||||
|
|
||||||
|
- name: set up JDK 17
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'corretto'
|
||||||
|
cache: gradle
|
||||||
|
|
||||||
|
- name: Comment out signing config in appkits gradle file
|
||||||
|
run: |
|
||||||
|
sed -i 's/sign publishing\.publications\.release/\/\/ sign publishing.publications.release/' appkit/maven-push.gradle
|
||||||
|
|
||||||
|
- name: Grant execute permission for gradlew for Appkit
|
||||||
|
run: chmod +x gradlew
|
||||||
|
|
||||||
|
- name: Compile appkit
|
||||||
|
run: ./gradlew publishToMavenLocal
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: set up JDK 17
|
- name: set up JDK 17
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
|
|||||||
9
FAQ.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
## 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.
|
||||||
|
|
||||||
|
Q: Will there ever be a versjon of Moshidon for iOS?
|
||||||
|
|
||||||
|
A: No. As android and iOS apps do not share code, it is incredibly hard to port.
|
||||||
69
README.md
@@ -19,28 +19,30 @@
|
|||||||
<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>
|
<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>
|
||||||
|
|
||||||
## Help out the project by donating at: https://github.com/sponsors/LucasGGamerM!
|
## Help out the project by donating at: https://github.com/sponsors/LucasGGamerM!
|
||||||
|
### We also support LiberaPay at: https://liberapay.com/LucasGGamerM/donate!
|
||||||
|
|
||||||
---
|
### You can also donate some Monero through this wallet address as well:
|
||||||
|
4886mdarcyB6Yf8Qc6vDJBK1fz6ibHFLZUmHb4GZZz9yLGNhcG3XC64e5UZ8dVQYTLZb82W6P9WhteowW4STJEec97Gf22j
|
||||||
## 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Key features
|
## Key features
|
||||||
|
|
||||||
### **The ability to add new custom local timelines!**
|
### **The ability to add other server's local timeline to your timelines**
|
||||||
|
|
||||||
#### It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts!
|
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!**
|
### **View remote profiles**
|
||||||
|
|
||||||
### **Show posts filtered with a warning!**
|
You can now see all of a profile follows and followers, by directly loading them from the profile's home instance. In case of a failed lookup, the app will automatically fall back to the older method.
|
||||||
|
|
||||||
**Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:**
|
### **Translate posts easily**
|
||||||
|
|
||||||
|
Allows you to easily translate posts in another language with a translate button! Your instance must support translation, otherwise it will not work.
|
||||||
|
|
||||||
|
### **Show posts filtered with a warning**
|
||||||
|
|
||||||
|
Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:
|
||||||
|
|
||||||
Before | After
|
Before | After
|
||||||
:-------------------------:|:-------------------------:
|
:-------------------------:|:-------------------------:
|
||||||
@@ -49,7 +51,7 @@ Before | After
|
|||||||
|
|
||||||
### **Color themes**
|
### **Color themes**
|
||||||
|
|
||||||
**Allows you to change theme within the app. Supports Purple, pink, green, blue, red, orange, yellow and Nord!**
|
Allows you to change theme within the app. Supports Material You, purple, pink, green, blue, red, orange, yellow and Nord!
|
||||||
|
|
||||||
### **Unlisted posting**
|
### **Unlisted posting**
|
||||||
|
|
||||||
@@ -73,6 +75,10 @@ That’s one of the reasons why choosing a small, **well-moderated instance is i
|
|||||||
|
|
||||||
This is important to **ensure the content you’re sharing is as accessible as possible** to people who can’t see the images and rely on software to read back the provided content descriptions. Thankfully, it’s quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way!
|
This is important to **ensure the content you’re sharing is as accessible as possible** to people who can’t see the images and rely on software to read back the provided content descriptions. Thankfully, it’s quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way!
|
||||||
|
|
||||||
|
### **Reminder to add alt text to attached media**
|
||||||
|
|
||||||
|
By default, Moshidon will show a warning to add alt text if your post has any attachments without any alt text. This is for better accessibility, and it can easily be bypassed and disabled in settings.
|
||||||
|
|
||||||
### **Pinning posts**
|
### **Pinning posts**
|
||||||
|
|
||||||
**This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in people’s profiles shows all the posts they pinned.**
|
**This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in people’s profiles shows all the posts they pinned.**
|
||||||
@@ -97,12 +103,22 @@ Moshidon is also available in [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/
|
|||||||
|
|
||||||
## Release variants
|
## Release variants
|
||||||
|
|
||||||
All downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
### Stable variant
|
||||||
|
|
||||||
|
All stable version downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||||
|
|
||||||
**`moshidon.apk`**
|
**`moshidon.apk`**
|
||||||
|
|
||||||
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
|
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
|
||||||
|
|
||||||
|
### Nightly variant
|
||||||
|
|
||||||
|
All nightly builds can be downloaded at [Nightly Releases](https://github.com/LucasGGamerM/moshidon-nightly/releases) page.
|
||||||
|
|
||||||
|
**`moshidon-nightly.apk`**
|
||||||
|
|
||||||
|
Unstable variant with an integrated updater. It's for development and testing purposes. If you find any bugs with it, please file a bug report at our [issues](https://github.com/LucasGGamerM/moshidon/issues) page.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
@@ -110,16 +126,18 @@ Variant with an integrated updater. If you download Moshidon from here (and not
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
* [Adding the ability to view other server's local timelines](https://github.com/LucasGGamerM/moshidon/tree/feature/local-timelines)
|
||||||
|
* [Adding the ability to load followers and following from remote instance](https://github.com/LucasGGamerM/moshidon/tree/feature/remote-followers)
|
||||||
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
|
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
|
||||||
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
|
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
|
||||||
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
|
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
|
||||||
* Adding a useful private profile note box!*
|
* Adding a useful private profile note box
|
||||||
* Auto hiding the compose button on scroll!*
|
* Auto hiding the compose button on scroll
|
||||||
* Adding the ability to remind yourself to add alt text to images!*
|
* Adding the ability to remind yourself to add alt text to images
|
||||||
* An indicator for if an image has alt text or not*
|
* An indicator for if an image has alt text or not
|
||||||
* Adding the ability to have drafts!*
|
* Adding the ability to have drafts
|
||||||
* Also adding the ability to view announcements from your instance!*
|
* Also adding the ability to view announcements from your instance
|
||||||
* Adding the ability to post for local timeline only (Only on instances that support it!)*
|
* Adding the ability to post for local timeline only (Only on instances that support it!)
|
||||||
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
||||||
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
||||||
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
||||||
@@ -141,6 +159,7 @@ Variant with an integrated updater. If you download Moshidon from here (and not
|
|||||||
|
|
||||||
### Behavior
|
### Behavior
|
||||||
|
|
||||||
|
* Allow for confirmation before reblogging
|
||||||
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
|
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
|
||||||
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
||||||
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
||||||
@@ -172,6 +191,12 @@ This project is released under the [GPL-3 License](./LICENSE).
|
|||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
[Official matrix chatroom:](https://matrix.to/#/#moshidon:matrix.org) https://matrix.to/#/#moshidon:matrix.org
|
[F.A.Q](FAQ.md)
|
||||||
|
|
||||||
|
[Official matrix chatroom:](https://matrix.to/#/#moshidon:floss.social) https://matrix.to/#/#moshidon:floss.social
|
||||||
|
|
||||||
|
[Moshidon roadmap](https://github.com/users/LucasGGamerM/projects/1)
|
||||||
|
|
||||||
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
|
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
|
||||||
|
|
||||||
|
---
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ buildscript {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.0.0'
|
classpath 'com.android.tools.build:gradle:8.0.0'
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ android {
|
|||||||
versionName "1.3.0+fork.100.moshinda"
|
versionName "1.3.0+fork.100.moshinda"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
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 {
|
signingConfigs {
|
||||||
nightly{
|
nightly{
|
||||||
@@ -62,7 +62,6 @@ android {
|
|||||||
initWith release
|
initWith release
|
||||||
}
|
}
|
||||||
nightly{
|
nightly{
|
||||||
initWith release
|
|
||||||
if(System.getenv("CURRENT_DATE") != null){
|
if(System.getenv("CURRENT_DATE") != null){
|
||||||
versionNameSuffix '-nightly+@' + System.getenv("CURRENT_DATE")
|
versionNameSuffix '-nightly+@' + System.getenv("CURRENT_DATE")
|
||||||
} else {
|
} else {
|
||||||
@@ -71,6 +70,7 @@ android {
|
|||||||
versionNameSuffix '-nightly+@' + properties.getProperty('CURRENT_DATE')
|
versionNameSuffix '-nightly+@' + properties.getProperty('CURRENT_DATE')
|
||||||
}
|
}
|
||||||
applicationIdSuffix '.nightly'
|
applicationIdSuffix '.nightly'
|
||||||
|
|
||||||
signingConfig signingConfigs.nightly
|
signingConfig signingConfigs.nightly
|
||||||
manifestPlaceholders = [oAuthScheme:"moshidon-android-nightly-auth"]
|
manifestPlaceholders = [oAuthScheme:"moshidon-android-nightly-auth"]
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ android {
|
|||||||
setRoot "src/github"
|
setRoot "src/github"
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
setRoot "src/github"
|
setRoot "src/debug"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace 'org.joinmastodon.android'
|
namespace 'org.joinmastodon.android'
|
||||||
@@ -114,7 +114,7 @@ dependencies {
|
|||||||
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
|
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
|
||||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||||
implementation 'me.grishka.appkit:appkit:1.2.7'
|
implementation 'me.grishka.appkit:appkit:1.2.8'
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
implementation 'org.jsoup:jsoup:1.14.3'
|
implementation 'org.jsoup:jsoup:1.14.3'
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.model.StatusContext;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ThreadFragmentTest {
|
||||||
|
|
||||||
|
private Status fakeStatus(String id, String inReplyTo) {
|
||||||
|
Status status = Status.ofFake(id, null, null);
|
||||||
|
status.inReplyToId = inReplyTo;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreadFragment.NeighborAncestryInfo fakeInfo(Status s, Status d, Status a) {
|
||||||
|
return new ThreadFragment.NeighborAncestryInfo(s, d, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapNeighborhoodAncestry() {
|
||||||
|
StatusContext context = new StatusContext();
|
||||||
|
context.ancestors = List.of(
|
||||||
|
fakeStatus("oldest ancestor", null),
|
||||||
|
fakeStatus("younger ancestor", "oldest ancestor")
|
||||||
|
);
|
||||||
|
Status mainStatus = fakeStatus("main status", "younger ancestor");
|
||||||
|
context.descendants = List.of(
|
||||||
|
fakeStatus("first reply", "main status"),
|
||||||
|
fakeStatus("reply to first reply", "first reply"),
|
||||||
|
fakeStatus("third level reply", "reply to first reply"),
|
||||||
|
fakeStatus("another reply", "main status")
|
||||||
|
);
|
||||||
|
|
||||||
|
List<ThreadFragment.NeighborAncestryInfo> neighbors =
|
||||||
|
ThreadFragment.mapNeighborhoodAncestry(mainStatus, context);
|
||||||
|
|
||||||
|
assertEquals(List.of(
|
||||||
|
fakeInfo(context.ancestors.get(0), context.ancestors.get(1), null),
|
||||||
|
fakeInfo(context.ancestors.get(1), mainStatus, context.ancestors.get(0)),
|
||||||
|
fakeInfo(mainStatus, context.descendants.get(0), context.ancestors.get(1)),
|
||||||
|
fakeInfo(context.descendants.get(0), context.descendants.get(1), mainStatus),
|
||||||
|
fakeInfo(context.descendants.get(1), context.descendants.get(2), context.descendants.get(0)),
|
||||||
|
fakeInfo(context.descendants.get(2), null, context.descendants.get(1)),
|
||||||
|
fakeInfo(context.descendants.get(3), null, null)
|
||||||
|
), neighbors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maybeApplyMainStatus() {
|
||||||
|
ThreadFragment fragment = new ThreadFragment();
|
||||||
|
fragment.contextInitiallyRendered = true;
|
||||||
|
fragment.mainStatus = Status.ofFake("123456", "original text", Instant.EPOCH);
|
||||||
|
|
||||||
|
Status update1 = Status.ofFake("123456", "updated text", Instant.EPOCH);
|
||||||
|
update1.editedAt = Instant.ofEpochSecond(1);
|
||||||
|
fragment.updatedStatus = update1;
|
||||||
|
StatusUpdatedEvent event1 = (StatusUpdatedEvent) fragment.maybeApplyMainStatus();
|
||||||
|
assertEquals("fired update event", update1, event1.status);
|
||||||
|
assertEquals("updated main status", update1, fragment.mainStatus);
|
||||||
|
|
||||||
|
Status update2 = Status.ofFake("123456", "updated text", Instant.EPOCH);
|
||||||
|
update2.favouritesCount = 123;
|
||||||
|
fragment.updatedStatus = update2;
|
||||||
|
StatusCountersUpdatedEvent event2 = (StatusCountersUpdatedEvent) fragment.maybeApplyMainStatus();
|
||||||
|
assertEquals("only fired counter update event", update2.id, event2.id);
|
||||||
|
assertEquals("updated counter is correct", 123, event2.favorites);
|
||||||
|
assertEquals("updated main status", update2, fragment.mainStatus);
|
||||||
|
|
||||||
|
Status update3 = Status.ofFake("123456", "whatever", Instant.EPOCH);
|
||||||
|
fragment.contextInitiallyRendered = false;
|
||||||
|
fragment.updatedStatus = update3;
|
||||||
|
assertNull("no update when context hasn't been rendered", fragment.maybeApplyMainStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sortStatusContext() {
|
||||||
|
StatusContext context = new StatusContext();
|
||||||
|
context.ancestors = List.of(
|
||||||
|
fakeStatus("younger ancestor", "oldest ancestor"),
|
||||||
|
fakeStatus("oldest ancestor", null)
|
||||||
|
);
|
||||||
|
context.descendants = List.of(
|
||||||
|
fakeStatus("reply to first reply", "first reply"),
|
||||||
|
fakeStatus("third level reply", "reply to first reply"),
|
||||||
|
fakeStatus("first reply", "main status"),
|
||||||
|
fakeStatus("another reply", "main status")
|
||||||
|
);
|
||||||
|
|
||||||
|
ThreadFragment.sortStatusContext(
|
||||||
|
fakeStatus("main status", "younger ancestor"),
|
||||||
|
context
|
||||||
|
);
|
||||||
|
List<Status> expectedAncestors = List.of(
|
||||||
|
fakeStatus("oldest ancestor", null),
|
||||||
|
fakeStatus("younger ancestor", "oldest ancestor")
|
||||||
|
);
|
||||||
|
List<Status> expectedDescendants = List.of(
|
||||||
|
fakeStatus("first reply", "main status"),
|
||||||
|
fakeStatus("reply to first reply", "first reply"),
|
||||||
|
fakeStatus("third level reply", "reply to first reply"),
|
||||||
|
fakeStatus("another reply", "main status")
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: ??? i have no idea how this code works. it certainly doesn't return what i'd expect
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package org.joinmastodon.android.ui.utils;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class UiUtilsTest {
|
||||||
|
@BeforeClass
|
||||||
|
public static void createDummySession() {
|
||||||
|
Instance dummyInstance = new Instance();
|
||||||
|
dummyInstance.uri = "test.tld";
|
||||||
|
Account dummyAccount = new Account();
|
||||||
|
dummyAccount.id = "123456";
|
||||||
|
AccountSessionManager.getInstance().addAccount(dummyInstance, null, dummyAccount, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void cleanUp() {
|
||||||
|
AccountSessionManager.getInstance().removeAccount("test.tld_123456");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseFediverseHandle() {
|
||||||
|
assertEquals(
|
||||||
|
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
|
||||||
|
UiUtils.parseFediverseHandle("megalodon@floss.social")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
|
||||||
|
UiUtils.parseFediverseHandle("@megalodon@floss.social")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.of(Pair.create("megalodon", Optional.empty())),
|
||||||
|
UiUtils.parseFediverseHandle("@megalodon")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
|
||||||
|
UiUtils.parseFediverseHandle("mailto:megalodon@floss.social")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.empty(),
|
||||||
|
UiUtils.parseFediverseHandle("megalodon")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.empty(),
|
||||||
|
UiUtils.parseFediverseHandle("this is not a fedi handle")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
Optional.empty(),
|
||||||
|
UiUtils.parseFediverseHandle("not@a-domain")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void acctMatches() {
|
||||||
|
assertTrue("local account, domain not specified", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"someone",
|
||||||
|
"someone",
|
||||||
|
null
|
||||||
|
));
|
||||||
|
|
||||||
|
assertTrue("domain not specified", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"someone@somewhere.social",
|
||||||
|
"someone",
|
||||||
|
null
|
||||||
|
));
|
||||||
|
|
||||||
|
assertTrue("local account, domain specified, different casing", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"SomeOne",
|
||||||
|
"someone",
|
||||||
|
"Test.TLD"
|
||||||
|
));
|
||||||
|
|
||||||
|
assertFalse("username doesn't match", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"someone-else@somewhere.social",
|
||||||
|
"someone",
|
||||||
|
"somewhere.social"
|
||||||
|
));
|
||||||
|
|
||||||
|
assertFalse("domain doesn't match", UiUtils.acctMatches(
|
||||||
|
"test.tld_123456",
|
||||||
|
"someone@somewhere.social",
|
||||||
|
"someone",
|
||||||
|
"somewhere.else"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package org.joinmastodon.android.utils;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterAction.*;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterContext.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class StatusFilterPredicateTest {
|
||||||
|
|
||||||
|
private static final Filter hideMeFilter = new Filter(), warnMeFilter = new Filter();
|
||||||
|
private static final List<Filter> allFilters = List.of(hideMeFilter, warnMeFilter);
|
||||||
|
|
||||||
|
private static final Status
|
||||||
|
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
|
||||||
|
warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now());
|
||||||
|
|
||||||
|
static {
|
||||||
|
hideMeFilter.phrase = "hide me";
|
||||||
|
hideMeFilter.filterAction = HIDE;
|
||||||
|
hideMeFilter.context = EnumSet.of(PUBLIC, HOME);
|
||||||
|
|
||||||
|
warnMeFilter.phrase = "warning";
|
||||||
|
warnMeFilter.filterAction = WARN;
|
||||||
|
warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHide() {
|
||||||
|
assertFalse("should not pass because matching filter applies to given context",
|
||||||
|
new StatusFilterPredicate(allFilters, HOME).test(hideInHomePublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHideRegardlessOfContext() {
|
||||||
|
assertTrue("filters without context should always pass",
|
||||||
|
new StatusFilterPredicate(allFilters, null).test(hideInHomePublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHideInDifferentContext() {
|
||||||
|
assertTrue("should pass because matching filter does not apply to given context",
|
||||||
|
new StatusFilterPredicate(allFilters, THREAD).test(hideInHomePublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHideWithWarningText() {
|
||||||
|
assertTrue("should pass because matching filter is for warnings",
|
||||||
|
new StatusFilterPredicate(allFilters, HOME).test(warnInHomePublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWarn() {
|
||||||
|
assertFalse("should not pass because filter applies to given context",
|
||||||
|
new StatusFilterPredicate(allFilters, HOME, WARN).test(warnInHomePublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWarnRegardlessOfContext() {
|
||||||
|
assertTrue("filters without context should always pass",
|
||||||
|
new StatusFilterPredicate(allFilters, null, WARN).test(warnInHomePublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWarnInDifferentContext() {
|
||||||
|
assertTrue("should pass because filter does not apply to given context",
|
||||||
|
new StatusFilterPredicate(allFilters, THREAD, WARN).test(warnInHomePublic));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWarnWithHideText() {
|
||||||
|
assertTrue("should pass because matching filter is for hiding",
|
||||||
|
new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic));
|
||||||
|
}
|
||||||
|
}
|
||||||
25
mastodon/src/debug/AndroidManifest.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?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
|
||||||
|
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>-->
|
||||||
|
<!-- <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>-->
|
||||||
|
<!-- </intent-filter>-->
|
||||||
|
<!-- </receiver>-->
|
||||||
|
<provider
|
||||||
|
android:authorities="${applicationId}.self_update_provider"
|
||||||
|
android:name=".updater.SelfUpdateContentProvider"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:exported="false"/>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
package org.joinmastodon.android.updater;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.DownloadManager;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageInstaller;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.BuildConfig;
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.MastodonApp;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||||
|
private static final long CHECK_PERIOD=6*3600*1000L;
|
||||||
|
private static final String TAG="GithubSelfUpdater";
|
||||||
|
|
||||||
|
private UpdateState state=UpdateState.NO_UPDATE;
|
||||||
|
private UpdateInfo info;
|
||||||
|
private long downloadID;
|
||||||
|
private BroadcastReceiver downloadCompletionReceiver=new BroadcastReceiver(){
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent){
|
||||||
|
if(downloadID!=0 && intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)==downloadID){
|
||||||
|
MastodonApp.context.unregisterReceiver(this);
|
||||||
|
setState(UpdateState.DOWNLOADED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public GithubSelfUpdaterImpl(){
|
||||||
|
SharedPreferences prefs=getPrefs();
|
||||||
|
int checkedByBuild=prefs.getInt("checkedByBuild", 0);
|
||||||
|
if(prefs.contains("version") && checkedByBuild==BuildConfig.VERSION_CODE){
|
||||||
|
info=new UpdateInfo();
|
||||||
|
info.version=prefs.getString("version", null);
|
||||||
|
info.size=prefs.getLong("apkSize", 0);
|
||||||
|
info.changelog=prefs.getString("changelog", null);
|
||||||
|
downloadID=prefs.getLong("downloadID", 0);
|
||||||
|
if(downloadID==0 || !getUpdateApkFile().exists()){
|
||||||
|
state=UpdateState.UPDATE_AVAILABLE;
|
||||||
|
}else{
|
||||||
|
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||||
|
state=dm.getUriForDownloadedFile(downloadID)==null ? UpdateState.DOWNLOADING : UpdateState.DOWNLOADED;
|
||||||
|
if(state==UpdateState.DOWNLOADING){
|
||||||
|
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else if(checkedByBuild!=BuildConfig.VERSION_CODE && checkedByBuild>0){
|
||||||
|
// We are in a new version, running for the first time after update. Gotta clean things up.
|
||||||
|
long id=getPrefs().getLong("downloadID", 0);
|
||||||
|
if(id!=0){
|
||||||
|
MastodonApp.context.getSystemService(DownloadManager.class).remove(id);
|
||||||
|
}
|
||||||
|
getUpdateApkFile().delete();
|
||||||
|
getPrefs().edit()
|
||||||
|
.remove("apkSize")
|
||||||
|
.remove("version")
|
||||||
|
.remove("apkURL")
|
||||||
|
.remove("checkedByBuild")
|
||||||
|
.remove("downloadID")
|
||||||
|
.remove("changelog")
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SharedPreferences getPrefs(){
|
||||||
|
return MastodonApp.context.getSharedPreferences("githubUpdater", Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void maybeCheckForUpdates(){
|
||||||
|
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
|
||||||
|
return;
|
||||||
|
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD);
|
||||||
|
if(timeSinceLastCheck>=CHECK_PERIOD){
|
||||||
|
setState(UpdateState.CHECKING);
|
||||||
|
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkForUpdates() {
|
||||||
|
setState(UpdateState.CHECKING);
|
||||||
|
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actuallyCheckForUpdates(){
|
||||||
|
Request req=new Request.Builder()
|
||||||
|
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases")
|
||||||
|
.build();
|
||||||
|
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||||
|
try(Response resp=call.execute()){
|
||||||
|
JsonArray arr=JsonParser.parseReader(resp.body().charStream()).getAsJsonArray();
|
||||||
|
for (JsonElement jsonElement : arr) {
|
||||||
|
JsonObject obj = jsonElement.getAsJsonObject();
|
||||||
|
if (obj.get("prerelease").getAsBoolean() && !GlobalUserPreferences.enablePreReleases) continue;
|
||||||
|
|
||||||
|
String tag=obj.get("tag_name").getAsString();
|
||||||
|
String changelog=obj.get("body").getAsString();
|
||||||
|
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
||||||
|
Matcher matcher=pattern.matcher(tag);
|
||||||
|
if(!matcher.find()){
|
||||||
|
Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int newMajor=Integer.parseInt(matcher.group(1)),
|
||||||
|
newMinor=Integer.parseInt(matcher.group(2)),
|
||||||
|
newRevision=Integer.parseInt(matcher.group(3)),
|
||||||
|
newForkNumber=Integer.parseInt(matcher.group(4));
|
||||||
|
matcher=pattern.matcher(BuildConfig.VERSION_NAME);
|
||||||
|
String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
|
||||||
|
if(!matcher.find()){
|
||||||
|
Log.w(TAG, "actuallyCheckForUpdates: current version has wrong format: "+BuildConfig.VERSION_NAME);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int curMajor=Integer.parseInt(matcher.group(1)),
|
||||||
|
curMinor=Integer.parseInt(matcher.group(2)),
|
||||||
|
curRevision=Integer.parseInt(matcher.group(3)),
|
||||||
|
curForkNumber=Integer.parseInt(matcher.group(4));
|
||||||
|
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
||||||
|
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
||||||
|
if(newVersion>curVersion || newForkNumber>curForkNumber){
|
||||||
|
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
||||||
|
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
||||||
|
for(JsonElement el:obj.getAsJsonArray("assets")){
|
||||||
|
JsonObject asset=el.getAsJsonObject();
|
||||||
|
if("moshidon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
||||||
|
long size=asset.get("size").getAsLong();
|
||||||
|
String url=asset.get("browser_download_url").getAsString();
|
||||||
|
|
||||||
|
UpdateInfo info=new UpdateInfo();
|
||||||
|
info.size=size;
|
||||||
|
info.version=version;
|
||||||
|
info.changelog=changelog;
|
||||||
|
this.info=info;
|
||||||
|
|
||||||
|
getPrefs().edit()
|
||||||
|
.putLong("apkSize", size)
|
||||||
|
.putString("version", version)
|
||||||
|
.putString("apkURL", url)
|
||||||
|
.putString("changelog", changelog)
|
||||||
|
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
||||||
|
.remove("downloadID")
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}catch(Exception x){
|
||||||
|
Log.w(TAG, "actuallyCheckForUpdates", x);
|
||||||
|
}finally{
|
||||||
|
setState(info==null ? UpdateState.NO_UPDATE : UpdateState.UPDATE_AVAILABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setState(UpdateState state){
|
||||||
|
this.state=state;
|
||||||
|
E.post(new SelfUpdateStateChangedEvent(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UpdateState getState(){
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UpdateInfo getUpdateInfo(){
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getUpdateApkFile(){
|
||||||
|
return new File(MastodonApp.context.getExternalCacheDir(), "update.apk");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadUpdate(){
|
||||||
|
if(state==UpdateState.DOWNLOADING)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||||
|
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||||
|
downloadID=dm.enqueue(
|
||||||
|
new DownloadManager.Request(Uri.parse(getPrefs().getString("apkURL", null)))
|
||||||
|
.setDestinationUri(Uri.fromFile(getUpdateApkFile()))
|
||||||
|
);
|
||||||
|
getPrefs().edit().putLong("downloadID", downloadID).apply();
|
||||||
|
setState(UpdateState.DOWNLOADING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void installUpdate(Activity activity){
|
||||||
|
if(state!=UpdateState.DOWNLOADED)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
Uri uri;
|
||||||
|
Intent intent=new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
||||||
|
uri=new Uri.Builder().scheme("content").authority(activity.getPackageName()+".self_update_provider").path("update.apk").build();
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
}else{
|
||||||
|
uri=Uri.fromFile(getUpdateApkFile());
|
||||||
|
}
|
||||||
|
intent.setDataAndType(uri, "application/vnd.android.package-archive");
|
||||||
|
activity.startActivity(intent);
|
||||||
|
|
||||||
|
// TODO figure out how to restart the app when updating via this new API
|
||||||
|
/*
|
||||||
|
PackageInstaller installer=activity.getPackageManager().getPackageInstaller();
|
||||||
|
try{
|
||||||
|
final int sid=installer.createSession(new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
|
||||||
|
installer.registerSessionCallback(new PackageInstaller.SessionCallback(){
|
||||||
|
@Override
|
||||||
|
public void onCreated(int i){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBadgingChanged(int i){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActiveChanged(int i, boolean b){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(int id, float progress){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinished(int id, boolean success){
|
||||||
|
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
||||||
|
PackageInstaller.Session session=installer.openSession(sid);
|
||||||
|
try(OutputStream out=session.openWrite("mastodon.apk", 0, info.size); InputStream in=new FileInputStream(getUpdateApkFile())){
|
||||||
|
byte[] buffer=new byte[16384];
|
||||||
|
int read;
|
||||||
|
while((read=in.read(buffer))>0){
|
||||||
|
out.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PendingIntent intent=PendingIntent.getBroadcast(activity, 1, new Intent(activity, InstallerStatusReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
|
||||||
|
PendingIntent intent=PendingIntent.getActivity(activity, 1, new Intent(activity, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
session.commit(intent.getIntentSender());
|
||||||
|
}catch(IOException x){
|
||||||
|
Log.w(TAG, "installUpdate", x);
|
||||||
|
Toast.makeText(activity, x.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getDownloadProgress(){
|
||||||
|
if(state!=UpdateState.DOWNLOADING)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||||
|
try(Cursor cursor=dm.query(new DownloadManager.Query().setFilterById(downloadID))){
|
||||||
|
if(cursor.moveToFirst()){
|
||||||
|
long loaded=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
|
||||||
|
long total=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
|
||||||
|
// Log.d(TAG, "getDownloadProgress: "+loaded+" of "+total);
|
||||||
|
return total>0 ? (float)loaded/total : 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cancelDownload(){
|
||||||
|
if(state!=UpdateState.DOWNLOADING)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||||
|
dm.remove(downloadID);
|
||||||
|
downloadID=0;
|
||||||
|
getPrefs().edit().remove("downloadID").apply();
|
||||||
|
setState(UpdateState.UPDATE_AVAILABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleIntentFromInstaller(Intent intent, Activity activity){
|
||||||
|
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
|
||||||
|
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
|
||||||
|
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||||
|
activity.startActivity(confirmIntent);
|
||||||
|
}else if(status!=PackageInstaller.STATUS_SUCCESS){
|
||||||
|
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||||
|
Toast.makeText(activity, activity.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public static class InstallerStatusReceiver extends BroadcastReceiver{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent){
|
||||||
|
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
|
||||||
|
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
|
||||||
|
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||||
|
context.startActivity(confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||||
|
}else if(status!=PackageInstaller.STATUS_SUCCESS){
|
||||||
|
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||||
|
Toast.makeText(context, context.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AfterUpdateRestartReceiver extends BroadcastReceiver{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent){
|
||||||
|
if(Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())){
|
||||||
|
context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||||
|
Toast.makeText(context, R.string.update_installed, Toast.LENGTH_SHORT).show();
|
||||||
|
Intent restartIntent=new Intent(context, MainActivity.class)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.setPackage(context.getPackageName());
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.P){
|
||||||
|
context.startActivity(restartIntent);
|
||||||
|
}else{
|
||||||
|
// Bypass activity starting restrictions by starting it from a notification
|
||||||
|
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
||||||
|
NotificationChannel chan=new NotificationChannel("selfUpdateRestart", context.getString(R.string.update_installed), NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
nm.createNotificationChannel(chan);
|
||||||
|
Notification n=new Notification.Builder(context, "selfUpdateRestart")
|
||||||
|
.setContentTitle(context.getString(R.string.update_installed))
|
||||||
|
.setContentIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
||||||
|
.setFullScreenIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE), true)
|
||||||
|
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||||
|
.build();
|
||||||
|
nm.notify(1, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package org.joinmastodon.android.updater;
|
||||||
|
|
||||||
|
import android.content.ContentProvider;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class SelfUpdateContentProvider extends ContentProvider{
|
||||||
|
@Override
|
||||||
|
public boolean onCreate(){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getType(@NonNull Uri uri){
|
||||||
|
if(isCorrectUri(uri))
|
||||||
|
return "application/vnd.android.package-archive";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException{
|
||||||
|
if(isCorrectUri(uri)){
|
||||||
|
return ParcelFileDescriptor.open(((GithubSelfUpdaterImpl)GithubSelfUpdater.getInstance()).getUpdateApkFile(), ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
}
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCorrectUri(Uri uri){
|
||||||
|
return "/update.apk".equals(uri.getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||||
|
<path android:pathData="M3.897 4.054L3.97 3.97c0.266-0.267 0.683-0.29 0.976-0.073L5.03 3.97 10 8.939l4.97-4.97c0.266-0.266 0.683-0.29 0.976-0.072L16.03 3.97c0.267 0.266 0.29 0.683 0.073 0.976L16.03 5.03 11.061 10l4.97 4.97c0.266 0.266 0.29 0.683 0.072 0.976L16.03 16.03c-0.266 0.267-0.683 0.29-0.976 0.073L14.97 16.03 10 11.061l-4.97 4.97c-0.266 0.266-0.683 0.29-0.976 0.072L3.97 16.03c-0.267-0.266-0.29-0.683-0.073-0.976L3.97 14.97 8.939 10l-4.97-4.97C3.704 4.764 3.68 4.347 3.898 4.054L3.97 3.97 3.897 4.054z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M22 6.5c0 3.038-2.462 5.5-5.5 5.5S11 9.538 11 6.5 13.462 1 16.5 1 22 3.462 22 6.5zm-7.146-2.354c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708L15.793 6.5l-1.647 1.646c-0.195 0.196-0.195 0.512 0 0.707 0.196 0.196 0.512 0.196 0.708 0L16.5 7.208l1.646 1.647c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.707L17.207 6.5l1.647-1.646c0.195-0.196 0.195-0.512 0-0.708-0.196-0.195-0.512-0.195-0.708 0L16.5 5.793l-1.646-1.647zM19.5 14v-1.732c0.551-0.287 1.056-0.651 1.5-1.078v7.56c0 1.733-1.357 3.15-3.066 3.245L17.75 22H6.25c-1.733 0-3.15-1.357-3.245-3.066L3 18.75V7.25C3 5.517 4.356 4.1 6.066 4.005L6.25 4h4.248c-0.198 0.474-0.34 0.977-0.422 1.5H6.25c-0.918 0-1.671 0.707-1.744 1.606L4.5 7.25V14H9c0.38 0 0.694 0.282 0.743 0.648L9.75 14.75C9.75 15.993 10.757 17 12 17c1.19 0 2.166-0.925 2.245-2.096l0.005-0.154c0-0.38 0.282-0.694 0.648-0.743L15 14h4.5zm-15 1.5v3.25c0 0.918 0.707 1.671 1.606 1.744L6.25 20.5h11.5c0.918 0 1.671-0.707 1.744-1.607L19.5 18.75V15.5h-3.825c-0.335 1.648-1.75 2.904-3.475 2.995L12 18.5c-1.747 0-3.215-1.195-3.632-2.812L8.325 15.5H4.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||||
|
<path android:pathData="M26 7.5c0 3.59-2.91 6.5-6.5 6.5S13 11.09 13 7.5 15.91 1 19.5 1 26 3.91 26 7.5zm-9.146-3.354c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708L18.793 7.5l-2.647 2.646c-0.195 0.196-0.195 0.512 0 0.708 0.196 0.195 0.512 0.195 0.708 0L19.5 8.207l2.646 2.647c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.708L20.207 7.5l2.647-2.646c0.195-0.196 0.195-0.512 0-0.708-0.196-0.195-0.512-0.195-0.708 0L19.5 6.793l-2.646-2.647zM25 22.75V12.6c-0.443 0.476-0.947 0.896-1.5 1.245V16h-6l-0.102 0.007c-0.366 0.05-0.648 0.363-0.648 0.743 0 1.519-1.231 2.75-2.75 2.75s-2.75-1.231-2.75-2.75l-0.007-0.102C11.193 16.282 10.88 16 10.5 16h-6V7.25c0-0.966 0.784-1.75 1.75-1.75h6.02c0.145-0.525 0.345-1.028 0.595-1.5H6.25C4.455 4 3 5.455 3 7.25v15.5C3 24.545 4.455 26 6.25 26h15.5c1.795 0 3.25-1.455 3.25-3.25zm-20.5 0V17.5h5.316l0.041 0.204C10.291 19.592 11.982 21 14 21l0.215-0.005c1.994-0.1 3.627-1.574 3.969-3.495H23.5v5.25c0 0.966-0.784 1.75-1.75 1.75H6.25c-0.966 0-1.75-0.784-1.75-1.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||||
|
<path android:pathData="M10 2c4.418 0 8 3.582 8 8 0 2.706-1.142 4.5-3 4.5-1.226 0-2.14-0.781-2.62-2.09C11.784 13.393 10.781 14 9.5 14 7.36 14 6 12.307 6 10c0-2.337 1.313-4 3.5-4 1.052 0 1.901 0.385 2.5 1.044V6.5C12 6.224 12.224 6 12.5 6c0.245 0 0.45 0.177 0.492 0.41L13 6.5V10c0 2.223 0.813 3.5 2 3.5s2-1.277 2-3.5c0-3.866-3.134-7-7-7s-7 3.134-7 7 3.134 7 7 7c0.823 0 1.626-0.142 2.383-0.416 0.26-0.094 0.547 0.04 0.64 0.3 0.095 0.26-0.04 0.546-0.3 0.64C11.859 17.838 10.94 18 10 18c-4.418 0-8-3.582-8-8s3.582-8 8-8zM9.5 7C7.924 7 7 8.17 7 10c0 1.797 0.966 3 2.5 3s2.5-1.203 2.5-3c0-1.83-0.924-3-2.5-3z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M6.25 4.5C5.283 4.5 4.5 5.284 4.5 6.25v11.5c0 0.966 0.783 1.75 1.75 1.75h11.5c0.966 0 1.75-0.784 1.75-1.75v-4c0-0.414 0.335-0.75 0.75-0.75 0.414 0 0.75 0.336 0.75 0.75v4c0 1.795-1.456 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V6.25C3 4.455 4.455 3 6.25 3h4C10.664 3 11 3.336 11 3.75S10.664 4.5 10.25 4.5h-4zM13 3.75C13 3.336 13.335 3 13.75 3h6.5C20.664 3 21 3.336 21 3.75v6.5c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75V5.56l-5.22 5.22c-0.293 0.293-0.768 0.293-1.06 0-0.293-0.293-0.293-0.768 0-1.06l5.22-5.22h-4.69C13.335 4.5 13 4.164 13 3.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M8.502 11.5c0.554 0 1.002 0.448 1.002 1.002 0 0.553-0.448 1.002-1.002 1.002-0.553 0-1.002-0.449-1.002-1.002 0-0.554 0.449-1.003 1.002-1.003zM12 4.353v6.651h7.442L17.72 9.28c-0.267-0.266-0.29-0.683-0.073-0.977L17.72 8.22c0.266-0.266 0.683-0.29 0.976-0.072L18.78 8.22l2.997 2.998c0.266 0.266 0.29 0.682 0.073 0.976l-0.073 0.084-2.996 3.003c-0.293 0.294-0.767 0.294-1.06 0.002-0.267-0.266-0.292-0.683-0.075-0.977l0.073-0.084 1.713-1.717h-7.431L12 19.25c0 0.466-0.421 0.82-0.88 0.738l-8.5-1.501C2.26 18.424 2 18.112 2 17.748V5.75c0-0.368 0.266-0.681 0.628-0.74l8.5-1.396C11.585 3.539 12 3.89 12 4.354zm-1.5 0.883l-7 1.15v10.732l7 1.236V5.237zM13 18.5h0.765l0.102-0.007c0.366-0.05 0.649-0.364 0.648-0.744l-0.007-4.25H13v5zm0.002-8.502L13 8.726V5h0.745c0.38 0 0.693 0.281 0.743 0.647l0.007 0.101L14.502 10h-1.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M14.704 3.44C14.895 3.667 15 3.953 15 4.248V19.75c0 0.69-0.56 1.25-1.25 1.25-0.296 0-0.582-0.105-0.808-0.296l-4.967-4.206H4.25c-1.243 0-2.25-1.008-2.25-2.25v-4.5c0-1.243 1.007-2.25 2.25-2.25h3.725l4.968-4.204c0.526-0.446 1.315-0.38 1.761 0.147zM13.5 4.787l-4.975 4.21H4.25c-0.414 0-0.75 0.337-0.75 0.75v4.5c0 0.415 0.336 0.75 0.75 0.75h4.275L13.5 19.21V4.787z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||||
|
<path android:pathData="M16.5 4.814c0-1.094-1.307-1.66-2.105-0.912l-4.937 4.63C9.134 8.836 8.706 9.005 8.261 9.005H5.25C3.455 9.005 2 10.46 2 12.255v3.492c0 1.795 1.455 3.25 3.25 3.25h3.012c0.444 0 0.872 0.17 1.196 0.473l4.937 4.626c0.799 0.748 2.105 0.182 2.105-0.912V4.814zm-6.016 4.812L15 5.39v17.216l-4.516-4.232c-0.602-0.564-1.397-0.878-2.222-0.878H5.25c-0.966 0-1.75-0.784-1.75-1.75v-3.492c0-0.966 0.784-1.75 1.75-1.75h3.011c0.826 0 1.62-0.314 2.223-0.88z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06L6.438 7.5H4.25C3.007 7.499 2 8.506 2 9.749v4.497c0 1.243 1.007 2.25 2.25 2.25h3.68c0.183 0 0.36 0.068 0.498 0.19l4.491 3.994C13.725 21.396 15 20.824 15 19.746V16.06l5.72 5.72c0.292 0.292 0.767 0.292 1.06 0 0.293-0.293 0.293-0.768 0-1.061L3.28 2.22zM13.5 14.56v4.629l-4.075-3.624c-0.412-0.366-0.944-0.569-1.495-0.569H4.25c-0.414 0-0.75-0.335-0.75-0.75V9.75C3.5 9.335 3.836 9 4.25 9h3.688l5.562 5.56zm0-9.753v5.511l1.5 1.5V4.25c0-1.079-1.274-1.65-2.08-0.934l-3.4 3.022 1.063 1.063L13.5 4.807zm3.641 9.152l1.138 1.138C18.741 14.163 19 13.111 19 12c0-1.203-0.304-2.338-0.84-3.328-0.198-0.364-0.653-0.5-1.017-0.303-0.364 0.197-0.5 0.653-0.303 1.017 0.42 0.777 0.66 1.666 0.66 2.614 0 0.691-0.127 1.351-0.359 1.96zm2.247 2.247l1.093 1.094C21.445 15.763 22 13.946 22 12c0-2.226-0.728-4.284-1.96-5.946-0.246-0.333-0.716-0.403-1.048-0.157-0.333 0.247-0.403 0.716-0.157 1.05C19.881 8.358 20.5 10.106 20.5 12c0 1.531-0.404 2.966-1.112 4.206z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
||||||
|
<path android:pathData="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06l5.724 5.725H5.25C3.455 9.005 2 10.46 2 12.255v3.492c0 1.795 1.455 3.25 3.25 3.25h3.012c0.444 0 0.872 0.17 1.196 0.473l4.937 4.626c0.799 0.748 2.105 0.182 2.105-0.912v-5.623l8.22 8.22c0.292 0.292 0.767 0.292 1.06 0 0.293-0.293 0.293-0.768 0-1.061L3.28 2.22zM15 16.06v6.547l-4.516-4.231c-0.602-0.565-1.397-0.879-2.222-0.879H5.25c-0.966 0-1.75-0.783-1.75-1.75v-3.492c0-0.966 0.784-1.75 1.75-1.75h3.011c0.35 0 0.693-0.056 1.02-0.164L15 16.061zm-4.378-8.62l1.061 1.061L15 5.392v6.427l1.5 1.5V4.814c0-1.094-1.307-1.66-2.105-0.912L10.622 7.44zm9.55 9.55l1.137 1.137C21.912 16.88 22.25 15.478 22.25 14c0-2.136-0.706-4.11-1.897-5.697-0.249-0.332-0.719-0.399-1.05-0.15-0.332 0.249-0.399 0.719-0.15 1.05C20.156 10.54 20.75 12.199 20.75 14c0 1.058-0.205 2.067-0.578 2.99zm2.803 2.803l1.095 1.096c1.224-2.008 1.93-4.366 1.93-6.89 0-3.35-1.245-6.414-3.298-8.747-0.274-0.31-0.747-0.341-1.058-0.068-0.311 0.274-0.342 0.748-0.068 1.059C23.396 8.313 24.5 11.027 24.5 14c0 2.107-0.554 4.084-1.525 5.793z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||||
|
</vector>
|
||||||
@@ -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>
|
||||||
6
mastodon/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -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/debug/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
mastodon/src/debug/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
mastodon/src/debug/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 988 B |
BIN
mastodon/src/debug/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
mastodon/src/debug/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
mastodon/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
mastodon/src/debug/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
mastodon/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
mastodon/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
mastodon/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
4
mastodon/src/debug/res/values/ic_launcher_background.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#000000</color>
|
||||||
|
</resources>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||||
@@ -40,6 +41,22 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".PanicResponderActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:noHistory="true"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ExitActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@android:style/Theme.NoDisplay" />
|
||||||
<activity android:name=".OAuthActivity" android:exported="true" android:configChanges="orientation|screenSize" android:launchMode="singleTask">
|
<activity android:name=".OAuthActivity" android:exported="true" android:configChanges="orientation|screenSize" android:launchMode="singleTask">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
# lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
|
|
||||||
# https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
|
|
||||||
# This list contains domains of toxic mastodon instances
|
|
||||||
# Last-Modified: 1672044500
|
|
||||||
|
|
||||||
# gab - a neonazi social network
|
|
||||||
gab.ai
|
|
||||||
gab.com
|
|
||||||
gab.protohype.net
|
|
||||||
|
|
||||||
# consequence-free speech
|
|
||||||
social.unzensiert.to
|
|
||||||
freeatlantis.com
|
|
||||||
|
|
||||||
# reactionary bigotry and hatespeech against marginalized groups
|
|
||||||
poa.st
|
|
||||||
freespeechextremist.com
|
|
||||||
rdrama.cc
|
|
||||||
outpoa.st
|
|
||||||
anime.website
|
|
||||||
gameliberty.club
|
|
||||||
social.byoblu.com
|
|
||||||
yggdrasil.social
|
|
||||||
smuglo.li
|
|
||||||
dogeposting.social
|
|
||||||
unsafe.space
|
|
||||||
freezepeach.xyz
|
|
||||||
|
|
||||||
# + CSAM
|
|
||||||
rojogato.com
|
|
||||||
|
|
||||||
# antivaxxer shitposting & fearmongering
|
|
||||||
shadowsocial.org
|
|
||||||
|
|
||||||
# Kiwifarms
|
|
||||||
kiwifarms.net
|
|
||||||
kiwifarms.cc
|
|
||||||
kiwifarms.is
|
|
||||||
kiwifarms.pleroma.net
|
|
||||||
|
|
||||||
|
|
||||||
# https://mastodon.art/@Curator/109649354849593592
|
|
||||||
|
|
||||||
poa.st antisemitic racist homophobic
|
|
||||||
nicecrew.digital antisemitic
|
|
||||||
beefyboys.win antisemitic racist homophobic harassment
|
|
||||||
cawfee.club antisemitic racist homophobic
|
|
||||||
comfyboy.club antisemitic racist homophobic
|
|
||||||
freespeechextremist.com racist homophobic
|
|
||||||
cum.salon racist misogynist
|
|
||||||
bae.st racist
|
|
||||||
natehiggers.online racist
|
|
||||||
rapemeat.solutions misogynist
|
|
||||||
rapist.town misogynist
|
|
||||||
rapefeminists.network misogynist
|
|
||||||
kiwifarms.cc harassment
|
|
||||||
noagendasocial.com noagenda
|
|
||||||
posting.lolicon.rocks underage
|
|
||||||
urchan.org harassment homophobic racist
|
|
||||||
ryona.agency harassment
|
|
||||||
yggdrasil.social antisemitic homophobic racist
|
|
||||||
genderheretics.xyz transphobic
|
|
||||||
baraag.net underage
|
|
||||||
lolison.top underage
|
|
||||||
shota.house underage
|
|
||||||
shota.social underage
|
|
||||||
aethy.com underage
|
|
||||||
taullo.social underage
|
|
||||||
childpawn.shop underage
|
|
||||||
posting.lolicon.rocks underage
|
|
||||||
loli.best underage
|
|
||||||
gothloli.club underage
|
|
||||||
smuglo.li underage
|
|
||||||
youjo.love underage
|
|
||||||
pedo.school underage
|
|
||||||
lolison.network underage
|
|
||||||
freak.university underage
|
|
||||||
mirr0r.city underage
|
|
||||||
xhais.love underage
|
|
||||||
refusal.biz underage
|
|
||||||
refusal.llc underage
|
|
||||||
mirr0r.city underage
|
|
||||||
nnia.space underage
|
|
||||||
ignorelist.com malicious
|
|
||||||
repl.co malicious
|
|
||||||
|
|
||||||
# custom
|
|
||||||
|
|
||||||
pawoo.net csam
|
|
||||||
|
171
mastodon/src/main/assets/blocks.txt
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
13bells.com
|
||||||
|
4aem.com
|
||||||
|
aethy.com
|
||||||
|
anime.website
|
||||||
|
annihilation.social
|
||||||
|
anon-kenkai.com
|
||||||
|
asbestos.cafe
|
||||||
|
bae.st
|
||||||
|
bajax.us
|
||||||
|
banepo.st
|
||||||
|
baraag.net
|
||||||
|
beefyboys.win
|
||||||
|
beepboop.ga
|
||||||
|
berserker.town
|
||||||
|
bikeshed.party
|
||||||
|
boks.moe
|
||||||
|
brainsoap.net
|
||||||
|
breastmilk.club
|
||||||
|
brighteon.social
|
||||||
|
cawfee.club
|
||||||
|
clew.lol
|
||||||
|
clubcyberia.co
|
||||||
|
collapsitarian.io
|
||||||
|
comfyboy.club
|
||||||
|
contrapointsfan.club
|
||||||
|
cum.camp
|
||||||
|
cum.salon
|
||||||
|
cybercriminal.eu
|
||||||
|
darknight-coffee.org
|
||||||
|
dembased.xyz
|
||||||
|
desupost.soy
|
||||||
|
detroitriotcity.com
|
||||||
|
eatthebugs.social
|
||||||
|
eientei.org
|
||||||
|
elementality.org
|
||||||
|
eveningzoo.club
|
||||||
|
firedragonstudios.com
|
||||||
|
firefaithfellowship.com
|
||||||
|
fluf.club
|
||||||
|
foxfam.club
|
||||||
|
freak.university
|
||||||
|
freeatlantis.com
|
||||||
|
freecumextremist.com
|
||||||
|
freedomstrike.org
|
||||||
|
freesoftwareextremist.com
|
||||||
|
freespeech.group
|
||||||
|
freespeechextremist.com
|
||||||
|
freetalklive.com
|
||||||
|
froth.zone
|
||||||
|
fulltermprivacy.com
|
||||||
|
gameliberty.club
|
||||||
|
gearlandia.haus
|
||||||
|
genderheretics.xyz
|
||||||
|
geofront.rocks
|
||||||
|
gleasonator.com
|
||||||
|
glee.li
|
||||||
|
glindr.org
|
||||||
|
goyim.app
|
||||||
|
goyslop.cafe
|
||||||
|
haeder.net
|
||||||
|
handholding.io
|
||||||
|
hidamari.apartments
|
||||||
|
hitchhiker.social
|
||||||
|
hunk.city
|
||||||
|
iddqd.social
|
||||||
|
intkos.link
|
||||||
|
justicewarrior.social
|
||||||
|
kawa-kun.com
|
||||||
|
kitsunemimi.club
|
||||||
|
kiwifarms.cc
|
||||||
|
kompost.cz
|
||||||
|
kurosawa.moe
|
||||||
|
leafposter.club
|
||||||
|
leftychan.net
|
||||||
|
lewdieheaven.com
|
||||||
|
liberdon.com
|
||||||
|
ligma.pro
|
||||||
|
lizards.live
|
||||||
|
lolicon.rocks
|
||||||
|
lolison.top
|
||||||
|
lovingexpressions.net
|
||||||
|
lucasvl.nl
|
||||||
|
mahodou.moe
|
||||||
|
makemysarcophagus.com
|
||||||
|
maladaptive.art
|
||||||
|
masochi.st
|
||||||
|
mastinator.com
|
||||||
|
merovingian.club
|
||||||
|
midwaytrades.com
|
||||||
|
mirr0r.city
|
||||||
|
moa.st
|
||||||
|
mouse.services
|
||||||
|
mugicha.club
|
||||||
|
narrativerry.xyz
|
||||||
|
natehiggers.online
|
||||||
|
neckbeard.xyz
|
||||||
|
needs.vodka
|
||||||
|
neenster.org
|
||||||
|
nicecrew.digital
|
||||||
|
nnia.space
|
||||||
|
noagendasocial.com
|
||||||
|
noagendasocial.nl
|
||||||
|
noagendatube.com
|
||||||
|
nobodyhasthe.biz
|
||||||
|
nukem.biz
|
||||||
|
obo.sh
|
||||||
|
onionfarms.org
|
||||||
|
outpoa.st
|
||||||
|
pawlicker.com
|
||||||
|
pawoo.net
|
||||||
|
pedo.school
|
||||||
|
piazza.today
|
||||||
|
pibvt.net
|
||||||
|
pieville.net
|
||||||
|
pisskey.io
|
||||||
|
plagu.ee
|
||||||
|
pmth.us
|
||||||
|
poa.st
|
||||||
|
poast.org
|
||||||
|
poast.tv
|
||||||
|
poster.place
|
||||||
|
prospeech.space
|
||||||
|
quodverum.com
|
||||||
|
rakket.app
|
||||||
|
rapemeat.solutions
|
||||||
|
rdrama.cc
|
||||||
|
rebelbase.site
|
||||||
|
retardedniggers.forsale
|
||||||
|
rojogato.com
|
||||||
|
ryona.agency
|
||||||
|
schwartzwelt.xyz
|
||||||
|
seal.cafe
|
||||||
|
shigusegubu.club
|
||||||
|
shitpost.cloud
|
||||||
|
shitposter.club
|
||||||
|
shota.house
|
||||||
|
silliness.observer
|
||||||
|
skinheads.eu
|
||||||
|
skinheads.io
|
||||||
|
skinheads.social
|
||||||
|
skinheads.uk
|
||||||
|
skippers-bin.com
|
||||||
|
skyshanty.xyz
|
||||||
|
slash.cl
|
||||||
|
sleepy.cafe
|
||||||
|
smuglo.li
|
||||||
|
sneed.social
|
||||||
|
sonichu.com
|
||||||
|
spinster.xyz
|
||||||
|
springbo.cc
|
||||||
|
starnix.network
|
||||||
|
stereophonic.space
|
||||||
|
strelizia.net
|
||||||
|
syspxl.xyz
|
||||||
|
tastingtraffic.net
|
||||||
|
teci.world
|
||||||
|
theapex.social
|
||||||
|
thepostearthdestination.com
|
||||||
|
tkammer.de
|
||||||
|
trumpislovetrumpis.life
|
||||||
|
truthsocial.co.in
|
||||||
|
urchan.org
|
||||||
|
varishangout.net
|
||||||
|
whinge.house
|
||||||
|
whinge.town
|
||||||
|
wideboys.org
|
||||||
|
wolfgirl.bar
|
||||||
|
xn--p1abe3d.xn--80asehdb
|
||||||
|
yggdrasil.social
|
||||||
|
youjo.love
|
||||||
|
zztails.gay
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
public class ExitActivity extends Activity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
finishAndRemoveTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exit(Context context) {
|
||||||
|
Intent intent = new Intent(context, ExitActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
package org.joinmastodon.android;
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.assist.AssistContent;
|
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Pair;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
@@ -19,6 +20,8 @@ import org.jsoup.internal.StringUtil;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
@@ -30,22 +33,51 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
|
|
||||||
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
||||||
boolean isMastodonURL = UiUtils.looksLikeMastodonUrl(text);
|
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
|
||||||
|
boolean isFediUrl = text.map(UiUtils::looksLikeMastodonUrl).orElse(false);
|
||||||
|
boolean isOpenable = isFediUrl || fediHandle.isPresent();
|
||||||
|
|
||||||
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
|
||||||
if(sessions.isEmpty()){
|
if (sessions.isEmpty()){
|
||||||
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
|
||||||
finish();
|
finish();
|
||||||
}else if(sessions.size()==1 && !isMastodonURL){
|
} else if (isOpenable || sessions.size() > 1) {
|
||||||
|
AccountSwitcherSheet sheet = new AccountSwitcherSheet(this, null, true, isOpenable);
|
||||||
|
sheet.setOnClick((accountId, open) -> {
|
||||||
|
if (open && text.isPresent()) {
|
||||||
|
BiConsumer<Class<? extends Fragment>, Bundle> callback = (clazz, args) -> {
|
||||||
|
if (clazz == null) {
|
||||||
|
Toast.makeText(this, R.string.sk_open_in_app_failed, Toast.LENGTH_SHORT).show();
|
||||||
|
// TODO: do something about the window getting leaked
|
||||||
|
sheet.dismiss();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
args.putString("fromExternalShare", clazz.getSimpleName());
|
||||||
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
|
intent.putExtras(args);
|
||||||
|
finish();
|
||||||
|
startActivity(intent);
|
||||||
|
};
|
||||||
|
|
||||||
|
fediHandle
|
||||||
|
.<MastodonAPIRequest<?>>map(handle ->
|
||||||
|
UiUtils.lookupAccountHandle(this, accountId, handle, callback))
|
||||||
|
.or(() ->
|
||||||
|
UiUtils.lookupURL(this, accountId, text.get(), callback))
|
||||||
|
.ifPresent(req ->
|
||||||
|
req.wrapProgress(this, R.string.loading, true, d -> {
|
||||||
|
UiUtils.transformDialogForLookup(this, accountId, isFediUrl ? text.get() : null, d);
|
||||||
|
d.setOnDismissListener((ev) -> finish());
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
openComposeFragment(accountId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sheet.show();
|
||||||
|
} else if (sessions.size() == 1) {
|
||||||
openComposeFragment(sessions.get(0).getID());
|
openComposeFragment(sessions.get(0).getID());
|
||||||
}else{
|
|
||||||
new AccountSwitcherSheet(this, false, false, isMastodonURL, accountSession -> {
|
|
||||||
if(accountSession!=null)
|
|
||||||
openComposeFragment(accountSession.getID());
|
|
||||||
else
|
|
||||||
UiUtils.openURL(this, AccountSessionManager.getInstance().getLastActiveAccountID(), text);
|
|
||||||
}).show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,11 +140,4 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
return null;
|
return null;
|
||||||
return new ArrayList<>(l);
|
return new ArrayList<>(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProvideAssistContent(AssistContent outContent) {
|
|
||||||
super.onProvideAssistContent(outContent);
|
|
||||||
|
|
||||||
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.os.Build;
|
|||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
@@ -30,14 +31,14 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean alwaysExpandContentWarnings;
|
public static boolean alwaysExpandContentWarnings;
|
||||||
public static boolean disableMarquee;
|
public static boolean disableMarquee;
|
||||||
public static boolean disableSwipe;
|
public static boolean disableSwipe;
|
||||||
public static boolean disableDividers;
|
public static boolean showDividers;
|
||||||
public static boolean voteButtonForSingleChoice;
|
public static boolean voteButtonForSingleChoice;
|
||||||
public static boolean uniformNotificationIcon;
|
|
||||||
public static boolean enableDeleteNotifications;
|
public static boolean enableDeleteNotifications;
|
||||||
|
public static boolean translateButtonOpenedOnly;
|
||||||
|
public static boolean uniformNotificationIcon;
|
||||||
public static boolean relocatePublishButton;
|
public static boolean relocatePublishButton;
|
||||||
public static boolean reduceMotion;
|
public static boolean reduceMotion;
|
||||||
public static boolean keepOnlyLatestNotification;
|
public static boolean keepOnlyLatestNotification;
|
||||||
public static boolean enableFabAutoHide;
|
|
||||||
public static boolean disableAltTextReminder;
|
public static boolean disableAltTextReminder;
|
||||||
public static boolean showAltIndicator;
|
public static boolean showAltIndicator;
|
||||||
public static boolean showNoAltIndicator;
|
public static boolean showNoAltIndicator;
|
||||||
@@ -48,23 +49,28 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean spectatorMode;
|
public static boolean spectatorMode;
|
||||||
public static boolean autoHideFab;
|
public static boolean autoHideFab;
|
||||||
public static boolean defaultToUnlistedReplies;
|
public static boolean defaultToUnlistedReplies;
|
||||||
public static boolean disableDoubleTapToSwipe;
|
public static boolean doubleTapToSwipe;
|
||||||
public static boolean compactReblogReplyLine;
|
public static boolean compactReblogReplyLine;
|
||||||
public static boolean confirmBeforeReblog;
|
public static boolean confirmBeforeReblog;
|
||||||
public static boolean replyLineAboveHeader;
|
public static boolean replyLineAboveHeader;
|
||||||
public static boolean swapBookmarkWithBoostAction;
|
public static boolean swapBookmarkWithBoostAction;
|
||||||
public static boolean loadRemoteAccountFollowers;
|
public static boolean loadRemoteAccountFollowers;
|
||||||
public static boolean mentionRebloggerAutomatically;
|
public static boolean mentionRebloggerAutomatically;
|
||||||
|
public static boolean allowRemoteLoading;
|
||||||
|
public static AutoRevealMode autoRevealEqualSpoilers;
|
||||||
public static String publishButtonText;
|
public static String publishButtonText;
|
||||||
public static ThemePreference theme;
|
public static ThemePreference theme;
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
|
|
||||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||||
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
||||||
|
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
|
||||||
public static Map<String, List<String>> recentLanguages;
|
public static Map<String, List<String>> recentLanguages;
|
||||||
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
||||||
public static Set<String> accountsWithLocalOnlySupport;
|
public static Set<String> accountsWithLocalOnlySupport;
|
||||||
public static Set<String> accountsInGlitchMode;
|
public static Set<String> accountsInGlitchMode;
|
||||||
|
public static Set<String> accountsWithContentTypesEnabled;
|
||||||
|
public static Map<String, ContentType> accountsDefaultContentTypes;
|
||||||
|
|
||||||
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||||
public static Map<String, Integer> recentEmojis;
|
public static Map<String, Integer> recentEmojis;
|
||||||
@@ -74,7 +80,6 @@ public class GlobalUserPreferences{
|
|||||||
*/
|
*/
|
||||||
public static String replyVisibility;
|
public static String replyVisibility;
|
||||||
|
|
||||||
|
|
||||||
public static SharedPreferences getPrefs(){
|
public static SharedPreferences getPrefs(){
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
@@ -85,6 +90,16 @@ public class GlobalUserPreferences{
|
|||||||
catch (JsonSyntaxException ignored) { return orElse; }
|
catch (JsonSyntaxException ignored) { return orElse; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void removeAccount(String accountId) {
|
||||||
|
recentLanguages.remove(accountId);
|
||||||
|
pinnedTimelines.remove(accountId);
|
||||||
|
accountsInGlitchMode.remove(accountId);
|
||||||
|
accountsWithLocalOnlySupport.remove(accountId);
|
||||||
|
accountsWithContentTypesEnabled.remove(accountId);
|
||||||
|
accountsDefaultContentTypes.remove(accountId);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
public static void load(){
|
public static void load(){
|
||||||
SharedPreferences prefs=getPrefs();
|
SharedPreferences prefs=getPrefs();
|
||||||
playGifs=prefs.getBoolean("playGifs", true);
|
playGifs=prefs.getBoolean("playGifs", true);
|
||||||
@@ -99,13 +114,14 @@ public class GlobalUserPreferences{
|
|||||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
||||||
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
||||||
disableDividers=prefs.getBoolean("disableDividers", true);
|
showDividers =prefs.getBoolean("showDividers", false);
|
||||||
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
|
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
|
||||||
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
||||||
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
||||||
|
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
|
||||||
|
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
||||||
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
||||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||||
enableFabAutoHide=prefs.getBoolean("enableFabAutoHide", true);
|
|
||||||
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
|
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
|
||||||
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
||||||
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
||||||
@@ -117,7 +133,7 @@ public class GlobalUserPreferences{
|
|||||||
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
||||||
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
||||||
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
|
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
|
||||||
disableDoubleTapToSwipe=prefs.getBoolean("disableDoubleTapToSwipe", false);
|
doubleTapToSwipe =prefs.getBoolean("doubleTapToSwipe", true);
|
||||||
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
|
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
|
||||||
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
||||||
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
|
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
|
||||||
@@ -133,6 +149,10 @@ public class GlobalUserPreferences{
|
|||||||
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
|
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
|
||||||
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
|
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
|
||||||
replyVisibility=prefs.getString("replyVisibility", null);
|
replyVisibility=prefs.getString("replyVisibility", null);
|
||||||
|
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
|
||||||
|
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
||||||
|
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
||||||
|
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
|
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
|
||||||
@@ -159,13 +179,12 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||||
.putBoolean("disableMarquee", disableMarquee)
|
.putBoolean("disableMarquee", disableMarquee)
|
||||||
.putBoolean("disableSwipe", disableSwipe)
|
.putBoolean("disableSwipe", disableSwipe)
|
||||||
.putBoolean("disableDividers", disableDividers)
|
.putBoolean("showDividers", showDividers)
|
||||||
.putBoolean("relocatePublishButton", relocatePublishButton)
|
.putBoolean("relocatePublishButton", relocatePublishButton)
|
||||||
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||||
.putBoolean("reduceMotion", reduceMotion)
|
.putBoolean("reduceMotion", reduceMotion)
|
||||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||||
.putBoolean("enableFabAutoHide", enableFabAutoHide)
|
|
||||||
.putBoolean("disableAltTextReminder", disableAltTextReminder)
|
.putBoolean("disableAltTextReminder", disableAltTextReminder)
|
||||||
.putBoolean("showAltIndicator", showAltIndicator)
|
.putBoolean("showAltIndicator", showAltIndicator)
|
||||||
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
||||||
@@ -174,10 +193,11 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("collapseLongPosts", collapseLongPosts)
|
.putBoolean("collapseLongPosts", collapseLongPosts)
|
||||||
.putBoolean("spectatorMode", spectatorMode)
|
.putBoolean("spectatorMode", spectatorMode)
|
||||||
.putBoolean("autoHideFab", autoHideFab)
|
.putBoolean("autoHideFab", autoHideFab)
|
||||||
|
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
||||||
.putString("publishButtonText", publishButtonText)
|
.putString("publishButtonText", publishButtonText)
|
||||||
.putBoolean("bottomEncoding", bottomEncoding)
|
.putBoolean("bottomEncoding", bottomEncoding)
|
||||||
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
|
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
|
||||||
.putBoolean("disableDoubleTapToSwipe", disableDoubleTapToSwipe)
|
.putBoolean("doubleTapToSwipe", doubleTapToSwipe)
|
||||||
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
||||||
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
|
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
|
||||||
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
|
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
|
||||||
@@ -192,6 +212,10 @@ public class GlobalUserPreferences{
|
|||||||
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
|
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
|
||||||
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
|
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
|
||||||
.putString("replyVisibility", replyVisibility)
|
.putString("replyVisibility", replyVisibility)
|
||||||
|
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
|
||||||
|
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
|
||||||
|
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||||
|
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,5 +236,10 @@ public class GlobalUserPreferences{
|
|||||||
LIGHT,
|
LIGHT,
|
||||||
DARK
|
DARK
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public enum AutoRevealMode {
|
||||||
|
NEVER,
|
||||||
|
THREADS,
|
||||||
|
DISCUSSIONS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
package org.joinmastodon.android;
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.fragments.ComposeFragment.CAMERA_PERMISSION_CODE;
|
||||||
|
import static org.joinmastodon.android.fragments.ComposeFragment.CAMERA_PIC_REQUEST_CODE;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.PictureTakenEvent;
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.fragments.HomeFragment;
|
import org.joinmastodon.android.fragments.HomeFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
@@ -22,13 +32,13 @@ import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
|||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
|
|
||||||
public class MainActivity extends FragmentStackActivity{
|
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||||
UiUtils.setUserPreferredTheme(this);
|
UiUtils.setUserPreferredTheme(this);
|
||||||
@@ -38,10 +48,18 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
||||||
}else{
|
}else{
|
||||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
|
||||||
AccountSession session;
|
AccountSession session;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
Intent intent=getIntent();
|
Intent intent=getIntent();
|
||||||
|
if(intent.hasExtra("fromExternalShare")) {
|
||||||
|
AccountSessionManager.getInstance()
|
||||||
|
.setLastActiveAccountID(intent.getStringExtra("account"));
|
||||||
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
|
||||||
|
AccountSessionManager.getInstance().getLastActiveAccount());
|
||||||
|
showFragmentForExternalShare(intent.getExtras());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
||||||
boolean hasNotification = intent.hasExtra("notification");
|
boolean hasNotification = intent.hasExtra("notification");
|
||||||
if(fromNotification){
|
if(fromNotification){
|
||||||
@@ -55,6 +73,7 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
}else{
|
}else{
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
}
|
}
|
||||||
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
|
||||||
args.putString("account", session.getID());
|
args.putString("account", session.getID());
|
||||||
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
@@ -78,12 +97,12 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent){
|
protected void onNewIntent(Intent intent){
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
if(intent.getBooleanExtra("fromNotification", false)){
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
||||||
|
if (intent.hasExtra("fromExternalShare")) showFragmentForExternalShare(intent.getExtras());
|
||||||
|
else if (intent.getBooleanExtra("fromNotification", false)) {
|
||||||
String accountID=intent.getStringExtra("accountID");
|
String accountID=intent.getStringExtra("accountID");
|
||||||
AccountSession accountSession;
|
|
||||||
try{
|
try{
|
||||||
accountSession=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
DomainManager.getInstance().setCurrentDomain(accountSession.domain);
|
|
||||||
}catch(IllegalStateException x){
|
}catch(IllegalStateException x){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -107,23 +126,24 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showFragmentForNotification(Notification notification, String accountID){
|
private void showFragmentForNotification(Notification notification, String accountID){
|
||||||
Fragment fragment;
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putBoolean("_can_go_back", true);
|
|
||||||
try{
|
try{
|
||||||
notification.postprocess();
|
notification.postprocess();
|
||||||
}catch(ObjectValidationException x){
|
}catch(ObjectValidationException x){
|
||||||
Log.w("MainActivity", x);
|
Log.w("MainActivity", x);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(notification.status!=null){
|
UiUtils.showFragmentForNotification(this, notification, accountID, null);
|
||||||
fragment=new ThreadFragment();
|
}
|
||||||
args.putParcelable("status", Parcels.wrap(notification.status));
|
|
||||||
}else{
|
private void showFragmentForExternalShare(Bundle args) {
|
||||||
fragment=new ProfileFragment();
|
String className = args.getString("fromExternalShare");
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(notification.account));
|
Fragment fragment = switch (className) {
|
||||||
}
|
case "ThreadFragment" -> new ThreadFragment();
|
||||||
|
case "ProfileFragment" -> new ProfileFragment();
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
if (fragment == null) return;
|
||||||
|
args.putBoolean("_can_go_back", true);
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
showFragment(fragment);
|
showFragment(fragment);
|
||||||
}
|
}
|
||||||
@@ -157,25 +177,61 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
(fragmentContainers.get(fragmentContainers.size() - 1)).getId()
|
(fragmentContainers.get(fragmentContainers.size() - 1)).getId()
|
||||||
);
|
);
|
||||||
Bundle currentArgs = currentFragment.getArguments();
|
Bundle currentArgs = currentFragment.getArguments();
|
||||||
if (this.fragmentContainers.size() == 1
|
if (fragmentContainers.size() != 1
|
||||||
&& currentArgs != null
|
|| currentArgs == null
|
||||||
&& currentArgs.getBoolean("_can_go_back", false)
|
|| !currentArgs.getBoolean("_can_go_back", false)) {
|
||||||
&& currentArgs.containsKey("account")) {
|
super.onBackPressed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentArgs.getBoolean("_finish_on_back", false)) {
|
||||||
|
finish();
|
||||||
|
} else if (currentArgs.containsKey("account")) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString("account", currentArgs.getString("account"));
|
args.putString("account", currentArgs.getString("account"));
|
||||||
args.putString("tab", "notifications");
|
if (getIntent().getBooleanExtra("fromNotification", false)) {
|
||||||
|
args.putString("tab", "notifications");
|
||||||
|
}
|
||||||
Fragment fragment=new HomeFragment();
|
Fragment fragment=new HomeFragment();
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
showFragmentClearingBackStack(fragment);
|
showFragmentClearingBackStack(fragment);
|
||||||
} else {
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override
|
|
||||||
public void onProvideAssistContent(AssistContent outContent) {
|
|
||||||
super.onProvideAssistContent(outContent);
|
|
||||||
|
|
||||||
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
|
if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode== Activity.RESULT_OK){
|
||||||
|
Bitmap image = (Bitmap) data.getExtras().get("data");
|
||||||
|
String path = MediaStore.Images.Media.insertImage(this.getContentResolver(), image, null, null);
|
||||||
|
E.post(new PictureTakenEvent(Uri.parse(path)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
|
||||||
|
if (requestCode == CAMERA_PERMISSION_CODE && (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||||
|
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST_CODE);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, R.string.permission_required, Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Fragment getCurrentFragment() {
|
||||||
|
for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
|
||||||
|
FrameLayout fl = fragmentContainers.get(i);
|
||||||
|
if (fl.getVisibility() == View.VISIBLE) {
|
||||||
|
return getFragmentManager().findFragmentById(fl.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
|
super.onProvideAssistContent(assistContent);
|
||||||
|
Fragment fragment = getCurrentFragment();
|
||||||
|
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ public class OAuthActivity extends Activity{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Token token){
|
public void onSuccess(Token token){
|
||||||
new GetOwnAccount()
|
new GetOwnAccount()
|
||||||
|
// in case the instance (looking at pixelfed) wants to redirect to a
|
||||||
|
// website, we need to pass a context so we can launch a browser
|
||||||
|
.setContext(OAuthActivity.this)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account account){
|
public void onSuccess(Account account){
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
|
|
||||||
|
public class PanicResponderActivity extends Activity {
|
||||||
|
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
final Intent intent = getIntent();
|
||||||
|
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
|
||||||
|
AccountSessionManager.getInstance().getLoggedInAccounts().forEach(accountSession -> logOut(accountSession.getID()));
|
||||||
|
ExitActivity.exit(this);
|
||||||
|
}
|
||||||
|
finishAndRemoveTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logOut(String accountID){
|
||||||
|
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(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
onLoggedOut(accountID);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLoggedOut(String accountID){
|
||||||
|
AccountSessionManager.getInstance().removeAccount(accountID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
|||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
import org.joinmastodon.android.events.NotificationReceivedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Mention;
|
||||||
import org.joinmastodon.android.model.NotificationAction;
|
import org.joinmastodon.android.model.NotificationAction;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
import org.joinmastodon.android.model.Preferences;
|
||||||
import org.joinmastodon.android.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
@@ -38,6 +39,7 @@ import org.joinmastodon.android.model.StatusPrivacy;
|
|||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
@@ -57,7 +59,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
private static final String ACTION_KEY_TEXT_REPLY = "ACTION_KEY_TEXT_REPLY";
|
private static final String ACTION_KEY_TEXT_REPLY = "ACTION_KEY_TEXT_REPLY";
|
||||||
|
|
||||||
private static final int SUMMARY_ID = 791;
|
private static final int SUMMARY_ID = 791;
|
||||||
private static int notificationId;
|
private static int notificationId = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent){
|
public void onReceive(Context context, Intent intent){
|
||||||
@@ -298,27 +300,60 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
}
|
}
|
||||||
CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY);
|
CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY);
|
||||||
|
|
||||||
|
// copied from ComposeFragment - TODO: generalize?
|
||||||
|
ArrayList<String> mentions=new ArrayList<>();
|
||||||
|
Status status = notification.status;
|
||||||
|
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
||||||
|
if(!status.account.id.equals(ownID))
|
||||||
|
mentions.add('@'+status.account.acct);
|
||||||
|
for(Mention mention:status.mentions){
|
||||||
|
if(mention.id.equals(ownID))
|
||||||
|
continue;
|
||||||
|
String m='@'+mention.acct;
|
||||||
|
if(!mentions.contains(m))
|
||||||
|
mentions.add(m);
|
||||||
|
}
|
||||||
|
String initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
|
||||||
|
|
||||||
CreateStatus.Request req=new CreateStatus.Request();
|
CreateStatus.Request req=new CreateStatus.Request();
|
||||||
req.status = input.toString() + "\n\n" + "@" + notification.status.account.acct;
|
req.status = initialText + input.toString();
|
||||||
req.language = notification.status.language;
|
req.language = preferences.postingDefaultLanguage;
|
||||||
req.visibility = (notification.status.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : notification.status.visibility);
|
req.visibility = preferences.postingDefaultVisibility;
|
||||||
req.inReplyToId = notification.status.id;
|
req.inReplyToId = notification.status.id;
|
||||||
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
|
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
|
||||||
req.spoilerText = "re: " + notification.status.spoilerText;
|
req.spoilerText = "re: " + notification.status.spoilerText;
|
||||||
}
|
}
|
||||||
|
|
||||||
new CreateStatus(req, UUID.randomUUID().toString()).exec(accountID);
|
new CreateStatus(req, UUID.randomUUID().toString()).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Status status) {
|
||||||
|
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);
|
||||||
|
|
||||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
notification.status = status;
|
||||||
Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
|
Intent contentIntent=new Intent(context, MainActivity.class);
|
||||||
new Notification.Builder(context, accountID+"_"+notification.type) :
|
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
new Notification.Builder(context)
|
contentIntent.putExtra("fromNotification", true);
|
||||||
.setPriority(Notification.PRIORITY_DEFAULT)
|
contentIntent.putExtra("accountID", accountID);
|
||||||
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
|
contentIntent.putExtra("notification", Parcels.wrap(notification));
|
||||||
|
|
||||||
Notification repliedNotification = builder.setSmallIcon(R.drawable.ic_ntf_logo)
|
Notification repliedNotification = builder.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||||
.setContentText(context.getString(R.string.mo_notification_action_replied, notification.status.account.getDisplayUsername()))
|
.setContentTitle(context.getString(R.string.sk_notification_action_replied, notification.status.account.displayName))
|
||||||
.build();
|
.setContentText(status.getStrippedText())
|
||||||
notificationManager.notify(accountID, notificationId, repliedNotification);
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
|
.setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
|
.build();
|
||||||
|
notificationManager.notify(accountID, notificationId, repliedNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse errorResponse) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,6 @@ import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
|||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
|
||||||
import org.joinmastodon.android.model.SearchResult;
|
import org.joinmastodon.android.model.SearchResult;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
@@ -37,7 +36,7 @@ import me.grishka.appkit.utils.WorkerThread;
|
|||||||
|
|
||||||
public class CacheController{
|
public class CacheController{
|
||||||
private static final String TAG="CacheController";
|
private static final String TAG="CacheController";
|
||||||
private static final int DB_VERSION=3;
|
private static final int DB_VERSION=4;
|
||||||
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
|
||||||
private static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
private static final Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
@@ -62,7 +61,7 @@ public class CacheController{
|
|||||||
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||||
if(!forceReload){
|
if(!forceReload){
|
||||||
SQLiteDatabase db=getOrOpenDatabase();
|
SQLiteDatabase db=getOrOpenDatabase();
|
||||||
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
|
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
|
||||||
if(cursor.getCount()==count){
|
if(cursor.getCount()==count){
|
||||||
ArrayList<Status> result=new ArrayList<>();
|
ArrayList<Status> result=new ArrayList<>();
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
@@ -74,10 +73,8 @@ public class CacheController{
|
|||||||
int flags=cursor.getInt(1);
|
int flags=cursor.getInt(1);
|
||||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
||||||
newMaxID=status.id;
|
newMaxID=status.id;
|
||||||
for(Filter filter:filters){
|
if (!new StatusFilterPredicate(filters, Filter.FilterContext.HOME).test(status))
|
||||||
if(filter.matches(status))
|
continue outer;
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
result.add(status);
|
result.add(status);
|
||||||
}while(cursor.moveToNext());
|
}while(cursor.moveToNext());
|
||||||
String _newMaxID=newMaxID;
|
String _newMaxID=newMaxID;
|
||||||
@@ -92,7 +89,7 @@ public class CacheController{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters, Filter.FilterContext.HOME)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||||
putHomeTimeline(result, maxID==null);
|
putHomeTimeline(result, maxID==null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +112,7 @@ public class CacheController{
|
|||||||
runOnDbThread((db)->{
|
runOnDbThread((db)->{
|
||||||
if(clear)
|
if(clear)
|
||||||
db.delete("home_timeline", null, null);
|
db.delete("home_timeline", null, null);
|
||||||
ContentValues values=new ContentValues(3);
|
ContentValues values=new ContentValues(4);
|
||||||
for(Status s:posts){
|
for(Status s:posts){
|
||||||
values.put("id", s.id);
|
values.put("id", s.id);
|
||||||
values.put("json", MastodonAPIController.gson.toJson(s));
|
values.put("json", MastodonAPIController.gson.toJson(s));
|
||||||
@@ -123,6 +120,7 @@ public class CacheController{
|
|||||||
if(s.hasGapAfter)
|
if(s.hasGapAfter)
|
||||||
flags|=POST_FLAG_GAP_AFTER;
|
flags|=POST_FLAG_GAP_AFTER;
|
||||||
values.put("flags", flags);
|
values.put("flags", flags);
|
||||||
|
values.put("time", s.createdAt.getEpochSecond());
|
||||||
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -137,7 +135,7 @@ public class CacheController{
|
|||||||
if(!forceReload){
|
if(!forceReload){
|
||||||
SQLiteDatabase db=getOrOpenDatabase();
|
SQLiteDatabase db=getOrOpenDatabase();
|
||||||
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||||
try(Cursor cursor=db.query(table, new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
|
try(Cursor cursor=db.query(table, new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
|
||||||
if(cursor.getCount()==count){
|
if(cursor.getCount()==count){
|
||||||
ArrayList<Notification> result=new ArrayList<>();
|
ArrayList<Notification> result=new ArrayList<>();
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
@@ -148,10 +146,8 @@ public class CacheController{
|
|||||||
ntf.postprocess();
|
ntf.postprocess();
|
||||||
newMaxID=ntf.id;
|
newMaxID=ntf.id;
|
||||||
if(ntf.status!=null){
|
if(ntf.status!=null){
|
||||||
for(Filter filter:filters){
|
if (!new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status))
|
||||||
if(filter.matches(ntf.status))
|
continue outer;
|
||||||
continue outer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result.add(ntf);
|
result.add(ntf);
|
||||||
}while(cursor.moveToNext());
|
}while(cursor.moveToNext());
|
||||||
@@ -164,17 +160,13 @@ public class CacheController{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
|
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)
|
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isAkkoma())
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Notification> result){
|
public void onSuccess(List<Notification> result){
|
||||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
|
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
|
||||||
if(ntf.status!=null){
|
if(ntf.status!=null){
|
||||||
for(Filter filter:filters){
|
return new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status);
|
||||||
if(filter.matches(ntf.status)){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||||
@@ -201,7 +193,7 @@ public class CacheController{
|
|||||||
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
|
||||||
if(clear)
|
if(clear)
|
||||||
db.delete(table, null, null);
|
db.delete(table, null, null);
|
||||||
ContentValues values=new ContentValues(3);
|
ContentValues values=new ContentValues(4);
|
||||||
for(Notification n:notifications){
|
for(Notification n:notifications){
|
||||||
if(n.type==null){
|
if(n.type==null){
|
||||||
continue;
|
continue;
|
||||||
@@ -209,6 +201,7 @@ public class CacheController{
|
|||||||
values.put("id", n.id);
|
values.put("id", n.id);
|
||||||
values.put("json", MastodonAPIController.gson.toJson(n));
|
values.put("json", MastodonAPIController.gson.toJson(n));
|
||||||
values.put("type", n.type.ordinal());
|
values.put("type", n.type.ordinal());
|
||||||
|
values.put("time", n.createdAt.getEpochSecond());
|
||||||
db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -305,21 +298,24 @@ public class CacheController{
|
|||||||
CREATE TABLE `home_timeline` (
|
CREATE TABLE `home_timeline` (
|
||||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||||
`json` TEXT NOT NULL,
|
`json` TEXT NOT NULL,
|
||||||
`flags` INTEGER NOT NULL DEFAULT 0
|
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`time` INTEGER NOT NULL
|
||||||
)""");
|
)""");
|
||||||
db.execSQL("""
|
db.execSQL("""
|
||||||
CREATE TABLE `notifications_all` (
|
CREATE TABLE `notifications_all` (
|
||||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||||
`json` TEXT NOT NULL,
|
`json` TEXT NOT NULL,
|
||||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||||
`type` INTEGER NOT NULL
|
`type` INTEGER NOT NULL,
|
||||||
|
`time` INTEGER NOT NULL
|
||||||
)""");
|
)""");
|
||||||
db.execSQL("""
|
db.execSQL("""
|
||||||
CREATE TABLE `notifications_mentions` (
|
CREATE TABLE `notifications_mentions` (
|
||||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||||
`json` TEXT NOT NULL,
|
`json` TEXT NOT NULL,
|
||||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||||
`type` INTEGER NOT NULL
|
`type` INTEGER NOT NULL,
|
||||||
|
`time` INTEGER NOT NULL
|
||||||
)""");
|
)""");
|
||||||
createRecentSearchesTable(db);
|
createRecentSearchesTable(db);
|
||||||
createPostsNotificationsTable(db);
|
createPostsNotificationsTable(db);
|
||||||
@@ -327,12 +323,16 @@ public class CacheController{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
|
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
|
||||||
if(oldVersion==1){
|
if(oldVersion<2){
|
||||||
createRecentSearchesTable(db);
|
createRecentSearchesTable(db);
|
||||||
}
|
}
|
||||||
if(oldVersion==2){
|
if(oldVersion<3){
|
||||||
|
// MEGALODON-SPECIFIC
|
||||||
createPostsNotificationsTable(db);
|
createPostsNotificationsTable(db);
|
||||||
}
|
}
|
||||||
|
if(oldVersion<4){
|
||||||
|
addTimeColumns(db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createRecentSearchesTable(SQLiteDatabase db){
|
private void createRecentSearchesTable(SQLiteDatabase db){
|
||||||
@@ -350,9 +350,21 @@ public class CacheController{
|
|||||||
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
`id` VARCHAR(25) NOT NULL PRIMARY KEY,
|
||||||
`json` TEXT NOT NULL,
|
`json` TEXT NOT NULL,
|
||||||
`flags` INTEGER NOT NULL DEFAULT 0,
|
`flags` INTEGER NOT NULL DEFAULT 0,
|
||||||
`type` INTEGER NOT NULL
|
`type` INTEGER NOT NULL,
|
||||||
|
`time` INTEGER NOT NULL
|
||||||
)""");
|
)""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addTimeColumns(SQLiteDatabase db){
|
||||||
|
db.execSQL("DELETE FROM `home_timeline`");
|
||||||
|
db.execSQL("DELETE FROM `notifications_all`");
|
||||||
|
db.execSQL("DELETE FROM `notifications_mentions`");
|
||||||
|
db.execSQL("DELETE FROM `notifications_posts`");
|
||||||
|
db.execSQL("ALTER TABLE `home_timeline` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||||
|
db.execSQL("ALTER TABLE `notifications_all` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||||
|
db.execSQL("ALTER TABLE `notifications_mentions` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||||
|
db.execSQL("ALTER TABLE `notifications_posts` ADD `time` INTEGER NOT NULL DEFAULT 0");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
|||||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -28,6 +29,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -51,7 +53,9 @@ public class MastodonAPIController{
|
|||||||
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
|
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
|
||||||
.create();
|
.create();
|
||||||
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
||||||
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
private static OkHttpClient httpClient=new OkHttpClient.Builder()
|
||||||
|
.readTimeout(5, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
private static List<String> badDomains = new ArrayList<>();
|
private static List<String> badDomains = new ArrayList<>();
|
||||||
@@ -60,7 +64,7 @@ public class MastodonAPIController{
|
|||||||
thread.start();
|
thread.start();
|
||||||
try {
|
try {
|
||||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||||
MastodonApp.context.getAssets().open("blocks.tsv")
|
MastodonApp.context.getAssets().open("blocks.txt")
|
||||||
));
|
));
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
@@ -91,7 +95,7 @@ public class MastodonAPIController{
|
|||||||
Request.Builder builder=new Request.Builder()
|
Request.Builder builder=new Request.Builder()
|
||||||
.url(req.getURL().toString())
|
.url(req.getURL().toString())
|
||||||
.method(req.getMethod(), req.getRequestBody())
|
.method(req.getMethod(), req.getRequestBody())
|
||||||
.header("User-Agent", "MastodonAndroid/"+BuildConfig.VERSION_NAME);
|
.header("User-Agent", "MoshidonAndroid/"+BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
String token=null;
|
String token=null;
|
||||||
if(session!=null)
|
if(session!=null)
|
||||||
@@ -158,6 +162,11 @@ public class MastodonAPIController{
|
|||||||
respObj=gson.fromJson(reader, req.respClass);
|
respObj=gson.fromJson(reader, req.respClass);
|
||||||
}
|
}
|
||||||
}catch(JsonIOException|JsonSyntaxException x){
|
}catch(JsonIOException|JsonSyntaxException x){
|
||||||
|
if (req.context != null && response.body().contentType().subtype().equals("html")) {
|
||||||
|
UiUtils.launchWebBrowser(req.context, response.request().url().toString());
|
||||||
|
req.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
|
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
|
||||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.api;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@@ -20,9 +21,11 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -44,10 +47,11 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
TypeToken<T> respTypeToken;
|
TypeToken<T> respTypeToken;
|
||||||
Call okhttpCall;
|
Call okhttpCall;
|
||||||
Token token;
|
Token token;
|
||||||
boolean canceled;
|
boolean canceled, isRemote;
|
||||||
Map<String, String> headers;
|
Map<String, String> headers;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
protected boolean removeUnsupportedItems;
|
protected boolean removeUnsupportedItems;
|
||||||
|
@Nullable Context context;
|
||||||
|
|
||||||
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
|
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
|
||||||
this.path=path;
|
this.path=path;
|
||||||
@@ -101,6 +105,21 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MastodonAPIRequest<T> execRemote(String domain) {
|
||||||
|
return execRemote(domain, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MastodonAPIRequest<T> execRemote(String domain, @Nullable AccountSession remoteSession) {
|
||||||
|
this.isRemote = true;
|
||||||
|
return Optional.ofNullable(remoteSession)
|
||||||
|
.or(() -> AccountSessionManager.getInstance().getLoggedInAccounts().stream()
|
||||||
|
.filter(acc -> acc.domain.equals(domain))
|
||||||
|
.findAny())
|
||||||
|
.map(AccountSession::getID)
|
||||||
|
.map(this::exec)
|
||||||
|
.orElse(this.execNoAuth(domain));
|
||||||
|
}
|
||||||
|
|
||||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
||||||
return wrapProgress(activity, message, cancelable, null);
|
return wrapProgress(activity, message, cancelable, null);
|
||||||
}
|
}
|
||||||
@@ -164,9 +183,20 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MastodonAPIRequest<T> setContext(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
|
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
|
||||||
if(respObj instanceof BaseModel){
|
if(respObj instanceof BaseModel){
|
||||||
|
((BaseModel) respObj).isRemote = isRemote;
|
||||||
((BaseModel) respObj).postprocess();
|
((BaseModel) respObj).postprocess();
|
||||||
}else if(respObj instanceof List){
|
}else if(respObj instanceof List){
|
||||||
if(removeUnsupportedItems){
|
if(removeUnsupportedItems){
|
||||||
@@ -175,6 +205,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
Object item=itr.next();
|
Object item=itr.next();
|
||||||
if(item instanceof BaseModel){
|
if(item instanceof BaseModel){
|
||||||
try{
|
try{
|
||||||
|
((BaseModel) item).isRemote = isRemote;
|
||||||
((BaseModel) item).postprocess();
|
((BaseModel) item).postprocess();
|
||||||
}catch(ObjectValidationException x){
|
}catch(ObjectValidationException x){
|
||||||
Log.w(TAG, "Removing invalid object from list", x);
|
Log.w(TAG, "Removing invalid object from list", x);
|
||||||
@@ -182,15 +213,20 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// no idea why we're post-processing twice, but well, as long
|
||||||
|
// as upstream does it like this, i don't wanna break anything
|
||||||
for(Object item:((List<?>) respObj)){
|
for(Object item:((List<?>) respObj)){
|
||||||
if(item instanceof BaseModel){
|
if(item instanceof BaseModel){
|
||||||
|
((BaseModel) item).isRemote = isRemote;
|
||||||
((BaseModel) item).postprocess();
|
((BaseModel) item).postprocess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
for(Object item:((List<?>) respObj)){
|
for(Object item:((List<?>) respObj)){
|
||||||
if(item instanceof BaseModel)
|
if(item instanceof BaseModel) {
|
||||||
|
((BaseModel) item).isRemote = isRemote;
|
||||||
((BaseModel) item).postprocess();
|
((BaseModel) item).postprocess();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public class StatusInteractionController{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
runningFavoriteRequests.remove(status.id);
|
runningFavoriteRequests.remove(status.id);
|
||||||
result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1);
|
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ public class StatusInteractionController{
|
|||||||
public void onSuccess(Status reblog){
|
public void onSuccess(Status reblog){
|
||||||
Status result = reblog.getContentStatus();
|
Status result = reblog.getContentStatus();
|
||||||
runningReblogRequests.remove(status.id);
|
runningReblogRequests.remove(status.id);
|
||||||
result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1);
|
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
|||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
|
||||||
public class GetAccountByHandle extends MastodonAPIRequest<Account>{
|
public class GetAccountByHandle extends MastodonAPIRequest<Account>{
|
||||||
|
/**
|
||||||
|
* note that this method usually only returns a result if the instance already knows about an
|
||||||
|
* account - so it makes sense for looking up local users, search might be preferred otherwise
|
||||||
|
*/
|
||||||
public GetAccountByHandle(String acct){
|
public GetAccountByHandle(String acct){
|
||||||
super(HttpMethod.GET, "/accounts/lookup", Account.class);
|
super(HttpMethod.GET, "/accounts/lookup", Account.class);
|
||||||
addQueryParameter("acct", acct);
|
addQueryParameter("acct", acct);
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.instance;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.DomainBlock;
|
||||||
|
import org.joinmastodon.android.model.ExtendedDescription;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetDomainBlocks extends MastodonAPIRequest<List<DomainBlock>>{
|
||||||
|
public GetDomainBlocks(){
|
||||||
|
super(HttpMethod.GET, "/instance/domain_blocks", new TypeToken<>(){});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.instance;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ExtendedDescription;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
|
||||||
|
public class GetExtendedDescription extends MastodonAPIRequest<ExtendedDescription>{
|
||||||
|
public GetExtendedDescription(){
|
||||||
|
super(HttpMethod.GET, "/instance/extended_description", ExtendedDescription.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.instance;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.WeeklyActivity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetWeeklyActivity extends MastodonAPIRequest<List<WeeklyActivity>>{
|
||||||
|
public GetWeeklyActivity(){
|
||||||
|
super(HttpMethod.GET, "/instance/activity", new TypeToken<>(){});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.notifications;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Notification;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
|
||||||
|
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
|
||||||
|
private String maxID;
|
||||||
|
public PleromaMarkNotificationsRead(String maxID) {
|
||||||
|
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
|
||||||
|
this.maxID = maxID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestBody getRequestBody() {
|
||||||
|
MultipartBody.Builder builder=new MultipartBody.Builder()
|
||||||
|
.setType(MultipartBody.FORM);
|
||||||
|
if(!TextUtils.isEmpty(maxID))
|
||||||
|
builder.addFormDataPart("max_id", maxID);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
@@ -46,6 +47,7 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
|||||||
public String language;
|
public String language;
|
||||||
|
|
||||||
public String quoteId;
|
public String quoteId;
|
||||||
|
public ContentType contentType;
|
||||||
|
|
||||||
public static class Poll{
|
public static class Poll{
|
||||||
public ArrayList<String> options=new ArrayList<>();
|
public ArrayList<String> options=new ArrayList<>();
|
||||||
|
|||||||
@@ -2,17 +2,22 @@ package org.joinmastodon.android.api.requests.statuses;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
import org.joinmastodon.android.model.BaseModel;
|
import org.joinmastodon.android.model.BaseModel;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
|
|
||||||
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
|
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
|
||||||
public GetStatusSourceText(String id){
|
public GetStatusSourceText(String id){
|
||||||
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
|
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllFieldsAreRequired
|
|
||||||
public static class Response extends BaseModel{
|
public static class Response extends BaseModel{
|
||||||
|
@RequiredField
|
||||||
public String id;
|
public String id;
|
||||||
|
@RequiredField
|
||||||
public String text;
|
public String text;
|
||||||
|
@RequiredField
|
||||||
public String spoilerText;
|
public String spoilerText;
|
||||||
|
public ContentType contentType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.joinmastodon.android.api.requests.timelines;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GetBubbleTimeline extends MastodonAPIRequest<List<Status>> {
|
||||||
|
public GetBubbleTimeline(String maxID, int limit) {
|
||||||
|
super(HttpMethod.GET, "/timelines/bubble", new TypeToken<>(){});
|
||||||
|
if(!TextUtils.isEmpty(maxID))
|
||||||
|
addQueryParameter("max_id", maxID);
|
||||||
|
if(limit>0)
|
||||||
|
addQueryParameter("limit", limit+"");
|
||||||
|
if(GlobalUserPreferences.replyVisibility != null)
|
||||||
|
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.api.requests.timelines;
|
|||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
@@ -16,5 +17,7 @@ public class GetHashtagTimeline extends MastodonAPIRequest<List<Status>>{
|
|||||||
addQueryParameter("min_id", minID);
|
addQueryParameter("min_id", minID);
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", ""+limit);
|
addQueryParameter("limit", ""+limit);
|
||||||
|
if(GlobalUserPreferences.replyVisibility != null)
|
||||||
|
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.api.requests.timelines;
|
|||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
@@ -18,5 +19,7 @@ public class GetListTimeline extends MastodonAPIRequest<List<Status>> {
|
|||||||
addQueryParameter("limit", ""+limit);
|
addQueryParameter("limit", ""+limit);
|
||||||
if(sinceID!=null)
|
if(sinceID!=null)
|
||||||
addQueryParameter("since_id", sinceID);
|
addQueryParameter("since_id", sinceID);
|
||||||
|
if(GlobalUserPreferences.replyVisibility != null)
|
||||||
|
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.text.TextUtils;
|
|||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
@@ -20,5 +21,7 @@ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
|
|||||||
addQueryParameter("max_id", maxID);
|
addQueryParameter("max_id", maxID);
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", limit+"");
|
addQueryParameter("limit", limit+"");
|
||||||
|
if(GlobalUserPreferences.replyVisibility != null)
|
||||||
|
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.joinmastodon.android.api.session;
|
package org.joinmastodon.android.api.session;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.CacheController;
|
import org.joinmastodon.android.api.CacheController;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||||
@@ -7,6 +9,7 @@ import org.joinmastodon.android.api.StatusInteractionController;
|
|||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Markers;
|
import org.joinmastodon.android.model.Markers;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
import org.joinmastodon.android.model.Preferences;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
@@ -14,6 +17,7 @@ import org.joinmastodon.android.model.Token;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class AccountSession{
|
public class AccountSession{
|
||||||
public Token token;
|
public Token token;
|
||||||
@@ -87,4 +91,15 @@ public class AccountSession{
|
|||||||
pushSubscriptionManager=new PushSubscriptionManager(getID());
|
pushSubscriptionManager=new PushSubscriptionManager(getID());
|
||||||
return pushSubscriptionManager;
|
return pushSubscriptionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional<Instance> getInstance() {
|
||||||
|
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getInstanceUri() {
|
||||||
|
return new Uri.Builder()
|
||||||
|
.scheme("https")
|
||||||
|
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import android.util.Log;
|
|||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
@@ -121,6 +122,12 @@ public class AccountSessionManager{
|
|||||||
sessions.put(session.getID(), session);
|
sessions.put(session.getID(), session);
|
||||||
lastActiveAccountID=session.getID();
|
lastActiveAccountID=session.getID();
|
||||||
writeAccountsFile();
|
writeAccountsFile();
|
||||||
|
|
||||||
|
// write initial instance info to file immediately to avoid sessions without instance info
|
||||||
|
InstanceInfoStorageWrapper wrapper = new InstanceInfoStorageWrapper();
|
||||||
|
wrapper.instance = instance;
|
||||||
|
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
|
||||||
|
|
||||||
updateMoreInstanceInfo(instance, instance.uri);
|
updateMoreInstanceInfo(instance, instance.uri);
|
||||||
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||||
@@ -129,14 +136,16 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void writeAccountsFile(){
|
public synchronized void writeAccountsFile(){
|
||||||
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
File tmpFile = new File(MastodonApp.context.getFilesDir(), "accounts.json~");
|
||||||
|
File file = new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
||||||
try{
|
try{
|
||||||
try(FileOutputStream out=new FileOutputStream(file)){
|
try(FileOutputStream out=new FileOutputStream(tmpFile)){
|
||||||
SessionsStorageWrapper w=new SessionsStorageWrapper();
|
SessionsStorageWrapper w=new SessionsStorageWrapper();
|
||||||
w.accounts=new ArrayList<>(sessions.values());
|
w.accounts=new ArrayList<>(sessions.values());
|
||||||
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
||||||
MastodonAPIController.gson.toJson(w, writer);
|
MastodonAPIController.gson.toJson(w, writer);
|
||||||
writer.flush();
|
writer.flush();
|
||||||
|
if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
|
||||||
}
|
}
|
||||||
}catch(IOException x){
|
}catch(IOException x){
|
||||||
Log.e(TAG, "Error writing accounts file", x);
|
Log.e(TAG, "Error writing accounts file", x);
|
||||||
@@ -162,6 +171,11 @@ public class AccountSessionManager{
|
|||||||
return sessions.get(id);
|
return sessions.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public AccountSession tryGetAccount(Account account) {
|
||||||
|
return sessions.get(account.getDomainFromURL() + "_" + account.id);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public AccountSession getLastActiveAccount(){
|
public AccountSession getLastActiveAccount(){
|
||||||
if(sessions.isEmpty() || lastActiveAccountID==null)
|
if(sessions.isEmpty() || lastActiveAccountID==null)
|
||||||
@@ -189,6 +203,7 @@ public class AccountSessionManager{
|
|||||||
AccountSession session=getAccount(id);
|
AccountSession session=getAccount(id);
|
||||||
session.getCacheController().closeDatabase();
|
session.getCacheController().closeDatabase();
|
||||||
MastodonApp.context.deleteDatabase(id+".db");
|
MastodonApp.context.deleteDatabase(id+".db");
|
||||||
|
GlobalUserPreferences.removeAccount(id);
|
||||||
sessions.remove(id);
|
sessions.remove(id);
|
||||||
if(lastActiveAccountID.equals(id)){
|
if(lastActiveAccountID.equals(id)){
|
||||||
if(sessions.isEmpty())
|
if(sessions.isEmpty())
|
||||||
@@ -259,31 +274,35 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void maybeUpdateLocalInfo(){
|
public void maybeUpdateLocalInfo(){
|
||||||
|
maybeUpdateLocalInfo(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void maybeUpdateLocalInfo(AccountSession activeSession){
|
||||||
long now=System.currentTimeMillis();
|
long now=System.currentTimeMillis();
|
||||||
HashSet<String> domains=new HashSet<>();
|
HashSet<String> domains=new HashSet<>();
|
||||||
for(AccountSession session:sessions.values()){
|
for(AccountSession session:sessions.values()){
|
||||||
domains.add(session.domain.toLowerCase());
|
domains.add(session.domain.toLowerCase());
|
||||||
// if(now-session.infoLastUpdated>24L*3600_000L){
|
if(now-session.infoLastUpdated>24L*3600_000L || session == activeSession){
|
||||||
updateSessionPreferences(session);
|
updateSessionPreferences(session);
|
||||||
updateSessionLocalInfo(session);
|
updateSessionLocalInfo(session);
|
||||||
// }
|
}
|
||||||
// if(now-session.filtersLastUpdated>3600_000L){
|
if(now-session.filtersLastUpdated>3600_000L || session == activeSession){
|
||||||
updateSessionWordFilters(session);
|
updateSessionWordFilters(session);
|
||||||
// }
|
}
|
||||||
updateSessionMarkers(session);
|
updateSessionMarkers(session);
|
||||||
}
|
}
|
||||||
if(loadedInstances){
|
if(loadedInstances){
|
||||||
maybeUpdateCustomEmojis(domains);
|
maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeUpdateCustomEmojis(Set<String> domains){
|
private void maybeUpdateCustomEmojis(Set<String> domains, String activeDomain){
|
||||||
long now=System.currentTimeMillis();
|
long now=System.currentTimeMillis();
|
||||||
for(String domain:domains){
|
for(String domain:domains){
|
||||||
// Long lastUpdated=instancesLastUpdated.get(domain);
|
Long lastUpdated=instancesLastUpdated.get(domain);
|
||||||
// if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
if(lastUpdated==null || now-lastUpdated>24L*3600_000L || domain.equals(activeDomain)){
|
||||||
updateInstanceInfo(domain);
|
updateInstanceInfo(domain);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +430,9 @@ public class AccountSessionManager{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
|
InstanceInfoStorageWrapper wrapper=new InstanceInfoStorageWrapper();
|
||||||
|
wrapper.instance = instance;
|
||||||
|
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, domain));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.execNoAuth(domain);
|
.execNoAuth(domain);
|
||||||
@@ -422,10 +443,13 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){
|
private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){
|
||||||
try(FileOutputStream out=new FileOutputStream(getInstanceInfoFile(domain))){
|
File file = getInstanceInfoFile(domain);
|
||||||
|
File tmpFile = new File(file.getPath() + "~");
|
||||||
|
try(FileOutputStream out=new FileOutputStream(tmpFile)){
|
||||||
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
||||||
MastodonAPIController.gson.toJson(emojis, writer);
|
MastodonAPIController.gson.toJson(emojis, writer);
|
||||||
writer.flush();
|
writer.flush();
|
||||||
|
if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
|
||||||
}catch(IOException x){
|
}catch(IOException x){
|
||||||
Log.w(TAG, "Error writing instance info file for "+domain, x);
|
Log.w(TAG, "Error writing instance info file for "+domain, x);
|
||||||
}
|
}
|
||||||
@@ -445,7 +469,7 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
if(!loadedInstances){
|
if(!loadedInstances){
|
||||||
loadedInstances=true;
|
loadedInstances=true;
|
||||||
maybeUpdateCustomEmojis(domains);
|
maybeUpdateCustomEmojis(domains, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,10 +493,6 @@ public class AccountSessionManager{
|
|||||||
return instances.get(domain);
|
return instances.get(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instance getInstanceInfoForAccount(String account) {
|
|
||||||
return AccountSessionManager.getInstance().getInstanceInfo(instance.getAccount(account).domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateAccountInfo(String id, Account account){
|
public void updateAccountInfo(String id, Account account){
|
||||||
AccountSession session=getAccount(id);
|
AccountSession session=getAccount(id);
|
||||||
session.self=account;
|
session.self=account;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.Poll;
|
||||||
|
|
||||||
|
public class PictureTakenEvent {
|
||||||
|
public Uri uri;
|
||||||
|
|
||||||
|
public PictureTakenEvent(Uri uri){
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.TranslateAnimation;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
@@ -23,6 +20,7 @@ import org.parceler.Parcels;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
@@ -64,7 +62,12 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(getActivity()==null) return;
|
if(getActivity()==null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.ACCOUNT)).collect(Collectors.toList());
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
|
result=result.stream().filter(status -> {
|
||||||
|
// don't hide own posts in own profile
|
||||||
|
if (asm.isSelf(accountID, user) && asm.isSelf(accountID, status.account)) return true;
|
||||||
|
else return new StatusFilterPredicate(accountID, getFilterContext()).test(status);
|
||||||
|
}).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -85,7 +88,8 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
protected void onStatusCreated(StatusCreatedEvent ev){
|
||||||
if(!AccountSessionManager.getInstance().isSelf(accountID, ev.status.account))
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
|
if(!asm.isSelf(accountID, ev.status.account) || !asm.isSelf(accountID, user))
|
||||||
return;
|
return;
|
||||||
if(filter==GetAccountStatuses.Filter.PINNED) return;
|
if(filter==GetAccountStatuses.Filter.PINNED) return;
|
||||||
if(filter==GetAccountStatuses.Filter.DEFAULT){
|
if(filter==GetAccountStatuses.Filter.DEFAULT){
|
||||||
@@ -93,10 +97,11 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
|
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
|
||||||
return;
|
return;
|
||||||
}else if(filter==GetAccountStatuses.Filter.MEDIA){
|
}else if(filter==GetAccountStatuses.Filter.MEDIA){
|
||||||
if(ev.status.mediaAttachments.isEmpty())
|
if(Optional.ofNullable(ev.status.mediaAttachments).map(List::isEmpty).orElse(true))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prependItems(Collections.singletonList(ev.status), true);
|
prependItems(Collections.singletonList(ev.status), true);
|
||||||
|
if (isOnTop()) scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStatusUnpinned(StatusUnpinnedEvent ev){
|
protected void onStatusUnpinned(StatusUnpinnedEvent ev){
|
||||||
@@ -123,4 +128,19 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.ACCOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
// could return different uris based on filter (e.g. media -> "/media"), but i want to
|
||||||
|
// return the remote url to the user, and i don't know whether i'd need to append
|
||||||
|
// '#media' (akkoma/pleroma) or '/media' (glitch/mastodon) since i don't know anything
|
||||||
|
// about the remote instance. so, just returning the base url to the user instead
|
||||||
|
return Uri.parse(user.url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.joinmastodon.android.fragments;
|
|||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -103,4 +104,9 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
|||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return isInstanceAkkoma() ? base.path("/announcements").build() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.assist.AssistContent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
@@ -13,7 +14,6 @@ import android.text.Layout;
|
|||||||
import android.text.StaticLayout;
|
import android.text.StaticLayout;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
@@ -21,14 +21,15 @@ import android.view.animation.TranslateAnimation;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.DomainManager;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.MainActivity;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
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.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
@@ -37,6 +38,7 @@ import org.joinmastodon.android.model.Relationship;
|
|||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
@@ -49,6 +51,7 @@ import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
|||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -73,7 +76,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, DomainDisplay{
|
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, IsOnTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
|
||||||
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
|
||||||
protected DisplayItemsAdapter adapter;
|
protected DisplayItemsAdapter adapter;
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
@@ -84,8 +87,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected Rect tmpRect=new Rect();
|
protected Rect tmpRect=new Rect();
|
||||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||||
|
protected boolean currentlyScrolling;
|
||||||
private final int THRESHOLD = 800;
|
|
||||||
|
|
||||||
public BaseStatusListFragment(){
|
public BaseStatusListFragment(){
|
||||||
super(20);
|
super(20);
|
||||||
@@ -99,7 +101,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
UiUtils.loadMaxWidth(getContext());
|
|
||||||
if(GlobalUserPreferences.disableMarquee){
|
if(GlobalUserPreferences.disableMarquee){
|
||||||
setTitleMarqueeEnabled(false);
|
setTitleMarqueeEnabled(false);
|
||||||
setSubtitleMarqueeEnabled(false);
|
setSubtitleMarqueeEnabled(false);
|
||||||
@@ -136,7 +137,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
displayItems.clear();
|
displayItems.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void prependItems(List<T> items, boolean notify){
|
protected int prependItems(List<T> items, boolean notify){
|
||||||
data.addAll(0, items);
|
data.addAll(0, items);
|
||||||
int offset=0;
|
int offset=0;
|
||||||
for(T s:items){
|
for(T s:items){
|
||||||
@@ -149,6 +150,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
if(notify)
|
if(notify)
|
||||||
adapter.notifyItemRangeInserted(0, offset);
|
adapter.notifyItemRangeInserted(0, offset);
|
||||||
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getMaxID(){
|
protected String getMaxID(){
|
||||||
@@ -209,7 +211,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
@Override
|
@Override
|
||||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
||||||
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
MediaAttachmentViewController holder=findPhotoViewHolder(index);
|
||||||
if(holder!=null){
|
if(holder!=null && list!=null){
|
||||||
transitioningHolder=holder;
|
transitioningHolder=holder;
|
||||||
View view=transitioningHolder.photo;
|
View view=transitioningHolder.photo;
|
||||||
int[] pos={0, 0};
|
int[] pos={0, 0};
|
||||||
@@ -275,34 +277,43 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public @Nullable View getFab() {
|
public @Nullable View getFab() {
|
||||||
if (getParentFragment() instanceof HasFab l) return l.getFab();
|
if (getParentFragment() instanceof HasFab l) return l.getFab();
|
||||||
else return fab;
|
else return fab;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void animateFab(boolean show) {
|
@Override
|
||||||
|
public void showFab() {
|
||||||
View fab = getFab();
|
View fab = getFab();
|
||||||
if (fab == null) return;
|
if (fab == null || fab.getVisibility() == View.VISIBLE) return;
|
||||||
if (show && fab.getVisibility() != View.VISIBLE) {
|
fab.setVisibility(View.VISIBLE);
|
||||||
fab.setVisibility(View.VISIBLE);
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
TranslateAnimation animate = new TranslateAnimation(
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
fab.getHeight() * 2,
|
||||||
fab.getHeight() * 2,
|
0);
|
||||||
0);
|
animate.setDuration(300);
|
||||||
animate.setDuration(300);
|
fab.startAnimation(animate);
|
||||||
fab.startAnimation(animate);
|
}
|
||||||
} else if (!show && fab.getVisibility() == View.VISIBLE) {
|
|
||||||
TranslateAnimation animate = new TranslateAnimation(
|
public boolean isScrolling() {
|
||||||
0,
|
return currentlyScrolling;
|
||||||
0,
|
}
|
||||||
0,
|
|
||||||
fab.getHeight() * 2);
|
@Override
|
||||||
animate.setDuration(300);
|
public void hideFab() {
|
||||||
fab.startAnimation(animate);
|
View fab = getFab();
|
||||||
fab.setVisibility(View.INVISIBLE);
|
if (fab == null || fab.getVisibility() != View.VISIBLE) return;
|
||||||
scrollDiff = 0;
|
TranslateAnimation animate = new TranslateAnimation(
|
||||||
}
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
fab.getHeight() * 2);
|
||||||
|
animate.setDuration(300);
|
||||||
|
fab.startAnimation(animate);
|
||||||
|
fab.setVisibility(View.INVISIBLE);
|
||||||
|
scrollDiff = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -317,12 +328,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
currentPhotoViewer.offsetView(-dx, -dy);
|
currentPhotoViewer.offsetView(-dx, -dy);
|
||||||
|
|
||||||
View fab = getFab();
|
View fab = getFab();
|
||||||
if (fab!=null && GlobalUserPreferences.enableFabAutoHide) {
|
if (fab!=null && GlobalUserPreferences.autoHideFab && dy != UiUtils.SCROLL_TO_TOP_DELTA) {
|
||||||
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
|
||||||
animateFab(false);
|
hideFab();
|
||||||
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
|
||||||
if (list.getChildAt(0).getTop() == 0 || scrollDiff > THRESHOLD) {
|
if (list.getChildAt(0).getTop() == 0 || scrollDiff > 400) {
|
||||||
animateFab(true);
|
showFab();
|
||||||
scrollDiff = 0;
|
scrollDiff = 0;
|
||||||
} else {
|
} else {
|
||||||
scrollDiff += Math.abs(dy);
|
scrollDiff += Math.abs(dy);
|
||||||
@@ -330,12 +341,20 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState);
|
||||||
|
currentlyScrolling = newState != RecyclerView.SCROLL_STATE_IDLE;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
list.addItemDecoration(new StatusListItemDecoration());
|
list.addItemDecoration(new StatusListItemDecoration());
|
||||||
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
||||||
private Rect tmpRect=new Rect();
|
private Rect tmpRect=new Rect();
|
||||||
@Override
|
@Override
|
||||||
public void getSelectorBounds(View view, Rect outRect){
|
public void getSelectorBounds(View view, Rect outRect){
|
||||||
|
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
|
||||||
|
int lastIndex = -1, firstIndex = -1;
|
||||||
list.getDecoratedBoundsWithMargins(view, outRect);
|
list.getDecoratedBoundsWithMargins(view, outRect);
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
|
||||||
if(holder instanceof StatusDisplayItem.Holder){
|
if(holder instanceof StatusDisplayItem.Holder){
|
||||||
@@ -347,18 +366,42 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
View child=list.getChildAt(i);
|
View child=list.getChildAt(i);
|
||||||
holder=list.getChildViewHolder(child);
|
holder=list.getChildViewHolder(child);
|
||||||
if(holder instanceof StatusDisplayItem.Holder){
|
if(holder instanceof StatusDisplayItem.Holder<?> h){
|
||||||
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
|
||||||
if(otherID.equals(id)){
|
if(otherID.equals(id)){
|
||||||
|
if (firstIndex < 0) firstIndex = i;
|
||||||
|
lastIndex = i;
|
||||||
|
StatusDisplayItem item = h.getItem();
|
||||||
|
hasDescendant = item.hasDescendantNeighbor;
|
||||||
|
// no for direct descendants because main status (right above) is
|
||||||
|
// being displayed with an extended footer - no connected layout
|
||||||
|
hasAncestor = item.hasAncestoringNeighbor && !item.isDirectDescendant;
|
||||||
list.getDecoratedBoundsWithMargins(child, tmpRect);
|
list.getDecoratedBoundsWithMargins(child, tmpRect);
|
||||||
outRect.left=Math.min(outRect.left, tmpRect.left);
|
outRect.left=Math.min(outRect.left, tmpRect.left);
|
||||||
outRect.top=Math.min(outRect.top, tmpRect.top);
|
outRect.top=Math.min(outRect.top, tmpRect.top);
|
||||||
outRect.right=Math.max(outRect.right, tmpRect.right);
|
outRect.right=Math.max(outRect.right, tmpRect.right);
|
||||||
outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom);
|
outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom);
|
||||||
|
if (holder instanceof WarningFilteredStatusDisplayItem.Holder) {
|
||||||
|
isWarning = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// shifting the selection box down
|
||||||
|
// see also: FooterStatusDisplayItem#onBind (setMargins)
|
||||||
|
if (isWarning || firstIndex < 0 || lastIndex < 0 ||
|
||||||
|
!(list.getChildViewHolder(list.getChildAt(lastIndex))
|
||||||
|
instanceof FooterStatusDisplayItem.Holder)) return;
|
||||||
|
int prevIndex = firstIndex - 1, nextIndex = lastIndex + 1;
|
||||||
|
boolean prevIsWarning = prevIndex > 0 && prevIndex < list.getChildCount() &&
|
||||||
|
list.getChildViewHolder(list.getChildAt(prevIndex))
|
||||||
|
instanceof WarningFilteredStatusDisplayItem.Holder;
|
||||||
|
boolean nextIsWarning = nextIndex > 0 && nextIndex < list.getChildCount() &&
|
||||||
|
list.getChildViewHolder(list.getChildAt(nextIndex))
|
||||||
|
instanceof WarningFilteredStatusDisplayItem.Holder;
|
||||||
|
if (!prevIsWarning && hasAncestor) outRect.top += V.dp(4);
|
||||||
|
if (!nextIsWarning && hasDescendant) outRect.bottom += V.dp(4);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
list.setItemAnimator(new BetterItemAnimator());
|
||||||
@@ -459,26 +502,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}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)));
|
submitPollVote(holder.getItemID(), poll.id, Collections.singletonList(poll.options.indexOf(option)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){
|
public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){
|
||||||
Poll poll=holder.getItem().poll;
|
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()));
|
submitPollVote(holder.getItemID(), poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,6 +596,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onImageUpdated(MediaGridStatusDisplayItem.Holder holder, int index) {
|
||||||
|
holder.rebind();
|
||||||
|
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
|
||||||
|
if(mediaGrid!=null){
|
||||||
|
adapter.notifyItemChanged(mediaGrid.getAbsoluteAdapterPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
||||||
|
|
||||||
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
||||||
@@ -578,6 +615,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
warning.getItem().status.filterRevealed = true;
|
warning.getItem().status.filterRevealed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getAccountID(){
|
public String getAccountID(){
|
||||||
return accountID;
|
return accountID;
|
||||||
}
|
}
|
||||||
@@ -648,8 +686,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isScrolledToTop() {
|
public boolean isOnTop() {
|
||||||
return list.getChildAt(0) == null || list.getChildAt(0).getTop() == 0;
|
return isRecyclerViewOnTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getListWidthForMediaLayout(){
|
protected int getListWidthForMediaLayout(){
|
||||||
@@ -716,6 +754,17 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return attachmentViewsPool;
|
return attachmentViewsPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
|
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDataLoaded(List<T> d, boolean more) {
|
||||||
|
super.onDataLoaded(d, more);
|
||||||
|
// more available, but the page isn't even full yet? seems wrong, let's load some more
|
||||||
|
if (more && d.size() < itemsPerPage) preloader.onScrolledToLastItem();
|
||||||
|
}
|
||||||
|
|
||||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
@@ -763,7 +812,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
private int currentMediaHiddenLayoutsWidth=0;
|
private int currentMediaHiddenLayoutsWidth=0;
|
||||||
|
|
||||||
{
|
{
|
||||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.disableDividers ? R.attr.colorWindowBackground : R.attr.colorPollVoted));
|
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.showDividers ? R.attr.colorPollVoted : R.attr.colorWindowBackground));
|
||||||
dividerPaint.setStyle(Paint.Style.STROKE);
|
dividerPaint.setStyle(Paint.Style.STROKE);
|
||||||
dividerPaint.setStrokeWidth(V.dp(1));
|
dividerPaint.setStrokeWidth(V.dp(1));
|
||||||
}
|
}
|
||||||
@@ -777,6 +826,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
|
||||||
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
|
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
|
||||||
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
|
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
|
||||||
|
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) continue;
|
||||||
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
|
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
@@ -35,4 +37,14 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
|
|||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.ACCOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path("/bookmarks").build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import static android.os.ext.SdkExtensions.getExtensionVersion;
|
|
||||||
|
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages;
|
import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages;
|
||||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.DRAFTS_AFTER_INSTANT;
|
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.DRAFTS_AFTER_INSTANT;
|
||||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDraftInstant;
|
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDraftInstant;
|
||||||
@@ -9,6 +7,7 @@ import static org.joinmastodon.android.ui.utils.UiUtils.isPhotoPickerAvailable;
|
|||||||
import static org.joinmastodon.android.utils.MastodonLanguage.allLanguages;
|
import static org.joinmastodon.android.utils.MastodonLanguage.allLanguages;
|
||||||
import static org.joinmastodon.android.utils.MastodonLanguage.defaultRecentLanguages;
|
import static org.joinmastodon.android.utils.MastodonLanguage.defaultRecentLanguages;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -17,6 +16,7 @@ import android.app.TimePickerDialog;
|
|||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@@ -29,7 +29,6 @@ import android.graphics.drawable.LayerDrawable;
|
|||||||
import android.icu.text.BreakIterator;
|
import android.icu.text.BreakIterator;
|
||||||
import android.media.MediaMetadataRetriever;
|
import android.media.MediaMetadataRetriever;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.opengl.Visibility;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
@@ -70,7 +69,10 @@ import android.widget.ScrollView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.github.bottomSoftwareFoundation.bottom.Bottom;
|
import com.github.bottomSoftwareFoundation.bottom.Bottom;
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
@@ -87,13 +89,17 @@ import org.joinmastodon.android.api.requests.statuses.GetAttachmentByID;
|
|||||||
import org.joinmastodon.android.api.requests.statuses.UploadAttachment;
|
import org.joinmastodon.android.api.requests.statuses.UploadAttachment;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.PictureTakenEvent;
|
||||||
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||||
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
|
import org.joinmastodon.android.fragments.settings.SettingsBaseFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
@@ -112,6 +118,7 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
|
|||||||
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
import org.joinmastodon.android.utils.TransferSpeedTracker;
|
import org.joinmastodon.android.utils.TransferSpeedTracker;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ComposeEditText;
|
import org.joinmastodon.android.ui.views.ComposeEditText;
|
||||||
@@ -151,7 +158,7 @@ import me.grishka.appkit.imageloader.ViewImageLoader;
|
|||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener{
|
public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener, HasAccountID {
|
||||||
|
|
||||||
private static final int MEDIA_RESULT=717;
|
private static final int MEDIA_RESULT=717;
|
||||||
private static final int IMAGE_DESCRIPTION_RESULT=363;
|
private static final int IMAGE_DESCRIPTION_RESULT=363;
|
||||||
@@ -160,6 +167,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private static final String GLITCH_LOCAL_ONLY_SUFFIX = "👁";
|
private static final String GLITCH_LOCAL_ONLY_SUFFIX = "👁";
|
||||||
private static final Pattern GLITCH_LOCAL_ONLY_PATTERN = Pattern.compile("[\\s\\S]*" + GLITCH_LOCAL_ONLY_SUFFIX + "[\uFE00-\uFE0F]*");
|
private static final Pattern GLITCH_LOCAL_ONLY_PATTERN = Pattern.compile("[\\s\\S]*" + GLITCH_LOCAL_ONLY_SUFFIX + "[\uFE00-\uFE0F]*");
|
||||||
private static final String TAG="ComposeFragment";
|
private static final String TAG="ComposeFragment";
|
||||||
|
public static final int CAMERA_PERMISSION_CODE = 626938;
|
||||||
|
public static final int CAMERA_PIC_REQUEST_CODE = 6242069;
|
||||||
|
|
||||||
public static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
public static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
@@ -182,8 +191,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private int charCount, charLimit, trimmedCharCount;
|
private int charCount, charLimit, trimmedCharCount;
|
||||||
|
|
||||||
private Button publishButton, languageButton, scheduleTimeBtn, draftsBtn;
|
private Button publishButton, languageButton, scheduleTimeBtn, draftsBtn;
|
||||||
private PopupMenu languagePopup, visibilityPopup, draftOptionsPopup;
|
private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup;
|
||||||
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn, scheduleDraftDismiss;
|
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn, scheduleDraftDismiss, contentTypeBtn;
|
||||||
private ImageView sensitiveIcon;
|
private ImageView sensitiveIcon;
|
||||||
private ComposeMediaLayout attachmentsView;
|
private ComposeMediaLayout attachmentsView;
|
||||||
private TextView replyText;
|
private TextView replyText;
|
||||||
@@ -237,6 +246,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private Runnable updateUploadEtaRunnable;
|
private Runnable updateUploadEtaRunnable;
|
||||||
|
|
||||||
private String language, encoding;
|
private String language, encoding;
|
||||||
|
private ContentType contentType;
|
||||||
private MastodonLanguage.LanguageResolver languageResolver;
|
private MastodonLanguage.LanguageResolver languageResolver;
|
||||||
|
|
||||||
private int navigationBarColorBefore;
|
private int navigationBarColorBefore;
|
||||||
@@ -244,11 +254,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
E.register(this);
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
navigationBarColorBefore = getActivity().getWindow().getNavigationBarColor();
|
navigationBarColorBefore = getActivity().getWindow().getNavigationBarColor();
|
||||||
getActivity().getWindow().setNavigationBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLightest));
|
getActivity().getWindow().setNavigationBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLightest));
|
||||||
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
|
contentType = GlobalUserPreferences.accountsDefaultContentTypes.get(accountID);
|
||||||
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
self=session.self;
|
self=session.self;
|
||||||
instanceDomain=session.domain;
|
instanceDomain=session.domain;
|
||||||
@@ -266,9 +279,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
Nav.finish(this);
|
Nav.finish(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(customEmojis.isEmpty()){
|
|
||||||
AccountSessionManager.getInstance().updateInstanceInfo(instanceDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
Bundle bundle = savedInstanceState != null ? savedInstanceState : getArguments();
|
Bundle bundle = savedInstanceState != null ? savedInstanceState : getArguments();
|
||||||
if (bundle.containsKey("scheduledStatus")) scheduledStatus=Parcels.unwrap(bundle.getParcelable("scheduledStatus"));
|
if (bundle.containsKey("scheduledStatus")) scheduledStatus=Parcels.unwrap(bundle.getParcelable("scheduledStatus"));
|
||||||
@@ -285,6 +295,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroy(){
|
public void onDestroy(){
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
E.unregister(this);
|
||||||
for(DraftMediaAttachment att:attachments){
|
for(DraftMediaAttachment att:attachments){
|
||||||
if(att.isUploadingOrProcessing())
|
if(att.isUploadingOrProcessing())
|
||||||
att.cancelUpload();
|
att.cancelUpload();
|
||||||
@@ -353,6 +364,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
emojiBtn=view.findViewById(R.id.btn_emoji);
|
emojiBtn=view.findViewById(R.id.btn_emoji);
|
||||||
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
||||||
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
||||||
|
contentTypeBtn=view.findViewById(R.id.btn_content_type);
|
||||||
scheduleDraftView=view.findViewById(R.id.schedule_draft_view);
|
scheduleDraftView=view.findViewById(R.id.schedule_draft_view);
|
||||||
scheduleDraftText=view.findViewById(R.id.schedule_draft_text);
|
scheduleDraftText=view.findViewById(R.id.schedule_draft_text);
|
||||||
scheduleDraftDismiss=view.findViewById(R.id.schedule_draft_dismiss);
|
scheduleDraftDismiss=view.findViewById(R.id.schedule_draft_dismiss);
|
||||||
@@ -363,19 +375,29 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text_below : R.id.reply_text)
|
view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text_below : R.id.reply_text)
|
||||||
.setVisibility(View.GONE);
|
.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (isPhotoPickerAvailable()) {
|
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
|
||||||
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
|
attachPopup.inflate(R.menu.attach);
|
||||||
attachPopup.inflate(R.menu.attach);
|
if(isPhotoPickerAvailable())
|
||||||
attachPopup.setOnMenuItemClickListener(i -> {
|
attachPopup.getMenu().findItem(R.id.media).setVisible(true);
|
||||||
|
|
||||||
|
attachPopup.setOnMenuItemClickListener(i -> {
|
||||||
|
if (i.getItemId() == R.id.camera){
|
||||||
|
if (getContext().checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST_CODE);
|
||||||
|
} else {
|
||||||
|
getActivity().requestPermissions(new String[] { Manifest.permission.CAMERA }, CAMERA_PERMISSION_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
openFilePicker(i.getItemId() == R.id.media);
|
openFilePicker(i.getItemId() == R.id.media);
|
||||||
return true;
|
}
|
||||||
});
|
return true;
|
||||||
UiUtils.enablePopupMenuIcons(getContext(), attachPopup);
|
});
|
||||||
mediaBtn.setOnClickListener(v->attachPopup.show());
|
UiUtils.enablePopupMenuIcons(getContext(), attachPopup);
|
||||||
mediaBtn.setOnTouchListener(attachPopup.getDragToOpenListener());
|
mediaBtn.setOnClickListener(v->attachPopup.show());
|
||||||
} else {
|
mediaBtn.setOnTouchListener(attachPopup.getDragToOpenListener());
|
||||||
mediaBtn.setOnClickListener(v -> openFilePicker(false));
|
if (isInstancePixelfed()) pollBtn.setVisibility(View.GONE);
|
||||||
}
|
|
||||||
pollBtn.setOnClickListener(v->togglePoll());
|
pollBtn.setOnClickListener(v->togglePoll());
|
||||||
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
||||||
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
||||||
@@ -387,6 +409,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
visibilityBtn.setOnClickListener(v->visibilityPopup.show());
|
visibilityBtn.setOnClickListener(v->visibilityPopup.show());
|
||||||
visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener());
|
visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener());
|
||||||
|
|
||||||
|
buildContentTypePopup(contentTypeBtn);
|
||||||
|
contentTypeBtn.setOnClickListener(v->contentTypePopup.show());
|
||||||
|
contentTypeBtn.setOnTouchListener(contentTypePopup.getDragToOpenListener());
|
||||||
|
|
||||||
scheduleDraftDismiss.setOnClickListener(v->updateScheduledAt(null));
|
scheduleDraftDismiss.setOnClickListener(v->updateScheduledAt(null));
|
||||||
scheduleTimeBtn.setOnClickListener(v->pickScheduledDateTime());
|
scheduleTimeBtn.setOnClickListener(v->pickScheduledDateTime());
|
||||||
|
|
||||||
@@ -489,8 +515,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(editingStatus!=null && editingStatus.visibility!=null) {
|
if (savedInstanceState != null) {
|
||||||
statusVisibility=editingStatus.visibility;
|
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||||
|
} else if (editingStatus != null && editingStatus.visibility != null) {
|
||||||
|
statusVisibility = editingStatus.visibility;
|
||||||
} else {
|
} else {
|
||||||
loadDefaultStatusVisibility(savedInstanceState);
|
loadDefaultStatusVisibility(savedInstanceState);
|
||||||
}
|
}
|
||||||
@@ -505,6 +533,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}).setChecked(true);
|
}).setChecked(true);
|
||||||
visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly);
|
visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly);
|
||||||
|
|
||||||
|
|
||||||
|
if (savedInstanceState != null && savedInstanceState.containsKey("contentType")) {
|
||||||
|
contentType = (ContentType) savedInstanceState.getSerializable("contentType");
|
||||||
|
} else if (getArguments().containsKey("sourceContentType")) {
|
||||||
|
try {
|
||||||
|
String val = getArguments().getString("sourceContentType");
|
||||||
|
contentType = val == null ? null : ContentType.valueOf(val);
|
||||||
|
} catch (IllegalArgumentException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
int contentTypeId = ContentType.getContentTypeRes(contentType);
|
||||||
|
contentTypePopup.getMenu().findItem(contentTypeId).setChecked(true);
|
||||||
|
contentTypeBtn.setSelected(contentTypeId != R.id.content_type_null && contentTypeId != R.id.content_type_plain);
|
||||||
|
|
||||||
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
|
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
|
||||||
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
|
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
|
||||||
View autocompleteView=autocompleteViewController.getView();
|
View autocompleteView=autocompleteViewController.getView();
|
||||||
@@ -541,10 +583,24 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
outState.putParcelableArrayList("attachments", serializedAttachments);
|
outState.putParcelableArrayList("attachments", serializedAttachments);
|
||||||
}
|
}
|
||||||
outState.putSerializable("visibility", statusVisibility);
|
outState.putSerializable("visibility", statusVisibility);
|
||||||
|
outState.putSerializable("contentType", contentType);
|
||||||
if (scheduledAt != null) outState.putSerializable("scheduledAt", scheduledAt);
|
if (scheduledAt != null) outState.putSerializable("scheduledAt", scheduledAt);
|
||||||
if (scheduledStatus != null) outState.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
|
if (scheduledStatus != null) outState.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
|
||||||
|
if (requestCode == CAMERA_PERMISSION_CODE && (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||||
|
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST_CODE);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), R.string.permission_required, Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume(){
|
public void onResume(){
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -579,8 +635,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s){
|
public void afterTextChanged(Editable s){
|
||||||
if(s.length()==0)
|
if(s.length()==0){
|
||||||
|
updateCharCounter();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
int start=lastChangeStart;
|
int start=lastChangeStart;
|
||||||
int count=lastChangeCount;
|
int count=lastChangeCount;
|
||||||
// offset one char back to catch an already typed '@' or '#' or ':'
|
// offset one char back to catch an already typed '@' or '#' or ':'
|
||||||
@@ -863,12 +921,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
|
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
|
||||||
buildLanguageSelector(languageButton);
|
buildLanguageSelector(languageButton);
|
||||||
|
|
||||||
if (editingStatus != null && scheduledStatus == null) {
|
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
|
||||||
|
if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) {
|
||||||
// editing an already published post
|
// editing an already published post
|
||||||
draftsBtn.setVisibility(View.GONE);
|
draftsBtn.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccountID() {
|
||||||
|
return accountID;
|
||||||
|
}
|
||||||
|
|
||||||
private void navigateToUnsentPosts() {
|
private void navigateToUnsentPosts() {
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -948,6 +1012,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getContentTypeName(String id) {
|
||||||
|
return switch (id) {
|
||||||
|
case "text/plain" -> R.string.sk_content_type_plain;
|
||||||
|
case "text/html" -> R.string.sk_content_type_html;
|
||||||
|
case "text/markdown" -> R.string.sk_content_type_markdown;
|
||||||
|
case "text/bbcode" -> R.string.sk_content_type_bbcode;
|
||||||
|
case "text/x.misskeymarkdown" -> R.string.sk_content_type_mfm;
|
||||||
|
default -> throw new IllegalArgumentException("Invalid content type");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void addBottomLanguage(Menu menu) {
|
private void addBottomLanguage(Menu menu) {
|
||||||
if (menu.findItem(allLanguages.size()) == null) {
|
if (menu.findItem(allLanguages.size()) == null) {
|
||||||
menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)");
|
menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)");
|
||||||
@@ -1017,8 +1092,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
if(att.state!=AttachmentUploadState.DONE)
|
if(att.state!=AttachmentUploadState.DONE)
|
||||||
nonDoneAttachmentCount++;
|
nonDoneAttachmentCount++;
|
||||||
}
|
}
|
||||||
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
publishButton.setEnabled((!isInstancePixelfed() || attachments.size() > 0) && (trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
||||||
// sendError.setVisibility(View.GONE);
|
sendError.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCustomEmojiClick(Emoji emoji){
|
private void onCustomEmojiClick(Emoji emoji){
|
||||||
@@ -1036,18 +1111,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onPublishClick(View v){
|
private void onPublishClick(View v){
|
||||||
if (!attachments.isEmpty()
|
publish();
|
||||||
&& statusVisibility != StatusPrivacy.DIRECT
|
|
||||||
&& !attachments.stream().allMatch(attachment -> attachment.description != null && !attachment.description.isBlank())) {
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.mo_no_image_desc_title)
|
|
||||||
.setMessage(R.string.mo_no_image_desc)
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.publish, (dialog, i)-> publish())
|
|
||||||
.show();
|
|
||||||
} else {
|
|
||||||
publish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publishErrorCallback(ErrorResponse error) {
|
private void publishErrorCallback(ErrorResponse error) {
|
||||||
@@ -1105,9 +1169,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
req.status=text;
|
req.status=text;
|
||||||
req.localOnly=localOnly;
|
req.localOnly=localOnly;
|
||||||
req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility;
|
req.visibility=localOnly && instance.isAkkoma() ? StatusPrivacy.LOCAL : statusVisibility;
|
||||||
req.sensitive=sensitive;
|
req.sensitive=sensitive;
|
||||||
req.language=language;
|
req.language=language;
|
||||||
|
req.contentType=contentType;
|
||||||
req.scheduledAt = scheduledAt;
|
req.scheduledAt = scheduledAt;
|
||||||
if(!attachments.isEmpty()){
|
if(!attachments.isEmpty()){
|
||||||
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
|
||||||
@@ -1169,7 +1234,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
sendProgress.setVisibility(View.VISIBLE);
|
sendProgress.setVisibility(View.VISIBLE);
|
||||||
sendError.setVisibility(View.GONE);
|
sendError.setVisibility(View.GONE);
|
||||||
|
|
||||||
Callback<Status> resCallback=new Callback<>(){
|
Callback<Status> resCallback = new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
maybeDeleteScheduledPost(() -> {
|
maybeDeleteScheduledPost(() -> {
|
||||||
@@ -1182,7 +1247,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
E.post(new StatusCountersUpdatedEvent(replyTo));
|
E.post(new StatusCountersUpdatedEvent(replyTo));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
E.post(new StatusUpdatedEvent(result));
|
// pixelfed doesn't return the edited status :/
|
||||||
|
Status editedStatus = result == null ? editingStatus : result;
|
||||||
|
if (result == null) {
|
||||||
|
editedStatus.text = req.status;
|
||||||
|
editedStatus.spoilerText = req.spoilerText;
|
||||||
|
editedStatus.sensitive = req.sensitive;
|
||||||
|
editedStatus.language = req.language;
|
||||||
|
// user will have to reload to see html
|
||||||
|
editedStatus.content = req.status;
|
||||||
|
}
|
||||||
|
E.post(new StatusUpdatedEvent(editedStatus));
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
|
||||||
Nav.finish(ComposeFragment.this);
|
Nav.finish(ComposeFragment.this);
|
||||||
@@ -1203,7 +1278,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if(editingStatus!=null && !redraftStatus){
|
if(editingStatus!=null && !redraftStatus){
|
||||||
new EditStatus(req, editingStatus.id)
|
new EditStatus(req, editingStatus.id)
|
||||||
.setCallback(resCallback)
|
.setCallback(resCallback)
|
||||||
@@ -1385,9 +1459,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode==Activity.RESULT_OK){
|
||||||
|
Bitmap image = (Bitmap) data.getExtras().get("data");
|
||||||
|
String path = MediaStore.Images.Media.insertImage(getContext().getContentResolver(), image, null, null);
|
||||||
|
addMediaAttachment(Uri.parse(path), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPictureTaken(PictureTakenEvent ev){
|
||||||
|
addMediaAttachment(ev.uri, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StringFormatInvalid")
|
|
||||||
private boolean addMediaAttachment(Uri uri, String description){
|
private boolean addMediaAttachment(Uri uri, String description){
|
||||||
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){
|
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){
|
||||||
showMediaAttachmentError(getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS));
|
showMediaAttachmentError(getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS));
|
||||||
@@ -1511,7 +1595,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
private void uploadMediaAttachment(DraftMediaAttachment attachment){
|
private void uploadMediaAttachment(DraftMediaAttachment attachment){
|
||||||
if(areThereAnyUploadingAttachments()){
|
if(areThereAnyUploadingAttachments()){
|
||||||
throw new IllegalStateException("there is already an attachment being uploaded");
|
throw new IllegalStateException("there is already an attachment being uploaded");
|
||||||
}
|
}
|
||||||
attachment.state=AttachmentUploadState.UPLOADING;
|
attachment.state=AttachmentUploadState.UPLOADING;
|
||||||
attachment.progressBar.setVisibility(View.VISIBLE);
|
attachment.progressBar.setVisibility(View.VISIBLE);
|
||||||
@@ -1710,6 +1794,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
|
||||||
if(att.serverAttachment==null)
|
if(att.serverAttachment==null)
|
||||||
return;
|
return;
|
||||||
|
editMediaDescription(att);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editMediaDescription(DraftMediaAttachment att) {
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putString("attachment", att.serverAttachment.id);
|
args.putString("attachment", att.serverAttachment.id);
|
||||||
@@ -1758,11 +1846,24 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
pollChanged=true;
|
pollChanged=true;
|
||||||
updatePublishButtonState();
|
updatePublishButtonState();
|
||||||
}));
|
}));
|
||||||
option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0 ? instance.configuration.polls.maxCharactersPerOption : 50)});
|
|
||||||
|
int maxCharactersPerOption = 50;
|
||||||
|
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
|
||||||
|
maxCharactersPerOption = instance.configuration.polls.maxCharactersPerOption;
|
||||||
|
else if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
|
||||||
|
maxCharactersPerOption = instance.pollLimits.maxOptionChars;
|
||||||
|
option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxCharactersPerOption)});
|
||||||
|
|
||||||
pollOptionsView.addView(option.view);
|
pollOptionsView.addView(option.view);
|
||||||
pollOptions.add(option);
|
pollOptions.add(option);
|
||||||
if(pollOptions.size()==(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0 ? instance.configuration.polls.maxOptions : 4))
|
|
||||||
|
int maxPollOptions = 4;
|
||||||
|
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
|
||||||
|
maxPollOptions = instance.configuration.polls.maxOptions;
|
||||||
|
else if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
|
||||||
|
maxPollOptions = instance.pollLimits.maxOptions;
|
||||||
|
|
||||||
|
if(pollOptions.size()==maxPollOptions)
|
||||||
addPollOptionBtn.setVisibility(View.GONE);
|
addPollOptionBtn.setVisibility(View.GONE);
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
@@ -1917,9 +2018,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
visibilityPopup=new PopupMenu(getActivity(), v);
|
visibilityPopup=new PopupMenu(getActivity(), v);
|
||||||
visibilityPopup.inflate(R.menu.compose_visibility);
|
visibilityPopup.inflate(R.menu.compose_visibility);
|
||||||
Menu m=visibilityPopup.getMenu();
|
Menu m=visibilityPopup.getMenu();
|
||||||
|
if (isInstancePixelfed()) {
|
||||||
|
m.findItem(R.id.vis_private).setVisible(false);
|
||||||
|
}
|
||||||
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
|
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
|
||||||
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||||
if (instance.pleroma != null) {
|
if (isInstanceAkkoma()) {
|
||||||
m.findItem(R.id.vis_local).setVisible(true);
|
m.findItem(R.id.vis_local).setVisible(true);
|
||||||
} else if (localOnly || prefsSaysSupported) {
|
} else if (localOnly || prefsSaysSupported) {
|
||||||
localOnlyItem.setVisible(true);
|
localOnlyItem.setVisible(true);
|
||||||
@@ -1963,16 +2067,38 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private void buildContentTypePopup(View btn) {
|
||||||
|
contentTypePopup=new PopupMenu(getActivity(), btn);
|
||||||
|
contentTypePopup.inflate(R.menu.compose_content_type);
|
||||||
|
Menu m = contentTypePopup.getMenu();
|
||||||
|
ContentType.adaptMenuToInstance(m, instance);
|
||||||
|
if (contentType != null) m.findItem(R.id.content_type_null).setVisible(false);
|
||||||
|
|
||||||
|
contentTypePopup.setOnMenuItemClickListener(i->{
|
||||||
|
int id=i.getItemId();
|
||||||
|
if (id == R.id.content_type_null) contentType = null;
|
||||||
|
else if (id == R.id.content_type_plain) contentType = ContentType.PLAIN;
|
||||||
|
else if (id == R.id.content_type_html) contentType = ContentType.HTML;
|
||||||
|
else if (id == R.id.content_type_markdown) contentType = ContentType.MARKDOWN;
|
||||||
|
else if (id == R.id.content_type_bbcode) contentType = ContentType.BBCODE;
|
||||||
|
else if (id == R.id.content_type_misskey_markdown) contentType = ContentType.MISSKEY_MARKDOWN;
|
||||||
|
else return false;
|
||||||
|
btn.setSelected(id != R.id.content_type_null && id != R.id.content_type_plain);
|
||||||
|
i.setChecked(true);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID)) {
|
||||||
|
btn.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
||||||
if(replyTo != null) {
|
if(replyTo != null) {
|
||||||
statusVisibility = (replyTo.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : replyTo.visibility);
|
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){
|
|
||||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountSessionManager asm = AccountSessionManager.getInstance();
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
Preferences prefs = asm.getAccount(accountID).preferences;
|
Preferences prefs = asm.getAccount(accountID).preferences;
|
||||||
if (prefs != null) {
|
if (prefs != null) {
|
||||||
@@ -2121,14 +2247,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private void editMediaDescription(DraftMediaAttachment att) {
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putString("attachment", att.serverAttachment.id);
|
|
||||||
args.putParcelable("uri", att.uri);
|
|
||||||
args.putString("existingDescription", att.description);
|
|
||||||
Nav.goForResult(getActivity(), ComposeImageDescriptionFragment.class, args, IMAGE_DESCRIPTION_RESULT, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence getTitle(){
|
public CharSequence getTitle(){
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.joinmastodon.android.DomainManager;
|
import org.joinmastodon.android.DomainManager;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -14,7 +16,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class CustomLocalTimelineFragment extends StatusListFragment {
|
public class CustomLocalTimelineFragment extends StatusListFragment implements ProvidesAssistContent.ProvidesWebUri {
|
||||||
// private String name;
|
// private String name;
|
||||||
private String domain;
|
private String domain;
|
||||||
|
|
||||||
@@ -56,7 +58,7 @@ public class CustomLocalTimelineFragment extends StatusListFragment {
|
|||||||
result.stream().forEach(status -> {
|
result.stream().forEach(status -> {
|
||||||
status.account.acct += "@"+domain;
|
status.account.acct += "@"+domain;
|
||||||
status.mentions.forEach(mention -> mention.id = null);
|
status.mentions.forEach(mention -> mention.id = null);
|
||||||
status.reloadWhenClicked = true;
|
status.isRemote = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
@@ -71,4 +73,14 @@ public class CustomLocalTimelineFragment extends StatusListFragment {
|
|||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return Uri.parse(domain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,12 @@ import org.joinmastodon.android.GlobalUserPreferences;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.CustomLocalTimeline;
|
import org.joinmastodon.android.model.CustomLocalTimeline;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
@@ -196,7 +199,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
|||||||
makeBackItem(listsMenu);
|
makeBackItem(listsMenu);
|
||||||
makeBackItem(hashtagsMenu);
|
makeBackItem(hashtagsMenu);
|
||||||
|
|
||||||
TimelineDefinition.ALL_TIMELINES.forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
TimelineDefinition.getAllTimelines(accountID).forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
||||||
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
||||||
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
||||||
|
|
||||||
@@ -222,7 +225,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES), false);
|
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)), false);
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,11 +239,6 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isScrolledToTop() {
|
|
||||||
return list.getChildAt(0).getTop() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
|
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
|
||||||
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
@@ -35,4 +37,16 @@ public class FavoritedStatusListFragment extends StatusListFragment{
|
|||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.ACCOUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.encodedPath(isInstanceAkkoma()
|
||||||
|
? '/' + getSession().self.username + "#favorites"
|
||||||
|
: "/favourites").build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -24,6 +25,7 @@ import org.joinmastodon.android.ui.text.HtmlParser;
|
|||||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -46,7 +48,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
|
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
private GetAccountRelationships relationshipsRequest;
|
private GetAccountRelationships relationshipsRequest;
|
||||||
@@ -149,8 +151,13 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isScrolledToTop() {
|
public String getAccountID() {
|
||||||
return list.getChildAt(0).getTop() == 0;
|
return accountID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path(isInstanceAkkoma() ? "/friend-requests" : "/follow_requests").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -14,14 +15,15 @@ import org.joinmastodon.android.model.Hashtag;
|
|||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop {
|
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||||
private String nextMaxID;
|
private String nextMaxID;
|
||||||
private String accountId;
|
private String accountID;
|
||||||
|
|
||||||
public FollowedHashtagsFragment() {
|
public FollowedHashtagsFragment() {
|
||||||
super(20);
|
super(20);
|
||||||
@@ -31,7 +33,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
|||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Bundle args=getArguments();
|
Bundle args=getArguments();
|
||||||
accountId=args.getString("account");
|
accountID=args.getString("account");
|
||||||
setTitle(R.string.sk_hashtags_you_follow);
|
setTitle(R.string.sk_hashtags_you_follow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
|||||||
onDataLoaded(result, nextMaxID!=null);
|
onDataLoaded(result, nextMaxID!=null);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountId);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,14 +72,20 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
|||||||
return new HashtagsAdapter();
|
return new HashtagsAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop() {
|
public void scrollToTop() {
|
||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isScrolledToTop() {
|
public String getAccountID() {
|
||||||
return list.getChildAt(0).getTop() == 0;
|
return accountID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return isInstanceAkkoma() ? null : base.path("/followed_tags").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||||
@@ -114,7 +122,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountId, item.name, item.following);
|
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface HasAccountID {
|
||||||
|
String getAccountID();
|
||||||
|
|
||||||
|
default AccountSession getSession() {
|
||||||
|
return AccountSessionManager.getInstance().getAccount(getAccountID());
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isInstanceAkkoma() {
|
||||||
|
return getInstance().map(Instance::isAkkoma).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isInstancePixelfed() {
|
||||||
|
return getInstance().map(Instance::isPixelfed).orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
default Optional<Instance> getInstance() {
|
||||||
|
return getSession().getInstance();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,4 +4,7 @@ import android.view.View;
|
|||||||
|
|
||||||
public interface HasFab {
|
public interface HasFab {
|
||||||
View getFab();
|
View getFab();
|
||||||
|
void showFab();
|
||||||
|
void hideFab();
|
||||||
|
boolean isScrolling();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -8,7 +9,6 @@ import android.view.MenuInflater;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.DomainManager;
|
import org.joinmastodon.android.DomainManager;
|
||||||
@@ -131,7 +131,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -162,4 +162,14 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
protected void onSetFabBottomInset(int inset){
|
protected void onSetFabBottomInset(int inset){
|
||||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.PUBLIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -20,6 +21,8 @@ import android.widget.LinearLayout;
|
|||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.DomainManager;
|
import org.joinmastodon.android.DomainManager;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
@@ -40,16 +43,13 @@ import org.joinmastodon.android.model.Notification;
|
|||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.TabBar;
|
import org.joinmastodon.android.ui.views.TabBar;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import androidx.annotation.IdRes;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
@@ -63,11 +63,9 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
|
||||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
public class HomeFragment extends AppKitFragment implements OnBackPressedListener, ProvidesAssistContent, HasAccountID {
|
||||||
private FragmentRootLinearLayout content;
|
private FragmentRootLinearLayout content;
|
||||||
|
|
||||||
private HomeTabFragment homeTabFragment;
|
private HomeTabFragment homeTabFragment;
|
||||||
|
|
||||||
private NotificationsFragment notificationsFragment;
|
private NotificationsFragment notificationsFragment;
|
||||||
private DiscoverFragment searchFragment;
|
private DiscoverFragment searchFragment;
|
||||||
private ProfileFragment profileFragment;
|
private ProfileFragment profileFragment;
|
||||||
@@ -79,6 +77,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
private int currentTab=R.id.tab_home;
|
private int currentTab=R.id.tab_home;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
private boolean isPleroma;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -86,18 +85,21 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
E.register(this);
|
E.register(this);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
setTitle(R.string.mo_app_name);
|
setTitle(R.string.mo_app_name);
|
||||||
|
isPleroma = AccountSessionManager.getInstance().getAccount(accountID).getInstance()
|
||||||
|
.map(Instance::isAkkoma)
|
||||||
|
.orElse(false);
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
|
||||||
|
// TODO: clean up
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
|
|
||||||
homeTabFragment=new HomeTabFragment();
|
homeTabFragment=new HomeTabFragment();
|
||||||
homeTabFragment.setArguments(args);
|
homeTabFragment.setArguments(args);
|
||||||
|
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
|
args.putBoolean("disableDiscover", isPleroma);
|
||||||
args.putBoolean("noAutoLoad", true);
|
args.putBoolean("noAutoLoad", true);
|
||||||
searchFragment=new DiscoverFragment();
|
searchFragment=new DiscoverFragment();
|
||||||
searchFragment.setArguments(args);
|
searchFragment.setArguments(args);
|
||||||
@@ -149,7 +151,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
|
|
||||||
String defaultTab=getArguments().getString("tab");
|
String defaultTab=getArguments().getString("tab");
|
||||||
if("notifications".equals(defaultTab)){
|
if("notifications".equals(defaultTab)){
|
||||||
tabBar.selectTab(R.id.tab_notifications);
|
tabBar.selectTab(R.id.tab_notifications);
|
||||||
@@ -170,18 +171,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
@Override
|
@Override
|
||||||
public void onViewStateRestored(Bundle savedInstanceState){
|
public void onViewStateRestored(Bundle savedInstanceState){
|
||||||
super.onViewStateRestored(savedInstanceState);
|
super.onViewStateRestored(savedInstanceState);
|
||||||
|
|
||||||
if(savedInstanceState==null) return;
|
if(savedInstanceState==null) return;
|
||||||
|
|
||||||
|
|
||||||
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
|
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
|
||||||
|
|
||||||
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
||||||
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
||||||
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
||||||
currentTab=savedInstanceState.getInt("selectedTab");
|
currentTab=savedInstanceState.getInt("selectedTab");
|
||||||
|
tabBar.selectTab(currentTab);
|
||||||
Fragment current=fragmentForTab(currentTab);
|
Fragment current=fragmentForTab(currentTab);
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.hide(homeTabFragment)
|
.hide(homeTabFragment)
|
||||||
.hide(searchFragment)
|
.hide(searchFragment)
|
||||||
@@ -189,15 +186,12 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
.hide(profileFragment)
|
.hide(profileFragment)
|
||||||
.show(current)
|
.show(current)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
maybeTriggerLoading(current);
|
maybeTriggerLoading(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHiddenChanged(boolean hidden){
|
public void onHiddenChanged(boolean hidden){
|
||||||
super.onHiddenChanged(hidden);
|
super.onHiddenChanged(hidden);
|
||||||
if (!hidden && fragmentForTab(currentTab) instanceof DomainDisplay display)
|
|
||||||
DomainManager.getInstance().setCurrentDomain(display.getDomain());
|
|
||||||
fragmentForTab(currentTab).onHiddenChanged(hidden);
|
fragmentForTab(currentTab).onHiddenChanged(hidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,9 +215,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
}
|
}
|
||||||
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
||||||
|
|
||||||
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
|
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
|
|
||||||
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
@@ -242,33 +234,28 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCurrentTab(@IdRes int tab){
|
||||||
|
if(tab==currentTab)
|
||||||
|
return;
|
||||||
|
tabBar.selectTab(tab);
|
||||||
|
onTabSelected(tab);
|
||||||
|
}
|
||||||
|
|
||||||
private void onTabSelected(@IdRes int tab){
|
private void onTabSelected(@IdRes int tab){
|
||||||
Fragment newFragment=fragmentForTab(tab);
|
Fragment newFragment=fragmentForTab(tab);
|
||||||
if(tab==currentTab){
|
if(tab==currentTab){
|
||||||
if(tab == R.id.tab_search){
|
if (tab == R.id.tab_search)
|
||||||
if(newFragment instanceof ScrollableToTop scrollable)
|
searchFragment.onSelect();
|
||||||
scrollable.scrollToTop();
|
else if(newFragment instanceof ScrollableToTop scrollable)
|
||||||
searchFragment.selectSearch();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(newFragment instanceof ScrollableToTop scrollable)
|
|
||||||
scrollable.scrollToTop();
|
scrollable.scrollToTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(tab==currentTab && tab == R.id.tab_search){
|
|
||||||
if(newFragment instanceof ScrollableToTop scrollable)
|
|
||||||
scrollable.scrollToTop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newFragment instanceof DomainDisplay display) {
|
|
||||||
DomainManager.getInstance().setCurrentDomain(display.getDomain());
|
|
||||||
}
|
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
|
||||||
maybeTriggerLoading(newFragment);
|
maybeTriggerLoading(newFragment);
|
||||||
|
if (newFragment instanceof HasFab fabulous && !fabulous.isScrolling()) fabulous.showFab();
|
||||||
currentTab=tab;
|
currentTab=tab;
|
||||||
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
|
||||||
|
if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeTriggerLoading(Fragment newFragment){
|
private void maybeTriggerLoading(Fragment newFragment){
|
||||||
@@ -295,10 +282,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||||
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
||||||
}
|
}
|
||||||
new AccountSwitcherSheet(getActivity(), true, true, false, accountSession -> {
|
new AccountSwitcherSheet(getActivity(), this).show();
|
||||||
getActivity().finish();
|
|
||||||
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
|
|
||||||
}).show();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(tab==R.id.tab_search){
|
if(tab==R.id.tab_search){
|
||||||
@@ -334,7 +318,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
public void onSaveInstanceState(Bundle outState){
|
public void onSaveInstanceState(Bundle outState){
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt("selectedTab", currentTab);
|
outState.putInt("selectedTab", currentTab);
|
||||||
|
|
||||||
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
|
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
|
||||||
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||||
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||||
@@ -343,10 +326,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
|
|
||||||
public void updateNotificationBadge() {
|
public void updateNotificationBadge() {
|
||||||
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
Optional<Instance> instance = session.getInstance();
|
||||||
if (instance == null) return;
|
if (instance.isEmpty()) return; // avoiding incompatibility with akkoma
|
||||||
|
|
||||||
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.pleroma != null)
|
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isAkkoma())
|
||||||
.setCallback(new Callback<>() {
|
.setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Notification> notifications) {
|
public void onSuccess(List<Notification> notifications) {
|
||||||
@@ -354,9 +337,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
try {
|
try {
|
||||||
long newestId = Long.parseLong(notifications.get(0).id);
|
long newestId = Long.parseLong(notifications.get(0).id);
|
||||||
long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId);
|
long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId);
|
||||||
System.out.println("NEWEST: " + newestId);
|
|
||||||
System.out.println("LAST SEEN: " + lastSeenId);
|
|
||||||
|
|
||||||
setNotificationBadge(newestId > lastSeenId);
|
setNotificationBadge(newestId > lastSeenId);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
setNotificationBadge(false);
|
setNotificationBadge(false);
|
||||||
@@ -370,6 +350,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNotificationBadge(boolean badge) {
|
public void setNotificationBadge(boolean badge) {
|
||||||
notificationTabIcon.setImageResource(badge
|
notificationTabIcon.setImageResource(badge
|
||||||
? R.drawable.ic_fluent_alert_28_selector_badged
|
? R.drawable.ic_fluent_alert_28_selector_badged
|
||||||
@@ -385,4 +366,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
|
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
|
||||||
setNotificationBadge(false);
|
setNotificationBadge(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccountID() {
|
||||||
|
return accountID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
|
callFragmentToProvideAssistContent(fragmentForTab(currentTab), assistContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.reduceMotion;
|
import static org.joinmastodon.android.GlobalUserPreferences.reduceMotion;
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.showNewPostsButton;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.AnimatorListenerAdapter;
|
import android.animation.AnimatorListenerAdapter;
|
||||||
@@ -11,6 +10,7 @@ import android.annotation.SuppressLint;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.FragmentTransaction;
|
import android.app.FragmentTransaction;
|
||||||
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -48,6 +48,7 @@ import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
|||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
|
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
|
||||||
import org.joinmastodon.android.model.Announcement;
|
import org.joinmastodon.android.model.Announcement;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
@@ -56,6 +57,7 @@ import org.joinmastodon.android.model.TimelineDefinition;
|
|||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -73,7 +75,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
|
|||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay, HasFab {
|
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, HasFab, ProvidesAssistContent {
|
||||||
private static final int ANNOUNCEMENTS_RESULT = 654;
|
private static final int ANNOUNCEMENTS_RESULT = 654;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
@@ -108,7 +110,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
E.register(this);
|
E.register(this);
|
||||||
accountID = getArguments().getString("account");
|
accountID = getArguments().getString("account");
|
||||||
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES);
|
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID));
|
||||||
assert timelineDefinitions != null;
|
assert timelineDefinitions != null;
|
||||||
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
|
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
|
||||||
count = timelineDefinitions.size();
|
count = timelineDefinitions.size();
|
||||||
@@ -209,10 +211,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
if (fragments[position] instanceof BaseRecyclerFragment<?> page){
|
if (fragments[position] instanceof BaseRecyclerFragment<?> page){
|
||||||
if(!page.loaded && !page.isDataLoading()) page.loadData();
|
if(!page.loaded && !page.isDataLoading()) page.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
//update recent app list url
|
|
||||||
if (fragments[position] instanceof DomainDisplay page)
|
|
||||||
DomainManager.getInstance().setCurrentDomain(page.getDomain());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -297,14 +295,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDomain() {
|
|
||||||
if (fragments[pager.getCurrentItem()] instanceof DomainDisplay page) {
|
|
||||||
return page.getDomain();
|
|
||||||
}
|
|
||||||
return DomainDisplay.super.getDomain();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
|
||||||
l.onFabClick(v);
|
l.onFabClick(v);
|
||||||
@@ -466,10 +456,26 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
updateSwitcherIcon(i);
|
updateSwitcherIcon(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showFab() {
|
||||||
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.showFab();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideFab() {
|
||||||
|
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.hideFab();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScrolling() {
|
||||||
|
return (fragments[pager.getCurrentItem()] instanceof HasFab fabulous)
|
||||||
|
&& fabulous.isScrolling();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSwitcherIcon(int i) {
|
private void updateSwitcherIcon(int i) {
|
||||||
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
||||||
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
||||||
if (fragments[i] instanceof BaseStatusListFragment<?> l) l.animateFab(true);
|
showFab();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -484,7 +490,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
getToolbar().post(() -> overflowPopup.show());
|
getToolbar().post(() -> overflowPopup.show());
|
||||||
return true;
|
return true;
|
||||||
} else if (id == R.id.settings || id == R.id.settings_action) {
|
} 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) {
|
} else if (id == R.id.announcements || id == R.id.announcements_action) {
|
||||||
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
||||||
} else if (id == R.id.edit_timelines) {
|
} else if (id == R.id.edit_timelines) {
|
||||||
@@ -504,8 +510,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop(){
|
||||||
if (((ScrollableToTop) fragments[pager.getCurrentItem()]).isScrolledToTop() &&
|
if (((IsOnTop) fragments[pager.getCurrentItem()]).isOnTop() &&
|
||||||
!GlobalUserPreferences.disableDoubleTapToSwipe && !newPostsBtnShown) {
|
GlobalUserPreferences.doubleTapToSwipe && !newPostsBtnShown) {
|
||||||
int nextPage = (pager.getCurrentItem() + 1) % count;
|
int nextPage = (pager.getCurrentItem() + 1) % count;
|
||||||
navigateTo(nextPage);
|
navigateTo(nextPage);
|
||||||
return;
|
return;
|
||||||
@@ -513,11 +519,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
|
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isScrolledToTop() {
|
|
||||||
return ((ScrollableToTop) fragments[pager.getCurrentItem()]).isScrolledToTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideNewPostsButton(){
|
public void hideNewPostsButton(){
|
||||||
if(!newPostsBtnShown)
|
if(!newPostsBtnShown)
|
||||||
return;
|
return;
|
||||||
@@ -712,6 +713,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
return fab;
|
return fab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
|
callFragmentToProvideAssistContent(fragments[pager.getCurrentItem()], assistContent);
|
||||||
|
}
|
||||||
|
|
||||||
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
|
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@@ -8,8 +9,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
@@ -156,7 +155,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
result.get(result.size()-1).hasGapAfter=true;
|
result.get(result.size()-1).hasGapAfter=true;
|
||||||
toAdd=result;
|
toAdd=result;
|
||||||
}
|
}
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
||||||
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
||||||
if(!toAdd.isEmpty()){
|
if(!toAdd.isEmpty()){
|
||||||
prependItems(toAdd, true);
|
prependItems(toAdd, true);
|
||||||
@@ -235,7 +234,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
targetList.clear();
|
targetList.clear();
|
||||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
||||||
for(Status s:result){
|
for(Status s:result){
|
||||||
if(idsBelowGap.contains(s.id))
|
if(idsBelowGap.contains(s.id))
|
||||||
break;
|
break;
|
||||||
@@ -288,4 +287,14 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.HOME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path("/").build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.instance.GetDomainBlocks;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.GoogleMadeMeAddThisFragment;
|
||||||
|
import org.joinmastodon.android.model.DomainBlock;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.model.Severity;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.fragments.LoaderFragment;
|
||||||
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class InstanceBlockListFragment extends LoaderFragment {
|
||||||
|
private UsableRecyclerView list;
|
||||||
|
private MergeRecyclerAdapter adapter;
|
||||||
|
private View buttonBar;
|
||||||
|
private Instance instance;
|
||||||
|
private ElevationOnScrollListener onScrollListener;
|
||||||
|
|
||||||
|
private List<DomainBlock> domainBlocks;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setRetainInstance(true);
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity){
|
||||||
|
super.onAttach(activity);
|
||||||
|
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||||
|
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||||
|
setTitle(R.string.mo_instance_info_moderated_servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
|
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||||
|
|
||||||
|
list=view.findViewById(R.id.list);
|
||||||
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
|
|
||||||
|
adapter=new MergeRecyclerAdapter();
|
||||||
|
adapter.addAdapter(new ItemsAdapter());
|
||||||
|
list.setAdapter(adapter);
|
||||||
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 0, 0, DividerItemDecoration.NOT_FIRST));
|
||||||
|
|
||||||
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
|
||||||
|
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData() {
|
||||||
|
currentRequest= new GetDomainBlocks().setCallback(new SimpleCallback<>(this) {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<DomainBlock> result) {
|
||||||
|
domainBlocks=result;
|
||||||
|
dataLoaded();
|
||||||
|
}
|
||||||
|
}).execNoAuth(instance.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
|
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
||||||
|
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onUpdateToolbar(){
|
||||||
|
super.onUpdateToolbar();
|
||||||
|
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
|
||||||
|
getToolbar().setElevation(0);
|
||||||
|
if(onScrollListener!=null){
|
||||||
|
onScrollListener.setViews(buttonBar, getToolbar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
|
if(Build.VERSION.SDK_INT>=27){
|
||||||
|
int inset=insets.getSystemWindowInsetBottom();
|
||||||
|
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||||
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
|
}else{
|
||||||
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh(){
|
||||||
|
doLoadData();
|
||||||
|
}
|
||||||
|
private class ItemsAdapter extends RecyclerView.Adapter<ItemViewHolder>{
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new ItemViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position){
|
||||||
|
holder.bind(domainBlocks.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return domainBlocks.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ItemViewHolder extends BindableViewHolder<DomainBlock>{
|
||||||
|
private final TextView instanceUri, reason;
|
||||||
|
private final ImageView severity;
|
||||||
|
|
||||||
|
public ItemViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_server_block, list);
|
||||||
|
instanceUri=findViewById(R.id.instance);
|
||||||
|
reason=findViewById(R.id.reason);
|
||||||
|
severity=findViewById(R.id.severity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
@Override
|
||||||
|
public void onBind(DomainBlock item){
|
||||||
|
instanceUri.setText(item.domain);
|
||||||
|
reason.setText(item.comment);
|
||||||
|
switch (item.severity) {
|
||||||
|
case SILENCE -> {
|
||||||
|
severity.setImageDrawable(getContext().getDrawable(R.drawable.ic_fluent_speaker_mute_28_regular));
|
||||||
|
severity.setContentDescription(getContext().getString(R.string.mo_severity_silence));
|
||||||
|
}
|
||||||
|
case SUSPEND -> {
|
||||||
|
severity.setImageDrawable(getContext().getDrawable(R.drawable.ic_fluent_shield_prohibited_28_regular));
|
||||||
|
severity.setContentDescription(getContext().getString(R.string.mo_severity_suspend));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
severity.setTooltipText(severity.getContentDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.Outline;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewOutlineProvider;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.DomainManager;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.instance.GetExtendedDescription;
|
||||||
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||||
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.AccountField;
|
||||||
|
import org.joinmastodon.android.model.Attachment;
|
||||||
|
import org.joinmastodon.android.model.ExtendedDescription;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
|
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
|
||||||
|
import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable;
|
||||||
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.CoverImageView;
|
||||||
|
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||||
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.fragments.LoaderFragment;
|
||||||
|
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
|
||||||
|
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
|
||||||
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class InstanceInfoFragment extends LoaderFragment {
|
||||||
|
|
||||||
|
private Instance instance;
|
||||||
|
private String extendedDescription;
|
||||||
|
private CoverImageView cover;
|
||||||
|
private TextView uri, description, readMore;
|
||||||
|
private SwipeRefreshLayout refreshLayout;
|
||||||
|
private final CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable();
|
||||||
|
private LinearLayout textWrap;
|
||||||
|
|
||||||
|
private ScrollView scrollView, textScrollView;
|
||||||
|
private float titleTransY;
|
||||||
|
|
||||||
|
private String accountID;
|
||||||
|
private Account account;
|
||||||
|
private String targetDomain;
|
||||||
|
private final ArrayList<AccountField> fields=new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean refreshing;
|
||||||
|
private boolean isExpanded = false;
|
||||||
|
|
||||||
|
public UsableRecyclerView list;
|
||||||
|
private List<AccountField> metadataListData=Collections.emptyList();
|
||||||
|
private MetadataAdapter adapter;
|
||||||
|
private ListImageLoaderWrapper imgLoader;
|
||||||
|
|
||||||
|
private int statusBarHeight;
|
||||||
|
|
||||||
|
private float textMaxHeight, textCollapsedHeight;
|
||||||
|
private LinearLayout.LayoutParams collapseParams, wrapParams;
|
||||||
|
|
||||||
|
public InstanceInfoFragment(){
|
||||||
|
super(R.layout.loader_fragment_overlay_toolbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState){
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
|
setRetainInstance(true);
|
||||||
|
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
account= AccountSessionManager.getInstance().getAccount(accountID).self;
|
||||||
|
targetDomain=getArguments().getString("instanceDomain");
|
||||||
|
loadData();
|
||||||
|
loadExtendedDescription();
|
||||||
|
DomainManager.getInstance().setCurrentDomain(targetDomain + "/about");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity){
|
||||||
|
super.onAttach(activity);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
|
View content=inflater.inflate(R.layout.fragment_instance_info, container, false);
|
||||||
|
|
||||||
|
refreshLayout=content.findViewById(R.id.refresh_layout);
|
||||||
|
scrollView=content.findViewById(R.id.scroll_view);
|
||||||
|
cover=content.findViewById(R.id.cover);
|
||||||
|
uri =content.findViewById(R.id.uri);
|
||||||
|
description=content.findViewById(R.id.description);
|
||||||
|
list=content.findViewById(R.id.metadata);
|
||||||
|
textScrollView=content.findViewById(R.id.text_scroll_view);
|
||||||
|
textWrap=content.findViewById(R.id.text_wrap);
|
||||||
|
readMore=content.findViewById(R.id.read_more);
|
||||||
|
textMaxHeight=getActivity().getResources().getDimension(R.dimen.description_max_height);
|
||||||
|
textCollapsedHeight=getActivity().getResources().getDimension(R.dimen.description_collapsed_height);
|
||||||
|
collapseParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) textCollapsedHeight);
|
||||||
|
wrapParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
|
||||||
|
cover.setForeground(coverGradient);
|
||||||
|
cover.setOnClickListener(this::onCoverClick);
|
||||||
|
readMore.setOnClickListener(this::onReadMoreClick);
|
||||||
|
refreshLayout.setOnRefreshListener(this);
|
||||||
|
|
||||||
|
if(loaded){
|
||||||
|
bindViews();
|
||||||
|
dataLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
list.setItemAnimator(new BetterItemAnimator());
|
||||||
|
list.setDrawSelectorOnTop(true);
|
||||||
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
|
imgLoader=new ListImageLoaderWrapper(getActivity(), list, new RecyclerViewDelegate(list), null);
|
||||||
|
list.setAdapter(adapter=new MetadataAdapter());
|
||||||
|
list.setClipToPadding(false);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(){
|
||||||
|
currentRequest=new GetInstance()
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Instance result){
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
instance = result;
|
||||||
|
bindViews();
|
||||||
|
dataLoaded();
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
if(refreshing) {
|
||||||
|
refreshing = false;
|
||||||
|
refreshLayout.setRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execNoAuth(targetDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadExtendedDescription() {
|
||||||
|
new GetExtendedDescription()
|
||||||
|
.setCallback(new SimpleCallback<>(this){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(ExtendedDescription result){
|
||||||
|
if (getActivity() == null || result == null || TextUtils.isEmpty(result.content)) return;
|
||||||
|
extendedDescription = result.content;
|
||||||
|
updateDescription();
|
||||||
|
collapseDescription();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execNoAuth(targetDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDescription() {
|
||||||
|
if (instance == null || description == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
description.setText(HtmlParser.parse(TextUtils.isEmpty(extendedDescription) ?
|
||||||
|
TextUtils.isEmpty(instance.description) ? instance.shortDescription : instance.description
|
||||||
|
: extendedDescription,
|
||||||
|
account.emojis, Collections.emptyList(), Collections.emptyList(), accountID));
|
||||||
|
|
||||||
|
description.measure(
|
||||||
|
View.MeasureSpec.makeMeasureSpec(textWrap.getWidth(), View.MeasureSpec.EXACTLY),
|
||||||
|
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh(){
|
||||||
|
if(refreshing)
|
||||||
|
return;
|
||||||
|
refreshing=true;
|
||||||
|
doLoadData();
|
||||||
|
loadExtendedDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dataLoaded(){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
setFields(fields);
|
||||||
|
super.dataLoaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
updateToolbar();
|
||||||
|
scrollView.setOnScrollChangeListener(this::onScrollChanged);
|
||||||
|
titleTransY=getToolbar().getLayoutParams().height;
|
||||||
|
if(toolbarTitleView!=null){
|
||||||
|
toolbarTitleView.setTranslationY(titleTransY);
|
||||||
|
toolbarSubtitleView.setTranslationY(titleTransY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig){
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
updateToolbar();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
|
statusBarHeight = insets.getSystemWindowInsetTop();
|
||||||
|
if(contentView!=null){
|
||||||
|
((ViewGroup.MarginLayoutParams) getToolbar().getLayoutParams()).topMargin= statusBarHeight;
|
||||||
|
}
|
||||||
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void bindViews(){
|
||||||
|
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(instance.thumbnail, 1000, 1000));
|
||||||
|
uri.setText(instance.title);
|
||||||
|
setTitle(instance.title);
|
||||||
|
setSubtitle(instance.uri);
|
||||||
|
|
||||||
|
updateDescription();
|
||||||
|
collapseDescription();
|
||||||
|
|
||||||
|
fields.clear();
|
||||||
|
|
||||||
|
|
||||||
|
if (instance.contactAccount != null) {
|
||||||
|
AccountField admin = new AccountField();
|
||||||
|
admin.parsedName=admin.name=getContext().getString(R.string.mo_instance_admin);
|
||||||
|
admin.parsedValue=buildLinkText(instance.contactAccount.url, instance.contactAccount.getDisplayUsername() + "@" + instance.uri);
|
||||||
|
fields.add(admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance.email != null) {
|
||||||
|
AccountField contact = new AccountField();
|
||||||
|
contact.parsedName=getContext().getString(R.string.mo_instance_contact);
|
||||||
|
contact.parsedValue=buildLinkText("mailto:" + instance.email, instance.email);
|
||||||
|
fields.add(contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance.stats != null) {
|
||||||
|
AccountField activeUsers = new AccountField();
|
||||||
|
activeUsers.parsedName=getContext().getString(R.string.mo_instance_users);
|
||||||
|
activeUsers.parsedValue= NumberFormat.getInstance().format(instance.stats.userCount);
|
||||||
|
fields.add(activeUsers);
|
||||||
|
|
||||||
|
AccountField statusCount = new AccountField();
|
||||||
|
statusCount.parsedName=getContext().getString(R.string.mo_instance_status);
|
||||||
|
statusCount.parsedValue= NumberFormat.getInstance().format(instance.stats.statusCount);
|
||||||
|
fields.add(statusCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountField registration = new AccountField();
|
||||||
|
registration.parsedName=getContext().getString(R.string.mo_instance_registration);
|
||||||
|
registration.parsedValue=getContext().getString(instance.registrations ? instance.approvalRequired ? R.string.mo_instance_registration_approval : R.string.mo_instance_registration_open : R.string.instance_signup_closed);
|
||||||
|
fields.add(registration);
|
||||||
|
|
||||||
|
setFields(fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpannableStringBuilder buildLinkText(String link, String text) {
|
||||||
|
String value = "<span class=\"h-card\"><a href=" + link + " class=\"u-url mention\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">" + text + "</a></span>";
|
||||||
|
return HtmlParser.parse(value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collapseDescription() {
|
||||||
|
textScrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
description.measure(
|
||||||
|
View.MeasureSpec.makeMeasureSpec(textWrap.getWidth(), View.MeasureSpec.EXACTLY),
|
||||||
|
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||||
|
|
||||||
|
readMore.setText(isExpanded ? R.string.sk_collapse : R.string.sk_expand);
|
||||||
|
description.post(() -> {
|
||||||
|
boolean tooBig = description.getMeasuredHeight() > textMaxHeight;
|
||||||
|
readMore.setVisibility(tooBig ? View.VISIBLE : View.GONE);
|
||||||
|
textScrollView.setLayoutParams(tooBig && !isExpanded ? collapseParams : wrapParams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateToolbar(){
|
||||||
|
getToolbar().setBackgroundColor(0);
|
||||||
|
if(toolbarTitleView!=null){
|
||||||
|
toolbarTitleView.setTranslationY(titleTransY);
|
||||||
|
toolbarSubtitleView.setTranslationY(titleTransY);
|
||||||
|
}
|
||||||
|
getToolbar().setOnClickListener(v->scrollToTop());
|
||||||
|
getToolbar().setNavigationContentDescription(R.string.back);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scrollToTop(){
|
||||||
|
scrollView.smoothScrollTo(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||||
|
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
||||||
|
if(scrollY>cover.getHeight()-topBarsH){
|
||||||
|
cover.setTranslationY(scrollY-(cover.getHeight()-topBarsH));
|
||||||
|
cover.setTranslationZ(V.dp(10));
|
||||||
|
cover.setTransform(cover.getHeight()/2f-topBarsH/2f, 1f);
|
||||||
|
}else{
|
||||||
|
cover.setTranslationY(0f);
|
||||||
|
cover.setTranslationZ(0f);
|
||||||
|
cover.setTransform(scrollY/2f, 1f);
|
||||||
|
}
|
||||||
|
coverGradient.setTopOffset(scrollY);
|
||||||
|
cover.invalidate();
|
||||||
|
titleTransY=getToolbar().getHeight();
|
||||||
|
if(scrollY>textWrap.getTop()-topBarsH){
|
||||||
|
titleTransY=Math.max(0f, titleTransY-(scrollY-(textWrap.getTop()-topBarsH)));
|
||||||
|
}
|
||||||
|
if(toolbarTitleView!=null){
|
||||||
|
toolbarTitleView.setTranslationY(titleTransY);
|
||||||
|
toolbarSubtitleView.setTranslationY(titleTransY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
|
if (instance != null) {
|
||||||
|
inflater.inflate(R.menu.instance_info, menu);
|
||||||
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu);
|
||||||
|
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, instance.uri));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
|
int id=item.getItemId();
|
||||||
|
if(id==R.id.share){
|
||||||
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
|
intent.setType("text/plain");
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, instance.uri);
|
||||||
|
startActivity(Intent.createChooser(intent, item.getTitle()));
|
||||||
|
} else if (id==R.id.open_timeline) {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putString("domain", instance.uri);
|
||||||
|
Nav.go(getActivity(), CustomLocalTimelineFragment.class, args);
|
||||||
|
}else if (id==R.id.rules) {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putParcelable("instance", Parcels.wrap(instance));
|
||||||
|
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||||
|
} else if (id==R.id.moderated_servers) {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putParcelable("instance", Parcels.wrap(instance));
|
||||||
|
Nav.go(getActivity(), InstanceBlockListFragment.class, args);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean wantsLightStatusBar(){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected int getToolbarResource(){
|
||||||
|
return R.layout.profile_toolbar;
|
||||||
|
}
|
||||||
|
private void onReadMoreClick(View view) {
|
||||||
|
isExpanded = !isExpanded;
|
||||||
|
bindViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void onCoverClick(View v){
|
||||||
|
Drawable drawable=cover.getDrawable();
|
||||||
|
if(drawable==null || drawable instanceof ColorDrawable)
|
||||||
|
return;
|
||||||
|
new PhotoViewer(getActivity(), Attachment.createFakeAttachments(instance.thumbnail, drawable), 0,
|
||||||
|
new SingleImagePhotoViewerListener(cover, cover, null, this, () -> {
|
||||||
|
}, () -> drawable, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFields(ArrayList<AccountField> fields){
|
||||||
|
metadataListData=fields;
|
||||||
|
if (adapter != null) adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> {
|
||||||
|
public MetadataAdapter(){
|
||||||
|
super(imgLoader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new AboutViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(BaseViewHolder holder, int position){
|
||||||
|
holder.bind(metadataListData.get(position));
|
||||||
|
super.onBindViewHolder(holder, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return metadataListData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class BaseViewHolder extends BindableViewHolder<AccountField> {
|
||||||
|
public BaseViewHolder(int layout){
|
||||||
|
super(getActivity(), layout, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AboutViewHolder extends BaseViewHolder {
|
||||||
|
private final TextView title;
|
||||||
|
private final LinkedTextView value;
|
||||||
|
|
||||||
|
public AboutViewHolder(){
|
||||||
|
super(R.layout.item_profile_about);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
value=findViewById(R.id.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(AccountField item){
|
||||||
|
title.setText(item.parsedName);
|
||||||
|
value.setText(item.parsedValue);
|
||||||
|
value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||||
|
value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent));
|
||||||
|
value.setCompoundDrawables(null, null, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result) {
|
public void onSuccess(List<Status> result) {
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.HOME)).collect(Collectors.toList());
|
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -162,4 +162,15 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
protected void onSetFabBottomInset(int inset) {
|
protected void onSetFabBottomInset(int inset) {
|
||||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.HOME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path("/lists/" + listID).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,263 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.CreateList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
|
||||||
|
|
||||||
public class ListTimelinesFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop {
|
|
||||||
private String accountId;
|
|
||||||
private String profileAccountId;
|
|
||||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
|
||||||
private final HashMap<String, Boolean> userInList = new HashMap<>();
|
|
||||||
private ListsAdapter adapter;
|
|
||||||
|
|
||||||
public ListTimelinesFragment() {
|
|
||||||
super(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
Bundle args=getArguments();
|
|
||||||
accountId=args.getString("account");
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
E.register(this);
|
|
||||||
|
|
||||||
if(args.containsKey("profileAccount")){
|
|
||||||
profileAccountId=args.getString("profileAccount");
|
|
||||||
String profileDisplayUsername = args.getString("profileDisplayUsername");
|
|
||||||
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
|
||||||
} else {
|
|
||||||
setTitle(R.string.sk_your_lists);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onShown(){
|
|
||||||
super.onShown();
|
|
||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.menu_list, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.create) {
|
|
||||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.sk_create_list_title)
|
|
||||||
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
|
||||||
.setView(editor)
|
|
||||||
.setPositiveButton(R.string.sk_create, (d, which) ->
|
|
||||||
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(ListTimeline list) {
|
|
||||||
data.add(0, list);
|
|
||||||
adapter.notifyItemRangeInserted(0, 1);
|
|
||||||
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountId)
|
|
||||||
)
|
|
||||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveListMembership(String listId, boolean isMember) {
|
|
||||||
userInList.put(listId, isMember);
|
|
||||||
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
|
||||||
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
|
|
||||||
req.setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Object o) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
userInListBefore.clear();
|
|
||||||
userInList.clear();
|
|
||||||
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
|
||||||
.setCallback(new SimpleCallback<>(this) {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<ListTimeline> lists) {
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
|
||||||
userInList.putAll(userInListBefore);
|
|
||||||
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
|
||||||
if (profileAccountId == null) return;
|
|
||||||
|
|
||||||
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<ListTimeline> allLists) {
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
List<ListTimeline> newLists = new ArrayList<>();
|
|
||||||
for (ListTimeline l : allLists) {
|
|
||||||
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
|
||||||
if (!userInListBefore.containsKey(l.id)) {
|
|
||||||
userInListBefore.put(l.id, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userInList.putAll(userInListBefore);
|
|
||||||
onDataLoaded(newLists, false);
|
|
||||||
}
|
|
||||||
}).exec(accountId);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onListDeletedEvent(ListDeletedEvent event) {
|
|
||||||
for (int i = 0; i < data.size(); i++) {
|
|
||||||
ListTimeline item = data.get(i);
|
|
||||||
if (item.id.equals(event.id)) {
|
|
||||||
data.remove(i);
|
|
||||||
adapter.notifyItemRemoved(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
|
||||||
for (int i = 0; i < data.size(); i++) {
|
|
||||||
ListTimeline item = data.get(i);
|
|
||||||
if (item.id.equals(event.id)) {
|
|
||||||
item.title = event.title;
|
|
||||||
item.repliesPolicy = event.repliesPolicy;
|
|
||||||
adapter.notifyItemChanged(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
|
|
||||||
return adapter = new ListsAdapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scrollToTop() {
|
|
||||||
smoothScrollRecyclerViewToTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isScrolledToTop() {
|
|
||||||
return list.getChildAt(0).getTop() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new ListViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
|
||||||
holder.bind(data.get(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return data.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
|
|
||||||
private final TextView title;
|
|
||||||
private final CheckBox listToggle;
|
|
||||||
|
|
||||||
public ListViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_text, list);
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
listToggle=findViewById(R.id.list_toggle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(ListTimeline item) {
|
|
||||||
title.setText(item.title);
|
|
||||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
|
|
||||||
if (profileAccountId != null) {
|
|
||||||
Boolean checked = userInList.get(item.id);
|
|
||||||
listToggle.setVisibility(View.VISIBLE);
|
|
||||||
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
|
||||||
listToggle.setOnClickListener(this::onClickToggle);
|
|
||||||
} else {
|
|
||||||
listToggle.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onClickToggle(View view) {
|
|
||||||
saveListMembership(item.id, listToggle.isChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountId);
|
|
||||||
args.putString("listID", item.id);
|
|
||||||
args.putString("listTitle", item.title);
|
|
||||||
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
|
|
||||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.CreateList;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
|
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
||||||
|
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||||
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class ListsFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
|
||||||
|
private String accountID;
|
||||||
|
private String profileAccountId;
|
||||||
|
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||||
|
private final HashMap<String, Boolean> userInList = new HashMap<>();
|
||||||
|
private ListsAdapter adapter;
|
||||||
|
|
||||||
|
public ListsFragment() {
|
||||||
|
super(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Bundle args = getArguments();
|
||||||
|
accountID = args.getString("account");
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
E.register(this);
|
||||||
|
|
||||||
|
if(args.containsKey("profileAccount")){
|
||||||
|
profileAccountId=args.getString("profileAccount");
|
||||||
|
String profileDisplayUsername = args.getString("profileDisplayUsername");
|
||||||
|
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
||||||
|
} else {
|
||||||
|
setTitle(R.string.sk_your_lists);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
|
super.onShown();
|
||||||
|
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.menu_list, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.create) {
|
||||||
|
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_create_list_title)
|
||||||
|
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
||||||
|
.setView(editor)
|
||||||
|
.setPositiveButton(R.string.sk_create, (d, which) ->
|
||||||
|
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(ListTimeline list) {
|
||||||
|
data.add(0, list);
|
||||||
|
adapter.notifyItemRangeInserted(0, 1);
|
||||||
|
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID)
|
||||||
|
)
|
||||||
|
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveListMembership(String listId, boolean isMember) {
|
||||||
|
userInList.put(listId, isMember);
|
||||||
|
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
||||||
|
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
|
||||||
|
req.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object o) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getContext());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
userInListBefore.clear();
|
||||||
|
userInList.clear();
|
||||||
|
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
||||||
|
.setCallback(new SimpleCallback<>(this) {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<ListTimeline> lists) {
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
||||||
|
userInList.putAll(userInListBefore);
|
||||||
|
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
||||||
|
if (profileAccountId == null) return;
|
||||||
|
|
||||||
|
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<ListTimeline> allLists) {
|
||||||
|
if (getActivity() == null) return;
|
||||||
|
List<ListTimeline> newLists = new ArrayList<>();
|
||||||
|
for (ListTimeline l : allLists) {
|
||||||
|
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
||||||
|
if (!userInListBefore.containsKey(l.id)) {
|
||||||
|
userInListBefore.put(l.id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userInList.putAll(userInListBefore);
|
||||||
|
onDataLoaded(newLists, false);
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onListDeletedEvent(ListDeletedEvent event) {
|
||||||
|
for (int i = 0; i < data.size(); i++) {
|
||||||
|
ListTimeline item = data.get(i);
|
||||||
|
if (item.id.equals(event.id)) {
|
||||||
|
data.remove(i);
|
||||||
|
adapter.notifyItemRemoved(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
||||||
|
for (int i = 0; i < data.size(); i++) {
|
||||||
|
ListTimeline item = data.get(i);
|
||||||
|
if (item.id.equals(event.id)) {
|
||||||
|
item.title = event.title;
|
||||||
|
item.repliesPolicy = event.repliesPolicy;
|
||||||
|
adapter.notifyItemChanged(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
|
||||||
|
return adapter = new ListsAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void scrollToTop() {
|
||||||
|
smoothScrollRecyclerViewToTop(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccountID() {
|
||||||
|
return accountID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path("/lists").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new ListViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
||||||
|
holder.bind(data.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
|
||||||
|
private final TextView title;
|
||||||
|
private final CheckBox listToggle;
|
||||||
|
|
||||||
|
public ListViewHolder(){
|
||||||
|
super(getActivity(), R.layout.item_text, list);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
listToggle=findViewById(R.id.list_toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(ListTimeline item) {
|
||||||
|
title.setText(item.title);
|
||||||
|
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
|
||||||
|
if (profileAccountId != null) {
|
||||||
|
Boolean checked = userInList.get(item.id);
|
||||||
|
listToggle.setVisibility(View.VISIBLE);
|
||||||
|
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
||||||
|
listToggle.setOnClickListener(this::onClickToggle);
|
||||||
|
} else {
|
||||||
|
listToggle.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClickToggle(View view) {
|
||||||
|
saveListMembership(item.id, listToggle.isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick() {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putString("listID", item.id);
|
||||||
|
args.putString("listTitle", item.title);
|
||||||
|
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
|
||||||
|
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,4 +30,9 @@ public abstract class MastodonToolbarFragment extends ToolbarFragment{
|
|||||||
toolbar.setNavigationContentDescription(R.string.back);
|
toolbar.setNavigationContentDescription(R.string.back);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean wantsToolbarMenuIconsTinted() {
|
||||||
|
return false; // else, badged icons don't work :(
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
|
import android.app.assist.AssistContent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -13,6 +14,12 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
@@ -24,12 +31,7 @@ import org.joinmastodon.android.ui.SimpleViewHolder;
|
|||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -37,7 +39,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, DomainDisplay{
|
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent {
|
||||||
|
|
||||||
private TabLayout tabLayout;
|
private TabLayout tabLayout;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
@@ -47,12 +49,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
private NotificationsListFragment allNotificationsFragment, mentionsFragment, postsFragment;
|
private NotificationsListFragment allNotificationsFragment, mentionsFragment, postsFragment;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDomain() {
|
|
||||||
return DomainDisplay.super.getDomain() + "/notifications";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -107,6 +103,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
|
|
||||||
tabLayout=view.findViewById(R.id.tabbar);
|
tabLayout=view.findViewById(R.id.tabbar);
|
||||||
pager=view.findViewById(R.id.pager);
|
pager=view.findViewById(R.id.pager);
|
||||||
|
UiUtils.reduceSwipeSensitivity(pager);
|
||||||
|
|
||||||
tabViews=new FrameLayout[3];
|
tabViews=new FrameLayout[3];
|
||||||
for(int i=0;i<tabViews.length;i++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
@@ -124,6 +121,18 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
|
|
||||||
tabLayout.setTabTextSize(V.dp(16));
|
tabLayout.setTabTextSize(V.dp(16));
|
||||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||||
|
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onTabSelected(TabLayout.Tab tab) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabReselected(TabLayout.Tab tab) {
|
||||||
|
scrollToTop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
pager.setOffscreenPageLimit(4);
|
pager.setOffscreenPageLimit(4);
|
||||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
||||||
@@ -145,20 +154,17 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putBoolean("__is_tab", true);
|
args.putBoolean("__is_tab", true);
|
||||||
args.putBoolean("noAutoLoad", true);
|
|
||||||
|
|
||||||
allNotificationsFragment=new NotificationsListFragment();
|
allNotificationsFragment=new NotificationsListFragment();
|
||||||
allNotificationsFragment.setArguments(args);
|
allNotificationsFragment.setArguments(args);
|
||||||
|
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
args.putBoolean("onlyMentions", true);
|
args.putBoolean("onlyMentions", true);
|
||||||
args.putBoolean("noAutoLoad", true);
|
|
||||||
mentionsFragment=new NotificationsListFragment();
|
mentionsFragment=new NotificationsListFragment();
|
||||||
mentionsFragment.setArguments(args);
|
mentionsFragment.setArguments(args);
|
||||||
|
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
args.putBoolean("onlyPosts", true);
|
args.putBoolean("onlyPosts", true);
|
||||||
args.putBoolean("noAutoLoad", true);
|
|
||||||
postsFragment=new NotificationsListFragment();
|
postsFragment=new NotificationsListFragment();
|
||||||
postsFragment.setArguments(args);
|
postsFragment.setArguments(args);
|
||||||
|
|
||||||
@@ -190,6 +196,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Account> accounts) {
|
public void onSuccess(HeaderPaginationList<Account> accounts) {
|
||||||
|
if (getActivity() == null) return;
|
||||||
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
|
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +212,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop(){
|
||||||
if (getFragmentForPage(pager.getCurrentItem()).isScrolledToTop() && !GlobalUserPreferences.disableDoubleTapToSwipe) {
|
if (getFragmentForPage(pager.getCurrentItem()).isOnTop() && GlobalUserPreferences.doubleTapToSwipe) {
|
||||||
int nextPage = (pager.getCurrentItem() + 1) % tabViews.length;
|
int nextPage = (pager.getCurrentItem() + 1) % tabViews.length;
|
||||||
pager.setCurrentItem(nextPage, true);
|
pager.setCurrentItem(nextPage, true);
|
||||||
return;
|
return;
|
||||||
@@ -213,11 +220,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
|
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isScrolledToTop() {
|
|
||||||
return getFragmentForPage(pager.getCurrentItem()).isScrolledToTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadData(){
|
public void loadData(){
|
||||||
refreshFollowRequestsBadge();
|
refreshFollowRequestsBadge();
|
||||||
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
|
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
|
||||||
@@ -228,6 +230,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
protected void updateToolbar(){
|
protected void updateToolbar(){
|
||||||
super.updateToolbar();
|
super.updateToolbar();
|
||||||
getToolbar().setOutlineProvider(null);
|
getToolbar().setOutlineProvider(null);
|
||||||
|
getToolbar().setOnClickListener(v->scrollToTop());
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationsListFragment getFragmentForPage(int page){
|
private NotificationsListFragment getFragmentForPage(int page){
|
||||||
@@ -239,6 +242,11 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
|
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
|
||||||
|
}
|
||||||
|
|
||||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -10,17 +11,23 @@ import com.squareup.otto.Subscribe;
|
|||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
|
import org.joinmastodon.android.api.requests.notifications.PleromaMarkNotificationsRead;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
import org.joinmastodon.android.model.Emoji;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.model.Markers;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
@@ -53,11 +60,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDomain() {
|
|
||||||
return super.getDomain() + "/notifications";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -156,12 +158,17 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
loadRelationships(needRelationships);
|
loadRelationships(needRelationships);
|
||||||
maxID=result.maxID;
|
maxID=result.maxID;
|
||||||
|
|
||||||
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && AccountSessionManager.getInstance().getAccount(accountID).markers.notifications != null){
|
Markers markers = AccountSessionManager.getInstance().getAccount(accountID).markers;
|
||||||
|
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && markers != null && markers.notifications != null){
|
||||||
E.post(new AllNotificationsSeenEvent());
|
E.post(new AllNotificationsSeenEvent());
|
||||||
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).markers
|
AccountSessionManager.getInstance().getAccount(accountID).markers
|
||||||
.notifications.lastReadId = result.items.get(0).id;
|
.notifications.lastReadId = result.items.get(0).id;
|
||||||
AccountSessionManager.getInstance().writeAccountsFile();
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
|
|
||||||
|
if (isInstanceAkkoma()) {
|
||||||
|
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -181,33 +188,17 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
@Override
|
@Override
|
||||||
protected void onShown(){
|
protected void onShown(){
|
||||||
super.onShown();
|
super.onShown();
|
||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading){
|
// if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||||
refreshing=true;
|
// loadData();
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(String id){
|
public void onItemClick(String id){
|
||||||
Notification n=getNotificationByID(id);
|
Notification n=getNotificationByID(id);
|
||||||
if(n.status!=null){
|
Bundle args = new Bundle();
|
||||||
Status status=n.status;
|
if(n.status != null && n.status.inReplyToAccountId != null && knownAccounts.containsKey(n.status.inReplyToAccountId))
|
||||||
Bundle args=new Bundle();
|
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(n.status.inReplyToAccountId)));
|
||||||
args.putString("account", accountID);
|
UiUtils.showFragmentForNotification(getContext(), n, accountID, args);
|
||||||
args.putParcelable("status", Parcels.wrap(status));
|
|
||||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
|
||||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
|
||||||
}else if(n.report != null){
|
|
||||||
String domain = AccountSessionManager.getInstance().getAccount(accountID).domain;
|
|
||||||
UiUtils.launchWebBrowser(getActivity(), "https://"+domain+"/admin/reports/"+n.report.id);
|
|
||||||
}else{
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(n.account));
|
|
||||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -239,6 +230,32 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copied from StatusListFragment.EventListener (just like the method above)
|
||||||
|
// (which assumes this.data to be a list of statuses...)
|
||||||
|
@Subscribe
|
||||||
|
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||||
|
for(Notification n:data){
|
||||||
|
if (n.status == null) continue;
|
||||||
|
if(n.status.getContentStatus().id.equals(ev.id)){
|
||||||
|
n.status.getContentStatus().update(ev);
|
||||||
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
|
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
|
||||||
|
footer.rebind();
|
||||||
|
}else if(holder instanceof ExtendedFooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
|
||||||
|
footer.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(Notification n:preloadedData){
|
||||||
|
if (n.status == null) continue;
|
||||||
|
if(n.status.getContentStatus().id.equals(ev.id)){
|
||||||
|
n.status.getContentStatus().update(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
public void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
|
||||||
if(!ev.accountID.equals(accountID) || ev.isUnfollow)
|
if(!ev.accountID.equals(accountID) || ev.isUnfollow)
|
||||||
@@ -271,4 +288,11 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
displayItems.subList(index, lastIndex).clear();
|
displayItems.subList(index, lastIndex).clear();
|
||||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path(isInstanceAkkoma()
|
||||||
|
? "/users/" + getSession().self.username + "/interactions"
|
||||||
|
: "/notifications").build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public abstract class PinnableStatusListFragment extends StatusListFragment impl
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
|
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.animation.AnimatorSet;
|
|||||||
import android.animation.ObjectAnimator;
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -19,6 +20,7 @@ import android.os.Bundle;
|
|||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ImageSpan;
|
import android.text.style.ImageSpan;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -52,6 +54,7 @@ import org.joinmastodon.android.DomainManager;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
@@ -82,6 +85,7 @@ import org.joinmastodon.android.ui.views.CoverImageView;
|
|||||||
import org.joinmastodon.android.ui.views.LinkedTextView;
|
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||||
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
|
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
|
||||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -92,9 +96,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
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.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -115,7 +123,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab{
|
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
|
||||||
private static final int AVATAR_RESULT=722;
|
private static final int AVATAR_RESULT=722;
|
||||||
private static final int COVER_RESULT=343;
|
private static final int COVER_RESULT=343;
|
||||||
|
|
||||||
@@ -143,8 +151,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
public FrameLayout noteWrap;
|
public FrameLayout noteWrap;
|
||||||
public EditText noteEdit;
|
public EditText noteEdit;
|
||||||
private String note;
|
private String note;
|
||||||
private Account account;
|
private Account account, remoteAccount;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
private String domain;
|
||||||
private Relationship relationship;
|
private Relationship relationship;
|
||||||
private int statusBarHeight;
|
private int statusBarHeight;
|
||||||
private boolean isOwnProfile;
|
private boolean isOwnProfile;
|
||||||
@@ -159,7 +168,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private PhotoViewer currentPhotoViewer;
|
private PhotoViewer currentPhotoViewer;
|
||||||
private boolean editModeLoading;
|
private boolean editModeLoading;
|
||||||
|
|
||||||
private static final int MAX_FIELDS=4;
|
private int maxFields = 4;
|
||||||
|
|
||||||
// from ProfileAboutFragment
|
// from ProfileAboutFragment
|
||||||
public UsableRecyclerView list;
|
public UsableRecyclerView list;
|
||||||
@@ -180,13 +189,22 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
if(getArguments().containsKey("profileAccount")){
|
domain=AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
|
if (getArguments().containsKey("remoteAccount")) {
|
||||||
|
remoteAccount = Parcels.unwrap(getArguments().getParcelable("remoteAccount"));
|
||||||
|
if(!getArguments().getBoolean("noAutoLoad", false))
|
||||||
|
loadData();
|
||||||
|
} else if(getArguments().containsKey("profileAccount")){
|
||||||
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||||
profileAccountID=account.id;
|
profileAccountID=account.id;
|
||||||
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
loaded=true;
|
loaded=true;
|
||||||
if(!isOwnProfile)
|
if(!isOwnProfile)
|
||||||
loadRelationship();
|
loadRelationship();
|
||||||
|
else if (isInstanceAkkoma() && getInstance().isPresent())
|
||||||
|
if(getInstance().get().pleroma != null && getInstance().get().pleroma.metadata != null && getInstance().get().pleroma.metadata.fieldsLimits != null){
|
||||||
|
maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
profileAccountID=getArguments().getString("profileAccountID");
|
profileAccountID=getArguments().getString("profileAccountID");
|
||||||
if(!getArguments().getBoolean("noAutoLoad", false))
|
if(!getArguments().getBoolean("noAutoLoad", false))
|
||||||
@@ -205,14 +223,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onHiddenChanged(boolean hidden) {
|
|
||||||
super.onHiddenChanged(hidden);
|
|
||||||
if (!hidden) {
|
|
||||||
DomainManager.getInstance().setCurrentDomain(account.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
View content=inflater.inflate(R.layout.fragment_profile, container, false);
|
View content=inflater.inflate(R.layout.fragment_profile, container, false);
|
||||||
@@ -270,7 +280,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
0,
|
0,
|
||||||
fab.getHeight() * 2);
|
fab.getHeight() * 2);
|
||||||
animate.setDuration(300);
|
animate.setDuration(300);
|
||||||
animate.setFillAfter(true);
|
|
||||||
fab.startAnimation(animate);
|
fab.startAnimation(animate);
|
||||||
|
|
||||||
noteEditConfirm.setVisibility(View.VISIBLE);
|
noteEditConfirm.setVisibility(View.VISIBLE);
|
||||||
@@ -285,7 +294,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
fab.getHeight() * 2,
|
fab.getHeight() * 2,
|
||||||
0);
|
0);
|
||||||
animate.setDuration(300);
|
animate.setDuration(300);
|
||||||
animate.setFillAfter(true);
|
|
||||||
fab.startAnimation(animate);
|
fab.startAnimation(animate);
|
||||||
|
|
||||||
noteEditConfirm.animate()
|
noteEditConfirm.animate()
|
||||||
@@ -384,10 +392,20 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||||
|
|
||||||
|
|
||||||
|
//this currently takes up the whole username
|
||||||
|
//in the future it might need to be change to only the instance uri
|
||||||
|
username.setOnClickListener(v -> {
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
args.putString("instanceDomain", Uri.parse(account.url).getHost());
|
||||||
|
Nav.go(getActivity(), InstanceInfoFragment.class, args);
|
||||||
|
});
|
||||||
|
|
||||||
username.setOnLongClickListener(v->{
|
username.setOnLongClickListener(v->{
|
||||||
String usernameString=account.acct;
|
String usernameString=account.acct;
|
||||||
if(!usernameString.contains("@")){
|
if(!usernameString.contains("@")){
|
||||||
usernameString+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
usernameString+="@"+domain;
|
||||||
}
|
}
|
||||||
UiUtils.copyText(username, '@'+usernameString);
|
UiUtils.copyText(username, '@'+usernameString);
|
||||||
return true;
|
return true;
|
||||||
@@ -404,6 +422,32 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return sizeWrapper;
|
return sizeWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onAccountLoaded(Account result) {
|
||||||
|
account=result;
|
||||||
|
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
|
bindHeaderView();
|
||||||
|
dataLoaded();
|
||||||
|
if(!tabLayoutMediator.isAttached())
|
||||||
|
tabLayoutMediator.attach();
|
||||||
|
if(!isOwnProfile)
|
||||||
|
loadRelationship();
|
||||||
|
else
|
||||||
|
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
||||||
|
if(refreshing){
|
||||||
|
refreshing=false;
|
||||||
|
refreshLayout.setRefreshing(false);
|
||||||
|
if(postsFragment.loaded)
|
||||||
|
postsFragment.onRefresh();
|
||||||
|
if(postsWithRepliesFragment.loaded)
|
||||||
|
postsWithRepliesFragment.onRefresh();
|
||||||
|
if(pinnedPostsFragment.loaded)
|
||||||
|
pinnedPostsFragment.onRefresh();
|
||||||
|
if(mediaFragment.loaded)
|
||||||
|
mediaFragment.onRefresh();
|
||||||
|
}
|
||||||
|
V.setVisibilityAnimated(fab, View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
public void setNote(String note){
|
public void setNote(String note){
|
||||||
this.note=note;
|
this.note=note;
|
||||||
noteWrap.setVisibility(View.VISIBLE);
|
noteWrap.setVisibility(View.VISIBLE);
|
||||||
@@ -417,42 +461,35 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
public void onSuccess(Relationship result) {}
|
public void onSuccess(Relationship result) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse result) {
|
public void onError(ErrorResponse error) {
|
||||||
Toast.makeText(getActivity(), getString(R.string.mo_personal_note_update_failed), Toast.LENGTH_LONG).show();
|
error.showToast(getActivity());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(){
|
protected void doLoadData(){
|
||||||
|
if (remoteAccount != null) {
|
||||||
|
UiUtils.lookupAccountHandle(getContext(), accountID, remoteAccount.getFullyQualifiedName(), (c, args) -> {
|
||||||
|
if (getContext() == null) return;
|
||||||
|
if (args == null || !args.containsKey("profileAccount")) {
|
||||||
|
Toast.makeText(getContext(), getContext().getString(
|
||||||
|
R.string.sk_error_loading_profile, domain
|
||||||
|
), Toast.LENGTH_SHORT).show();
|
||||||
|
Nav.finish(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onAccountLoaded(Parcels.unwrap(args.getParcelable("profileAccount")));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
currentRequest=new GetAccountByID(profileAccountID)
|
currentRequest=new GetAccountByID(profileAccountID)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
if (getActivity() == null) return;
|
if (getActivity() == null) return;
|
||||||
account=result;
|
onAccountLoaded(result);
|
||||||
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
|
|
||||||
bindHeaderView();
|
|
||||||
dataLoaded();
|
|
||||||
if(!tabLayoutMediator.isAttached())
|
|
||||||
tabLayoutMediator.attach();
|
|
||||||
if(!isOwnProfile)
|
|
||||||
loadRelationship();
|
|
||||||
else
|
|
||||||
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
|
|
||||||
if(refreshing){
|
|
||||||
refreshing=false;
|
|
||||||
refreshLayout.setRefreshing(false);
|
|
||||||
if(postsFragment.loaded)
|
|
||||||
postsFragment.onRefresh();
|
|
||||||
if(postsWithRepliesFragment.loaded)
|
|
||||||
postsWithRepliesFragment.onRefresh();
|
|
||||||
if(pinnedPostsFragment.loaded)
|
|
||||||
pinnedPostsFragment.onRefresh();
|
|
||||||
if(mediaFragment.loaded)
|
|
||||||
mediaFragment.onRefresh();
|
|
||||||
}
|
|
||||||
V.setVisibilityAnimated(fab, View.VISIBLE);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -592,13 +629,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
|
|
||||||
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
|
||||||
|
|
||||||
|
String acct = ((isSelf || account.isRemote)
|
||||||
|
? account.getFullyQualifiedName()
|
||||||
|
: account.acct);
|
||||||
if(account.locked){
|
if(account.locked){
|
||||||
ssb=new SpannableStringBuilder("@");
|
ssb=new SpannableStringBuilder("@");
|
||||||
ssb.append(account.acct);
|
ssb.append(acct);
|
||||||
if(isSelf){
|
|
||||||
ssb.append('@');
|
|
||||||
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
|
|
||||||
}
|
|
||||||
ssb.append(" ");
|
ssb.append(" ");
|
||||||
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
|
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
|
||||||
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
|
||||||
@@ -620,17 +656,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
username.setText(ssb);
|
username.setText(ssb);
|
||||||
}else{
|
}else{
|
||||||
// noinspection SetTextI18n
|
// noinspection SetTextI18n
|
||||||
username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
|
username.setText('@'+acct);
|
||||||
|
}
|
||||||
|
CharSequence parsedBio = null;
|
||||||
|
if(account.note != null){
|
||||||
|
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
}
|
}
|
||||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
|
||||||
if(TextUtils.isEmpty(parsedBio)){
|
if(TextUtils.isEmpty(parsedBio)){
|
||||||
bio.setVisibility(View.GONE);
|
bio.setVisibility(View.GONE);
|
||||||
}else{
|
}else{
|
||||||
bio.setVisibility(View.VISIBLE);
|
bio.setVisibility(View.VISIBLE);
|
||||||
bio.setText(parsedBio);
|
bio.setText(parsedBio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
|
||||||
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
|
||||||
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
|
||||||
@@ -692,6 +729,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean wantsToolbarMenuIconsTinted() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
if(isOwnProfile && isInEditMode){
|
if(isOwnProfile && isInEditMode){
|
||||||
@@ -726,8 +768,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
||||||
if(isOwnProfile)
|
if(isOwnProfile) {
|
||||||
|
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MenuItem mute = menu.findItem(R.id.mute);
|
MenuItem mute = menu.findItem(R.id.mute);
|
||||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||||
@@ -809,7 +853,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
args.putString("profileAccount", profileAccountID);
|
args.putString("profileAccount", profileAccountID);
|
||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||||
}
|
}
|
||||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
Nav.go(getActivity(), ListsFragment.class, args);
|
||||||
}else if(id==R.id.followed_hashtags){
|
}else if(id==R.id.followed_hashtags){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -862,13 +906,28 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
// aboutFragment.setNote(relationship.note, accountID, profileAccountID);
|
// 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));
|
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() {
|
public ImageButton getFab() {
|
||||||
return fab;
|
return fab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showFab() {
|
||||||
|
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.showFab();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideFab() {
|
||||||
|
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.hideFab();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScrolling() {
|
||||||
|
return getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous
|
||||||
|
&& fabulous.isScrolling();
|
||||||
|
}
|
||||||
|
|
||||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||||
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
int topBarsH=getToolbar().getHeight()+statusBarHeight;
|
||||||
if(scrollY>avatarBorder.getTop()-topBarsH){
|
if(scrollY>avatarBorder.getTop()-topBarsH){
|
||||||
@@ -1199,11 +1258,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
scrollView.smoothScrollTo(0, 0);
|
scrollView.smoothScrollTo(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isScrolledToTop() {
|
|
||||||
return list.getChildAt(0).getTop() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFollowersOrFollowingClick(View v){
|
private void onFollowersOrFollowingClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -1268,6 +1322,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if (adapter != null) adapter.notifyDataSetChanged();
|
if (adapter != null) adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
|
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccountID() {
|
||||||
|
return accountID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return Uri.parse(account.url);
|
||||||
|
}
|
||||||
|
|
||||||
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter {
|
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter {
|
||||||
public MetadataAdapter(){
|
public MetadataAdapter(){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
@@ -1298,7 +1367,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
if(isInEditMode){
|
if(isInEditMode){
|
||||||
int size=metadataListData.size();
|
int size=metadataListData.size();
|
||||||
if(size<MAX_FIELDS)
|
if(size<maxFields)
|
||||||
size++;
|
size++;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
@@ -1432,7 +1501,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick(){
|
||||||
metadataListData.add(new AccountField());
|
metadataListData.add(new AccountField());
|
||||||
if(metadataListData.size()==MAX_FIELDS){ // replace this row with new row
|
if(metadataListData.size()==maxFields){ // replace this row with new row
|
||||||
adapter.notifyItemChanged(metadataListData.size()-1);
|
adapter.notifyItemChanged(metadataListData.size()-1);
|
||||||
}else{
|
}else{
|
||||||
adapter.notifyItemInserted(metadataListData.size()-1);
|
adapter.notifyItemInserted(metadataListData.size()-1);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
@@ -80,7 +81,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, Filter.FilterContext.HOME);
|
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -97,6 +98,8 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
args.putString("sourceText", status.text);
|
args.putString("sourceText", status.text);
|
||||||
args.putString("sourceSpoiler", status.spoilerText);
|
args.putString("sourceSpoiler", status.spoilerText);
|
||||||
args.putBoolean("redraftStatus", true);
|
args.putBoolean("redraftStatus", true);
|
||||||
|
args.putString("sourceContentType", scheduledStatus.params.contentType != null ?
|
||||||
|
scheduledStatus.params.contentType.name() : null);
|
||||||
setResult(true, null);
|
setResult(true, null);
|
||||||
|
|
||||||
// closing this scheduled status list if another status list is opened from compose fragment
|
// closing this scheduled status list if another status list is opened from compose fragment
|
||||||
@@ -180,4 +183,10 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
// TODO: adapt when frontends finally implement a scheduled posts list
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package org.joinmastodon.android.fragments;
|
|||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
public interface ScrollableToTop{
|
public interface ScrollableToTop{
|
||||||
boolean isScrolledToTop();
|
// boolean isScrolledToTop();
|
||||||
|
|
||||||
void scrollToTop();
|
void scrollToTop();
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ public interface ScrollableToTop{
|
|||||||
@Override
|
@Override
|
||||||
public boolean onPreDraw(){
|
public boolean onPreDraw(){
|
||||||
list.getViewTreeObserver().removeOnPreDrawListener(this);
|
list.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
list.scrollBy(0, V.dp(300));
|
list.scrollBy(0, UiUtils.SCROLL_TO_TOP_DELTA);
|
||||||
list.smoothScrollToPosition(0);
|
list.smoothScrollToPosition(0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,11 @@ import android.graphics.Rect;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.LruCache;
|
import android.util.LruCache;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -24,8 +22,6 @@ import android.view.WindowInsets;
|
|||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.LinearInterpolator;
|
import android.view.animation.LinearInterpolator;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
@@ -39,13 +35,12 @@ import android.widget.Toast;
|
|||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.DomainManager;
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.PushNotificationReceiver;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||||
@@ -55,6 +50,7 @@ import org.joinmastodon.android.api.session.AccountSession;
|
|||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
||||||
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
import org.joinmastodon.android.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
@@ -62,15 +58,18 @@ import org.joinmastodon.android.model.PushSubscription;
|
|||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Map;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
@@ -82,13 +81,13 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class SettingsFragment extends MastodonToolbarFragment{
|
public class SettingsFragment extends MastodonToolbarFragment implements ProvidesAssistContent.ProvidesWebUri {
|
||||||
private View view;
|
|
||||||
private UsableRecyclerView list;
|
private UsableRecyclerView list;
|
||||||
private ArrayList<Item> items=new ArrayList<>();
|
private ArrayList<Item> items=new ArrayList<>();
|
||||||
private ThemeItem themeItem;
|
private ThemeItem themeItem;
|
||||||
private NotificationPolicyItem notificationPolicyItem;
|
private NotificationPolicyItem notificationPolicyItem;
|
||||||
private SwitchItem showNewPostsButtonItem, glitchModeItem, compactReblogReplyLineItem;
|
private SwitchItem showNewPostsItem, glitchModeItem, compactReblogReplyLineItem, alwaysRevealSpoilersItem;
|
||||||
|
private ButtonItem defaultContentTypeButtonItem, autoRevealSpoilersItem;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private boolean needUpdateNotificationSettings;
|
private boolean needUpdateNotificationSettings;
|
||||||
private boolean needAppRestart;
|
private boolean needAppRestart;
|
||||||
@@ -97,7 +96,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
private ImageView themeTransitionWindowView;
|
private ImageView themeTransitionWindowView;
|
||||||
private TextItem checkForUpdateItem, clearImageCacheItem;
|
private TextItem checkForUpdateItem, clearImageCacheItem;
|
||||||
private ImageCache imageCache;
|
private ImageCache imageCache;
|
||||||
|
private Menu contentTypeMenu;
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -107,22 +108,20 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
imageCache = ImageCache.getInstance(getActivity());
|
imageCache = ImageCache.getInstance(getActivity());
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
Optional<Instance> instance = session.getInstance();
|
||||||
String instanceName = UiUtils.getInstanceName(accountID);
|
String instanceName = UiUtils.getInstanceName(accountID);
|
||||||
|
|
||||||
DomainManager.getInstance().setCurrentDomain(session.domain + "/settings");
|
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||||
GithubSelfUpdater.UpdateState state=updater.getState();
|
GithubSelfUpdater.UpdateState state=updater.getState();
|
||||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING && updater.getUpdateInfo() != null){
|
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING){
|
||||||
items.add(new UpdateItem());
|
items.add(new UpdateItem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_theme));
|
items.add(new HeaderItem(R.string.settings_theme));
|
||||||
|
|
||||||
items.add(themeItem=new ThemeItem());
|
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 ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
|
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 popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||||
popupMenu.inflate(R.menu.color_palettes);
|
popupMenu.inflate(R.menu.color_palettes);
|
||||||
@@ -130,35 +129,54 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
popupMenu.setOnMenuItemClickListener(SettingsFragment.this::onColorPreferenceClick);
|
popupMenu.setOnMenuItemClickListener(SettingsFragment.this::onColorPreferenceClick);
|
||||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||||
b.setOnClickListener(v->popupMenu.show());
|
b.setOnClickListener(v->popupMenu.show());
|
||||||
b.setText(switch(GlobalUserPreferences.color){
|
// b.setText(switch(GlobalUserPreferences.color){
|
||||||
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
// case MATERIAL3 -> R.string.sk_color_palette_material3;
|
||||||
case PINK -> R.string.sk_color_palette_pink;
|
// case PINK -> R.string.sk_color_palette_pink;
|
||||||
case PURPLE -> R.string.sk_color_palette_purple;
|
// case PURPLE -> R.string.sk_color_palette_purple;
|
||||||
case GREEN -> R.string.sk_color_palette_green;
|
// case GREEN -> R.string.sk_color_palette_green;
|
||||||
case BLUE -> R.string.sk_color_palette_blue;
|
// case BLUE -> R.string.sk_color_palette_blue;
|
||||||
case BROWN -> R.string.sk_color_palette_brown;
|
// case BROWN -> R.string.sk_color_palette_brown;
|
||||||
case RED -> R.string.sk_color_palette_red;
|
// case RED -> R.string.sk_color_palette_red;
|
||||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
// case YELLOW -> R.string.sk_color_palette_yellow;
|
||||||
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 ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b->{
|
||||||
items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
|
updatePublishText(b);
|
||||||
GlobalUserPreferences.disableMarquee=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
b.setOnClickListener(l->{
|
||||||
needAppRestart=true;
|
TextInputFrameLayout input = new TextInputFrameLayout(
|
||||||
|
getContext(),
|
||||||
|
getString(R.string.publish),
|
||||||
|
GlobalUserPreferences.publishButtonText.trim()
|
||||||
|
);
|
||||||
|
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(input)
|
||||||
|
.setPositiveButton(R.string.save, (d, which) -> {
|
||||||
|
GlobalUserPreferences.publishButtonText = input.getEditText().getText().toString().trim();
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
updatePublishText(b);
|
||||||
|
})
|
||||||
|
.setNeutralButton(R.string.clear, (d, which) -> {
|
||||||
|
GlobalUserPreferences.publishButtonText = "";
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
updatePublishText(b);
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
||||||
|
.show();
|
||||||
|
});
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
|
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.uniformNotificationIcon=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
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_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
|
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.reduceMotion=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_behavior));
|
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->{
|
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||||
GlobalUserPreferences.playGifs=i.checked;
|
GlobalUserPreferences.playGifs=i.checked;
|
||||||
@@ -168,107 +186,61 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.useCustomTabs=i.checked;
|
GlobalUserPreferences.useCustomTabs=i.checked;
|
||||||
GlobalUserPreferences.save();
|
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->{
|
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.showInteractionCounts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
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->{
|
items.add(alwaysRevealSpoilersItem = 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.alwaysExpandContentWarnings=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
|
if (list.findViewHolderForAdapterPosition(items.indexOf(autoRevealSpoilersItem)) instanceof ButtonViewHolder bvh) bvh.rebind();
|
||||||
|
}));
|
||||||
|
items.add(autoRevealSpoilersItem = new ButtonItem(R.string.sk_settings_auto_reveal_equal_spoilers, R.drawable.ic_fluent_eye_24_regular, b->{
|
||||||
|
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||||
|
popupMenu.inflate(R.menu.settings_auto_reveal_spoiler);
|
||||||
|
popupMenu.setOnMenuItemClickListener(i -> onAutoRevealSpoilerClick(i, b));
|
||||||
|
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||||
|
b.setOnClickListener(v->popupMenu.show());
|
||||||
|
onAutoRevealSpoilerChanged(b);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 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->{
|
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.disableSwipe=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
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->{
|
items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
||||||
GlobalUserPreferences.disableDoubleTapToSwipe=i.checked;
|
GlobalUserPreferences.enableDeleteNotifications=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
needAppRestart=true;
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
|
items.add(new SwitchItem(R.string.sk_settings_disable_alt_text_reminder, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, 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();
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.mo_mention_reblogger_automatically, R.drawable.ic_fluent_comment_mention_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.disableAltTextReminder=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
|
||||||
|
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
|
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.prefixRepliesWithRe=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
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.sk_settings_allow_remote_loading, R.drawable.ic_fluent_communication_24_regular, GlobalUserPreferences.allowRemoteLoading, i->{
|
||||||
|
GlobalUserPreferences.allowRemoteLoading=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SmallTextItem(R.string.sk_settings_allow_remote_loading_explanation));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.sk_timelines));
|
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->{
|
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.showReplies=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
if (instance.pleroma != null) {
|
if (isInstanceAkkoma()) {
|
||||||
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
|
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 popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||||
popupMenu.inflate(R.menu.reply_visibility);
|
popupMenu.inflate(R.menu.reply_visibility);
|
||||||
@@ -290,33 +262,35 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
||||||
GlobalUserPreferences.loadNewPosts=i.checked;
|
GlobalUserPreferences.loadNewPosts=i.checked;
|
||||||
showNewPostsButtonItem.enabled = i.checked;
|
showNewPostsItem.enabled = i.checked;
|
||||||
if (!i.checked) {
|
if (!i.checked) {
|
||||||
GlobalUserPreferences.showNewPostsButton = false;
|
GlobalUserPreferences.showNewPostsButton = false;
|
||||||
showNewPostsButtonItem.checked = false;
|
showNewPostsItem.checked = false;
|
||||||
}
|
}
|
||||||
if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsButtonItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||||
GlobalUserPreferences.save();
|
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->{
|
items.add(showNewPostsItem = 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.showNewPostsButton=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_alt_indicator, R.drawable.ic_fluent_scan_text_24_regular, GlobalUserPreferences.showAltIndicator, i->{
|
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.showAltIndicator=i.checked;
|
||||||
GlobalUserPreferences.save();
|
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->{
|
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.showNoAltIndicator=i.checked;
|
||||||
GlobalUserPreferences.save();
|
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->{
|
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.collapseLongPosts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_hide_interaction, R.drawable.ic_fluent_star_off_24_regular, GlobalUserPreferences.spectatorMode, i->{
|
||||||
|
GlobalUserPreferences.spectatorMode=i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
needAppRestart=true;
|
||||||
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_hide_fab, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.autoHideFab, i->{
|
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.autoHideFab=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
@@ -337,16 +311,17 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
needAppRestart=true;
|
needAppRestart=true;
|
||||||
}));
|
}));
|
||||||
compactReblogReplyLineItem.enabled=GlobalUserPreferences.replyLineAboveHeader;
|
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.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
// items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
||||||
// GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
//// GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
||||||
// GlobalUserPreferences.save();
|
// GlobalUserPreferences.save();
|
||||||
// needAppRestart=true;
|
// needAppRestart=true;
|
||||||
// }));
|
// }));
|
||||||
|
boolean translationAvailable = instance
|
||||||
|
.map(i -> i.v2 != null && i.v2.configuration.translation != null && i.v2.configuration.translation.enabled)
|
||||||
|
.orElse(false);
|
||||||
|
items.add(new SmallTextItem(getString(translationAvailable ?
|
||||||
|
R.string.sk_settings_translation_availability_note_available :
|
||||||
|
R.string.sk_settings_translation_availability_note_unavailable, instanceName)));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_notifications));
|
items.add(new HeaderItem(R.string.settings_notifications));
|
||||||
items.add(notificationPolicyItem=new NotificationPolicyItem());
|
items.add(notificationPolicyItem=new NotificationPolicyItem());
|
||||||
@@ -361,24 +336,6 @@ 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_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 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 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_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_posting, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/preferences/other"), R.drawable.ic_fluent_open_24_regular));
|
||||||
@@ -386,23 +343,55 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
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 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(instanceName));
|
items.add(new HeaderItem(instanceName));
|
||||||
items.add(new TextItem(R.string.sk_settings_rules, ()->{
|
items.add(new TextItem(R.string.sk_settings_rules, instance.<Runnable>map(i -> () -> {
|
||||||
Bundle args=new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putParcelable("instance", Parcels.wrap(instance));
|
args.putParcelable("instance", Parcels.wrap(i));
|
||||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||||
}, R.drawable.ic_fluent_task_list_ltr_24_regular));
|
}).orElse(null), 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.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_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.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));
|
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
|
||||||
if (!TextUtils.isEmpty(instance.version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, instance.version)));
|
items.add(new SmallTextItem(instance
|
||||||
|
.map(i -> getString(R.string.sk_settings_server_version, i.version))
|
||||||
|
.orElse(getString(R.string.sk_instance_info_unavailable))));
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.sk_instance_features));
|
items.add(new HeaderItem(R.string.sk_instance_features));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_content_types, 0, GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID), (i)->{
|
||||||
|
if (i.checked) {
|
||||||
|
GlobalUserPreferences.accountsWithContentTypesEnabled.add(accountID);
|
||||||
|
if (GlobalUserPreferences.accountsDefaultContentTypes.get(accountID) == null) {
|
||||||
|
GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, ContentType.PLAIN);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GlobalUserPreferences.accountsWithContentTypesEnabled.remove(accountID);
|
||||||
|
GlobalUserPreferences.accountsDefaultContentTypes.remove(accountID);
|
||||||
|
}
|
||||||
|
if (list.findViewHolderForAdapterPosition(items.indexOf(defaultContentTypeButtonItem))
|
||||||
|
instanceof ButtonViewHolder bvh) bvh.rebind();
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
|
items.add(new SmallTextItem(getString(R.string.sk_settings_content_types_explanation)));
|
||||||
|
items.add(defaultContentTypeButtonItem = new ButtonItem(R.string.sk_settings_default_content_type, 0, b->{
|
||||||
|
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||||
|
popupMenu.inflate(R.menu.compose_content_type);
|
||||||
|
popupMenu.setOnMenuItemClickListener(item -> this.onContentTypeChanged(item, b));
|
||||||
|
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||||
|
b.setOnClickListener(v->popupMenu.show());
|
||||||
|
ContentType contentType = GlobalUserPreferences.accountsDefaultContentTypes.get(accountID);
|
||||||
|
b.setText(getContentTypeString(contentType));
|
||||||
|
contentTypeMenu = popupMenu.getMenu();
|
||||||
|
contentTypeMenu.findItem(ContentType.getContentTypeRes(contentType)).setChecked(true);
|
||||||
|
instance.ifPresent(i -> ContentType.adaptMenuToInstance(contentTypeMenu, i));
|
||||||
|
}));
|
||||||
|
items.add(new SmallTextItem(getString(R.string.sk_settings_default_content_type_explanation)));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
|
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
|
||||||
glitchModeItem.enabled = i.checked;
|
glitchModeItem.enabled = i.checked;
|
||||||
if (i.checked) {
|
if (i.checked) {
|
||||||
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
|
||||||
if (instance.pleroma == null) GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
if (!isInstanceAkkoma()) {
|
||||||
|
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
|
GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
|
||||||
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
|
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
|
||||||
@@ -423,31 +412,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
glitchModeItem.enabled = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
glitchModeItem.enabled = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||||
items.add(new SmallTextItem(getString(R.string.sk_settings_glitch_mode_explanation)));
|
items.add(new SmallTextItem(getString(R.string.sk_settings_glitch_mode_explanation)));
|
||||||
|
|
||||||
|
|
||||||
boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled;
|
|
||||||
items.add(new SmallTextItem(getString(translationAvailable ?
|
|
||||||
R.string.sk_settings_translation_availability_note_available :
|
|
||||||
R.string.sk_settings_translation_availability_note_unavailable, instance.title)));
|
|
||||||
|
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.sk_settings_about));
|
items.add(new HeaderItem(R.string.sk_settings_about));
|
||||||
|
items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon"), R.drawable.ic_fluent_open_24_regular));
|
||||||
// if(BuildConfig.BUILD_TYPE.equals("nightly")){
|
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://ko-fi.com/xsk22"), R.drawable.ic_fluent_heart_24_regular));
|
||||||
// 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);
|
|
||||||
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();
|
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);
|
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(clearImageCacheItem);
|
||||||
@@ -455,16 +422,18 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.recentLanguages.remove(accountID);
|
GlobalUserPreferences.recentLanguages.remove(accountID);
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
})));
|
})));
|
||||||
|
if (GithubSelfUpdater.needSelfUpdating()) {
|
||||||
items.add(new TextItem(R.string.mo_clear_recent_emoji, ()-> {
|
items.add(new SwitchItem(R.string.sk_updater_enable_pre_releases, 0, GlobalUserPreferences.enablePreReleases, i->{
|
||||||
GlobalUserPreferences.recentEmojis.clear();
|
GlobalUserPreferences.enablePreReleases=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||||
|
items.add(checkForUpdateItem);
|
||||||
|
}
|
||||||
|
|
||||||
if(BuildConfig.DEBUG){
|
if(BuildConfig.DEBUG){
|
||||||
items.add(new RedHeaderItem("Debug options"));
|
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);
|
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
sess.activated=false;
|
sess.activated=false;
|
||||||
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
|
||||||
@@ -473,35 +442,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
args.putBoolean("debug", true);
|
args.putBoolean("debug", true);
|
||||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
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(getString(R.string.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||||
items.add(new FooterItem(version, () -> UiUtils.copyText(view, version)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePublishText(Button btn) {
|
private void updatePublishText(Button btn) {
|
||||||
@@ -560,7 +503,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
this.view = view;
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating())
|
if(GithubSelfUpdater.needSelfUpdating())
|
||||||
E.register(this);
|
E.register(this);
|
||||||
}
|
}
|
||||||
@@ -590,7 +532,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
else if (id == R.id.brown_color) pref = ColorPreference.BROWN;
|
else if (id == R.id.brown_color) pref = ColorPreference.BROWN;
|
||||||
else if (id == R.id.red_color) pref = ColorPreference.RED;
|
else if (id == R.id.red_color) pref = ColorPreference.RED;
|
||||||
else if (id == R.id.yellow_color) pref = ColorPreference.YELLOW;
|
else if (id == R.id.yellow_color) pref = ColorPreference.YELLOW;
|
||||||
else if (id == R.id.nord_color) pref = ColorPreference.NORD;
|
|
||||||
|
|
||||||
if (pref == null) return false;
|
if (pref == null) return false;
|
||||||
|
|
||||||
@@ -600,6 +541,35 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean onAutoRevealSpoilerClick(MenuItem item, Button btn) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
AutoRevealMode mode = AutoRevealMode.NEVER;
|
||||||
|
if (id == R.id.auto_reveal_threads) mode = AutoRevealMode.THREADS;
|
||||||
|
else if (id == R.id.auto_reveal_discussions) mode = AutoRevealMode.DISCUSSIONS;
|
||||||
|
|
||||||
|
GlobalUserPreferences.alwaysExpandContentWarnings = false;
|
||||||
|
GlobalUserPreferences.autoRevealEqualSpoilers = mode;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
onAutoRevealSpoilerChanged(btn);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAutoRevealSpoilerChanged(Button b) {
|
||||||
|
if (GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||||
|
b.setText(R.string.sk_settings_auto_reveal_always);
|
||||||
|
} else {
|
||||||
|
b.setText(switch(GlobalUserPreferences.autoRevealEqualSpoilers){
|
||||||
|
case THREADS -> R.string.sk_settings_auto_reveal_threads;
|
||||||
|
case DISCUSSIONS -> R.string.sk_settings_auto_reveal_discussions;
|
||||||
|
default -> R.string.sk_settings_auto_reveal_never;
|
||||||
|
});
|
||||||
|
if (alwaysRevealSpoilersItem.checked != GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||||
|
alwaysRevealSpoilersItem.checked = GlobalUserPreferences.alwaysExpandContentWarnings;
|
||||||
|
if (list.findViewHolderForAdapterPosition(items.indexOf(alwaysRevealSpoilersItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onTrueBlackThemeChanged(SwitchItem item){
|
private void onTrueBlackThemeChanged(SwitchItem item){
|
||||||
GlobalUserPreferences.trueBlackTheme=item.checked;
|
GlobalUserPreferences.trueBlackTheme=item.checked;
|
||||||
@@ -617,6 +587,34 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @StringRes int getContentTypeString(@Nullable ContentType contentType) {
|
||||||
|
if (contentType == null) return R.string.sk_content_type_unspecified;
|
||||||
|
return switch (contentType) {
|
||||||
|
case PLAIN -> R.string.sk_content_type_plain;
|
||||||
|
case HTML -> R.string.sk_content_type_html;
|
||||||
|
case MARKDOWN -> R.string.sk_content_type_markdown;
|
||||||
|
case BBCODE -> R.string.sk_content_type_bbcode;
|
||||||
|
case MISSKEY_MARKDOWN -> R.string.sk_content_type_mfm;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onContentTypeChanged(MenuItem item, Button btn){
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
ContentType contentType = null;
|
||||||
|
if (id == R.id.content_type_plain) contentType = ContentType.PLAIN;
|
||||||
|
else if (id == R.id.content_type_html) contentType = ContentType.HTML;
|
||||||
|
else if (id == R.id.content_type_markdown) contentType = ContentType.MARKDOWN;
|
||||||
|
else if (id == R.id.content_type_bbcode) contentType = ContentType.BBCODE;
|
||||||
|
else if (id == R.id.content_type_misskey_markdown) contentType = ContentType.MISSKEY_MARKDOWN;
|
||||||
|
|
||||||
|
GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, contentType);
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
btn.setText(getContentTypeString(contentType));
|
||||||
|
item.setChecked(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
|
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
|
||||||
String pref = null;
|
String pref = null;
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
@@ -686,12 +684,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
needUpdateNotificationSettings=true;
|
needUpdateNotificationSettings=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNotificationStyleChanged(SwitchItem item){
|
|
||||||
GlobalUserPreferences.uniformNotificationIcon=item.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
|
private void onNotificationsPolicyChanged(PushSubscription.Policy policy){
|
||||||
PushSubscription subscription=getPushSubscription();
|
PushSubscription subscription=getPushSubscription();
|
||||||
PushSubscription.Policy prevPolicy=subscription.policy;
|
PushSubscription.Policy prevPolicy=subscription.policy;
|
||||||
@@ -795,6 +787,16 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path(isInstanceAkkoma() ? "/about" : "/settings").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccountID() {
|
||||||
|
return accountID;
|
||||||
|
}
|
||||||
|
|
||||||
private static abstract class Item{
|
private static abstract class Item{
|
||||||
public abstract int getViewType();
|
public abstract int getViewType();
|
||||||
}
|
}
|
||||||
@@ -861,13 +863,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ColorPicker extends Item{
|
|
||||||
@Override
|
|
||||||
public int getViewType(){
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ThemeItem extends Item{
|
private static class ThemeItem extends Item{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -885,7 +880,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class SmallTextItem extends Item {
|
private class SmallTextItem extends Item {
|
||||||
private String text;
|
private final String text;
|
||||||
|
|
||||||
|
public SmallTextItem(@StringRes int text) {
|
||||||
|
this.text = getString(text);
|
||||||
|
}
|
||||||
|
|
||||||
public SmallTextItem(String text) {
|
public SmallTextItem(String text) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
@@ -929,12 +928,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
this.onClick=onClick;
|
this.onClick=onClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextItem(String text, Runnable onClick, @DrawableRes int icon){
|
|
||||||
this.text=text;
|
|
||||||
this.onClick=onClick;
|
|
||||||
this.icon=icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
return 4;
|
return 4;
|
||||||
@@ -959,11 +952,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
|
|
||||||
private class FooterItem extends Item{
|
private class FooterItem extends Item{
|
||||||
private String text;
|
private String text;
|
||||||
private Runnable onClick;
|
|
||||||
|
|
||||||
public FooterItem(String text, Runnable onClick){
|
public FooterItem(String text){
|
||||||
this.text=text;
|
this.text=text;
|
||||||
this.onClick=onClick;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1129,13 +1120,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
|
private class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
|
||||||
private final Button button;
|
private final Button button;
|
||||||
private final ImageView icon;
|
private final ImageView icon;
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
public ButtonViewHolder(){
|
public ButtonViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_button, list);
|
super(getActivity(), R.layout.item_settings_button, list);
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
@@ -1143,10 +1132,17 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
button=findViewById(R.id.button);
|
button=findViewById(R.id.button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public void onBind(ButtonItem item){
|
public void onBind(ButtonItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
icon.setImageResource(item.icon);
|
icon.setVisibility(item.icon == 0 ? View.GONE : View.VISIBLE);
|
||||||
|
icon.setImageResource(item.icon == 0 ? 0 : item.icon);
|
||||||
|
// reset listeners before letting the button consumer consume the button
|
||||||
|
// (and potentially set some listeners, but not others)
|
||||||
|
button.setOnTouchListener(null);
|
||||||
|
button.setOnClickListener(null);
|
||||||
|
button.setOnLongClickListener(null);
|
||||||
item.buttonConsumer.accept(button);
|
item.buttonConsumer.accept(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1242,7 +1238,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FooterViewHolder extends BindableViewHolder<FooterItem> implements UsableRecyclerView.Clickable{
|
private class FooterViewHolder extends BindableViewHolder<FooterItem>{
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
public FooterViewHolder(){
|
public FooterViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_footer, list);
|
super(getActivity(), R.layout.item_settings_footer, list);
|
||||||
@@ -1253,11 +1249,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
public void onBind(FooterItem item){
|
public void onBind(FooterItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(){
|
|
||||||
item.onClick.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||||
@@ -1298,10 +1289,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
||||||
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
||||||
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
||||||
text.setText(getString(R.string.mo_update_available, info.version));
|
text.setText(getString(R.string.sk_update_available, info.version));
|
||||||
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
||||||
}else{
|
}else{
|
||||||
text.setText(getString(R.string.mo_update_ready, info.version));
|
text.setText(getString(R.string.sk_update_ready, info.version));
|
||||||
button.setText(R.string.install_update);
|
button.setText(R.string.install_update);
|
||||||
}
|
}
|
||||||
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
||||||
@@ -1318,7 +1309,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
progress.removeCallbacks(progressUpdater);
|
progress.removeCallbacks(progressUpdater);
|
||||||
}
|
}
|
||||||
changelog.setText(info.changelog);
|
changelog.setText(info.changelog);
|
||||||
// changelog.setText(getString(R.string.sk_changelog, info.changelog));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateProgress(){
|
private void updateProgress(){
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@@ -24,13 +25,13 @@ import java.util.stream.Collectors;
|
|||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class StatusEditHistoryFragment extends StatusListFragment{
|
public class StatusEditHistoryFragment extends StatusListFragment{
|
||||||
private String id;
|
private String id, url;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
id=getArguments().getString("id");
|
id=getArguments().getString("id");
|
||||||
|
url=getArguments().getString("url");
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, Filter.FilterContext.HOME);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, null);
|
||||||
int idx=data.indexOf(s);
|
int idx=data.indexOf(s);
|
||||||
if(idx>=0){
|
if(idx>=0){
|
||||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||||
@@ -157,4 +158,14 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
public boolean isItemEnabled(String id){
|
public boolean isItemEnabled(String id){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return Uri.parse(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.app.assist.AssistContent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
@@ -36,9 +37,11 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
|
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
|
||||||
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
|
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
|
||||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, Filter.FilterContext.HOME);
|
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, getFilterContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Filter.FilterContext getFilterContext();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addAccountToKnown(Status s){
|
protected void addAccountToKnown(Status s){
|
||||||
if(!knownAccounts.containsKey(s.account.id))
|
if(!knownAccounts.containsKey(s.account.id))
|
||||||
@@ -64,7 +67,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
Status status=getContentStatusByID(id);
|
Status status=getContentStatusByID(id);
|
||||||
if(status==null)
|
if(status==null)
|
||||||
return;
|
return;
|
||||||
if(status.reloadWhenClicked){
|
if(status.isRemote){
|
||||||
UiUtils.lookupStatus(getContext(), status, accountID, null, status1 -> {
|
UiUtils.lookupStatus(getContext(), status, accountID, null, status1 -> {
|
||||||
status1.filterRevealed = true;
|
status1.filterRevealed = true;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
@@ -79,7 +82,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
status.filterRevealed = true;
|
status.filterRevealed = true;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelable("status", Parcels.wrap(status));
|
args.putParcelable("status", Parcels.wrap(status.clone()));
|
||||||
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
|
||||||
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
Nav.go(getActivity(), ThreadFragment.class, args);
|
||||||
@@ -194,7 +197,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig){
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo();
|
if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,58 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.DomainManager;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusContext;
|
import org.joinmastodon.android.model.StatusContext;
|
||||||
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ThreadFragment extends StatusListFragment implements DomainDisplay{
|
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
||||||
protected Status mainStatus;
|
protected Status mainStatus, updatedStatus;
|
||||||
|
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
|
||||||
@Override
|
protected boolean contextInitiallyRendered;
|
||||||
public String getDomain() {
|
|
||||||
return mainStatus.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -48,64 +64,83 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
|
|||||||
data.add(mainStatus);
|
data.add(mainStatus);
|
||||||
onAppendItems(Collections.singletonList(mainStatus));
|
onAppendItems(Collections.singletonList(mainStatus));
|
||||||
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
|
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
|
||||||
|
|
||||||
DomainManager.getInstance().setCurrentDomain(getDomain());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=super.buildDisplayItems(s);
|
List<StatusDisplayItem> items=super.buildDisplayItems(s);
|
||||||
if(s.id.equals(mainStatus.id)){
|
// "what the fuck is a deque"? yes
|
||||||
for(StatusDisplayItem item:items){
|
// (it's just so the last-added item automatically comes first when looping over it)
|
||||||
|
Deque<Integer> deleteTheseItems = new ArrayDeque<>();
|
||||||
|
|
||||||
|
// modifying hidden filtered items if status is displayed as a warning
|
||||||
|
List<StatusDisplayItem> itemsToModify =
|
||||||
|
(items.get(0) instanceof WarningFilteredStatusDisplayItem warning)
|
||||||
|
? warning.filteredItems
|
||||||
|
: items;
|
||||||
|
|
||||||
|
for(int i = 0; i < itemsToModify.size(); i++){
|
||||||
|
StatusDisplayItem item = itemsToModify.get(i);
|
||||||
|
NeighborAncestryInfo ancestryInfo = ancestryMap.get(s.id);
|
||||||
|
if (ancestryInfo != null) {
|
||||||
|
item.setAncestryInfo(
|
||||||
|
ancestryInfo.descendantNeighbor != null,
|
||||||
|
ancestryInfo.ancestoringNeighbor != null,
|
||||||
|
s.id.equals(mainStatus.id),
|
||||||
|
Optional.ofNullable(ancestryInfo.ancestoringNeighbor)
|
||||||
|
.map(ancestor -> ancestor.id.equals(mainStatus.id))
|
||||||
|
.orElse(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item instanceof ReblogOrReplyLineStatusDisplayItem &&
|
||||||
|
(!item.isDirectDescendant && item.hasAncestoringNeighbor)) {
|
||||||
|
deleteTheseItems.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s.id.equals(mainStatus.id)){
|
||||||
if(item instanceof TextStatusDisplayItem text)
|
if(item instanceof TextStatusDisplayItem text)
|
||||||
text.textSelectable=true;
|
text.textSelectable=true;
|
||||||
else if(item instanceof FooterStatusDisplayItem footer)
|
else if(item instanceof FooterStatusDisplayItem footer)
|
||||||
footer.hideCounts=true;
|
footer.hideCounts=true;
|
||||||
}
|
}
|
||||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, s.getContentStatus()));
|
}
|
||||||
|
for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem);
|
||||||
|
if(s.id.equals(mainStatus.id)) {
|
||||||
|
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, getAccountID(), s.getContentStatus()));
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
|
if (refreshing) loadMainStatus();
|
||||||
currentRequest=new GetStatusContext(mainStatus.id)
|
currentRequest=new GetStatusContext(mainStatus.id)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(StatusContext result){
|
public void onSuccess(StatusContext result){
|
||||||
if (getActivity() == null) return;
|
if (getContext() == null) return;
|
||||||
|
Map<String, Status> oldData = null;
|
||||||
if(refreshing){
|
if(refreshing){
|
||||||
|
oldData = new HashMap<>(data.size());
|
||||||
|
for (Status s : data) oldData.put(s.id, s);
|
||||||
data.clear();
|
data.clear();
|
||||||
|
ancestryMap.clear();
|
||||||
displayItems.clear();
|
displayItems.clear();
|
||||||
data.add(mainStatus);
|
data.add(mainStatus);
|
||||||
onAppendItems(Collections.singletonList(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());
|
// TODO: figure out how this code works
|
||||||
result.descendants=getDescendantsOrdered(mainStatus.id,
|
if(isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||||
result.descendants.stream()
|
|
||||||
.filter(s -> threadIds.contains(s.id))
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
result.descendants=filterStatuses(result.descendants);
|
result.descendants=filterStatuses(result.descendants);
|
||||||
result.ancestors=filterStatuses(result.ancestors);
|
result.ancestors=filterStatuses(result.ancestors);
|
||||||
|
|
||||||
|
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
||||||
|
ancestryMap.put(i.status.id, i);
|
||||||
|
}
|
||||||
|
|
||||||
if(footerProgress!=null)
|
if(footerProgress!=null)
|
||||||
footerProgress.setVisibility(View.GONE);
|
footerProgress.setVisibility(View.GONE);
|
||||||
data.addAll(result.descendants);
|
data.addAll(result.descendants);
|
||||||
@@ -114,19 +149,141 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
|
|||||||
int count=displayItems.size();
|
int count=displayItems.size();
|
||||||
if(!refreshing)
|
if(!refreshing)
|
||||||
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
||||||
prependItems(result.ancestors, !refreshing);
|
int prependedCount = prependItems(result.ancestors, !refreshing);
|
||||||
|
if (prependedCount > 0 && displayItems.get(prependedCount) instanceof ReblogOrReplyLineStatusDisplayItem) {
|
||||||
|
displayItems.remove(prependedCount);
|
||||||
|
adapter.notifyItemRemoved(prependedCount);
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Status s : data) {
|
||||||
|
Status oldStatus = oldData == null ? null : oldData.get(s.id);
|
||||||
|
// restore previous spoiler/filter revealed states when refreshing
|
||||||
|
if (oldStatus != null) {
|
||||||
|
s.spoilerRevealed = oldStatus.spoilerRevealed;
|
||||||
|
s.filterRevealed = oldStatus.filterRevealed;
|
||||||
|
} else if (GlobalUserPreferences.autoRevealEqualSpoilers != AutoRevealMode.NEVER &&
|
||||||
|
s.spoilerText != null &&
|
||||||
|
s.spoilerText.equals(mainStatus.spoilerText) &&
|
||||||
|
mainStatus.spoilerRevealed) {
|
||||||
|
if (GlobalUserPreferences.autoRevealEqualSpoilers == AutoRevealMode.DISCUSSIONS || Objects.equals(mainStatus.account.id, s.account.id)) {
|
||||||
|
s.spoilerRevealed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dataLoaded();
|
dataLoaded();
|
||||||
if(refreshing){
|
if(refreshing){
|
||||||
refreshDone();
|
refreshDone();
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
list.scrollToPosition(displayItems.size()-count);
|
list.scrollToPosition(displayItems.size()-count);
|
||||||
|
|
||||||
|
// no animation is going to happen, so proceeding to apply right now
|
||||||
|
if (data.size() == 1) {
|
||||||
|
contextInitiallyRendered = true;
|
||||||
|
// for the case that the main status has already finished loading
|
||||||
|
maybeApplyMainStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
private void loadMainStatus() {
|
||||||
|
new GetStatusByID(mainStatus.id)
|
||||||
|
.setCallback(new Callback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Status status) {
|
||||||
|
if (getContext() == null || status == null) return;
|
||||||
|
updatedStatus = status;
|
||||||
|
// for the case that the context has already loaded (and the animation has
|
||||||
|
// already finished), falling back to applying it ourselves:
|
||||||
|
maybeApplyMainStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {}
|
||||||
|
}).exec(accountID);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object maybeApplyMainStatus() {
|
||||||
|
if (updatedStatus == null || !contextInitiallyRendered) return null;
|
||||||
|
|
||||||
|
// restore revealed states for main status because it gets updated after doLoadData
|
||||||
|
updatedStatus.filterRevealed = mainStatus.filterRevealed;
|
||||||
|
updatedStatus.spoilerRevealed = mainStatus.spoilerRevealed;
|
||||||
|
|
||||||
|
// returning fired event object to facilitate testing
|
||||||
|
Object event;
|
||||||
|
if (updatedStatus.editedAt != null &&
|
||||||
|
(mainStatus.editedAt == null ||
|
||||||
|
updatedStatus.editedAt.isAfter(mainStatus.editedAt))) {
|
||||||
|
event = new StatusUpdatedEvent(updatedStatus);
|
||||||
|
} else {
|
||||||
|
event = new StatusCountersUpdatedEvent(updatedStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
mainStatus = updatedStatus;
|
||||||
|
updatedStatus = null;
|
||||||
|
E.post(event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<NeighborAncestryInfo> mapNeighborhoodAncestry(Status mainStatus, StatusContext context) {
|
||||||
|
List<NeighborAncestryInfo> ancestry = new ArrayList<>();
|
||||||
|
|
||||||
|
List<Status> statuses = new ArrayList<>(context.ancestors);
|
||||||
|
statuses.add(mainStatus);
|
||||||
|
statuses.addAll(context.descendants);
|
||||||
|
|
||||||
|
int count = statuses.size();
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
Status current = statuses.get(index);
|
||||||
|
ancestry.add(new NeighborAncestryInfo(
|
||||||
|
current,
|
||||||
|
// descendant neighbor
|
||||||
|
Optional
|
||||||
|
.ofNullable(count > index + 1 ? statuses.get(index + 1) : null)
|
||||||
|
.filter(s -> s.inReplyToId.equals(current.id))
|
||||||
|
.orElse(null),
|
||||||
|
// ancestoring neighbor
|
||||||
|
Optional.ofNullable(index > 0 ? ancestry.get(index - 1) : null)
|
||||||
|
.filter(ancestor -> Optional.ofNullable(ancestor.descendantNeighbor)
|
||||||
|
.map(ancestorsDescendant -> ancestorsDescendant.id.equals(current.id))
|
||||||
|
.orElse(false))
|
||||||
|
.map(a -> a.status)
|
||||||
|
.orElse(null)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancestry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sortStatusContext(Status mainStatus, StatusContext context) {
|
||||||
|
List<String> threadIds=new ArrayList<>();
|
||||||
|
threadIds.add(mainStatus.id);
|
||||||
|
for(Status s:context.descendants){
|
||||||
|
if(threadIds.contains(s.inReplyToId)){
|
||||||
|
threadIds.add(s.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
threadIds.add(mainStatus.inReplyToId);
|
||||||
|
for(int i=context.ancestors.size()-1; i >= 0; i--){
|
||||||
|
Status s=context.ancestors.get(i);
|
||||||
|
if(s.inReplyToId != null && threadIds.contains(s.id)){
|
||||||
|
threadIds.add(s.inReplyToId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.ancestors=context.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
|
||||||
|
context.descendants=getDescendantsOrdered(mainStatus.id,
|
||||||
|
context.descendants.stream()
|
||||||
|
.filter(s -> threadIds.contains(s.id))
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Status> getDescendantsOrdered(String id, List<Status> statuses){
|
||||||
List<Status> out=new ArrayList<>();
|
List<Status> out=new ArrayList<>();
|
||||||
for(Status s:getDirectDescendants(id, statuses)){
|
for(Status s:getDirectDescendants(id, statuses)){
|
||||||
out.add(s);
|
out.add(s);
|
||||||
@@ -138,14 +295,14 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> getDirectDescendants(String id, List<Status> statuses){
|
private static List<Status> getDirectDescendants(String id, List<Status> statuses){
|
||||||
return statuses.stream()
|
return statuses.stream()
|
||||||
.filter(s -> s.inReplyToId.equals(id))
|
.filter(s -> s.inReplyToId.equals(id))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> filterStatuses(List<Status> statuses){
|
private List<Status> filterStatuses(List<Status> statuses){
|
||||||
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
|
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext());
|
||||||
return statuses.stream()
|
return statuses.stream()
|
||||||
.filter(statusFilterPredicate)
|
.filter(statusFilterPredicate)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
@@ -167,17 +324,28 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
|
|||||||
showContent();
|
showContent();
|
||||||
if(!loaded)
|
if(!loaded)
|
||||||
footerProgress.setVisibility(View.VISIBLE);
|
footerProgress.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
list.setItemAnimator(new BetterItemAnimator() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
|
super.onAnimationFinished(viewHolder);
|
||||||
|
contextInitiallyRendered = true;
|
||||||
|
// for the case that both requests are already done (and thus won't apply it)
|
||||||
|
maybeApplyMainStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
protected void onStatusCreated(StatusCreatedEvent ev){
|
||||||
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
|
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
|
||||||
|
data.add(ev.status);
|
||||||
onAppendItems(Collections.singletonList(ev.status));
|
onAppendItems(Collections.singletonList(ev.status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isItemEnabled(String id){
|
public boolean isItemEnabled(String id){
|
||||||
return !id.equals(mainStatus.id);
|
return !id.equals(mainStatus.id) || !mainStatus.filterRevealed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -189,4 +357,52 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{
|
|||||||
public boolean wantsLightNavigationBar(){
|
public boolean wantsLightNavigationBar(){
|
||||||
return !UiUtils.isDarkTheme();
|
return !UiUtils.isDarkTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter.FilterContext getFilterContext() {
|
||||||
|
return Filter.FilterContext.THREAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return Uri.parse(mainStatus.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class NeighborAncestryInfo {
|
||||||
|
protected Status status, descendantNeighbor, ancestoringNeighbor;
|
||||||
|
|
||||||
|
protected NeighborAncestryInfo(@NonNull Status status, Status descendantNeighbor, Status ancestoringNeighbor) {
|
||||||
|
this.status = status;
|
||||||
|
this.descendantNeighbor = descendantNeighbor;
|
||||||
|
this.ancestoringNeighbor = ancestoringNeighbor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
NeighborAncestryInfo that = (NeighborAncestryInfo) o;
|
||||||
|
return status.equals(that.status)
|
||||||
|
&& Objects.equals(descendantNeighbor, that.descendantNeighbor)
|
||||||
|
&& Objects.equals(ancestoringNeighbor, that.ancestoringNeighbor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(status, descendantNeighbor, ancestoringNeighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onErrorRetryClick(){
|
||||||
|
if(preloadingFailed){
|
||||||
|
preloadingFailed=false;
|
||||||
|
V.setVisibilityAnimated(footerProgress, View.VISIBLE);
|
||||||
|
V.setVisibilityAnimated(footerError, View.GONE);
|
||||||
|
doLoadData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onErrorRetryClick();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,68 @@
|
|||||||
package org.joinmastodon.android.fragments.account_list;
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetAccountByHandle;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
public abstract class AccountRelatedAccountListFragment extends PaginatedAccountListFragment{
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public abstract class AccountRelatedAccountListFragment extends PaginatedAccountListFragment<Account> {
|
||||||
protected Account account;
|
protected Account account;
|
||||||
|
protected String initialSubtitle = "";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
|
||||||
|
if (getArguments().containsKey("remoteAccount")) {
|
||||||
|
remoteInfo = Parcels.unwrap(getArguments().getParcelable("remoteAccount"));
|
||||||
|
}
|
||||||
setTitle("@"+account.acct);
|
setTitle("@"+account.acct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
|
return base.path(isInstanceAkkoma()
|
||||||
|
? "/users/" + account.id
|
||||||
|
: '@' + account.acct).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRemoteDomain() {
|
||||||
|
return account.getDomainFromURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Account getCurrentInfo() {
|
||||||
|
return doneWithHomeInstance && remoteInfo != null ? remoteInfo : account;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MastodonAPIRequest<Account> loadRemoteInfo() {
|
||||||
|
return new GetAccountByHandle(account.acct);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AccountSession getRemoteSession() {
|
||||||
|
return Optional.ofNullable(remoteInfo)
|
||||||
|
.map(AccountSessionManager.getInstance()::tryGetAccount)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRemoteLoadingFailed() {
|
||||||
|
super.onRemoteLoadingFailed();
|
||||||
|
String prefix = initialSubtitle == null ? "" :
|
||||||
|
initialSubtitle + " " + getContext().getString(R.string.sk_separator) + " ";
|
||||||
|
String str = prefix +
|
||||||
|
getContext().getString(R.string.sk_no_remote_info_hint, getSession().domain);
|
||||||
|
setSubtitle(str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package org.joinmastodon.android.fragments.account_list;
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
@@ -23,7 +25,8 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
import org.joinmastodon.android.fragments.HasAccountID;
|
||||||
|
import org.joinmastodon.android.fragments.ListsFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.RecyclerFragment;
|
import org.joinmastodon.android.fragments.RecyclerFragment;
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||||
@@ -34,6 +37,7 @@ import org.joinmastodon.android.ui.OutlineProviders;
|
|||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -44,6 +48,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
@@ -57,7 +62,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> {
|
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> implements ProvidesAssistContent.ProvidesWebUri {
|
||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected String accountID;
|
protected String accountID;
|
||||||
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
|
||||||
@@ -79,10 +84,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
if(refreshing){
|
if(refreshing){
|
||||||
relationships.clear();
|
relationships.clear();
|
||||||
}
|
}
|
||||||
if(!d.isEmpty() && !d.get(0).account.reloadWhenClicked){
|
loadRelationships(d);
|
||||||
loadRelationships(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onDataLoaded(d, more);
|
super.onDataLoaded(d, more);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +177,16 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
super.onApplyWindowInsets(insets);
|
super.onApplyWindowInsets(insets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAccountID() {
|
||||||
|
return accountID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
|
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||||
|
}
|
||||||
|
|
||||||
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
public AccountsAdapter(){
|
public AccountsAdapter(){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
@@ -235,22 +247,20 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
@Override
|
@Override
|
||||||
public void onBind(AccountItem item){
|
public void onBind(AccountItem item){
|
||||||
name.setText(item.parsedName);
|
name.setText(item.parsedName);
|
||||||
username.setText("@"+item.account.acct);
|
username.setText("@"+ (item.account.isRemote
|
||||||
|
? item.account.getFullyQualifiedName()
|
||||||
|
: item.account.acct));
|
||||||
bindRelationship();
|
bindRelationship();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bindRelationship(){
|
public void bindRelationship(){
|
||||||
Relationship rel=relationships.get(item.account.id);
|
Relationship rel=relationships.get(item.account.id);
|
||||||
if(rel==null || AccountSessionManager.getInstance().isSelf(accountID, item.account)){
|
if(rel==null || item.account.isRemote || AccountSessionManager.getInstance().isSelf(accountID, item.account)){
|
||||||
if(item.account.reloadWhenClicked){
|
button.setVisibility(View.GONE);
|
||||||
button.setVisibility(View.VISIBLE);
|
|
||||||
button.setText(R.string.button_follow);
|
|
||||||
} else {
|
|
||||||
button.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}else{
|
}else{
|
||||||
button.setVisibility(View.VISIBLE);
|
button.setVisibility(View.VISIBLE);
|
||||||
UiUtils.setRelationshipToActionButton(rel, button);
|
UiUtils.setRelationshipToActionButton(rel, button);
|
||||||
@@ -277,18 +287,10 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
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();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
if (item.account.isRemote) args.putParcelable("remoteAccount", Parcels.wrap(item.account));
|
||||||
|
else args.putParcelable("profileAccount", Parcels.wrap(item.account));
|
||||||
Nav.go(getActivity(), ProfileFragment.class, args);
|
Nav.go(getActivity(), ProfileFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +408,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putString("profileAccount", account.id);
|
args.putString("profileAccount", account.id);
|
||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
Nav.go(getActivity(), ListsFragment.class, args);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -429,5 +431,10 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
|||||||
emojiHelper=new CustomEmojiHelper();
|
emojiHelper=new CustomEmojiHelper();
|
||||||
emojiHelper.setText(parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis));
|
emojiHelper.setText(parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
return obj instanceof AccountItem i && i.account.url.equals(account.url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments.account_list;
|
package org.joinmastodon.android.fragments.account_list;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
@@ -12,17 +13,17 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
targetAccount = account;
|
setSubtitle(initialSubtitle = getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
|
||||||
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
|
||||||
return new GetAccountFollowers(account.id, maxID, count);
|
return new GetAccountFollowers(getCurrentInfo().id, maxID, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count){
|
public Uri getWebUri(Uri.Builder base) {
|
||||||
return new GetAccountFollowers(id, maxID, count);
|
return super.getWebUri(base).buildUpon()
|
||||||
|
.appendPath(isInstanceAkkoma() ? "#followers" : "/followers").build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||