Compare commits

...

1473 Commits

Author SHA1 Message Date
LucasGGamerM
d87d656002 chore: add 105 changelog 2024-05-05 16:52:55 -03:00
LucasGGamerM
9e9061e29c build: bump version number 2024-05-05 16:49:48 -03:00
LucasGGamerM
70d0ba88e4 fix(custom-tabs-setting): use upstream implementation
Seriously, why do we do this?
2024-05-05 16:47:12 -03:00
LucasGGamerM
9cb48e2f25 fix(mute-confirmation-sheet-timer-dialog): use Ok instead of Save 2024-05-05 16:21:44 -03:00
LucasGGamerM
6f89dd7331 fix(photo-viewer-menu-icons): readd share button on non-status media viewers 2024-05-05 16:18:11 -03:00
LucasGGamerM
a59c20d239 fix(menu-icons): disable them for android 14 and higher devices
This is due to a shitty bug with android 14 and InsetDrawables which I couldn´t fix yet
2024-05-05 16:07:26 -03:00
LucasGGamerM
3e36a72852 Merge pull request #386 from FineFindus/feat/open-about-links
feat(about): try to open links in-app
2024-05-05 16:06:22 -03:00
FineFindus
7801d28a23 refactor(SettingsServerAboutFragment): always use UiUtils.openURL 2024-05-05 21:02:40 +02:00
LucasGGamerM
c2e6c802a1 Merge pull request #385 from FineFindus/feat/remote-edit-history
feat(StatusEditHistory): load remote history
2024-05-05 16:02:19 -03:00
FineFindus
2dbfe88397 feat(StatusEditHistory): display subtitle on failed remote loading 2024-05-05 21:00:07 +02:00
LucasGGamerM
60f0a3d5ee Merge pull request #384 from FineFindus/fix/save-lists
fix(lists): send requests for list membership
2024-05-04 09:59:27 -03:00
FineFindus
86506f9ba4 feat(about): try to open links in-app
The About screen often contains links the local accounts (e.g.
moderators, admins) instead of always opening them in the browser, try
to open them in-app first.
2024-04-30 19:56:29 +02:00
FineFindus
7269788831 feat(StatusEditHistory): load remote history
Sometimes the server sends only the latest status instead of the full
history. Try to avoid this by requesting the history directly from the
server.
2024-04-30 19:10:03 +02:00
FineFindus
f7d0bda90f fix(lists): send requests for list membership
Fixes a regression in 0af8dbf09b, where
lists memberships where not saved, as the requests where not send to the
server.
2024-04-30 18:21:23 +02:00
LucasGGamerM
d5cd016776 fix(push-notification-receiver): check if status is null before doing stuff 2024-04-18 13:44:17 -03:00
LucasGGamerM
849172ad7f Merge pull request #377 from FineFindus/feat/post-menu
fix(compose): disable group divider on EMUI
2024-04-17 20:17:10 -03:00
LucasGGamerM
de57ceb939 fix(thread-fragment): use itemsToModify instead of items when adding the extended footer to the ThreadFragment 2024-04-17 07:44:16 -03:00
FineFindus
bf36f12f79 fix(compose): disable group divider on EMUI 2024-04-16 09:15:48 +02:00
LucasGGamerM
e9684441c0 Merge remote-tracking branch 'weblate/master' 2024-04-15 15:55:54 -03:00
Eugen Rochko
aa4bd9d5a3 New translations strings.xml (French) 2024-04-15 15:53:22 -03:00
Eugen Rochko
86f82f88da New translations strings.xml (French) 2024-04-15 15:53:22 -03:00
Eugen Rochko
fb5c8504ab New translations strings.xml (Portuguese, Brazilian) 2024-04-15 15:53:21 -03:00
Eugen Rochko
cff824e745 New translations strings.xml (Portuguese, Brazilian) 2024-04-15 15:53:21 -03:00
Eugen Rochko
200583f492 New translations strings.xml (Japanese) 2024-04-15 15:53:21 -03:00
Eugen Rochko
49e8083c9a New translations strings.xml (Turkish) 2024-04-15 15:53:21 -03:00
Eugen Rochko
15fe3595a3 New translations strings.xml (Portuguese, Brazilian) 2024-04-15 15:53:20 -03:00
Eugen Rochko
dd7e9be803 New translations strings.xml (Turkish) 2024-04-15 15:53:03 -03:00
Eugen Rochko
8ce7987591 New translations strings.xml (Vietnamese) 2024-04-15 15:52:54 -03:00
Eugen Rochko
0b7f5f7f64 New translations strings.xml (Basque) 2024-04-15 15:52:47 -03:00
Eugen Rochko
db7d223be6 New translations strings.xml (Thai) 2024-04-15 15:52:46 -03:00
Eugen Rochko
cabbc934ec New translations strings.xml (Icelandic) 2024-04-15 15:52:46 -03:00
Eugen Rochko
c277a13d39 New translations strings.xml (Italian) 2024-04-15 15:52:46 -03:00
Eugen Rochko
404de46eee New translations strings.xml (French) 2024-04-15 15:52:38 -03:00
Eugen Rochko
b5f062ca99 New translations strings.xml (French) 2024-04-15 15:52:37 -03:00
Eugen Rochko
6d1ef8b59d New translations strings.xml (Chinese Traditional) 2024-04-15 15:52:37 -03:00
Eugen Rochko
83ced914ef New translations strings.xml (Slovenian) 2024-04-15 15:52:37 -03:00
Eugen Rochko
08a468c56f New translations strings.xml (Dutch) 2024-04-15 15:52:31 -03:00
Eugen Rochko
66cb74249e New translations strings.xml (French) 2024-04-15 15:52:31 -03:00
Eugen Rochko
2d10387404 New translations strings.xml (Dutch) 2024-04-15 15:52:31 -03:00
Eugen Rochko
60de667feb New translations strings.xml (Icelandic) 2024-04-15 15:52:25 -03:00
Eugen Rochko
d01fa27f85 New translations strings.xml (Icelandic) 2024-04-15 15:52:24 -03:00
Eugen Rochko
56560e741c New translations strings.xml (French) 2024-04-15 15:52:17 -03:00
Eugen Rochko
3d57954866 New translations strings.xml (French) 2024-04-15 15:52:17 -03:00
Eugen Rochko
dab3ad79ed New translations strings.xml (Lithuanian) 2024-04-15 15:51:20 -03:00
Eugen Rochko
0d9ac99313 New translations strings.xml (Basque) 2024-04-15 15:51:12 -03:00
Eugen Rochko
2883881402 New translations strings.xml (Armenian) 2024-04-15 15:51:04 -03:00
Eugen Rochko
760cfa0b9f New translations strings.xml (Armenian) 2024-04-15 15:51:04 -03:00
Eugen Rochko
ceb89e4dd7 New translations strings.xml (Armenian) 2024-04-15 15:51:04 -03:00
Eugen Rochko
f3c9b4e2cf New translations strings.xml (Armenian) 2024-04-15 15:51:03 -03:00
Eugen Rochko
cc07c539e0 New translations strings.xml (Japanese) 2024-04-15 15:50:50 -03:00
Eugen Rochko
57ff23e8c1 New translations strings.xml (Chinese Traditional) 2024-04-15 15:50:45 -03:00
Eugen Rochko
bfe6559690 New translations strings.xml (Russian) 2024-04-15 15:50:32 -03:00
Eugen Rochko
138e633acd New translations strings.xml (Czech) 2024-04-15 15:50:27 -03:00
Eugen Rochko
68ab66bed7 New translations strings.xml (Czech) 2024-04-15 15:50:26 -03:00
Eugen Rochko
f22de651c9 New translations strings.xml (Greek) 2024-04-15 15:50:20 -03:00
Eugen Rochko
5a46ae8d7b New translations strings.xml (Greek) 2024-04-15 15:50:19 -03:00
Eugen Rochko
297c37a72e New translations strings.xml (Hungarian) 2024-04-15 15:50:14 -03:00
Eugen Rochko
a6f654cb36 New translations strings.xml (Hungarian) 2024-04-15 15:50:14 -03:00
Eugen Rochko
f6a844138b New translations strings.xml (Thai) 2024-04-15 15:50:09 -03:00
Eugen Rochko
2d857f4f1b New translations strings.xml (Chinese Traditional) 2024-04-15 15:38:04 -03:00
Eugen Rochko
915f2ca108 New translations strings.xml (Slovenian) 2024-04-15 15:38:04 -03:00
Eugen Rochko
cd49b5f0b4 New translations strings.xml (Japanese) 2024-04-15 15:38:04 -03:00
Eugen Rochko
7d22f93b5d New translations strings.xml (Spanish) 2024-04-15 15:38:04 -03:00
Eugen Rochko
da74ae0a7c New translations strings.xml (Spanish) 2024-04-15 15:37:51 -03:00
Eugen Rochko
d67ab02443 New translations strings.xml (Spanish) 2024-04-15 15:37:50 -03:00
Eugen Rochko
c821326326 New translations strings.xml (Slovenian) 2024-04-15 15:37:44 -03:00
Eugen Rochko
464c80ed8f New translations strings.xml (Japanese) 2024-04-15 15:37:37 -03:00
Eugen Rochko
4adf250645 New translations strings.xml (Japanese) 2024-04-15 15:37:37 -03:00
Eugen Rochko
190755d2cc New translations strings.xml (Chinese Traditional) 2024-04-15 15:37:31 -03:00
Eugen Rochko
6deae2341b New translations strings.xml (Chinese Traditional) 2024-04-15 15:37:30 -03:00
Eugen Rochko
a23b39255d New translations strings.xml (Kabyle) 2024-04-15 15:37:23 -03:00
Eugen Rochko
20378cc1c5 New translations strings.xml (Scottish Gaelic) 2024-04-15 15:37:22 -03:00
Eugen Rochko
0c5f7552e0 New translations strings.xml (Bosnian) 2024-04-15 15:37:22 -03:00
Eugen Rochko
99e382c9f8 New translations strings.xml (Filipino) 2024-04-15 15:37:22 -03:00
Eugen Rochko
ec332cb867 New translations strings.xml (Burmese) 2024-04-15 15:37:22 -03:00
Eugen Rochko
4e3ddaf782 New translations strings.xml (Croatian) 2024-04-15 15:37:21 -03:00
Eugen Rochko
4a4fec06f6 New translations strings.xml (Thai) 2024-04-15 15:37:21 -03:00
Eugen Rochko
43d4978b1b New translations strings.xml (Bengali) 2024-04-15 15:37:21 -03:00
Eugen Rochko
7fbfae7388 New translations strings.xml (Indonesian) 2024-04-15 15:37:20 -03:00
Eugen Rochko
1772655c8c New translations strings.xml (Portuguese, Brazilian) 2024-04-15 15:37:20 -03:00
Eugen Rochko
6631de85e3 New translations strings.xml (Icelandic) 2024-04-15 15:37:20 -03:00
Eugen Rochko
a19a39ff51 New translations strings.xml (Galician) 2024-04-15 15:37:20 -03:00
Eugen Rochko
89f96cba52 New translations strings.xml (Vietnamese) 2024-04-15 15:37:19 -03:00
Eugen Rochko
dae32e2645 New translations strings.xml (Chinese Traditional) 2024-04-15 15:37:19 -03:00
Eugen Rochko
3af3993e1d New translations strings.xml (Chinese Simplified) 2024-04-15 15:37:19 -03:00
Eugen Rochko
9cd0b90c9a New translations strings.xml (Ukrainian) 2024-04-15 15:37:18 -03:00
Eugen Rochko
798325339f New translations strings.xml (Turkish) 2024-04-15 15:37:18 -03:00
Eugen Rochko
d17e25e7eb New translations strings.xml (Swedish) 2024-04-15 15:37:18 -03:00
Eugen Rochko
73f15caae2 New translations strings.xml (Slovenian) 2024-04-15 15:37:18 -03:00
Eugen Rochko
76c64195b0 New translations strings.xml (Portuguese) 2024-04-15 15:37:17 -03:00
Eugen Rochko
e1315eafde New translations strings.xml (Norwegian) 2024-04-15 15:37:17 -03:00
Eugen Rochko
1c9b7b8cbe New translations strings.xml (Lithuanian) 2024-04-15 15:37:17 -03:00
Eugen Rochko
f9844a7337 New translations strings.xml (Korean) 2024-04-15 15:36:54 -03:00
Eugen Rochko
4cb0eed126 New translations strings.xml (Georgian) 2024-04-15 15:36:53 -03:00
Eugen Rochko
bfab791850 New translations strings.xml (Japanese) 2024-04-15 15:36:49 -03:00
Eugen Rochko
a2767d6002 New translations strings.xml (Italian) 2024-04-15 15:36:48 -03:00
Eugen Rochko
62a65ab4c6 New translations strings.xml (Armenian) 2024-04-15 15:36:48 -03:00
Eugen Rochko
04d6ca9485 New translations strings.xml (Finnish) 2024-04-15 15:36:48 -03:00
Eugen Rochko
36620d7415 New translations strings.xml (Basque) 2024-04-15 15:36:48 -03:00
Eugen Rochko
d5e9697420 New translations strings.xml (German) 2024-04-15 15:36:47 -03:00
Eugen Rochko
e5da0681ae New translations strings.xml (Danish) 2024-04-15 15:36:47 -03:00
Eugen Rochko
863ff48bd4 New translations strings.xml (Czech) 2024-04-15 15:36:47 -03:00
Eugen Rochko
a9948ee996 New translations strings.xml (Arabic) 2024-04-15 15:36:47 -03:00
Eugen Rochko
fba231c855 New translations strings.xml (Spanish) 2024-04-15 15:36:47 -03:00
Eugen Rochko
09911e4494 New translations strings.xml (French) 2024-04-15 15:36:46 -03:00
Eugen Rochko
ff2407239f New translations strings.xml (Dutch) 2024-04-15 15:36:46 -03:00
Eugen Rochko
4d990647f3 New translations strings.xml (Persian) 2024-04-15 15:36:46 -03:00
Eugen Rochko
5ac91605ed New translations strings.xml (Catalan) 2024-04-15 15:36:45 -03:00
Eugen Rochko
4a8a3530ab New translations strings.xml (Belarusian) 2024-04-15 15:36:45 -03:00
Eugen Rochko
e8a600ff29 New translations strings.xml (Hindi) 2024-04-15 15:36:45 -03:00
Eugen Rochko
6df1cffc55 New translations strings.xml (Greek) 2024-04-15 15:36:40 -03:00
Eugen Rochko
5826b17840 New translations strings.xml (Russian) 2024-04-15 15:36:39 -03:00
Eugen Rochko
fdc052f8f1 New translations strings.xml (Polish) 2024-04-15 15:36:39 -03:00
Eugen Rochko
a59774fe78 New translations strings.xml (Hungarian) 2024-04-15 15:36:39 -03:00
Eugen Rochko
19bf9c4ee6 New translations strings.xml (Dutch) 2024-04-15 15:36:31 -03:00
edxkl
aab829ac4d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (100 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pt_BR/
2024-04-13 21:18:24 +00:00
LucasGGamerM
d047379785 fix(compose-autocomplete): add null checks to hashtags logic
Fuck java
2024-04-12 16:53:43 -03:00
LucasGGamerM
ff9587661e fix(emoji-selector): fixes crash when you typed an emoji's name and pressed the virtual keyboard's emoji suggestion
@sk22 might like this one
2024-04-12 16:29:49 -03:00
LucasGGamerM
e6a4f81b78 fix(client-filters): readd the necessary logic for them to work again 2024-04-12 14:39:33 -03:00
LucasGGamerM
038923bf8f Merge pull request #374 from FineFindus/fix/show-filter-warning
fix: replace correct filter item
2024-04-10 12:39:54 -03:00
LucasGGamerM
b0518b807e Merge pull request #373 from FineFindus/fix/open-url
fix(search): open lemmy URLs
2024-04-10 12:39:19 -03:00
LucasGGamerM
2c743b11e6 fix(unmuting): add request body when unmuting to prevent failed POST request 2024-04-10 12:37:55 -03:00
LucasGGamerM
6c519b3cb9 feat(mute-sheet): use secondary button for mute duration instead of a new row 2024-04-10 12:30:02 -03:00
FineFindus
5ed4fdbb36 fix: replace correct filter item 2024-04-10 17:22:11 +02:00
FineFindus
a844852669 fix(search): open lemmy URLs
Closes https://github.com/LucasGGamerM/moshidon/issues/349.
2024-04-10 16:45:02 +02:00
LucasGGamerM
4f3d711d2b Merge pull request #371
feat: add mute duration and notifications to mute confirmation sheet
2024-04-07 13:08:50 -03:00
LucasGGamerM
24be1c68ad fix(restriction-confirmation-sheet): make secondary button appear on top of primary button
This is the first part of the chore to readd the mute timer thing
2024-04-07 08:42:36 -03:00
FineFindus
fe96ee5f2a feat: propagate row clicks to child 2024-04-02 20:04:40 +02:00
FineFindus
e6501ad8a3 feat: add mute notifications toggle 2024-04-02 19:53:54 +02:00
FineFindus
18f8c1d29e refactor: only add body for muting requests 2024-04-02 19:34:26 +02:00
FineFindus
56540d7e7b feat: re-add mute duration option 2024-04-02 19:32:08 +02:00
FineFindus
37f63b1142 feat: add support for rows with custom views 2024-04-02 19:31:22 +02:00
LucasGGamerM
da3c19cdd0 Merge pull request #367 from Mattis142/master
Add Korean Store listing translation
2024-03-30 08:09:49 -03:00
LucasGGamerM
9a28d23be0 fix(photo-viewer-sheet): make alt text selectable again 2024-03-30 07:56:14 -03:00
LucasGGamerM
51a9c7c58e Merge branch 'fix/double_push' 2024-03-29 18:00:02 -03:00
lyyn
8e00b04f71 Fix missing case for STATUS notification title 2024-03-29 17:53:48 -03:00
LucasGGamerM
d08c64e749 Merge pull request #368 from FineFindus/fix/open-link-browser
fix: use correct openURL function
2024-03-28 17:32:44 -03:00
FineFindus
974774a913 fix: use correct openURL function 2024-03-28 20:49:51 +01:00
Mattis142
608ac60aec Delete metadata/ko/images/phoneScreenshots/test 2024-03-27 16:48:48 +01:00
Mattis142
a4586f2c0c Added Korean translation for Store listing 2024-03-27 16:48:13 +01:00
Mattis142
1da2df220b Create test 2024-03-27 16:46:53 +01:00
CDN18
13ed669423 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (100 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2024-03-25 21:18:21 +00:00
Titanium
0046fa5d9e Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (100 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2024-03-25 21:18:21 +00:00
LucasGGamerM
0931702802 fix(custom-local-timelines): revert 629262c266 and fix the crash for good
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2024-03-24 15:13:24 -03:00
LucasGGamerM
d7a229afeb Merge pull request #366 from FineFindus/feat/remove-home-longpress
feat: remove home tab longpress
2024-03-24 14:50:13 -03:00
LucasGGamerM
14f260d6a1 fix(#359): use upstream ripple overlay alpha value 2024-03-24 14:49:40 -03:00
FineFindus
2ab74de236 feat: remove home tab longpress 2024-03-23 12:03:40 +01:00
LucasGGamerM
7a4387a459 Merge pull request #364 from FineFindus/feat/replybar-longpress
feat: support longpressing replyBar
2024-03-22 18:18:50 -03:00
LucasGGamerM
629262c266 fix(custom-local-timelines): crash whenever a mention is shown
I don't know what caused this. Probably the HTML parser, but its now fixed forever, hopefully this thing does not happen again :)
2024-03-22 15:25:06 -03:00
FineFindus
a7c558afd8 feat: support longpressing replyBar 2024-03-22 13:21:31 +01:00
LucasGGamerM
8d6593e12c feat(search): add back bot indicator to account search results preview 2024-03-20 17:20:19 -03:00
LucasGGamerM
72cf5c3b59 fix(profile-fragment): add a margin to the domain button in the profile 2024-03-20 16:02:46 -03:00
LucasGGamerM
b018b817e8 Merge pull request #362
fix(profile): display username icons after server
2024-03-20 15:46:11 -03:00
LucasGGamerM
bf0c80d6b9 Merge pull request #363
fix: increase boost button witdth
2024-03-20 15:45:30 -03:00
FineFindus
daa391a877 fix: increase boost button witdth 2024-03-20 13:42:15 +01:00
FineFindus
1fedfce00d fix(profile): display username icons after server 2024-03-20 13:23:00 +01:00
gallegonovato
78805116f8 Translated using Weblate (Spanish)
Currently translated at 100.0% (38 of 38 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2024-03-20 11:53:34 +00:00
butterflyoffire
98a9966461 Translated using Weblate (French)
Currently translated at 92.0% (92 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/fr/
2024-03-20 11:53:34 +00:00
gallegonovato
2d127daf67 Translated using Weblate (Spanish)
Currently translated at 100.0% (100 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2024-03-20 11:53:34 +00:00
LucasGGamerM
2497ece9f1 fix(avatar-span): don´t check for handleClick to be null
This makes it so the avatar span is always shown, even when handleClick is null
2024-03-19 14:30:27 -03:00
LucasGGamerM
44b9f8c0ec Merge pull request #360 from FineFindus/fix/inline-reply-short
fix: display inline avatar for boosted replies
2024-03-19 14:18:11 -03:00
FineFindus
6ae234cf42 refactor(inline-avatar): find displayname using string search 2024-03-19 15:43:21 +01:00
FineFindus
39f3e72a47 fix: display inline avatar on short text 2024-03-19 09:14:09 +01:00
SomeTr
b1e2cab5fa Translated using Weblate (Ukrainian)
Currently translated at 100.0% (38 of 38 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/uk/
2024-03-18 17:53:34 +00:00
CDN18
75da570682 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (38 of 38 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hans/
2024-03-18 17:53:34 +00:00
ghose
e08718fc08 Translated using Weblate (Galician)
Currently translated at 50.0% (19 of 38 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/gl/
2024-03-18 17:53:34 +00:00
CDN18
4ebe4434f2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (100 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2024-03-18 17:53:33 +00:00
SomeTr
5cc9ecc8e0 Translated using Weblate (Ukrainian)
Currently translated at 84.0% (84 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2024-03-18 17:53:33 +00:00
ghose
e943ac1a05 Translated using Weblate (Galician)
Currently translated at 100.0% (100 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/gl/
2024-03-18 17:53:33 +00:00
locness3
c439171a08 Translated using Weblate (French)
Currently translated at 89.0% (89 of 100 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/fr/
2024-03-18 17:53:33 +00:00
LucasGGamerM
a9c6bf24fd fix(avatar-span): make the avatar appear more centered relative to the rest of the text 2024-03-17 20:16:14 -03:00
LucasGGamerM
ad4423526d fix(extended-footer): readd "last edit"'s button icon 2024-03-17 20:01:30 -03:00
LucasGGamerM
445bf28e94 fix(extended-footer): make bottom padding smaller 2024-03-17 19:57:00 -03:00
LucasGGamerM
f33edfa70f Merge branch 'feat/inline-reply-avatar' 2024-03-17 16:31:47 -03:00
LucasGGamerM
e694715dae Merge branch 'bugfix/965'
# Conflicts:
#	mastodon/src/main/AndroidManifest.xml
2024-03-17 16:25:49 -03:00
LucasGGamerM
8f69358d34 Merge pull request #357
feat: use PhotoViewerInfoSheet buttons
2024-03-17 13:39:25 -03:00
ptrwrbl
f425a73c74 Translated using Weblate (Polish)
Currently translated at 100.0% (37 of 37 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/pl/
2024-03-17 16:28:29 +00:00
ptrwrbl
1b1138242b Translated using Weblate (Polish)
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2024-03-17 16:28:29 +00:00
dogasert01
5fae68f68f Translated using Weblate (Turkish)
Currently translated at 21.2% (21 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/tr/
2024-03-17 16:28:29 +00:00
mihn
0100ecff9b Translated using Weblate (Vietnamese)
Currently translated at 98.9% (98 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/vi/
2024-03-17 16:28:29 +00:00
SomeTr
8651db933a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (37 of 37 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/uk/
2024-03-17 16:28:29 +00:00
dontobi
5082930a80 Translated using Weblate (German)
Currently translated at 100.0% (37 of 37 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/de/
2024-03-17 16:28:29 +00:00
dabid
cc8a81aa31 Translated using Weblate (Basque)
Currently translated at 2.7% (1 of 37 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/eu/
2024-03-17 16:28:29 +00:00
gallegonovato
ccb22802da Translated using Weblate (Spanish)
Currently translated at 100.0% (37 of 37 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2024-03-17 16:28:29 +00:00
dabid
c55f2056c2 Translated using Weblate (Basque)
Currently translated at 70.7% (70 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/eu/
2024-03-17 16:28:29 +00:00
dabid
6f5af40f2a Translated using Weblate (Spanish)
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2024-03-17 16:28:29 +00:00
dabid
2fb879bd0d Translated using Weblate (Basque)
Currently translated at 64.6% (64 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/eu/
2024-03-17 16:28:29 +00:00
LucasGGamerM
a95c0058d8 Merge pull request #358
feat: add instance info button to DecentralizationExplainerSheet
2024-03-17 13:28:11 -03:00
LucasGGamerM
b1b03a3ada Merge pull request #355
fix: use correct hashtag text to prevent crash
2024-03-17 13:26:19 -03:00
LucasGGamerM
6b5344bd11 Merge pull request #356
feat: display custom emoji in reply button
2024-03-17 13:23:17 -03:00
FineFindus
4d91ff3866 feat(DecentralizationExplainerSheet): add button to open about page 2024-03-17 15:55:14 +01:00
FineFindus
a488776daa feat(profile): display DecentralizationExplainerSheet by clicking on username 2024-03-17 15:53:49 +01:00
FineFindus
740ff45bb6 feat: use photo info sheet buttons 2024-03-17 15:33:16 +01:00
FineFindus
7773c08387 fix(sheet-photo-viewer-info): increase button width 2024-03-17 15:30:03 +01:00
LucasGGamerM
4a13398801 feat(profile): make the new decentralization sheet appear only when holding the instance button 2024-03-17 10:37:02 -03:00
FineFindus
685e6e947f feat: display custom emoji in reply button 2024-03-17 14:18:20 +01:00
LucasGGamerM
1dd46df540 feat(profile): add the upstream handle thingys 2024-03-17 10:11:40 -03:00
FineFindus
c73fa2dd6b fix: use correct hashtag text
Fixes a regression introduced in 0af8dbf09b, taht caused the app to crash when long pressing on a hastag to copy it
2024-03-17 14:11:32 +01:00
LucasGGamerM
ad1de60968 fix(extended-footer): make it appear on top of the standard post footer 2024-03-17 09:49:56 -03:00
LucasGGamerM
d8a88d1803 fix(extended-footer-icons): makes them look good again 2024-03-17 09:46:30 -03:00
LucasGGamerM
f29a9b6748 Revert "feat(extended-footer): use lighter background color"
This reverts commit 7c8d0cb459.
2024-03-17 09:38:29 -03:00
LucasGGamerM
60be7191aa Merge pull request #352
feat: improve extended footer
2024-03-17 09:37:22 -03:00
LucasGGamerM
307f886ea5 Merge pull request #351
feat: show reply sheet on reply button
2024-03-17 09:32:25 -03:00
LucasGGamerM
47473a6372 Merge pull request #353
fix(remote-info): remove null check
2024-03-17 09:21:38 -03:00
LucasGGamerM
25f9e60527 fix(mentions-tab): make mentions tab actually show only mentions 2024-03-17 09:16:35 -03:00
FineFindus
a7b51095fb fix(remote-info): remove null check
Fixes a regression introduced  in 76867a971b, that caused remote info to be permanently disabled
2024-03-17 00:29:33 +01:00
FineFindus
75f14371d5 feat(extended-footer): add icons to boost/fav btns 2024-03-17 00:07:57 +01:00
FineFindus
7c8d0cb459 feat(extended-footer): use lighter background color 2024-03-17 00:07:36 +01:00
FineFindus
14a36d2602 feat: show reply sheet on reply button 2024-03-16 23:34:00 +01:00
LucasGGamerM
3939fc9795 chore(make-app-compile-again): just what the actual fuck did I just do 2024-03-16 18:23:11 -03:00
LucasGGamerM
06a26d308f Merge branch 'merge-upstream'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java
#	mastodon/src/main/java/org/joinmastodon/android/ExternalShareActivity.java
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
#	mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java
#	mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/AddAccountsToList.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/CreateList.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/DeleteList.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/GetList.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/GetLists.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/RemoveAccountsFromList.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/UpdateList.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusEditHistory.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java
#	mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
#	mastodon/src/main/java/org/joinmastodon/android/events/ListDeletedEvent.java
#	mastodon/src/main/java/org/joinmastodon/android/events/ListUpdatedCreatedEvent.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/CustomLocalTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ListsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/account_list/PaginatedAccountListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/model/Status.java
#	mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
#	mastodon/src/main/java/org/joinmastodon/android/model/viewmodel/AccountViewModel.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/LinkCardStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/photoviewer/PhotoViewer.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/sheets/AccountSwitcherSheet.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/ActionModeHelper.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposeLanguageAlertViewController.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/views/ListEditor.java
#	mastodon/src/main/res/drawable/fg_link_card.xml
#	mastodon/src/main/res/drawable/ic_arrow_drop_down_24px.xml
#	mastodon/src/main/res/drawable/ic_arrow_right_24px.xml
#	mastodon/src/main/res/drawable/ic_arrow_upward_24px.xml
#	mastodon/src/main/res/drawable/ic_bookmark_fill1_24px.xml
#	mastodon/src/main/res/drawable/ic_boost_24px.xml
#	mastodon/src/main/res/drawable/ic_boost_private.xml
#	mastodon/src/main/res/drawable/ic_help_24px.xml
#	mastodon/src/main/res/drawable/ic_info_fill1_24px.xml
#	mastodon/src/main/res/drawable/ic_m3_cancel.xml
#	mastodon/src/main/res/layout/display_item_extended_footer.xml
#	mastodon/src/main/res/layout/display_item_link_card.xml
#	mastodon/src/main/res/layout/fragment_onboarding_follow_suggestions.xml
#	mastodon/src/main/res/layout/fragment_onboarding_profile_setup.xml
#	mastodon/src/main/res/layout/home_toolbar.xml
#	mastodon/src/main/res/layout/item_discover_account.xml
#	mastodon/src/main/res/layout/item_generic_list_content.xml
#	mastodon/src/main/res/menu/post.xml
#	mastodon/src/main/res/menu/profile.xml
#	mastodon/src/main/res/values-ar-rSA/strings.xml
#	mastodon/src/main/res/values-be-rBY/strings.xml
#	mastodon/src/main/res/values-ca-rES/strings.xml
#	mastodon/src/main/res/values-cs-rCZ/strings.xml
#	mastodon/src/main/res/values-da-rDK/strings.xml
#	mastodon/src/main/res/values-eu-rES/strings.xml
#	mastodon/src/main/res/values-fa-rIR/strings.xml
#	mastodon/src/main/res/values-fi-rFI/strings.xml
#	mastodon/src/main/res/values-fil-rPH/strings.xml
#	mastodon/src/main/res/values-fr-rFR/strings.xml
#	mastodon/src/main/res/values-gd-rGB/strings.xml
#	mastodon/src/main/res/values-gl-rES/strings.xml
#	mastodon/src/main/res/values-hu-rHU/strings.xml
#	mastodon/src/main/res/values-hy-rAM/strings.xml
#	mastodon/src/main/res/values-in-rID/strings.xml
#	mastodon/src/main/res/values-is-rIS/strings.xml
#	mastodon/src/main/res/values-it-rIT/strings.xml
#	mastodon/src/main/res/values-ja-rJP/strings.xml
#	mastodon/src/main/res/values-kab/strings.xml
#	mastodon/src/main/res/values-ko-rKR/strings.xml
#	mastodon/src/main/res/values-nl-rNL/strings.xml
#	mastodon/src/main/res/values-pl-rPL/strings.xml
#	mastodon/src/main/res/values-pt-rBR/strings.xml
#	mastodon/src/main/res/values-pt-rPT/strings.xml
#	mastodon/src/main/res/values-ro-rRO/strings.xml
#	mastodon/src/main/res/values-ru-rRU/strings.xml
#	mastodon/src/main/res/values-sl-rSI/strings.xml
#	mastodon/src/main/res/values-sv-rSE/strings.xml
#	mastodon/src/main/res/values-tr-rTR/strings.xml
#	mastodon/src/main/res/values-uk-rUA/strings.xml
#	mastodon/src/main/res/values-zh-rCN/strings.xml
#	mastodon/src/main/res/values/attrs.xml
#	mastodon/src/main/res/values/palettes.xml
#	mastodon/src/main/res/values/strings.xml
#	mastodon/src/main/res/values/styles.xml
2024-03-16 18:21:22 -03:00
LucasGGamerM
d1656a525e fix: make action button text on account card look decent again 2024-03-16 17:59:17 -03:00
LucasGGamerM
e9b6acb92d feat: readd prereply sheets from upstream back into the code 2024-03-04 21:00:30 -03:00
LucasGGamerM
75e8d738a4 fix: crash when opening other people's profiles 2024-03-04 20:42:34 -03:00
LucasGGamerM
ec02aed557 feat: add the new block confirmation sheet from upstream 2024-03-04 20:25:24 -03:00
LucasGGamerM
20d0acc88c chore: make the mute confirmation dialog work
TODO: Readd the time option
2024-03-04 16:40:10 -03:00
LucasGGamerM
71dd974d19 chore: readd all the new bottom sheet code 2024-03-04 16:29:57 -03:00
LucasGGamerM
0d7e10ca8a fix(status-header): use correct icon for manage user lists menu entry 2024-03-03 16:46:03 -03:00
LucasGGamerM
fa31b0f2d6 fix(status-header): use correct name for manage user lists and remove duplicate copy link menu entrys 2024-03-03 16:44:06 -03:00
LucasGGamerM
a91e08fe1a fix(onboarding-follow-suggestions): use the tabbar background drawable there
This makes the fragment look good
2024-03-03 16:43:22 -03:00
LucasGGamerM
3ea8b370bb fix(link-card): use placeholder image when there is no card image
And round off the corners too
2024-03-03 16:25:09 -03:00
LucasGGamerM
02eb178443 fix(onboarding-follow-suggestions): remove non-wanted code and fix a crash 2024-03-03 16:04:42 -03:00
LucasGGamerM
e8b77d0dfa fix(list-members fragment): fix compiler cries and readd some code 2024-03-03 15:42:17 -03:00
LucasGGamerM
f880d4b7b6 fix(snackbars): add the required colors to pallete to make them look good 2024-03-03 12:03:57 -03:00
LucasGGamerM
f5514e35e1 fix(extended-footer): use proper colors for extended footer 2024-03-03 11:50:44 -03:00
LucasGGamerM
193ef88814 fix(extended-footer): readd visibility indicator 2024-03-03 11:43:13 -03:00
LucasGGamerM
7de58a5e35 fix(extended-footer): make everything align 2024-03-03 11:35:38 -03:00
LucasGGamerM
633261c16b chore(merge-upstream): commenting out the lines that made the compilation fail 2024-03-03 11:02:12 -03:00
LucasGGamerM
e295c8c381 chore(merge-upstream): readd has subtitle method to BaseAccountListFragment 2024-03-03 10:33:49 -03:00
LucasGGamerM
e71232ad40 chore(merge-upstream): use extended footer from upstream for now 2024-03-03 10:23:38 -03:00
Steph Kraemer
123ed6c56d Fix for #965 - don't backup accounts.json 2024-03-01 20:22:33 -05:00
LucasGGamerM
cf31a1be57 chore(merge-upstream): a bunch of stuff is still left to be done :D 2024-02-27 20:53:32 -03:00
LucasGGamerM
64e82bdeed chore(merge-upstream): still 21 things to do 2024-02-18 14:19:05 -03:00
LucasGGamerM
76867a971b chore(merge-upstream): more conflict solving tomfoolery 2024-02-16 20:21:38 -03:00
LucasGGamerM
0af8dbf09b chore(merging-upstream): bunch of conflicts to solve 2024-02-14 21:08:16 -03:00
Grishka
8dffbff97c Domain badges & info sheet & my fanciest animation yet 2024-02-13 07:31:42 +03:00
Grishka
efb8cd565b Update locales 2024-02-12 21:40:28 +03:00
Grishka
1f5bdb975b Merge branch 'l10n_master' 2024-02-12 21:38:00 +03:00
Grishka
22dfc33974 Update strings 2024-02-12 21:37:44 +03:00
FineFindus
2071602607 feat: display boost timestamp 2024-02-10 14:54:58 +01:00
FineFindus
72940832a6 fix(ReblogOrReplyLineStatusDisplayItem): only display avatar for bossting 2024-02-10 13:55:31 +01:00
FineFindus
8e984a7cad feat: inline avatars for reblogline 2024-02-09 23:13:50 +01:00
Grishka
6915d19fb4 fix 2024-02-09 03:38:00 +03:00
Grishka
ad2ef39ace AND-122 Mute, block, and domain block confirmation screens 2024-02-09 03:27:05 +03:00
Eugen Rochko
3cff655e6f New translations strings.xml (Persian) 2024-02-08 14:34:47 +01:00
Eugen Rochko
ed86a5a3e8 New translations strings.xml (Russian) 2024-02-07 19:48:30 +01:00
Eugen Rochko
f329435f51 New translations strings.xml (Catalan) 2024-02-06 18:19:58 +01:00
S1m
c8604ad68e Fix NullPointerException with double push 2024-02-06 09:14:31 +01:00
Eugen Rochko
6a6a80bcd7 New translations strings.xml (Belarusian) 2024-02-03 17:27:48 +01:00
Eugen Rochko
62e4983f02 New translations strings.xml (Hindi) 2024-02-03 09:56:54 +01:00
Eugen Rochko
6dfd991e87 New translations strings.xml (Hindi) 2024-02-03 08:52:38 +01:00
Eugen Rochko
e205462bf4 New translations short_description.txt (Hindi) 2024-02-03 07:47:35 +01:00
Eugen Rochko
03f341f6f8 New translations full_description.txt (Hindi) 2024-02-03 07:47:34 +01:00
Eugen Rochko
b9b08c5ea7 New translations strings.xml (Hindi) 2024-02-03 07:47:33 +01:00
Eugen Rochko
2b5498ff5d New translations full_description.txt (Hindi) 2024-02-03 06:46:31 +01:00
Eugen Rochko
84b058873d New translations strings.xml (Hindi) 2024-02-03 06:46:30 +01:00
Eugen Rochko
fcf5c0822e New translations strings.xml (Hindi) 2024-02-03 05:07:26 +01:00
Eugen Rochko
53c3da6a3d New translations strings.xml (Hindi) 2024-02-02 18:19:31 +01:00
Eugen Rochko
68371c9a0f New translations strings.xml (Hindi) 2024-02-02 16:58:56 +01:00
Grishka
e7295aac07 Fix #770 2024-02-02 15:34:06 +01:00
Eugen Rochko
ae7f65954a New translations strings.xml (Hindi) 2024-02-02 15:30:16 +01:00
Grishka
350a73c3eb Fix ripple color on Android 14
closes #767
2024-02-02 15:23:52 +01:00
LucasGGamerM
85b135fa34 docs: add 104 changelog 2024-01-27 10:28:56 -03:00
LucasGGamerM
a195aa56ca build: bump version number 2024-01-27 10:24:28 -03:00
butterflyoffire
b694329bda Translated using Weblate (Arabic (Algeria))
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ar_DZ/
2024-01-27 13:21:36 +00:00
CDN18
79362b59c4 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (36 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hans/
2024-01-27 13:21:36 +00:00
CDN18
fa51b6acb2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (36 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hans/
2024-01-27 13:21:36 +00:00
CDN18
0aa277ee72 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2024-01-27 13:21:36 +00:00
Linerly
d111be7293 Translated using Weblate (Indonesian)
Currently translated at 100.0% (36 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/id/
2024-01-27 13:21:36 +00:00
Linerly
dd06292b53 Translated using Weblate (Indonesian)
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/id/
2024-01-27 13:21:36 +00:00
SomeTr
98953e8afa Translated using Weblate (Ukrainian)
Currently translated at 100.0% (36 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/uk/
2024-01-27 13:21:36 +00:00
legiz
8a8cefe68a Translated using Weblate (Russian)
Currently translated at 22.2% (8 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/ru/
2024-01-27 13:21:36 +00:00
legiz
bbd72e299b Translated using Weblate (Russian)
Currently translated at 96.9% (96 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ru/
2024-01-27 13:21:36 +00:00
gallegonovato
24b227f665 Translated using Weblate (Spanish)
Currently translated at 100.0% (36 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2024-01-27 13:21:36 +00:00
gallegonovato
71118b1f84 Translated using Weblate (Spanish)
Currently translated at 97.2% (35 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2024-01-27 13:21:36 +00:00
rffontenelle
095e1543fe Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pt_BR/
2024-01-27 13:21:36 +00:00
alextecplayz
7bdd9811da Translated using Weblate (Romanian)
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ro/
2024-01-27 13:21:36 +00:00
dontobi
c086900ae6 Translated using Weblate (German)
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/de/
2024-01-27 13:21:36 +00:00
dontobi
143df89855 Translated using Weblate (German)
Currently translated at 100.0% (36 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/de/
2024-01-27 13:21:36 +00:00
RTRedreovic
b0da6897a0 Translated using Weblate (Esperanto)
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/eo/
2024-01-27 13:21:36 +00:00
SomeTr
b798bbf234 Translated using Weblate (Ukrainian)
Currently translated at 97.2% (35 of 36 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/uk/
2024-01-27 13:21:36 +00:00
SomeTr
93ba515a29 Translated using Weblate (Ukrainian)
Currently translated at 83.8% (83 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2024-01-27 13:21:36 +00:00
ghose
df5bfa77c6 Translated using Weblate (Galician)
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/gl/
2024-01-27 13:21:36 +00:00
gallegonovato
797293fc4b Translated using Weblate (Spanish)
Currently translated at 100.0% (99 of 99 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2024-01-27 13:21:36 +00:00
Eugen Rochko
66d8ba9b5d New translations strings.xml (Hungarian) 2024-01-26 21:49:50 +01:00
Eugen Rochko
f944b12f45 New translations strings.xml (Hungarian) 2024-01-26 20:20:12 +01:00
Eugen Rochko
61928a1cf0 New translations strings.xml (Greek) 2024-01-26 16:15:35 +01:00
Eugen Rochko
f06196802e New translations strings.xml (Greek) 2024-01-26 11:34:11 +01:00
Grishka
e162833ad7 Update fastlane screenshots (closes #633) 2024-01-26 13:28:43 +03:00
Eugen Rochko
936ffdc793 New translations strings.xml (Swedish) 2024-01-25 21:43:29 +01:00
Eugen Rochko
0bbf6abc0c New translations strings.xml (Russian) 2024-01-25 11:03:38 +01:00
Gregory K
5552dc2ac6 Merge pull request #769 from jixiaoyong/master
fix: NullPointerException crash while change post language twice
2024-01-25 11:14:52 +03:00
JI,XIAOYONG
a65d6fbeb3 fix: NullPointerException crash while change post language twice
Closes https://github.com/mastodon/mastodon-android/issues/766
2024-01-25 15:54:15 +08:00
Eugen Rochko
43612ffbc1 New translations strings.xml (Polish) 2024-01-24 20:55:45 +01:00
Eugen Rochko
971881bbd3 New translations strings.xml (Slovenian) 2024-01-21 13:00:18 +01:00
Eugen Rochko
390cc6b65d New translations strings.xml (Slovenian) 2024-01-21 12:01:14 +01:00
Eugen Rochko
ee31288769 New translations strings.xml (Thai) 2024-01-20 22:09:59 +01:00
Eugen Rochko
401986af29 New translations strings.xml (Galician) 2024-01-19 15:25:19 +01:00
Eugen Rochko
e41e89c5cd New translations strings.xml (Hungarian) 2024-01-19 15:25:18 +01:00
Eugen Rochko
53de0cfc63 New translations strings.xml (Polish) 2024-01-19 14:07:59 +01:00
Eugen Rochko
e68481395f New translations strings.xml (Polish) 2024-01-19 13:07:50 +01:00
Eugen Rochko
9a361e0688 New translations strings.xml (Czech) 2024-01-16 15:39:51 +01:00
Eugen Rochko
b8cce74824 New translations strings.xml (Portuguese) 2024-01-16 14:39:36 +01:00
Eugen Rochko
f1ad6fc511 New translations strings.xml (Hungarian) 2024-01-16 14:39:35 +01:00
Eugen Rochko
2aa4cc1a88 New translations strings.xml (Czech) 2024-01-16 14:39:34 +01:00
Eugen Rochko
fb17ba4777 New translations strings.xml (Portuguese, Brazilian) 2024-01-16 12:35:13 +01:00
Eugen Rochko
6e3c464c97 New translations strings.xml (Portuguese) 2024-01-16 12:35:12 +01:00
Eugen Rochko
640e5163a8 New translations strings.xml (Portuguese, Brazilian) 2024-01-16 10:41:19 +01:00
Eugen Rochko
fdd3f2f398 New translations strings.xml (Italian) 2024-01-16 00:28:25 +01:00
Eugen Rochko
dfc55a13b8 New translations strings.xml (Italian) 2024-01-15 23:26:28 +01:00
Eugen Rochko
5a83b79ac2 New translations strings.xml (Polish) 2024-01-15 01:45:23 +01:00
Eugen Rochko
7d954ab3c2 New translations strings.xml (Polish) 2024-01-15 00:43:45 +01:00
Eugen Rochko
ec0b830f4f New translations strings.xml (Polish) 2024-01-14 23:43:15 +01:00
Eugen Rochko
26256b67d3 New translations strings.xml (Polish) 2024-01-14 20:48:10 +01:00
Eugen Rochko
2f9d60b9c0 New translations strings.xml (Polish) 2024-01-14 19:51:03 +01:00
Eugen Rochko
499a325bc8 New translations strings.xml (Polish) 2024-01-14 18:07:35 +01:00
LucasGGamerM
a1a4c59b83 fix(polls): maybe fix on vote poll crash issues 2024-01-12 20:51:59 -03:00
Eugen Rochko
97ca2634a0 New translations strings.xml (Lithuanian) 2024-01-12 16:34:20 +01:00
Eugen Rochko
6630f0f8da New translations strings.xml (Lithuanian) 2024-01-12 14:51:16 +01:00
Eugen Rochko
129b253176 New translations strings.xml (Armenian) 2024-01-11 20:43:52 +01:00
Eugen Rochko
c2382d065e New translations strings.xml (Armenian) 2024-01-11 19:46:15 +01:00
LucasGGamerM
38f74c96bf feat(metadata): use @luis142's in store listing images! 2024-01-10 21:20:36 -03:00
Eugen Rochko
085264755a New translations strings.xml (Chinese Traditional) 2024-01-08 14:54:56 +01:00
Eugen Rochko
baac955e52 New translations strings.xml (Chinese Traditional) 2024-01-08 13:30:18 +01:00
Eugen Rochko
4a0501209a New translations strings.xml (Turkish) 2024-01-07 21:08:49 +01:00
Eugen Rochko
e471b36d39 New translations strings.xml (Japanese) 2024-01-07 09:53:25 +01:00
Eugen Rochko
5c2961cf7c New translations strings.xml (Japanese) 2024-01-07 08:56:52 +01:00
Eugen Rochko
6e980f17c6 New translations strings.xml (Vietnamese) 2024-01-07 06:43:58 +01:00
Eugen Rochko
2860ce8755 New translations strings.xml (Vietnamese) 2024-01-07 05:25:19 +01:00
Eugen Rochko
ca25a868a0 New translations strings.xml (Thai) 2024-01-04 21:01:23 +01:00
Eugen Rochko
74f3bd5905 New translations strings.xml (Thai) 2024-01-04 19:45:26 +01:00
Eugen Rochko
e0a53b4296 New translations strings.xml (Slovenian) 2024-01-04 14:57:53 +01:00
Eugen Rochko
c20f043f38 New translations strings.xml (Icelandic) 2024-01-04 12:50:19 +01:00
Eugen Rochko
daf3005178 New translations strings.xml (Dutch) 2024-01-04 11:32:03 +01:00
Eugen Rochko
d17e24faae New translations strings.xml (Scottish Gaelic) 2024-01-03 22:18:18 +01:00
Eugen Rochko
0cd17accf9 New translations strings.xml (Thai) 2024-01-03 22:18:13 +01:00
Eugen Rochko
65f7b97e60 New translations strings.xml (Persian) 2024-01-03 22:18:11 +01:00
Eugen Rochko
c7324285f3 New translations strings.xml (Indonesian) 2024-01-03 22:18:10 +01:00
Eugen Rochko
6bc795ebea New translations strings.xml (Portuguese, Brazilian) 2024-01-03 22:18:09 +01:00
Eugen Rochko
f2616cdd58 New translations strings.xml (Galician) 2024-01-03 22:18:08 +01:00
Eugen Rochko
d50f65ffd8 New translations strings.xml (Vietnamese) 2024-01-03 22:18:07 +01:00
Eugen Rochko
b39b2d0544 New translations strings.xml (Chinese Traditional) 2024-01-03 22:18:06 +01:00
Eugen Rochko
cdaaa91bcc New translations strings.xml (Ukrainian) 2024-01-03 22:18:05 +01:00
Eugen Rochko
109dca0b8a New translations strings.xml (Turkish) 2024-01-03 22:18:03 +01:00
Eugen Rochko
ee87da564b New translations strings.xml (Swedish) 2024-01-03 22:18:02 +01:00
Eugen Rochko
b143559a0f New translations strings.xml (Russian) 2024-01-03 22:18:01 +01:00
Eugen Rochko
9b89727c80 New translations strings.xml (Polish) 2024-01-03 22:17:59 +01:00
Eugen Rochko
68a252c85c New translations strings.xml (Norwegian) 2024-01-03 22:17:58 +01:00
Eugen Rochko
d99cb91e89 New translations strings.xml (Lithuanian) 2024-01-03 22:17:57 +01:00
Eugen Rochko
38879cd2fe New translations strings.xml (Korean) 2024-01-03 22:17:56 +01:00
Eugen Rochko
af4d98a48b New translations strings.xml (Japanese) 2024-01-03 22:17:54 +01:00
Eugen Rochko
39bb93d650 New translations strings.xml (Italian) 2024-01-03 22:17:53 +01:00
Eugen Rochko
0a3568f424 New translations strings.xml (Hungarian) 2024-01-03 22:17:52 +01:00
Eugen Rochko
e0b45720f0 New translations strings.xml (Finnish) 2024-01-03 22:17:50 +01:00
Eugen Rochko
f5b7024bb5 New translations strings.xml (Basque) 2024-01-03 22:17:49 +01:00
Eugen Rochko
f1bfa1f598 New translations strings.xml (Greek) 2024-01-03 22:17:48 +01:00
Eugen Rochko
653304f9a4 New translations strings.xml (German) 2024-01-03 22:17:47 +01:00
Eugen Rochko
3d416a038a New translations strings.xml (Danish) 2024-01-03 22:17:46 +01:00
Eugen Rochko
e0eeb87182 New translations strings.xml (Czech) 2024-01-03 22:17:45 +01:00
Eugen Rochko
2570a86da9 New translations strings.xml (Belarusian) 2024-01-03 22:17:43 +01:00
Eugen Rochko
7b110f16b3 New translations strings.xml (Arabic) 2024-01-03 22:17:42 +01:00
Eugen Rochko
d170e87325 New translations strings.xml (Spanish) 2024-01-03 22:17:41 +01:00
Eugen Rochko
4a60f0c576 New translations strings.xml (Icelandic) 2024-01-03 22:17:39 +01:00
Eugen Rochko
4b5e9d604c New translations strings.xml (French) 2024-01-03 22:17:38 +01:00
Eugen Rochko
f9562d5087 New translations strings.xml (Chinese Simplified) 2024-01-03 22:17:37 +01:00
Eugen Rochko
786091c0a4 New translations strings.xml (Armenian) 2024-01-03 22:17:36 +01:00
Eugen Rochko
436b8240ef New translations strings.xml (Slovenian) 2024-01-03 22:17:35 +01:00
Eugen Rochko
e7253dcf97 New translations strings.xml (Dutch) 2024-01-03 22:17:34 +01:00
Grishka
48f9aabaf7 Support for invite links (AND-90) 2024-01-03 23:51:35 +03:00
Eugen Rochko
14d353ae27 New translations strings.xml (Icelandic) 2024-01-03 14:57:33 +01:00
Eugen Rochko
9a82846b84 New translations strings.xml (Icelandic) 2024-01-02 15:54:39 +01:00
Eugen Rochko
a4c9bbadc4 New translations strings.xml (Icelandic) 2024-01-02 14:01:52 +01:00
Eugen Rochko
fa70c55084 New translations strings.xml (French) 2023-12-30 16:27:55 +01:00
Eugen Rochko
8d0a89fb06 New translations strings.xml (Chinese Simplified) 2023-12-29 18:47:34 +01:00
Eugen Rochko
3caf6cb94c New translations strings.xml (Chinese Simplified) 2023-12-29 17:46:24 +01:00
LucasGGamerM
9f29d72212 chore: change order of toolbar icons in photoViewer 2023-12-27 21:09:29 -03:00
Eugen Rochko
f4854061ea New translations strings.xml (Armenian) 2023-12-27 21:54:36 +01:00
Eugen Rochko
bf7607674e New translations strings.xml (Armenian) 2023-12-27 20:06:13 +01:00
LucasGGamerM
9786e324b7 style: fix typo in PhotoViewer error logs 2023-12-27 10:34:59 -03:00
LucasGGamerM
a7fef67d48 fix: swap saveViaDownloadManager to shareAfterDownloading in failed sharing action
I forgot to change the functions when I wrote it
2023-12-27 10:12:32 -03:00
LucasGGamerM
30726cb364 feat: allow sharing attachments from PhotoViewer
Addresses #290
2023-12-27 09:59:13 -03:00
LucasGGamerM
e0ff1f6725 fix(#307): use longs instead of ints in Pleroma/Akkoma instance poll configs
Who the hell had the idea to use longs for this >:C
2023-12-26 18:05:48 -03:00
LucasGGamerM
d8cc578537 fix(profile): take away favorites from the topbar icons list
This fixes a thing where the menu entry would just be weird
2023-12-24 23:20:48 -03:00
LucasGGamerM
f9f863ea5e fix(profile-note): add noteEdit must not be empty before hiding the note button 2023-12-23 15:37:23 -03:00
LucasGGamerM
2efb79f6cb fix(profile-note): hide keyboard when edit note box goes away 2023-12-23 10:10:46 -03:00
LucasGGamerM
d534557915 fix(profile): throw share button back into 3-dot menu 2023-12-23 10:03:06 -03:00
LucasGGamerM
68ebc1aa93 feat: swap note button icon 2023-12-23 10:01:35 -03:00
LucasGGamerM
251d90e0ec Merge pull request #321 from FineFindus/fix/profile-appbar-menu
feat: move icons to profile appbar
2023-12-23 09:46:13 -03:00
FineFindus
ccd313533b feat(profile): add note delete icon 2023-12-23 11:54:06 +01:00
FineFindus
f70ea97d9e feat(profile): always show add note and share 2023-12-23 11:28:54 +01:00
FineFindus
3c9efdbbf2 feat(profile): save note onHidden 2023-12-23 11:27:07 +01:00
FineFindus
3bd67f9ab1 feat(profile): move favorites to overflow menu 2023-12-23 11:25:36 +01:00
FineFindus
42946eca44 Revert "feat: make profile note box be visible at all times"
This reverts commit d6c05f0850.
2023-12-23 11:23:51 +01:00
LucasGGamerM
b2d502ae79 feat: add open in browser button to instance about fragment 2023-12-22 10:09:28 -03:00
LucasGGamerM
4d9f625ff4 refactor(statusDisplayItem): move status hidden filter check to start of method to allow an early return
This optimises things by not computing the list of stuff when the status is simply not going to be rendered
2023-12-21 20:01:52 -03:00
LucasGGamerM
18e3fadb26 refactor(profile-note): remove save button, automatically save on focus change/exit 2023-12-21 19:42:29 -03:00
LucasGGamerM
0364d95300 feat: make profile note box be consistent with the other text input boxes in the app 2023-12-21 19:34:47 -03:00
LucasGGamerM
d6c05f0850 feat: make profile note box be visible at all times 2023-12-21 18:38:59 -03:00
Eugen Rochko
137a8ca27b New translations strings.xml (Slovenian) 2023-12-21 22:08:11 +01:00
Eugen Rochko
b9ed4e0ee2 New translations strings.xml (Dutch) 2023-12-21 15:36:39 +01:00
Eugen Rochko
bc04672d32 New translations strings.xml (Dutch) 2023-12-21 12:26:19 +01:00
Eugen Rochko
70c668ecf1 New translations strings.xml (Polish) 2023-12-17 22:13:43 +01:00
LucasGGamerM
5cc94fa2b0 fix: hopefully this addresses a crash when opening an unsent post with an audio file (again)
Crash fix hopefully
2023-12-17 17:20:36 -03:00
Eugen Rochko
64bbe2c438 New translations strings.xml (Polish) 2023-12-17 21:16:34 +01:00
LucasGGamerM
08d4c135ea fix: hopefully this addresses a crash when opening an unsent post with an audio file
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
2023-12-17 17:04:22 -03:00
LucasGGamerM
6220a20734 fix: crash when publishing a scheduled post with the relocated publish button and an error occurs
Goddamnit, at least its fixed
2023-12-17 16:48:30 -03:00
LucasGGamerM
f1c55aa5e8 fix: crash that sometimes happens when longclicking emoji in compose
I have no idea how it happens, nor why. But I am sure this will fix it
2023-12-17 13:23:09 -03:00
Eugen Rochko
32209e766e New translations strings.xml (Greek) 2023-12-17 11:29:11 +01:00
Eugen Rochko
99349cff0a New translations strings.xml (Persian) 2023-12-17 09:35:27 +01:00
Eugen Rochko
74ca1961e0 New translations strings.xml (Spanish) 2023-12-17 03:38:08 +01:00
Eugen Rochko
ef6ba7fe0c New translations strings.xml (Spanish) 2023-12-17 02:07:50 +01:00
Eugen Rochko
9ace2b71cc New translations strings.xml (Spanish) 2023-12-16 22:08:55 +01:00
Eugen Rochko
0c54654b8b New translations strings.xml (Basque) 2023-12-16 20:25:55 +01:00
Eugen Rochko
bf686309fb New translations strings.xml (Basque) 2023-12-16 18:48:35 +01:00
Eugen Rochko
ce4f46537b New translations strings.xml (Spanish) 2023-12-16 17:44:05 +01:00
Eugen Rochko
4c43207f17 New translations strings.xml (Spanish) 2023-12-16 16:46:39 +01:00
LucasGGamerM
b8fe1fd640 build: not compile appkit unnecessarily on nightly builds 2023-12-15 18:21:28 -03:00
LucasGGamerM
928506b360 build: comment out release signing config because nightly refuses to build with it
Wtf is wrong with this
2023-12-15 18:17:21 -03:00
LucasGGamerM
167a2e1e2f build: add release signing config for fdroidRelease 2023-12-15 13:04:06 -03:00
Eugen Rochko
afe5bcd1f3 New translations strings.xml (Portuguese, Brazilian) 2023-12-15 17:01:27 +01:00
Eugen Rochko
3bda81bd43 New translations strings.xml (Portuguese, Brazilian) 2023-12-15 16:02:20 +01:00
Eugen Rochko
7339b2325f New translations strings.xml (Armenian) 2023-12-15 12:30:11 +01:00
Eugen Rochko
ee84a9ee7e New translations strings.xml (Armenian) 2023-12-15 11:09:33 +01:00
Eugen Rochko
fef594150a New translations strings.xml (Turkish) 2023-12-13 17:09:47 +01:00
Eugen Rochko
10371f69cb New translations strings.xml (Turkish) 2023-12-13 15:52:37 +01:00
Eugen Rochko
75cf3d76fb New translations strings.xml (Dutch) 2023-12-12 16:01:19 +01:00
Eugen Rochko
51a7d00c47 New translations strings.xml (Lithuanian) 2023-12-11 15:13:33 +01:00
Eugen Rochko
9ac8261cc4 New translations strings.xml (Lithuanian) 2023-12-11 10:21:28 +01:00
Eugen Rochko
1f4ad80b7d New translations strings.xml (Lithuanian) 2023-12-11 07:10:38 +01:00
Eugen Rochko
4b090f0d68 New translations strings.xml (Lithuanian) 2023-12-10 20:30:16 +01:00
Eugen Rochko
4002bcde26 New translations strings.xml (Lithuanian) 2023-12-10 10:29:09 +01:00
Eugen Rochko
ded3777b40 New translations strings.xml (Lithuanian) 2023-12-10 09:10:25 +01:00
Eugen Rochko
7236066003 New translations strings.xml (Vietnamese) 2023-12-10 03:30:15 +01:00
Eugen Rochko
033f07ea09 New translations strings.xml (Lithuanian) 2023-12-09 22:16:10 +01:00
Eugen Rochko
283c0cba4b New translations strings.xml (Lithuanian) 2023-12-09 21:05:47 +01:00
Eugen Rochko
e3a1fc2fbb New translations strings.xml (Lithuanian) 2023-12-09 19:50:11 +01:00
Eugen Rochko
95de9e2917 New translations strings.xml (Lithuanian) 2023-12-09 16:56:00 +01:00
Eugen Rochko
a82ebeed11 New translations strings.xml (Basque) 2023-12-09 15:50:00 +01:00
Eugen Rochko
3a3aa0be1c New translations strings.xml (Lithuanian) 2023-12-09 07:31:00 +01:00
Eugen Rochko
e72491c2d1 New translations strings.xml (Lithuanian) 2023-12-08 19:29:39 +01:00
LucasGGamerM
522763b33a docs: add 103 changelog 2023-12-08 15:07:14 -03:00
LucasGGamerM
3cae5cbadf build: bump version number 2023-12-08 14:59:46 -03:00
RTRedreovic
ba7b2c8067 Translated using Weblate (Esperanto)
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/eo/
2023-12-08 17:47:50 +00:00
RTRedreovic
14901f5fac Translated using Weblate (Esperanto)
Currently translated at 20.5% (7 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/eo/
2023-12-08 17:47:50 +00:00
Weblate
28bbf7c7be Added translation using Weblate (Esperanto) 2023-12-08 17:47:50 +00:00
ghose
7315c6cd5c Translated using Weblate (Galician)
Currently translated at 52.9% (18 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/gl/
2023-12-08 17:47:50 +00:00
ghose
dda473f377 Translated using Weblate (Galician)
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/gl/
2023-12-08 17:47:50 +00:00
CDN18
3b5e148ca9 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2023-12-08 17:47:50 +00:00
Oliebol
f019497044 Translated using Weblate (Dutch)
Currently translated at 88.7% (87 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/nl/
2023-12-08 17:47:50 +00:00
SomeTr
f1cdce38c4 Translated using Weblate (Ukrainian)
Currently translated at 82.6% (81 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2023-12-08 17:47:50 +00:00
SomeTr
4a329fc4c2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (34 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/uk/
2023-12-08 17:47:50 +00:00
SomeTr
7b440d37b4 Translated using Weblate (Ukrainian)
Currently translated at 80.6% (79 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2023-12-08 17:47:50 +00:00
astro-ray
1cd8f5688d Translated using Weblate (Bengali)
Currently translated at 23.4% (23 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/bn/
2023-12-08 17:47:50 +00:00
astro-ray
d02d285c1f Translated using Weblate (Bengali)
Currently translated at 5.8% (2 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/bn/
2023-12-08 17:47:50 +00:00
Weblate
011a49b180 Added translation using Weblate (Bengali) 2023-12-08 17:47:50 +00:00
alextecplayz
59e4b13d82 Translated using Weblate (Romanian)
Currently translated at 100.0% (34 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/ro/
2023-12-08 17:47:50 +00:00
alextecplayz
72bfa12dec Translated using Weblate (Romanian)
Currently translated at 100.0% (34 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/ro/
2023-12-08 17:47:50 +00:00
butterflyoffire
dbf0c1047f Translated using Weblate (Arabic (Algeria))
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ar_DZ/
2023-12-08 17:47:50 +00:00
Linerly
fb38b3563c Translated using Weblate (Indonesian)
Currently translated at 100.0% (34 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/id/
2023-12-08 17:47:49 +00:00
Linerly
601cfeffe1 Translated using Weblate (Indonesian)
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/id/
2023-12-08 17:47:49 +00:00
alextecplayz
e2dfef22a9 Translated using Weblate (Romanian)
Currently translated at 100.0% (34 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/ro/
2023-12-08 17:47:49 +00:00
butterflyoffire
3293c95b84 Translated using Weblate (Arabic (Algeria))
Currently translated at 94.8% (93 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ar_DZ/
2023-12-08 17:47:49 +00:00
dontobi
33b9de5e5d Translated using Weblate (German)
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/de/
2023-12-08 17:47:49 +00:00
alextecplayz
2b5a9171dd Translated using Weblate (Romanian)
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ro/
2023-12-08 17:47:49 +00:00
edxkl
2de2efd4be Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pt_BR/
2023-12-08 17:47:49 +00:00
gallegonovato
43dd8931ff Translated using Weblate (Spanish)
Currently translated at 100.0% (98 of 98 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-12-08 17:47:49 +00:00
Andre-LA
f2c6dda26b Translated using Weblate (Portuguese (Brazil))
Currently translated at 29.4% (10 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/pt_BR/
2023-12-08 17:47:49 +00:00
alextecplayz
ce7e7f63bb Translated using Weblate (Romanian)
Currently translated at 44.1% (15 of 34 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/ro/
2023-12-08 17:47:49 +00:00
alextecplayz
f0fe1f6cf9 Translated using Weblate (Romanian)
Currently translated at 100.0% (97 of 97 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ro/
2023-12-08 17:47:49 +00:00
Oliebol
a7350d82d6 Translated using Weblate (Dutch)
Currently translated at 87.6% (85 of 97 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/nl/
2023-12-08 17:47:49 +00:00
Eugen Rochko
36dede1f93 New translations strings.xml (Lithuanian) 2023-12-08 16:59:20 +01:00
Eugen Rochko
ed15daf9e9 New translations strings.xml (Lithuanian) 2023-12-08 15:26:26 +01:00
Eugen Rochko
c6052c841d New translations strings.xml (Lithuanian) 2023-12-08 14:07:22 +01:00
LucasGGamerM
3bf63e1090 fix: a possible crash that might happen when the acestor descendant is null
Wtf java, also cc @sk22
2023-12-08 09:55:18 -03:00
LucasGGamerM
3d48443a23 fix: possibly fixes a crash where inReplyToId is null in a ThreadFragment
cc: @sk22

Btw check the commit before this one, because there is a fix that applies for Megalodon there as well
2023-12-08 09:45:18 -03:00
Eugen Rochko
ce39c7ca8f New translations strings.xml (Lithuanian) 2023-12-08 10:16:11 +01:00
Eugen Rochko
b7723dcb98 New translations strings.xml (Lithuanian) 2023-12-08 08:50:52 +01:00
Eugen Rochko
ad0774f8a5 New translations strings.xml (Armenian) 2023-12-07 22:59:36 +01:00
Eugen Rochko
9172feb72b New translations strings.xml (Italian) 2023-12-07 21:21:39 +01:00
Eugen Rochko
a297bd3281 New translations strings.xml (Lithuanian) 2023-12-07 16:52:37 +01:00
Eugen Rochko
e713a9cfc3 New translations strings.xml (Lithuanian) 2023-12-07 15:46:22 +01:00
Eugen Rochko
195395a22d New translations strings.xml (Lithuanian) 2023-12-07 14:14:31 +01:00
Eugen Rochko
7b6a62b047 New translations strings.xml (Lithuanian) 2023-12-07 13:14:37 +01:00
Eugen Rochko
ada1c9ff6d New translations strings.xml (Lithuanian) 2023-12-07 12:05:29 +01:00
Eugen Rochko
5a0a14ed56 New translations strings.xml (Lithuanian) 2023-12-07 09:11:41 +01:00
Eugen Rochko
cad3879646 New translations strings.xml (Galician) 2023-12-07 07:52:19 +01:00
LucasGGamerM
46115e8f3b docs: add get it on fdroid Fdroid badge to readme
Damn, I totally forgot to do this earlier. Thank you @IzzyOnDroid for reminding me!
2023-12-06 20:48:49 -03:00
Eugen Rochko
5d961991d4 New translations strings.xml (Lithuanian) 2023-12-06 22:10:19 +01:00
Eugen Rochko
e27536743f New translations strings.xml (Czech) 2023-12-06 22:10:18 +01:00
Eugen Rochko
9f8d4a0f34 New translations strings.xml (Lithuanian) 2023-12-06 20:56:46 +01:00
Eugen Rochko
67b6a89fd9 New translations strings.xml (Swedish) 2023-12-06 20:56:45 +01:00
LucasGGamerM
a27ef27f8d fix: fixes unfollow dialog missing title 2023-12-06 16:30:47 -03:00
LucasGGamerM
b42862dd9b feat: add alt text translation to previewlessMedia items 2023-12-06 16:23:09 -03:00
LucasGGamerM
533032ca75 fix: fixes crashes when adding a hashtag timeline
Closes #308
2023-12-06 15:20:22 -03:00
Eugen Rochko
dabc4058ba New translations strings.xml (Lithuanian) 2023-12-06 18:59:35 +01:00
Eugen Rochko
6c468602c6 New translations full_description.txt (Lithuanian) 2023-12-06 17:20:30 +01:00
Eugen Rochko
9c5d29a860 New translations strings.xml (Lithuanian) 2023-12-06 17:20:29 +01:00
Eugen Rochko
da5e2a6b50 New translations short_description.txt (Lithuanian) 2023-12-06 16:23:00 +01:00
Eugen Rochko
a194569fd4 New translations full_description.txt (Lithuanian) 2023-12-06 16:22:58 +01:00
Eugen Rochko
78a4ace9b2 New translations strings.xml (Lithuanian) 2023-12-06 14:26:32 +01:00
Eugen Rochko
9a664088cd New translations strings.xml (Lithuanian) 2023-12-06 13:02:34 +01:00
Eugen Rochko
1d2e6f880b New translations strings.xml (Lithuanian) 2023-12-06 11:26:57 +01:00
Eugen Rochko
2cd2918d53 New translations strings.xml (Japanese) 2023-12-06 11:26:56 +01:00
Eugen Rochko
9b49db6677 New translations strings.xml (French) 2023-12-06 08:58:00 +01:00
Eugen Rochko
9f6c61e5c0 New translations title.txt (Lithuanian) 2023-12-05 21:30:20 +01:00
Eugen Rochko
b6a2bb7881 New translations short_description.txt (Lithuanian) 2023-12-05 21:30:19 +01:00
Eugen Rochko
62262010b9 New translations full_description.txt (Lithuanian) 2023-12-05 21:30:18 +01:00
Eugen Rochko
72fe9a04a6 New translations strings.xml (Lithuanian) 2023-12-05 21:30:17 +01:00
Eugen Rochko
d8cf55ae21 New translations strings.xml (Greek) 2023-12-05 17:58:41 +01:00
Eugen Rochko
dfb393b934 New translations strings.xml (Chinese Traditional) 2023-12-05 07:46:21 +01:00
Eugen Rochko
cd27716f6a New translations strings.xml (Russian) 2023-12-05 07:46:20 +01:00
Eugen Rochko
469553b34e New translations strings.xml (Thai) 2023-12-05 06:46:35 +01:00
Grishka
5d7c37262e Info sheet in media viewer (AND-109) 2023-12-04 21:33:25 +03:00
Eugen Rochko
3f3867473f New translations strings.xml (Czech) 2023-12-04 11:21:21 +01:00
Grishka
b08cd1eb4b Crash fixes 2023-12-04 06:22:21 +03:00
Grishka
1f9ff8d341 Increase timeout to 60 seconds 2023-12-03 21:38:00 +03:00
Eugen Rochko
528b362f64 New translations strings.xml (Thai) 2023-12-03 09:24:19 +01:00
Eugen Rochko
1db10c5047 New translations strings.xml (Thai) 2023-12-03 08:16:34 +01:00
Eugen Rochko
f295f5f4e7 New translations strings.xml (Japanese) 2023-12-02 17:44:51 +01:00
Eugen Rochko
08924bd9b0 New translations strings.xml (Russian) 2023-12-02 15:31:03 +01:00
Eugen Rochko
5d432435a1 New translations strings.xml (Russian) 2023-12-02 14:26:43 +01:00
Eugen Rochko
8bd76aa833 New translations strings.xml (Italian) 2023-12-02 00:37:04 +01:00
Eugen Rochko
2147cb87ac New translations strings.xml (Slovenian) 2023-12-01 18:50:27 +01:00
Eugen Rochko
00ed0f5402 New translations strings.xml (Galician) 2023-12-01 16:15:43 +01:00
Eugen Rochko
870f79f6cd New translations strings.xml (Galician) 2023-12-01 14:51:25 +01:00
Eugen Rochko
da879213fc New translations strings.xml (Greek) 2023-11-30 23:34:22 +01:00
Eugen Rochko
db66974bd6 New translations strings.xml (Chinese Traditional) 2023-11-30 02:07:51 +01:00
Eugen Rochko
e3d5ae1d65 New translations strings.xml (Thai) 2023-11-29 20:48:42 +01:00
Eugen Rochko
b06bc5b3b7 New translations strings.xml (Russian) 2023-11-29 11:46:16 +01:00
Eugen Rochko
a4c988012d New translations strings.xml (Georgian) 2023-11-29 09:16:14 +01:00
Grishka
a200701e4c Link card improvements (AND-115)
closes #651
2023-11-29 07:51:11 +03:00
Eugen Rochko
e8f604792c New translations strings.xml (Chinese Traditional) 2023-11-29 03:02:35 +01:00
Eugen Rochko
c8b0666ef9 New translations strings.xml (Scottish Gaelic) 2023-11-29 00:35:44 +01:00
Eugen Rochko
13aa72b150 New translations strings.xml (Thai) 2023-11-29 00:35:39 +01:00
Eugen Rochko
6694074b18 New translations strings.xml (Persian) 2023-11-29 00:35:37 +01:00
Eugen Rochko
63aa32c636 New translations strings.xml (Indonesian) 2023-11-29 00:35:36 +01:00
Eugen Rochko
5fbab870c3 New translations strings.xml (Portuguese, Brazilian) 2023-11-29 00:35:35 +01:00
Eugen Rochko
4a34e248e0 New translations strings.xml (Icelandic) 2023-11-29 00:35:34 +01:00
Eugen Rochko
2c45165e53 New translations strings.xml (Galician) 2023-11-29 00:35:33 +01:00
Eugen Rochko
3f029ac45b New translations strings.xml (Chinese Traditional) 2023-11-29 00:35:32 +01:00
Eugen Rochko
a4cf76d5ba New translations strings.xml (Chinese Simplified) 2023-11-29 00:35:31 +01:00
Eugen Rochko
3044000cf8 New translations strings.xml (Ukrainian) 2023-11-29 00:35:30 +01:00
Eugen Rochko
ab1ef5cfd8 New translations strings.xml (Turkish) 2023-11-29 00:35:29 +01:00
Eugen Rochko
16b91a283a New translations strings.xml (Swedish) 2023-11-29 00:35:28 +01:00
Eugen Rochko
e9fbdc21fa New translations strings.xml (Slovenian) 2023-11-29 00:35:27 +01:00
Eugen Rochko
b429e662aa New translations strings.xml (Russian) 2023-11-29 00:35:26 +01:00
Eugen Rochko
834ad1736e New translations strings.xml (Polish) 2023-11-29 00:35:24 +01:00
Eugen Rochko
91021699d2 New translations strings.xml (Norwegian) 2023-11-29 00:35:23 +01:00
Eugen Rochko
0f86aa12ab New translations strings.xml (Japanese) 2023-11-29 00:35:21 +01:00
Eugen Rochko
fb7bf6f308 New translations strings.xml (Italian) 2023-11-29 00:35:20 +01:00
Eugen Rochko
5aa67aaa78 New translations strings.xml (Hungarian) 2023-11-29 00:35:19 +01:00
Eugen Rochko
2e892e7305 New translations strings.xml (Finnish) 2023-11-29 00:35:16 +01:00
Eugen Rochko
6486a1689f New translations strings.xml (Basque) 2023-11-29 00:35:15 +01:00
Eugen Rochko
5966535111 New translations strings.xml (German) 2023-11-29 00:35:14 +01:00
Eugen Rochko
a2cf4bda99 New translations strings.xml (Danish) 2023-11-29 00:35:13 +01:00
Eugen Rochko
7a93c8615d New translations strings.xml (Arabic) 2023-11-29 00:35:12 +01:00
Eugen Rochko
cf29f11cea New translations strings.xml (Spanish) 2023-11-29 00:35:10 +01:00
Eugen Rochko
23188a26d7 New translations strings.xml (Belarusian) 2023-11-29 00:35:08 +01:00
Eugen Rochko
0480dc0140 New translations strings.xml (French) 2023-11-29 00:35:07 +01:00
Eugen Rochko
cb14b29c78 New translations strings.xml (Armenian) 2023-11-29 00:35:06 +01:00
Eugen Rochko
bf68272de3 New translations strings.xml (Greek) 2023-11-29 00:35:05 +01:00
Eugen Rochko
730f5f8cc9 New translations strings.xml (Dutch) 2023-11-29 00:35:03 +01:00
Eugen Rochko
4b6d328e3d New translations strings.xml (Czech) 2023-11-29 00:35:02 +01:00
Eugen Rochko
cfde38be2d New translations strings.xml (Vietnamese) 2023-11-29 00:35:01 +01:00
Grishka
a2ea8e76fb Improve follow recommendations screen (AND-101) 2023-11-29 02:09:59 +03:00
Grishka
e797d8a1c2 Revert "Update icon"
This reverts commit b58c157c87.
2023-11-29 01:26:51 +03:00
Grishka
b58c157c87 Update icon 2023-11-29 01:22:27 +03:00
LucasGGamerM
69a2fc2d9f fix: remove duplicate view results button on poll footer 2023-11-28 14:34:15 -03:00
LucasGGamerM
2ef3e7eab7 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PollFooterStatusDisplayItem.java
2023-11-28 14:31:42 -03:00
Eugen Rochko
58f746a285 New translations strings.xml (Georgian) 2023-11-28 10:52:56 +01:00
Eugen Rochko
a6bba42a49 New translations title.txt (Georgian) 2023-11-28 07:16:50 +01:00
Eugen Rochko
519d6868b2 New translations short_description.txt (Georgian) 2023-11-28 07:16:49 +01:00
Eugen Rochko
5322120097 New translations full_description.txt (Georgian) 2023-11-28 07:16:48 +01:00
Eugen Rochko
2c88c86480 New translations strings.xml (Georgian) 2023-11-28 07:16:47 +01:00
Eugen Rochko
55f32fd45b New translations strings.xml (Dutch) 2023-11-28 01:12:21 +01:00
Eugen Rochko
f39f0b03d1 New translations strings.xml (Dutch) 2023-11-27 23:54:03 +01:00
sk
f7dfebcbea fix schedule date picker not time zone safe
closes sk22#940
2023-11-27 22:20:12 +01:00
sk
d8c73ea9af fix system-wide bold text
closes sk22#225
2023-11-27 21:56:19 +01:00
Eugen Rochko
ff2f1a4955 New translations strings.xml (Dutch) 2023-11-27 21:48:52 +01:00
FineFindus
79d5067c97 feat: add option to view poll results (#935)
* feat: add option to view poll results

* fix: button showing wrong text when refreshing

* fix: hide results button when voted

* remove unused string

* change view results layout, remove unused setting

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-27 21:44:53 +01:00
LucasGGamerM
ed986b11af chore: make app compile again 2023-11-27 16:41:17 -03:00
LucasGGamerM
6c94eb50ea Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
2023-11-27 16:39:44 -03:00
FineFindus
70b9d08830 fix: hide link card to akkoma hastags (#939)
* fix: hide link card to akkoma hastags

* generify method name

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-27 19:07:43 +01:00
Grishka
b283e216a7 Increase default HTTP timeouts to 30 seconds
fixes #751
2023-11-27 20:30:33 +03:00
Eugen Rochko
4328d568b3 New translations strings.xml (Belarusian) 2023-11-27 15:23:31 +01:00
Eugen Rochko
8edc47703f New translations strings.xml (Dutch) 2023-11-27 14:08:47 +01:00
Eugen Rochko
92ce906163 New translations strings.xml (Dutch) 2023-11-27 05:46:48 +01:00
Eugen Rochko
6e141e360e New translations strings.xml (French) 2023-11-25 22:27:46 +01:00
Grishka
d1aba87e13 Fix #747 2023-11-25 03:38:57 +03:00
Grishka
723853079e Fix "go to account" in search 2023-11-25 03:35:21 +03:00
Eugen Rochko
cd0742c093 New translations strings.xml (Armenian) 2023-11-24 23:26:11 +01:00
Eugen Rochko
52d5de5aec New translations strings.xml (Dutch) 2023-11-24 23:26:10 +01:00
Eugen Rochko
4f8a5ae5db New translations strings.xml (Dutch) 2023-11-24 21:58:52 +01:00
Eugen Rochko
616f2463c7 New translations strings.xml (Dutch) 2023-11-24 20:51:33 +01:00
Eugen Rochko
d3b711a966 New translations strings.xml (Greek) 2023-11-24 19:55:43 +01:00
Eugen Rochko
827fe34709 New translations strings.xml (Dutch) 2023-11-24 19:55:41 +01:00
Eugen Rochko
4b6c0242d5 New translations strings.xml (Dutch) 2023-11-24 18:54:54 +01:00
Eugen Rochko
c3cbc16084 New translations strings.xml (Czech) 2023-11-23 18:37:42 +01:00
Eugen Rochko
36493bfc88 New translations strings.xml (Vietnamese) 2023-11-23 18:37:41 +01:00
Eugen Rochko
66ce93a3ff New translations strings.xml (Vietnamese) 2023-11-23 16:34:34 +01:00
Grishka
957bc76dbb Merge branch 'l10n_master' 2023-11-23 00:27:58 +03:00
Grishka
1f5a28fb33 Add top margin to pre-reply shits 2023-11-23 00:27:24 +03:00
LucasGGamerM
40711fbd28 fix: make purple color be before pink
I don't like pink, It must be second
2023-11-22 14:37:27 -03:00
Grishka
045c58ce66 fix color 2023-11-22 18:26:56 +03:00
Grishka
e2dde7239f Render custom emojis in non-mutual pre-reply sheet 2023-11-22 18:06:14 +03:00
Eugen Rochko
512ad93eea New translations strings.xml (Turkish) 2023-11-22 00:00:30 +01:00
Grishka
19759023a4 Bump version 2023-11-21 22:34:51 +03:00
Grishka
714d3399ce Merge branch 'l10n_master' 2023-11-21 22:34:30 +03:00
Grishka
e1850e5282 Validate timezone and locale against what server supports
closes #654, AND-118
2023-11-21 22:31:26 +03:00
Grishka
a05c917b2c Assorted crash fixes 2023-11-21 21:46:41 +03:00
Grishka
8f3a9c265c Fix a rare crash when opening a notification 2023-11-21 21:27:43 +03:00
Grishka
6f1a33b76e Fix #741 2023-11-21 21:17:30 +03:00
Eugen Rochko
8f0451175f New translations strings.xml (Japanese) 2023-11-21 13:38:37 +01:00
Eugen Rochko
37a3a4f1c0 New translations strings.xml (Chinese Simplified) 2023-11-21 05:53:58 +01:00
Eugen Rochko
bd85746726 New translations strings.xml (Chinese Simplified) 2023-11-21 04:58:34 +01:00
Grishka
96265010bf Merge branch 'l10n_master' 2023-11-20 17:40:05 +03:00
Eugen Rochko
4209951ce3 New translations strings.xml (Armenian) 2023-11-19 11:46:47 +01:00
Eugen Rochko
f1cbd95439 New translations strings.xml (Thai) 2023-11-18 10:24:17 +01:00
Eugen Rochko
d63382c6d9 New translations strings.xml (Thai) 2023-11-18 09:18:16 +01:00
Grishka
20697fb334 Show bio instead of fields in non-mutual pre-reply sheet 2023-11-18 10:58:09 +03:00
Eugen Rochko
1090d1ca42 New translations strings.xml (Thai) 2023-11-17 21:37:58 +01:00
LucasGGamerM
1525ef8bdf Merge branch 'feat/view-poll-results' 2023-11-17 17:00:23 -03:00
FineFindus
f5e1326d9f fix: hide results button when voted 2023-11-17 20:27:38 +01:00
LucasGGamerM
43fe03c943 Merge branch 'feat/view-poll-results'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PollFooterStatusDisplayItem.java
2023-11-17 16:16:29 -03:00
LucasGGamerM
1ee96f3703 chore: make app compile again 2023-11-17 16:08:03 -03:00
LucasGGamerM
da5186c7b5 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetPrivateNote.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/DiffRemovedSpan.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
#	mastodon/src/main/res/drawable/bg_note_edit.xml
#	mastodon/src/main/res/layout/fragment_profile.xml
#	metadata/uk/full_description.txt
2023-11-17 16:02:23 -03:00
Eugen Rochko
bec4acdf51 New translations strings.xml (Japanese) 2023-11-17 17:27:10 +01:00
Eugen Rochko
800b78bfd8 New translations strings.xml (Vietnamese) 2023-11-17 16:15:13 +01:00
Eugen Rochko
52b01b7bbe New translations strings.xml (Slovenian) 2023-11-17 16:15:12 +01:00
Eugen Rochko
8b71764207 New translations strings.xml (Japanese) 2023-11-17 14:56:57 +01:00
Eugen Rochko
a5d7a75f32 New translations strings.xml (Slovenian) 2023-11-17 14:00:00 +01:00
Eugen Rochko
8839bcb7aa New translations strings.xml (Slovenian) 2023-11-17 12:03:35 +01:00
Eugen Rochko
bcaf71760d New translations strings.xml (Chinese Traditional) 2023-11-17 08:51:19 +01:00
Eugen Rochko
1d95204648 New translations strings.xml (Russian) 2023-11-17 07:26:31 +01:00
Eugen Rochko
83bf2a808f New translations strings.xml (Russian) 2023-11-17 06:19:30 +01:00
FineFindus
dca68de77e fix: button showing wrong text when refreshing 2023-11-16 22:23:49 +01:00
FineFindus
0ef879ebd3 feat: add option to view poll results 2023-11-16 22:16:28 +01:00
Eugen Rochko
7ffb0a01c6 New translations strings.xml (Italian) 2023-11-16 22:09:36 +01:00
Eugen Rochko
0710113148 New translations strings.xml (Thai) 2023-11-16 20:31:10 +01:00
Eugen Rochko
7c43e9a1af New translations strings.xml (Norwegian) 2023-11-16 20:31:08 +01:00
Eugen Rochko
f9e768c378 New translations strings.xml (Thai) 2023-11-16 19:20:56 +01:00
Grishka
2063dbd0b0 Copy app version when tapped 2023-11-16 18:15:59 +03:00
Eugen Rochko
057683c72f New translations strings.xml (Kabyle) 2023-11-16 12:47:38 +01:00
Eugen Rochko
4963a0e722 New translations strings.xml (Occitan) 2023-11-16 12:47:36 +01:00
Eugen Rochko
1d66d288bd New translations strings.xml (Scottish Gaelic) 2023-11-16 12:47:35 +01:00
Eugen Rochko
b8f49157c3 New translations strings.xml (Bosnian) 2023-11-16 12:47:33 +01:00
Eugen Rochko
d77a62ef6f New translations strings.xml (Filipino) 2023-11-16 12:47:32 +01:00
Eugen Rochko
c531150483 New translations strings.xml (Burmese) 2023-11-16 12:47:31 +01:00
Eugen Rochko
991f41c531 New translations strings.xml (Croatian) 2023-11-16 12:47:30 +01:00
Eugen Rochko
b5fb7dd2ec New translations strings.xml (Thai) 2023-11-16 12:47:29 +01:00
Eugen Rochko
4fe8532971 New translations strings.xml (Bengali) 2023-11-16 12:47:27 +01:00
Eugen Rochko
4ae1e7d33e New translations strings.xml (Indonesian) 2023-11-16 12:47:26 +01:00
Eugen Rochko
43fa4526a4 New translations strings.xml (Portuguese, Brazilian) 2023-11-16 12:47:25 +01:00
Eugen Rochko
fc4b1da323 New translations strings.xml (Galician) 2023-11-16 12:47:24 +01:00
Eugen Rochko
843755f4e4 New translations strings.xml (Chinese Traditional) 2023-11-16 12:47:23 +01:00
Eugen Rochko
80e02f7520 New translations strings.xml (Chinese Simplified) 2023-11-16 12:47:22 +01:00
Eugen Rochko
af8f52e589 New translations strings.xml (Swedish) 2023-11-16 12:47:21 +01:00
Eugen Rochko
bc3f48dec9 New translations strings.xml (Russian) 2023-11-16 12:47:20 +01:00
Eugen Rochko
74ee832507 New translations strings.xml (Portuguese) 2023-11-16 12:47:18 +01:00
Eugen Rochko
da1b2d09b1 New translations strings.xml (Polish) 2023-11-16 12:47:17 +01:00
Eugen Rochko
99f8607211 New translations strings.xml (Norwegian) 2023-11-16 12:47:16 +01:00
Eugen Rochko
ef293088e1 New translations strings.xml (Dutch) 2023-11-16 12:47:15 +01:00
Eugen Rochko
e08e72ccb0 New translations strings.xml (Korean) 2023-11-16 12:47:14 +01:00
Eugen Rochko
b692440bab New translations strings.xml (Japanese) 2023-11-16 12:47:13 +01:00
Eugen Rochko
7061abc64b New translations strings.xml (Italian) 2023-11-16 12:47:12 +01:00
Eugen Rochko
0dd5064abb New translations strings.xml (Hungarian) 2023-11-16 12:47:11 +01:00
Eugen Rochko
a1aafff6ce New translations strings.xml (Hebrew) 2023-11-16 12:47:09 +01:00
Eugen Rochko
1f88f154af New translations strings.xml (German) 2023-11-16 12:47:08 +01:00
Eugen Rochko
3d1e0364c6 New translations strings.xml (Danish) 2023-11-16 12:47:06 +01:00
Eugen Rochko
0f1b5431bb New translations strings.xml (Czech) 2023-11-16 12:47:05 +01:00
Eugen Rochko
0369d3fa62 New translations strings.xml (Catalan) 2023-11-16 12:47:04 +01:00
Eugen Rochko
154e3a732a New translations strings.xml (Belarusian) 2023-11-16 12:47:03 +01:00
Eugen Rochko
9c979db043 New translations strings.xml (Arabic) 2023-11-16 12:47:02 +01:00
Eugen Rochko
0af089db89 New translations strings.xml (Spanish) 2023-11-16 12:47:00 +01:00
Eugen Rochko
1335613860 New translations strings.xml (Vietnamese) 2023-11-16 12:46:58 +01:00
Eugen Rochko
cb86bfd8dc New translations strings.xml (Persian) 2023-11-16 12:46:57 +01:00
Eugen Rochko
a0d32ae493 New translations strings.xml (Turkish) 2023-11-16 12:46:56 +01:00
Eugen Rochko
f7e56a6c40 New translations strings.xml (Slovenian) 2023-11-16 12:46:55 +01:00
Eugen Rochko
56613c75f7 New translations strings.xml (Armenian) 2023-11-16 12:46:54 +01:00
Eugen Rochko
fb3c35c0a0 New translations strings.xml (Basque) 2023-11-16 12:46:53 +01:00
Eugen Rochko
4b3dc0a59f New translations strings.xml (French) 2023-11-16 12:46:52 +01:00
Eugen Rochko
7855615a7b New translations strings.xml (Icelandic) 2023-11-16 12:46:50 +01:00
Eugen Rochko
ff6576f4da New translations strings.xml (Ukrainian) 2023-11-16 12:46:49 +01:00
Eugen Rochko
931fa9a9b0 New translations strings.xml (Finnish) 2023-11-16 12:46:48 +01:00
Eugen Rochko
77a70967f2 New translations strings.xml (Greek) 2023-11-16 12:46:47 +01:00
Grishka
e5506d952c Onboarding: replace fields with discoverability toggle (AND-100) 2023-11-16 12:15:17 +03:00
Eugen Rochko
2c2dbd0761 New translations strings.xml (Kabyle) 2023-11-16 09:56:39 +01:00
Eugen Rochko
e6f5ecd496 New translations strings.xml (Scottish Gaelic) 2023-11-16 09:56:37 +01:00
Eugen Rochko
73cea2d83c New translations strings.xml (Filipino) 2023-11-16 09:56:34 +01:00
Eugen Rochko
835a576f44 New translations strings.xml (Thai) 2023-11-16 09:56:31 +01:00
Eugen Rochko
0a090341cc New translations strings.xml (Indonesian) 2023-11-16 09:56:29 +01:00
Eugen Rochko
453671abfb New translations strings.xml (Portuguese, Brazilian) 2023-11-16 09:56:27 +01:00
Eugen Rochko
cfa7daa984 New translations strings.xml (Galician) 2023-11-16 09:56:26 +01:00
Eugen Rochko
88f913f586 New translations strings.xml (Chinese Traditional) 2023-11-16 09:56:25 +01:00
Eugen Rochko
5b4aeb4923 New translations strings.xml (Chinese Simplified) 2023-11-16 09:56:24 +01:00
Eugen Rochko
19133a2913 New translations strings.xml (Swedish) 2023-11-16 09:56:23 +01:00
Eugen Rochko
293035b7c8 New translations strings.xml (Russian) 2023-11-16 09:56:22 +01:00
Eugen Rochko
d06723de5c New translations strings.xml (Portuguese) 2023-11-16 09:56:20 +01:00
Eugen Rochko
bc45d0c499 New translations strings.xml (Polish) 2023-11-16 09:56:19 +01:00
Eugen Rochko
c320cccf6f New translations strings.xml (Norwegian) 2023-11-16 09:56:18 +01:00
Eugen Rochko
e3aebbd145 New translations strings.xml (Dutch) 2023-11-16 09:56:17 +01:00
Eugen Rochko
e15d378e46 New translations strings.xml (Korean) 2023-11-16 09:56:16 +01:00
Eugen Rochko
b6720d10fb New translations strings.xml (Japanese) 2023-11-16 09:56:15 +01:00
Eugen Rochko
83a2dbe8a1 New translations strings.xml (Italian) 2023-11-16 09:56:13 +01:00
Eugen Rochko
5b8592a99d New translations strings.xml (Hungarian) 2023-11-16 09:56:12 +01:00
Eugen Rochko
18a094c06c New translations strings.xml (German) 2023-11-16 09:56:10 +01:00
Eugen Rochko
a319ff3dc0 New translations strings.xml (Danish) 2023-11-16 09:56:08 +01:00
Eugen Rochko
0cb46eca1a New translations strings.xml (Czech) 2023-11-16 09:56:07 +01:00
Eugen Rochko
d85c814cba New translations strings.xml (Catalan) 2023-11-16 09:56:06 +01:00
Eugen Rochko
f49e660f29 New translations strings.xml (Belarusian) 2023-11-16 09:56:05 +01:00
Eugen Rochko
afa407e7d1 New translations strings.xml (Arabic) 2023-11-16 09:56:04 +01:00
Eugen Rochko
37e0f5ecea New translations strings.xml (Spanish) 2023-11-16 09:56:02 +01:00
Eugen Rochko
5000fdcfea New translations strings.xml (Vietnamese) 2023-11-16 09:56:00 +01:00
Eugen Rochko
2ec7489dbf New translations strings.xml (Persian) 2023-11-16 09:55:59 +01:00
Eugen Rochko
05965cea6e New translations strings.xml (Turkish) 2023-11-16 09:55:58 +01:00
Eugen Rochko
279e22ccb3 New translations strings.xml (Slovenian) 2023-11-16 09:55:57 +01:00
Eugen Rochko
6a6fc1ca8b New translations strings.xml (Armenian) 2023-11-16 09:55:56 +01:00
Eugen Rochko
b6b5426297 New translations strings.xml (Basque) 2023-11-16 09:55:55 +01:00
Eugen Rochko
e332ddda74 New translations strings.xml (French) 2023-11-16 09:55:54 +01:00
Eugen Rochko
2cd4cfb883 New translations strings.xml (Icelandic) 2023-11-16 09:55:52 +01:00
Eugen Rochko
ef12d09d35 New translations strings.xml (Ukrainian) 2023-11-16 09:55:51 +01:00
Eugen Rochko
1e365a8a7c New translations strings.xml (Finnish) 2023-11-16 09:55:50 +01:00
Eugen Rochko
e9363b41fd New translations strings.xml (Greek) 2023-11-16 09:55:49 +01:00
Grishka
5e99df137a Remove unused resources 2023-11-16 11:44:27 +03:00
Grishka
c0d0b45e24 Replace boost icons 2023-11-16 11:38:18 +03:00
Eugen Rochko
3340b4cdfa New translations strings.xml (Norwegian) 2023-11-15 22:47:52 +01:00
Eugen Rochko
d4afcc3383 New translations strings.xml (Norwegian) 2023-11-15 21:19:47 +01:00
Eugen Rochko
dad423eb04 New translations strings.xml (German) 2023-11-15 20:06:02 +01:00
Eugen Rochko
7b275d7e3d New translations strings.xml (Chinese Traditional) 2023-11-15 19:10:30 +01:00
SomeTr
d96b9558b4 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (417 of 417 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-11-15 17:48:41 +00:00
sk
1ed3f00f82 bump version 2023-11-15 18:48:12 +01:00
sk
327aef0271 fix crash for non-github/debug builds 2023-11-15 18:47:07 +01:00
Eugen Rochko
5274ecb721 New translations strings.xml (Chinese Traditional) 2023-11-15 18:12:43 +01:00
Eugen Rochko
e45367a482 New translations strings.xml (Japanese) 2023-11-15 18:12:42 +01:00
sk22
66f8cc5d18 Translated using Weblate (German)
Currently translated at 100.0% (20 of 20 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/de/
2023-11-15 16:00:40 +00:00
sk
36699e0ab1 change change log 2023-11-15 16:58:49 +01:00
sk22
1a382606d7 Translated using Weblate (German)
Currently translated at 100.0% (417 of 417 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-11-15 15:50:50 +00:00
sk22
2522c9c428 Translated using Weblate (German)
Currently translated at 97.6% (407 of 417 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-11-15 15:46:34 +00:00
gallegonovato
d1ee12f248 Translated using Weblate (Spanish)
Currently translated at 100.0% (412 of 412 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-11-15 15:46:34 +00:00
SomeTr
d5f9c19fd3 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (19 of 19 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-11-15 15:46:34 +00:00
SomeTr
dc700d9e37 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (412 of 412 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-11-15 15:46:34 +00:00
qbane
6eb87149e2 Translated using Weblate (Chinese (Traditional))
Currently translated at 24.0% (96 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hant/
2023-11-15 15:46:34 +00:00
qbane
79a0f8a891 Translated using Weblate (Chinese (Traditional))
Currently translated at 17.7% (71 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hant/
2023-11-15 15:46:34 +00:00
alextecplayz
26d41b006f Translated using Weblate (Romanian)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-11-15 15:46:34 +00:00
EndermanCo
60d9dc22d2 Translated using Weblate (Persian)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-11-15 15:46:34 +00:00
SomeTr
c0bf78fa7c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-11-15 15:46:34 +00:00
ling0412
f0c2bd0fe6 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/zh_Hans/
2023-11-15 15:46:34 +00:00
Arkxv
235cfd1e80 Translated using Weblate (Japanese)
Currently translated at 93.9% (375 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ja/
2023-11-15 15:46:34 +00:00
SomeTr
c96931ba72 Translated using Weblate (Croatian)
Currently translated at 64.4% (257 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/hr/
2023-11-15 15:46:34 +00:00
butterflyoffire
2c654a28d6 Translated using Weblate (French)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-11-15 15:46:33 +00:00
Andrewblasco
72d3fc84f5 Translated using Weblate (Spanish)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-11-15 15:46:33 +00:00
ling0412
3b77604ae6 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (398 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-11-15 15:46:33 +00:00
poesty
4e5f49b37d Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (398 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-11-15 15:46:33 +00:00
sk22
a89c075082 Added translation using Weblate (Esperanto) 2023-11-15 15:46:33 +00:00
sk
99e881bb95 change string name 2023-11-15 16:46:24 +01:00
sk
4c585fc5d0 change confirm string 2023-11-15 16:45:29 +01:00
Eugen Rochko
83532edaab New translations strings.xml (Urdu (India)) 2023-11-15 16:43:17 +01:00
Eugen Rochko
793d28da6a New translations strings.xml (Kabyle) 2023-11-15 16:43:16 +01:00
Eugen Rochko
a8e575f680 New translations strings.xml (Igbo) 2023-11-15 16:43:15 +01:00
Eugen Rochko
98b0b3f9dd New translations strings.xml (Occitan) 2023-11-15 16:43:14 +01:00
Eugen Rochko
2e6d9c296a New translations strings.xml (Scottish Gaelic) 2023-11-15 16:43:13 +01:00
Eugen Rochko
a07dc96ef9 New translations strings.xml (Sinhala) 2023-11-15 16:43:12 +01:00
Eugen Rochko
8ba097a68a New translations strings.xml (Bosnian) 2023-11-15 16:43:11 +01:00
Eugen Rochko
b1dd990fea New translations strings.xml (Filipino) 2023-11-15 16:43:09 +01:00
Eugen Rochko
ba7864b910 New translations strings.xml (Burmese) 2023-11-15 16:43:08 +01:00
Eugen Rochko
5d8fa343cd New translations strings.xml (Hindi) 2023-11-15 16:43:07 +01:00
Eugen Rochko
3fc49c431b New translations strings.xml (Croatian) 2023-11-15 16:43:06 +01:00
Eugen Rochko
79b6e65ce3 New translations strings.xml (Thai) 2023-11-15 16:43:05 +01:00
Eugen Rochko
9f457d0d76 New translations strings.xml (Bengali) 2023-11-15 16:43:03 +01:00
Eugen Rochko
aa2ff62db4 New translations strings.xml (Indonesian) 2023-11-15 16:43:02 +01:00
Eugen Rochko
73fffca569 New translations strings.xml (Portuguese, Brazilian) 2023-11-15 16:43:01 +01:00
Eugen Rochko
45589fc033 New translations strings.xml (Galician) 2023-11-15 16:42:59 +01:00
Eugen Rochko
79b81ed932 New translations strings.xml (Chinese Traditional) 2023-11-15 16:42:58 +01:00
Eugen Rochko
d1242870df New translations strings.xml (Chinese Simplified) 2023-11-15 16:42:57 +01:00
Eugen Rochko
e0dbbc4bc0 New translations strings.xml (Swedish) 2023-11-15 16:42:56 +01:00
Eugen Rochko
bf89791817 New translations strings.xml (Russian) 2023-11-15 16:42:55 +01:00
Eugen Rochko
e3197f6dc1 New translations strings.xml (Portuguese) 2023-11-15 16:42:54 +01:00
Eugen Rochko
e6317aa898 New translations strings.xml (Polish) 2023-11-15 16:42:52 +01:00
Eugen Rochko
c73dc326fd New translations strings.xml (Norwegian) 2023-11-15 16:42:51 +01:00
Eugen Rochko
287e250357 New translations strings.xml (Dutch) 2023-11-15 16:42:50 +01:00
Eugen Rochko
9673a14420 New translations strings.xml (Korean) 2023-11-15 16:42:49 +01:00
Eugen Rochko
3333fdc8d7 New translations strings.xml (Japanese) 2023-11-15 16:42:48 +01:00
Eugen Rochko
9fb4b8bb6e New translations strings.xml (Italian) 2023-11-15 16:42:47 +01:00
Eugen Rochko
7b10ed13f4 New translations strings.xml (Hungarian) 2023-11-15 16:42:45 +01:00
Eugen Rochko
c528bd797d New translations strings.xml (Hebrew) 2023-11-15 16:42:44 +01:00
Eugen Rochko
264529705c New translations strings.xml (Irish) 2023-11-15 16:42:43 +01:00
Eugen Rochko
4669e3dfc7 New translations strings.xml (German) 2023-11-15 16:42:42 +01:00
Eugen Rochko
eff3798964 New translations strings.xml (Danish) 2023-11-15 16:42:41 +01:00
Eugen Rochko
78c526c25b New translations strings.xml (Czech) 2023-11-15 16:42:40 +01:00
Eugen Rochko
ec13415d1f New translations strings.xml (Catalan) 2023-11-15 16:42:39 +01:00
Eugen Rochko
96622184ae New translations strings.xml (Belarusian) 2023-11-15 16:42:38 +01:00
Eugen Rochko
3742c1c862 New translations strings.xml (Arabic) 2023-11-15 16:42:37 +01:00
Eugen Rochko
a0c7757428 New translations strings.xml (Spanish) 2023-11-15 16:42:36 +01:00
Eugen Rochko
15f9f4906a New translations strings.xml (Romanian) 2023-11-15 16:42:34 +01:00
Eugen Rochko
d577cd9b21 New translations strings.xml (Vietnamese) 2023-11-15 16:42:33 +01:00
Eugen Rochko
cf610cbb87 New translations strings.xml (Persian) 2023-11-15 16:42:32 +01:00
Eugen Rochko
1e1095204d New translations strings.xml (Turkish) 2023-11-15 16:42:31 +01:00
Eugen Rochko
3fb6a13a3a New translations strings.xml (Slovenian) 2023-11-15 16:42:30 +01:00
Eugen Rochko
2826655fe2 New translations strings.xml (Armenian) 2023-11-15 16:42:29 +01:00
Eugen Rochko
0ccf450b28 New translations strings.xml (Basque) 2023-11-15 16:42:28 +01:00
Eugen Rochko
d28b9460af New translations strings.xml (French) 2023-11-15 16:42:26 +01:00
Eugen Rochko
3e1bdf98c2 New translations strings.xml (Icelandic) 2023-11-15 16:42:25 +01:00
Eugen Rochko
3f87764230 New translations strings.xml (Ukrainian) 2023-11-15 16:42:24 +01:00
Eugen Rochko
25e8e2e9e1 New translations strings.xml (Finnish) 2023-11-15 16:42:23 +01:00
sk
ae934b5167 add changelog 2023-11-15 16:42:22 +01:00
Eugen Rochko
3faf2ce9b9 New translations strings.xml (Greek) 2023-11-15 16:42:22 +01:00
Grishka
cbe243fc9e Open link in browser when a post/account links to itself
closes #739
2023-11-15 18:17:08 +03:00
Grishka
a438f633be Pre-reply sheets 2023-11-15 18:05:38 +03:00
Jacoco
6b234209c6 Previewing posts on Akkoma (#933)
* Previewing posts on Akkoma

* move preview property into status

* clean up code

* revert imm-related changes

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-15 15:17:29 +01:00
Jacoco
786ce78b08 Translations for Akkoma (#934)
* Translations for Akkoma

* simplify akkoma translation code

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-15 14:32:56 +01:00
sk
cde332684e bump version 2023-11-15 14:07:00 +01:00
Eugen Rochko
37ef67d7ac New translations strings.xml (Italian) 2023-11-15 13:33:31 +01:00
sk
d51e06b61f save latest crash log
closes sk22#932
closes sk22#419
2023-11-15 12:06:54 +01:00
Eugen Rochko
67d631b0f0 New translations strings.xml (Russian) 2023-11-15 07:59:18 +01:00
Eugen Rochko
fc302ffa5f New translations strings.xml (Belarusian) 2023-11-15 03:27:35 +01:00
LucasGGamerM
231d494955 Merge pull request #302 from FineFindus/fix/notification-filter-reset
fix(notification/filter) rename reset button
2023-11-14 19:17:05 -03:00
LucasGGamerM
df05fa9f3b Merge pull request #301 from FineFindus/feat/move-unlisted-replies
feat(settings): move unlisted replies to privacy page
2023-11-14 19:15:37 -03:00
FineFindus
a64f6c1625 refactor(notifications/filter): move duplicated code to function 2023-11-14 22:51:37 +01:00
FineFindus
cb7586f973 fix(notifications/filter): update reset button title 2023-11-14 22:28:31 +01:00
FineFindus
a078bb3597 feat(settings): move unlisted replies to privacy page 2023-11-14 22:14:38 +01:00
Eugen Rochko
8c28556a94 New translations strings.xml (Chinese Traditional) 2023-11-14 20:53:35 +01:00
Grishka
45cc531eec Thread fragment tweaks part 2 2023-11-14 21:27:15 +03:00
Grishka
5c9ad9286d Thread fragment tweaks part 1 2023-11-14 19:23:42 +03:00
Eugen Rochko
ad1c9486d7 New translations short_description.txt (Hungarian) 2023-11-14 15:33:01 +01:00
Eugen Rochko
ad6a03b712 New translations strings.xml (Hungarian) 2023-11-14 15:33:00 +01:00
Eugen Rochko
36bb8010bc New translations strings.xml (Swedish) 2023-11-14 13:43:32 +01:00
Eugen Rochko
2200da7a16 New translations strings.xml (Hungarian) 2023-11-14 13:43:31 +01:00
Grishka
688c0e2e85 Use sp units in more places
#723
2023-11-14 09:54:55 +03:00
LucasGGamerM
e80348ac7d Merge pull request #299 from FineFindus/fix/notification-header
fix(notification): allow clicking on status item to open profile
2023-11-13 21:59:08 -03:00
sk
0c376d57e7 fix timeline editor messing with non-hashtag tls 2023-11-13 21:31:54 +01:00
sk
ee82772fee weather icons :) 2023-11-13 21:30:06 +01:00
sk
d5b6750abe fix hashtag options showing up for all timelines 2023-11-13 21:17:58 +01:00
sk
70328217c6 add bookmarks and favorites as timelines
closes sk22#908
2023-11-13 21:10:33 +01:00
sk
733fb3f53a re-add pre-releases setting
closes sk22#906
2023-11-13 21:02:06 +01:00
sk
f82eb96a35 increase translate timeout to 1 minute
closes sk22#910
2023-11-13 20:50:43 +01:00
sk
148952b96c update cache on poll vote
closes sk22#924
2023-11-13 20:48:16 +01:00
sk
52928e1577 increase default read timeout
closes sk22#923
2023-11-13 20:43:34 +01:00
sk
cd13777c06 fix report view, allow opening posts in list
closes sk22#928
closes sk22#441
2023-11-13 20:39:27 +01:00
Eugen Rochko
714345a65d New translations strings.xml (French) 2023-11-13 19:16:10 +01:00
sk
b21e2acdb6 fix metadata items being cut off
closes sk22#921
closes sk22#648
2023-11-13 19:11:04 +01:00
sk
2b65aeb8b4 fix edit note item visible in context menu 2023-11-13 18:48:24 +01:00
Jacoco
a8dcb11094 feature: Display post that's being quoted on Akkoma (#927)
* Displaying Akkoma quote status

* Dummy display items for quote posts

* Only remove quote-inline with RE:

* fix null reference (reply-to instead of quote status)

* fix text bottom padding in quote

* Postprocess status quote

* fix rounded bottom for quoted media

closes sk22#929

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-13 18:46:15 +01:00
Gregory K
34a1c7e408 Merge pull request #736 from alex-vit/alexv/fix/delete_account_shared_pref
Delete `id.xml` shared pref from the correct dir
2023-11-13 18:07:24 +03:00
Aleksandrs Vitjukovs
6255221d6a Delete id.xml shared pref from the correct dir 2023-11-13 16:54:10 +02:00
Eugen Rochko
58364de72a New translations strings.xml (Armenian) 2023-11-13 05:28:24 +01:00
sk
aa42873274 use verified/error text colors for diff 2023-11-12 23:01:46 +01:00
FineFindus
8a5b36db96 feat(profile): add note (#918)
* feat(profile): add note

* simplify note code

* adjust spacing

* size and hitbox adjustments, progress

* profile menu item to add note

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-12 22:37:31 +01:00
FineFindus
0dcb9361ea fix(notification): allow clicking on status item to open profile 2023-11-11 19:27:35 +01:00
Eugen Rochko
6d64df4ee4 New translations strings.xml (Turkish) 2023-11-11 15:16:48 +01:00
Gregory K
7bac2f206b Merge pull request #734 from FineFindus/feat/translate-media-upstream
Allow translation of attachments, spoilers and polls
2023-11-11 08:01:13 +03:00
FineFindus
75e1a17a2c fix: add args in correct order 2023-11-10 22:05:21 +01:00
FineFindus
47b13384a8 feat(status/translation): support translating spoiler 2023-11-10 22:04:07 +01:00
FineFindus
77b9efa7d1 feat(status/translation): support translating spoiler 2023-11-10 22:01:24 +01:00
FineFindus
be5f3b18af feat(status): translate poll options 2023-11-10 22:00:47 +01:00
FineFindus
d5d12a7ce5 fix(status/translation): do not require all fields 2023-11-10 22:00:47 +01:00
Eugen Rochko
d7726d7755 New translations strings.xml (Norwegian) 2023-11-10 21:26:30 +01:00
FineFindus
0cd0d37eff feat(status): translate media attachments 2023-11-10 21:07:24 +01:00
FineFindus
c85af5502d feat: translate media attachments and poll options (#916)
* feat(status): translate media attachments

* feat(status): translate poll options

* fix(status/translation): do not require all fields

* feat(status/translation): support translating spoiler
2023-11-10 20:44:06 +01:00
FineFindus
06698d3c52 feat: display edit history diff (#922)
* build: add google's diff-match-patch

Copied from 62f2e689f4/java/src/name/fraser/neil/plaintext/diff_match_patch.java

* feat(status/edit-history): display diff for text

Closes https://github.com/sk22/megalodon/issues/789

* fix(status/edit-history): add fake poll id

* code style adjustments

* don't diff if only formatting changed

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-11-10 20:40:34 +01:00
S1m
2818672cda Fix NullPointerException when receiving push when killed (#914) 2023-11-10 20:15:45 +01:00
S1m
24794f28aa Fix: registering multi account for UnifiedPush (#907)
* Register all account when enabling UnifiedPush
* Register new account to UnifiedPush if enabled
2023-11-10 20:12:27 +01:00
FineFindus
50d1523210 feat: re-add 12 hour option to polls (#917) 2023-11-10 20:10:50 +01:00
Eugen Rochko
4521def103 New translations strings.xml (Norwegian) 2023-11-10 20:08:28 +01:00
sk
613cd2a1ea remove unused playRelease build type 2023-11-10 20:06:18 +01:00
sk
295cb74287 don't show translate for empty decoding 2023-11-10 16:30:46 +01:00
sk
95dd3ff068 fix translation language name 2023-11-10 16:13:43 +01:00
sk
d6aeb753fc allow exclamation/question marks in pronouns 2023-11-10 16:13:14 +01:00
sk
a085744038 fix missing parens on start of pronouns 2023-11-10 15:37:31 +01:00
sk
26391a6f14 hopefully fix string index out of bounds
https://paste.crdroid.net/3FNRe7
2023-11-10 15:37:03 +01:00
sk
4cf734ce9a avoid IllegalArgumentException 2023-11-10 15:23:42 +01:00
Eugen Rochko
5c70f0a758 New translations strings.xml (Arabic) 2023-11-09 23:12:18 +01:00
Eugen Rochko
c12c2c0416 New translations strings.xml (Arabic) 2023-11-09 22:09:22 +01:00
Eugen Rochko
db45c422e7 New translations strings.xml (Arabic) 2023-11-09 21:07:57 +01:00
Eugen Rochko
affd9a95c5 New translations strings.xml (Hungarian) 2023-11-09 15:25:20 +01:00
Gregory K
7baf25869a Merge pull request #732 from FineFindus/fix/invisible-menu
fix: disable group divider on EMUI
2023-11-08 22:35:22 +03:00
FineFindus
12096fb427 fix: disable group divider on EMUI 2023-11-08 20:25:25 +01:00
Gregory K
ef7136cb81 Merge pull request #731 from FineFindus/fix/edit-history-crash
fix: edit history crash
2023-11-08 22:00:48 +03:00
FineFindus
3c4baf0126 fix(status/edit-history): set fake poll fields 2023-11-08 19:45:49 +01:00
FineFindus
f0b87c62a5 fix(status/edit-history): check for negative array index 2023-11-08 19:36:24 +01:00
LucasGGamerM
d27295739b Merge pull request #297 from FineFindus/feat/merge-commits
Feat/merge commits
2023-11-08 14:55:40 -03:00
LucasGGamerM
99e8a5372b Merge pull request #292 from FineFindus/feat/hide-empty-link-previews
feat: hide empty link previews
2023-11-08 14:51:53 -03:00
FineFindus
b1f7f3e351 Merge remote-tracking branch 'origin/feat/translate-media' into feat/merge-commits 2023-11-08 18:42:18 +01:00
FineFindus
6cfc1f3955 Merge remote-tracking branch 'origin/feat/edit-history-diff' into feat/merge-commits 2023-11-08 18:39:52 +01:00
FineFindus
f1a5e3afae Merge remote-tracking branch 'origin/feat/12-hours-polls' into feat/merge-commits 2023-11-08 18:39:04 +01:00
FineFindus
d8b932c9ad merge: 'origin/feat/hide-empty-link-previews' into feat/merge-commits 2023-11-08 18:36:36 +01:00
Eugen Rochko
a319435e91 New translations strings.xml (Belarusian) 2023-11-08 12:18:27 +00:00
Eugen Rochko
5bd0e988e3 New translations strings.xml (Belarusian) 2023-11-08 11:22:42 +00:00
Eugen Rochko
b2be669b9e New translations strings.xml (Belarusian) 2023-11-08 09:22:28 +00:00
FineFindus
536bd11798 fix(status/edit-history): add fake poll id 2023-11-05 15:16:04 +01:00
FineFindus
b74bd66cf2 feat(status/edit-history): display diff for text
Closes https://github.com/sk22/megalodon/issues/789
2023-11-05 14:59:50 +01:00
FineFindus
5e92562c99 build: add google's diff-match-patch
Copied from 62f2e689f4/java/src/name/fraser/neil/plaintext/diff_match_patch.java
2023-11-05 14:06:24 +01:00
Eugen Rochko
51952b0485 New translations strings.xml (Portuguese, Brazilian) 2023-11-05 10:47:44 +00:00
Eugen Rochko
2b0c5e7fac New translations strings.xml (Basque) 2023-11-04 19:16:42 +00:00
Eugen Rochko
3e6cea1a6a New translations strings.xml (Basque) 2023-11-04 17:29:35 +00:00
Eugen Rochko
1aec7c0999 New translations strings.xml (Russian) 2023-11-03 08:28:42 +00:00
Eugen Rochko
5da98809a5 New translations strings.xml (Russian) 2023-11-03 06:34:07 +00:00
Eugen Rochko
49695614b7 New translations strings.xml (Russian) 2023-11-03 05:25:21 +00:00
Eugen Rochko
3fbbc104b7 New translations strings.xml (Galician) 2023-11-02 08:42:01 +00:00
Otavio Pliger
022e61b2d0 Don't display empty image preview if thumbnail image is null 2023-11-02 09:13:55 +01:00
FineFindus
4a58fae111 feat: re-add 12 hour option to polls 2023-11-02 09:08:28 +01:00
Eugen Rochko
2fe7c0b85e New translations strings.xml (Slovenian) 2023-11-02 00:28:09 +00:00
Grishka
09d0e82216 Fix video player state after app resumption 2023-11-02 02:21:11 +03:00
Eugen Rochko
d208fcea7d New translations strings.xml (Persian) 2023-11-01 21:16:18 +00:00
Eugen Rochko
cc0674db34 New translations strings.xml (Persian) 2023-11-01 20:07:40 +00:00
FineFindus
47549fa88c feat(status/translation): support translating spoiler 2023-11-01 20:44:24 +01:00
FineFindus
be2b290300 fix(status/translation): do not require all fields 2023-11-01 19:56:59 +01:00
FineFindus
6c9db5ce0d feat(status): translate poll options 2023-11-01 19:52:45 +01:00
FineFindus
24677ea239 feat(status): translate media attachments 2023-11-01 19:23:09 +01:00
Grishka
1d5b84943d Merge branch 'l10n_master' 2023-11-01 17:38:39 +03:00
Eugen Rochko
14fe992ca5 New translations strings.xml (Turkish) 2023-11-01 14:37:37 +00:00
Grishka
15232bddaf Merge branch 'l10n_master' 2023-11-01 17:35:51 +03:00
Grishka
160ef25621 Also try resolving URLs from link cards 2023-11-01 17:32:50 +03:00
Eugen Rochko
2afb8688a3 New translations strings.xml (Vietnamese) 2023-10-31 11:03:23 +00:00
Eugen Rochko
9d1af035ea New translations strings.xml (Slovenian) 2023-10-31 08:55:58 +00:00
Eugen Rochko
fb7574d814 New translations strings.xml (Persian) 2023-10-30 16:47:36 +01:00
Eugen Rochko
201a3cb9e3 New translations strings.xml (Slovenian) 2023-10-30 15:17:03 +01:00
Eugen Rochko
cc735ee6a1 New translations strings.xml (Turkish) 2023-10-30 13:09:02 +01:00
Eugen Rochko
0165e14ea0 New translations strings.xml (Turkish) 2023-10-30 11:23:35 +01:00
Grishka
97d19605d5 Fix link cards 2023-10-29 14:43:10 +03:00
Grishka
bc490218f9 Open profile and post links in-app (AND-114) 2023-10-29 14:18:29 +03:00
Grishka
6dac05a21d Fix pagination in lists 2023-10-29 09:59:47 +03:00
Grishka
fd3fff6322 fix 2023-10-28 21:24:04 +03:00
Grishka
edb64fff2e Add info banner to the new local timeline 2023-10-28 18:56:15 +03:00
Grishka
fe0e854e72 Remove local timeline from search tab 2023-10-28 18:43:54 +03:00
LucasGGamerM
288043cf49 chore: deleting megalodon 108 changelog 2023-10-28 10:47:56 -03:00
LucasGGamerM
fd5beba780 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	metadata/en-US/full_description.txt
#	metadata/ja-JP/title.txt
2023-10-28 10:46:33 -03:00
sk
c8122aa65b fuck it, production release 2023-10-28 15:09:00 +02:00
sk
ef1584de55 merge upstream strings 2023-10-28 14:50:56 +02:00
Eugen Rochko
06c85fb203 New translations strings.xml (Slovenian) 2023-10-27 23:25:59 +02:00
Eugen Rochko
69926c4ae1 New translations strings.xml (Slovenian) 2023-10-27 22:19:05 +02:00
Eugen Rochko
ef44b0a412 New translations strings.xml (Slovenian) 2023-10-27 20:59:33 +02:00
Eugen Rochko
8577ac1027 New translations strings.xml (Armenian) 2023-10-27 17:46:27 +02:00
sk
3075030b1c add todo 2023-10-27 17:38:54 +02:00
sk
42c56401db fix wrong check for gap item
re sk22#898
2023-10-27 17:34:47 +02:00
sk
a18a4383e5 update gap in database when deleting status
closes sk22#898
2023-10-27 17:19:59 +02:00
sk
375b5b3133 fix gaps not showing time
closes sk22#889
2023-10-27 16:14:16 +02:00
sk
4cab916957 move status list fragment check out of predicate 2023-10-27 16:02:14 +02:00
Eugen Rochko
32da050106 New translations strings.xml (Armenian) 2023-10-27 15:59:36 +02:00
Eugen Rochko
526b74b3ef New translations strings.xml (Basque) 2023-10-27 12:15:13 +02:00
Eugen Rochko
97ab328a9c New translations strings.xml (Basque) 2023-10-27 09:50:11 +02:00
sk
7902691093 avoid index out of bounds
closes sk22#904
2023-10-27 01:28:27 +02:00
sk
d5d9e20a06 update strings 2023-10-26 18:53:40 +02:00
Codeberg Translate
1eb55428db Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/
2023-10-26 16:39:24 +00:00
reindex
9b82d704f6 Translated using Weblate (Japanese)
Currently translated at 11.1% (2 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ja/
2023-10-26 16:39:24 +00:00
reindex
dbf3d2776e Translated using Weblate (Japanese)
Currently translated at 86.4% (345 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ja/
2023-10-26 16:39:24 +00:00
Espasant3
c077b9d47c Translated using Weblate (Galician)
Currently translated at 98.4% (393 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-10-26 16:39:24 +00:00
kallekn
34157805a1 Translated using Weblate (Finnish)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fi/
2023-10-26 16:39:24 +00:00
AiOO
576da2a3eb Translated using Weblate (Korean)
Currently translated at 99.2% (396 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ko/
2023-10-26 16:39:24 +00:00
SomeTr
e99e5b2836 Translated using Weblate (Ukrainian)
Currently translated at 99.7% (398 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-26 16:39:24 +00:00
Oliebol
fe25974958 Translated using Weblate (Dutch)
Currently translated at 91.7% (366 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-10-26 16:39:24 +00:00
Linerly
1a95b4e361 Translated using Weblate (Indonesian)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-10-26 16:39:24 +00:00
Choukajohn
35e3d5afc4 Translated using Weblate (French)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-26 16:39:24 +00:00
gallegonovato
cc1e13d1bd Translated using Weblate (Spanish)
Currently translated at 100.0% (399 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-10-26 16:39:24 +00:00
poesty
0b622f8f2a Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (398 of 399 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-26 16:39:24 +00:00
gallegonovato
9cf7da6419 Translated using Weblate (Spanish)
Currently translated at 100.0% (398 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-10-26 16:39:24 +00:00
sk
812ab7198c revert, i said!
actually, the cached posts limit can stay
2023-10-26 18:38:06 +02:00
sk
e8c9253a76 don't remove gap when removing status
re: sk22#898
2023-10-26 18:32:46 +02:00
sk
c8f633ae3b don't hide gap behind warning
closes sk22#899
2023-10-26 18:23:56 +02:00
sk
af339a833f fix gap time being wrong when next status is own
closes sk22#902
2023-10-26 18:11:54 +02:00
sk
708142e1b8 Revert "refresh data from cache"
This reverts commit 60a998be89.
2023-10-26 17:53:36 +02:00
sk
55f32671c5 Revert "fix timeline breaking when max_id is null"
This reverts commit 289db09770.
2023-10-26 17:52:43 +02:00
Eugen Rochko
21603eedcf New translations strings.xml (Basque) 2023-10-26 16:45:54 +02:00
Eugen Rochko
7fbef273a1 New translations strings.xml (French) 2023-10-26 12:03:07 +02:00
Eugen Rochko
9e19716504 New translations strings.xml (Icelandic) 2023-10-26 10:47:01 +02:00
LucasGGamerM
828d07d6b5 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposePollViewController.java
#	metadata/uk/changelogs/56.txt
2023-10-25 20:10:41 -03:00
Eugen Rochko
b473642ab4 New translations strings.xml (Ukrainian) 2023-10-26 00:20:48 +02:00
Gregory K
fba55f01a0 Merge pull request #722 from untitaker/fix-gboard-garbage-alt-text
Remove garbage alt text from images attached via Gboard
2023-10-25 22:56:13 +03:00
Markus Unterwaditzer
015e63ba66 apply review feedback 2023-10-25 21:50:10 +02:00
Markus Unterwaditzer
d92e2407f3 Remove garbage alt text from images attached via Gboard
See also: https://github.com/tuskyapp/Tusky/pull/4068

----

Steps to reproduce:

1. install Gboard
   (https://play.google.com/store/apps/details?id=com.google.android.inputmethod.latin)

2. open a direct link to any image in Firefox
3. long-press the image to get a "Copy Image" dialogue (and copy the
   image)
4. compose a new post in Tusky
5. Gboard will suggest to paste the image from clipboard
6. paste image, see that when opening alt text editor, it is filled out
   with this garbage string

Why is this bad? It's not when I just fix the alt text. But it breaks
every mechanism that is supposed to remind me of adding alt text.

It's hard to argue that this is within scope of this app but I
also don't see it getting fixed in Gboard, so here we go.
2023-10-25 14:44:20 +02:00
sk
289db09770 fix timeline breaking when max_id is null 2023-10-25 14:42:35 +02:00
Eugen Rochko
a4f84fb8cd New translations strings.xml (Finnish) 2023-10-25 12:13:33 +02:00
Eugen Rochko
bfe88745ca New translations strings.xml (Greek) 2023-10-25 12:13:31 +02:00
Grishka
0d334237ba Merge branch 'l10n_master' 2023-10-25 08:15:22 +03:00
Eugen Rochko
fd5cff3fea New translations strings.xml (Italian) 2023-10-24 23:34:04 +02:00
Eugen Rochko
af5b82e9fd New translations strings.xml (Italian) 2023-10-24 22:37:17 +02:00
Eugen Rochko
d3561748c8 New translations strings.xml (Scottish Gaelic) 2023-10-24 14:45:08 +02:00
Eugen Rochko
791a1d804b New translations strings.xml (Japanese) 2023-10-24 03:33:27 +02:00
Eugen Rochko
2442424e3b New translations strings.xml (Chinese Traditional) 2023-10-24 01:10:21 +02:00
sk
89d7dfd694 don't globally remove status on refresh
closes sk22#896
2023-10-23 23:55:04 +02:00
Eugen Rochko
0ecedd2820 New translations strings.xml (Thai) 2023-10-23 18:31:33 +02:00
Eugen Rochko
958d62ec0c New translations strings.xml (Persian) 2023-10-23 17:11:47 +02:00
Eugen Rochko
400cfb2141 New translations strings.xml (Galician) 2023-10-23 17:11:44 +02:00
Eugen Rochko
52b860dd8f New translations strings.xml (Vietnamese) 2023-10-23 17:11:43 +02:00
Eugen Rochko
4d57d8d576 New translations strings.xml (Greek) 2023-10-23 17:11:31 +02:00
Eugen Rochko
9a098accd8 New translations strings.xml (German) 2023-10-23 17:11:29 +02:00
Eugen Rochko
62f3b2522c New translations strings.xml (Spanish) 2023-10-23 17:11:24 +02:00
Eugen Rochko
9b48cd2037 New translations strings.xml (Chinese Traditional) 2023-10-23 17:11:21 +02:00
Eugen Rochko
69776d45d1 New translations strings.xml (Swedish) 2023-10-23 17:11:18 +02:00
Eugen Rochko
b8fb2660a4 New translations strings.xml (Japanese) 2023-10-23 17:11:14 +02:00
Eugen Rochko
941281298d New translations strings.xml (Icelandic) 2023-10-23 17:11:13 +02:00
Eugen Rochko
8afc4511a6 New translations strings.xml (Thai) 2023-10-23 17:11:12 +02:00
Grishka
f43ef325ae Following -> Home 2023-10-23 17:12:22 +03:00
Eugen Rochko
bbdc323204 New translations strings.xml (Icelandic) 2023-10-23 15:21:14 +02:00
sk
60a998be89 refresh data from cache 2023-10-23 01:00:26 +02:00
sk
128e75bc24 fix animatable avatars not playing in notification headers
closes sk22#882
2023-10-22 23:35:09 +02:00
sk
69c60d484c load custom emoji in reply header
closes sk22#886
2023-10-22 23:30:33 +02:00
sk
c70750a508 fix strings 2023-10-22 23:21:47 +02:00
sk
5553ca25a1 Merge remote-tracking branch 'upstream/l10n_master' 2023-10-22 23:10:56 +02:00
sk
ff87829e72 Merge remote-tracking branch 'weblate/main' 2023-10-22 23:09:45 +02:00
sk
02983c76b9 add mutuals follow button state
closes sk22#890
2023-10-22 23:09:35 +02:00
sk
3e28eb2ccf fix polls not updating when behind spoiler
closes sk22#892
2023-10-22 22:39:10 +02:00
sk
3cf23474e3 fix issue with too-wide context menu icons
closes sk22#893
2023-10-22 22:10:25 +02:00
sk
9f0ff2dcd4 fix editing scheduled/drafted polls
closes sk22#894
2023-10-22 21:44:02 +02:00
Eugen Rochko
8ed9fb6276 New translations strings.xml (Chinese Traditional) 2023-10-22 08:53:38 +02:00
Grishka
27cbb70352 Overdraw fix 2023-10-22 05:18:10 +03:00
Grishka
f5b10b516c Show all followers when creating a list 2023-10-22 05:14:21 +03:00
Grishka
5580308968 Improve lists caching and consistency 2023-10-22 05:07:28 +03:00
SomeTr
df7a0ee490 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-10-21 20:53:12 +00:00
Espasant3
2e360dc275 Translated using Weblate (Galician)
Currently translated at 98.7% (393 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-10-21 20:53:12 +00:00
poesty
8dc88d2fd7 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (397 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-21 20:53:12 +00:00
Eugen Rochko
901c70efc3 New translations strings.xml (Icelandic) 2023-10-21 19:24:31 +02:00
Eugen Rochko
3d44e5d2cc New translations strings.xml (Icelandic) 2023-10-21 18:27:37 +02:00
LucasGGamerM
894c8cacd6 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
2023-10-21 09:32:19 -03:00
Eugen Rochko
33ea3da84d New translations strings.xml (Basque) 2023-10-21 11:37:37 +02:00
Grishka
572901ec9d minor fixes 2023-10-21 11:52:15 +03:00
Grishka
965239d215 Apply filters on more screens 2023-10-21 11:44:39 +03:00
Grishka
ac1e5e991e Don't auto-refresh notifications if the user scrolled too far 2023-10-21 11:34:55 +03:00
Eugen Rochko
e97203a6e3 New translations strings.xml (Thai) 2023-10-21 09:36:50 +02:00
Grishka
66b7b127f9 List creation (AND-98) 2023-10-21 09:13:01 +03:00
Eugen Rochko
b3f2987b14 New translations strings.xml (Vietnamese) 2023-10-21 03:54:29 +02:00
Eugen Rochko
c7426453a5 New translations strings.xml (German) 2023-10-21 02:52:53 +02:00
sk
b60c1a5db3 add back old see_new_posts strings 2023-10-21 01:43:03 +02:00
Eugen Rochko
664d5cc4c3 New translations strings.xml (German) 2023-10-21 01:04:51 +02:00
sk
47d1b182ac fix see new posts button design 2023-10-21 01:00:51 +02:00
sk
c33c3d9112 fix compatibility issue 2023-10-21 00:54:38 +02:00
sk
ff99e1023a Revert "use tonal background for see new posts button"
This reverts commit 8009dc0a75.
2023-10-20 16:23:02 +02:00
sk
8009dc0a75 use tonal background for see new posts button 2023-10-20 15:31:06 +02:00
sk
20039ec97e bump version 2023-10-20 13:33:02 +02:00
SomeTr
172d25eeb0 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (398 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-20 11:32:21 +00:00
alextecplayz
fa04e00032 Translated using Weblate (Romanian)
Currently translated at 100.0% (398 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-10-20 11:32:21 +00:00
Linerly
3ff442894d Translated using Weblate (Indonesian)
Currently translated at 100.0% (398 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-10-20 11:32:21 +00:00
Choukajohn
edb0823bf9 Translated using Weblate (French)
Currently translated at 100.0% (398 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-20 11:32:21 +00:00
sk
9de83355e1 revert api timeout and increase for search
closes sk22#881
2023-10-20 13:31:27 +02:00
sk
183fb0e8c0 use fluent back button 2023-10-20 12:54:51 +02:00
sk
d624a04e18 fix see new posts button font size 2023-10-20 12:50:34 +02:00
sk
303461d803 take switcher width into account for button 2023-10-20 12:17:12 +02:00
sk
11c7816bb1 make show new posts button less disruptive 2023-10-20 11:43:35 +02:00
sk
b5eae13a16 introduce separate tonal selector backgrounds 2023-10-20 11:43:15 +02:00
sk
59026286a1 don't cache as many posts 2023-10-20 10:46:56 +02:00
sk
ef0fbb26a4 don't return before refreshDone 2023-10-20 10:33:45 +02:00
sk
387b31193f hopefully unfuck removing statuses and reblogs 2023-10-19 21:42:17 +02:00
LucasGGamerM
28e816fd89 chore: fix build issues 2023-10-19 15:17:35 -03:00
LucasGGamerM
77081f6cc9 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsBehaviorFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsNotificationsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/model/Status.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/res/menu/profile_own.xml
2023-10-19 15:01:22 -03:00
sk22
95fa547f15 Translated using Weblate (German)
Currently translated at 100.0% (398 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-10-19 15:13:41 +00:00
sk22
3198c9adb1 Translated using Weblate (German)
Currently translated at 99.2% (395 of 398 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-10-19 15:07:29 +00:00
SomeTr
e1af6f4643 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-10-19 15:07:29 +00:00
SomeTr
cc52e491b0 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (396 of 396 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-19 15:07:29 +00:00
Choukajohn
b0db0d9f2e Translated using Weblate (French)
Currently translated at 100.0% (396 of 396 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-19 15:07:29 +00:00
sk
d699788a12 fix string 2023-10-19 17:07:18 +02:00
sk
bdbf441e1a Merge remote-tracking branch 'upstream/l10n_master' 2023-10-19 17:01:46 +02:00
sk
0deba2885b Merge remote-tracking branch 'weblate/main' 2023-10-19 17:01:13 +02:00
sk
48b39fd3bc boop version 2023-10-19 16:59:45 +02:00
sk
875b235e6d restart when show replies/boosts changes
closes sk22#879
2023-10-19 16:58:26 +02:00
sk
1711682322 don't display extra text if visibility == local 2023-10-19 16:54:23 +02:00
sk
70f5ec7500 update header layout 2023-10-19 16:54:07 +02:00
sk
d34c73c210 add options for locking account and default vis 2023-10-19 16:49:23 +02:00
sk
50a0586e8a fix content type index and title 2023-10-19 16:02:51 +02:00
Jacoco
1d2ca1e500 fix: hiding discovery on Akkoma + hiding privacy settings (#867)
* Hide privacy settings on Akkoma

* Fix Akkoma skipping discovery

* Make settings order more clear in the code
2023-10-19 15:42:29 +02:00
EndermanCo
34b7123a8d Translated using Weblate (Persian)
Currently translated at 100.0% (394 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-10-19 10:17:38 +00:00
butterflyoffire
ec718ff58e Translated using Weblate (Arabic)
Currently translated at 80.4% (317 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-10-19 10:17:38 +00:00
ihor_ck
20a442e27f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (394 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-19 10:17:38 +00:00
SomeTr
47cb74abc9 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (394 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-19 10:17:37 +00:00
SomeTr
a11e53c89a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (394 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-19 10:17:37 +00:00
David Lapshin
d721e8ca46 Translated using Weblate (Russian)
Currently translated at 99.7% (393 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-10-19 10:17:37 +00:00
alextecplayz
23e5dcd030 Translated using Weblate (Romanian)
Currently translated at 100.0% (394 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-10-19 10:17:37 +00:00
Linerly
25dd2cfab8 Translated using Weblate (Indonesian)
Currently translated at 100.0% (394 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-10-19 10:17:37 +00:00
Choukajohn
8537c7a7ae Translated using Weblate (French)
Currently translated at 100.0% (394 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-19 10:17:37 +00:00
gallegonovato
ebd7f9c36c Translated using Weblate (Spanish)
Currently translated at 100.0% (394 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-10-19 10:17:37 +00:00
poesty
49cda3aeb2 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (393 of 394 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-19 10:17:37 +00:00
sk
67cbc8aff2 don't load new posts every time we hit the top 2023-10-19 01:06:03 +02:00
sk
5db44cbf9d don't save bidi-safe string in account
also support custom emoji in account switcher
closes sk22#859
2023-10-19 01:01:53 +02:00
sk
95858e3280 remove debug println 2023-10-19 00:44:10 +02:00
sk
36846acbe5 fix null pointer when content language is null 2023-10-19 00:38:06 +02:00
sk
b95d944003 save own posts from timeline before removing them 2023-10-19 00:22:52 +02:00
sk
f52886af74 don't add own boosts to home
closes sk22#875
2023-10-19 00:09:09 +02:00
sk
6cbe406cef load new posts on reload 2023-10-19 00:04:54 +02:00
sk
3097bc7168 oops: put home timeline if result NOT empty 2023-10-18 23:28:44 +02:00
sk
5f7faa69e8 remove existing statuses to move them up 2023-10-18 22:07:10 +02:00
sk
ed24e89a96 don't compare newly fetched posts to created post
closes sk22#866
2023-10-18 21:48:11 +02:00
sk
8ea05c6ebd fix menu grouping 2023-10-18 21:24:59 +02:00
sk
a30fcbbe34 reorder profile menu
closes sk22#873
2023-10-18 21:23:58 +02:00
sk
39f76f9988 always show "boosted" text in reply/boost line
closes sk22#874
2023-10-18 21:21:03 +02:00
sk
5135653cd3 wow!! configurable first fraction 2023-10-18 21:11:48 +02:00
sk
2e4f04cd88 fix divide by zero error 2023-10-18 20:40:23 +02:00
Eugen Rochko
a44e0e036a New translations strings.xml (Persian) 2023-10-18 20:27:30 +02:00
Eugen Rochko
5d54c1bae4 New translations strings.xml (Spanish) 2023-10-18 20:27:29 +02:00
sk
09577074a9 merge settings changes 2023-10-18 20:26:20 +02:00
LucasGGamerM
5c071aa3ec chore: make app compile again 2023-10-18 14:47:21 -03:00
sk
53f0e2a933 change extra text size 2023-10-18 19:39:12 +02:00
sk
b8dccbbef1 now hopefully fixing header/subtitle extra sizing 2023-10-18 19:23:13 +02:00
Eugen Rochko
2ef17ba051 New translations strings.xml (Spanish) 2023-10-18 19:15:17 +02:00
Eugen Rochko
f63daf3a4e New translations strings.xml (Thai) 2023-10-18 19:15:16 +02:00
Eugen Rochko
52b079be2a New translations strings.xml (Thai) 2023-10-18 18:11:40 +02:00
LucasGGamerM
c5a61c3916 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FileStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/res/menu/profile.xml
#	metadata/cy/full_description.txt
2023-10-18 08:21:03 -03:00
Eugen Rochko
efeca17106 New translations strings.xml (Chinese Traditional) 2023-10-18 11:34:03 +02:00
Eugen Rochko
6827166c1d New translations strings.xml (Galician) 2023-10-18 08:51:11 +02:00
Eugen Rochko
04483e61e8 New translations strings.xml (Galician) 2023-10-18 07:49:15 +02:00
Grishka
0ed858b99c Update string 2023-10-18 02:56:13 +03:00
Grishka
9b3e153a4d Add "manage accounts" item to settings 2023-10-18 01:41:10 +03:00
Grishka
e525aef3d9 Improve new posts button animation 2023-10-18 01:37:02 +03:00
Eugen Rochko
22fe174922 New translations strings.xml (Icelandic) 2023-10-17 20:07:09 +02:00
Eugen Rochko
f143da3913 New translations strings.xml (Kabyle) 2023-10-17 04:25:08 +02:00
Eugen Rochko
7e9f41c74b New translations strings.xml (Scottish Gaelic) 2023-10-17 04:25:05 +02:00
Eugen Rochko
a1474d0d29 New translations strings.xml (Filipino) 2023-10-17 04:25:03 +02:00
Eugen Rochko
0dce936ad3 New translations strings.xml (Persian) 2023-10-17 04:24:58 +02:00
Eugen Rochko
6ebbbb4c6c New translations strings.xml (Indonesian) 2023-10-17 04:24:57 +02:00
Eugen Rochko
dc6ddbd0ee New translations strings.xml (Portuguese, Brazilian) 2023-10-17 04:24:56 +02:00
Eugen Rochko
86c81d6b53 New translations strings.xml (Galician) 2023-10-17 04:24:55 +02:00
Eugen Rochko
451a92aa36 New translations strings.xml (Vietnamese) 2023-10-17 04:24:53 +02:00
Eugen Rochko
5c42e67e73 New translations strings.xml (Chinese Simplified) 2023-10-17 04:24:52 +02:00
Eugen Rochko
d20d36d964 New translations strings.xml (Turkish) 2023-10-17 04:24:51 +02:00
Eugen Rochko
1a8d46c71e New translations strings.xml (Slovenian) 2023-10-17 04:24:50 +02:00
Eugen Rochko
91da10eca3 New translations strings.xml (Portuguese) 2023-10-17 04:24:49 +02:00
Eugen Rochko
3bb0dcee53 New translations strings.xml (Polish) 2023-10-17 04:24:48 +02:00
Eugen Rochko
d3fd4b200f New translations strings.xml (Norwegian) 2023-10-17 04:24:47 +02:00
Eugen Rochko
fed96864e1 New translations strings.xml (Dutch) 2023-10-17 04:24:46 +02:00
Eugen Rochko
3b351bea27 New translations strings.xml (Korean) 2023-10-17 04:24:45 +02:00
Eugen Rochko
254e01dca1 New translations strings.xml (Armenian) 2023-10-17 04:24:44 +02:00
Eugen Rochko
19158e1d48 New translations strings.xml (Hungarian) 2023-10-17 04:24:43 +02:00
Eugen Rochko
bffb78fccf New translations strings.xml (Basque) 2023-10-17 04:24:41 +02:00
Eugen Rochko
a3800592a2 New translations strings.xml (Greek) 2023-10-17 04:24:39 +02:00
Eugen Rochko
22a498dfc9 New translations strings.xml (German) 2023-10-17 04:24:38 +02:00
Eugen Rochko
9ea96e32bd New translations strings.xml (Danish) 2023-10-17 04:24:37 +02:00
Eugen Rochko
68f51a123e New translations strings.xml (Czech) 2023-10-17 04:24:36 +02:00
Eugen Rochko
43b1b63581 New translations strings.xml (Catalan) 2023-10-17 04:24:35 +02:00
Eugen Rochko
43b4a2c515 New translations strings.xml (Belarusian) 2023-10-17 04:24:34 +02:00
Eugen Rochko
5b9cfdb689 New translations strings.xml (Arabic) 2023-10-17 04:24:33 +02:00
Eugen Rochko
b43cd7103a New translations strings.xml (Spanish) 2023-10-17 04:24:32 +02:00
Eugen Rochko
30e4f6e0f5 New translations strings.xml (French) 2023-10-17 04:24:30 +02:00
Eugen Rochko
1d0ebf889b New translations strings.xml (Chinese Traditional) 2023-10-17 04:24:28 +02:00
Eugen Rochko
7c4f1da485 New translations strings.xml (Finnish) 2023-10-17 04:24:27 +02:00
Eugen Rochko
8163921014 New translations strings.xml (Russian) 2023-10-17 04:24:26 +02:00
Eugen Rochko
993393dd96 New translations strings.xml (Swedish) 2023-10-17 04:24:25 +02:00
Eugen Rochko
82aed43934 New translations strings.xml (Italian) 2023-10-17 04:24:24 +02:00
Eugen Rochko
fa65134c26 New translations strings.xml (Ukrainian) 2023-10-17 04:24:23 +02:00
Eugen Rochko
af4266c739 New translations strings.xml (Japanese) 2023-10-17 04:24:22 +02:00
Eugen Rochko
f72ea2e763 New translations strings.xml (Icelandic) 2023-10-17 04:24:21 +02:00
Eugen Rochko
c5540270a3 New translations strings.xml (Thai) 2023-10-17 04:24:20 +02:00
Grishka
201b72c9c8 Add "copy link" to post context menu 2023-10-17 04:41:18 +03:00
Grishka
26b99f5f68 New and improved™ "new posts" button (AND-102) 2023-10-17 04:31:07 +03:00
Grishka
d3dc774492 Empty states for home timeline popup submenus (AND-99) 2023-10-17 03:22:23 +03:00
Grishka
1f7155a932 Reorder notification tabs and remember selection (AND-82) 2023-10-17 03:00:05 +03:00
Grishka
02729fe02b Always allow marking notifications as read 2023-10-17 02:47:50 +03:00
sk
5840c94395 isolate bi-directional text in display name 2023-10-17 01:03:06 +02:00
sk
92d7ae67ef don't break custom emoji in pronouns 2023-10-17 00:38:27 +02:00
sk
fc09514a4d the pronouns are stored in the balls (extra text)
also, improve max-width behavior
2023-10-17 00:28:00 +02:00
sk
300fa97781 remove comment 2023-10-16 19:36:30 +02:00
sk
e8f0891f3a re-aligning buttons with avatar 2023-10-16 19:36:12 +02:00
sk
f25906a694 Merge remote-tracking branch 'upstream/l10n_master' 2023-10-16 19:28:27 +02:00
sk
e52699bb1c Merge remote-tracking branch 'weblate/main' 2023-10-16 19:27:38 +02:00
SomeTr
712826451f Translated using Weblate (Welsh)
Currently translated at 77.7% (14 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/cy/
2023-10-16 17:27:28 +00:00
ihor_ck
3b3065d8bd Translated using Weblate (Ukrainian)
Currently translated at 100.0% (393 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-16 17:27:28 +00:00
alextecplayz
e11fe7d8cf Translated using Weblate (Romanian)
Currently translated at 100.0% (393 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-10-16 17:27:28 +00:00
SomeTr
9a2f7475c9 Translated using Weblate (German)
Currently translated at 100.0% (393 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-10-16 17:27:28 +00:00
sk
6dffb10906 make version go up 2023-10-16 19:27:12 +02:00
sk
71039d6901 the changes i made were wrong (what a surprise) 2023-10-16 19:26:29 +02:00
sk
db18c7a0d0 events for boosts, adapt removeStatus method 2023-10-16 19:14:16 +02:00
sk
7e80ed6af2 fix and refactor removing statuses 2023-10-16 18:28:47 +02:00
sk
bdd2b90581 allow entering full handles instead of domain
closes sk22#646
2023-10-16 01:03:19 +02:00
sk
50d017d8ba fix account context menu
closes sk22#652
2023-10-16 00:44:47 +02:00
sk
98e003437c fix confirm unfollow 2023-10-16 00:11:47 +02:00
sk
d5d06af614 fix keyboard gap in scheduled post list height 2023-10-15 23:59:26 +02:00
sk
1c930ca3bb check for draft before checking alt texts 2023-10-15 23:42:53 +02:00
sk
0f0c1093d4 don't hide draft options when redrafting 2023-10-15 23:35:01 +02:00
sk
2ad5dc5a74 make unfinished attachment dialog make more sense 2023-10-15 23:20:40 +02:00
sk
e02e0865bd fix softlock when re-saving draft
closes sk22#659
2023-10-15 23:05:29 +02:00
sk
59ee1af75d fix border radius
closes sk22#861
2023-10-15 22:53:23 +02:00
sk
c04584dfa6 fix alignments 2023-10-15 21:17:38 +02:00
sk
190a3b5b08 use fluent dismiss button 2023-10-15 21:17:26 +02:00
sk
f5a67e65f0 fix missing content descriptions, button alignment 2023-10-15 21:16:42 +02:00
Eugen Rochko
498078b6e0 New translations strings.xml (Swedish) 2023-10-15 20:37:55 +02:00
sk
de8c289ca7 finally!! fix reblog/-ply line with extra emoji
closes sk22#468
2023-10-15 20:29:15 +02:00
sk
07b205a746 remove fullText emoji helper, fix content description 2023-10-15 20:18:40 +02:00
sk
a7941310bc no more vertical reblog/reply line layout
closes sk22#834
2023-10-15 20:11:03 +02:00
sk
864b6dcdac remove outdated comment 2023-10-15 20:04:39 +02:00
sk
d3744bb397 remove compact reblog line setting 2023-10-15 19:57:22 +02:00
sk
7d1853bc88 fix saving wrong item
closes sk22#863
2023-10-15 19:46:14 +02:00
sk
9f0db755d1 fix text/buttons alignment, margin
closes sk22#864
2023-10-15 19:44:37 +02:00
sk
06819806be "fix" trends loading indefinitely
closes sk22#781
2023-10-15 19:23:57 +02:00
Eugen Rochko
526f5e319b New translations strings.xml (Swedish) 2023-10-15 18:51:35 +02:00
sk
30a4d0efd9 rename vars, compare content status id for reply 2023-10-15 18:10:57 +02:00
Eugen Rochko
d419dba44a New translations strings.xml (Swedish) 2023-10-15 14:26:36 +02:00
Choukajohn
87da6b9b81 Translated using Weblate (French)
Currently translated at 100.0% (393 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-15 01:20:04 +00:00
LucasGGamerM
71970b2249 chora: make app compile again 2023-10-14 14:59:03 -03:00
LucasGGamerM
49dc34b256 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/src/main/res/layout/display_item_footer.xml
2023-10-14 14:54:46 -03:00
LucasGGamerM
1ca95be7e0 fix(compose): fix publish button being cut off on small screens 2023-10-14 10:34:36 -03:00
LucasGGamerM
8a5b881a57 fix: show media sensitive button even if a CW is present 2023-10-14 10:26:57 -03:00
Linerly
67cc1553da Translated using Weblate (Indonesian)
Currently translated at 100.0% (393 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-10-14 05:22:21 +00:00
sk
9a985aad29 fix deleting boosted posts in profile 2023-10-14 01:47:13 +02:00
Eugen Rochko
fd98159fce New translations strings.xml (Greek) 2023-10-14 00:27:35 +02:00
sk
42fac30e63 wrap into full-width view 2023-10-13 23:50:49 +02:00
sk
18e7f14c16 remove duplicate selectable background 2023-10-13 23:49:54 +02:00
sk
7b263800a6 reduce button margins, realign buttons
closes sk22#
2023-10-13 23:27:49 +02:00
sk
17387a32b2 don't scale buttons with text
re: sk22#787
2023-10-13 23:04:45 +02:00
sk
3fe642c2f2 remove unwanted margin 2023-10-13 22:36:15 +02:00
sk
9225447409 use action bar icons color for toolbar
closes sk22#860
2023-10-13 22:27:53 +02:00
SomeTr
e05dee64b7 Translated using Weblate (Ukrainian)
Currently translated at 99.4% (391 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-13 20:23:08 +00:00
Choukajohn
7f30973b39 Translated using Weblate (French)
Currently translated at 100.0% (393 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-13 20:23:08 +00:00
gallegonovato
25da9bb2d0 Translated using Weblate (Spanish)
Currently translated at 100.0% (393 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-10-13 20:23:08 +00:00
poesty
e17b49e704 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (392 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-13 20:23:08 +00:00
sk
db661b56cb fix inconsistent styles, fix non-sp font
closes sk22#862
2023-10-13 22:17:45 +02:00
sk
638e1bf8e9 do show labels per default
This reverts commit a1c81e89e8.
2023-10-13 21:16:52 +02:00
LucasGGamerM
dafe233315 chore: make app compile again 2023-10-13 11:17:50 -03:00
LucasGGamerM
4107169c73 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/res/layout/fragment_compose.xml
2023-10-13 11:16:10 -03:00
sk
83786171a7 ellipsize tab bar labels 2023-10-13 13:49:54 +02:00
sk
f638a538c1 Merge remote-tracking branch 'upstream/l10n_master' 2023-10-13 13:45:36 +02:00
sk22
11e2316cbc Translated using Weblate (German)
Currently translated at 100.0% (393 of 393 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-10-13 11:45:15 +00:00
ihor_ck
1b372c7dca Translated using Weblate (Ukrainian)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-13 11:43:24 +00:00
ihor_ck
cf4e37fade Translated using Weblate (Ukrainian)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-13 11:43:24 +00:00
SomeTr
f3fc60ac00 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-13 11:43:24 +00:00
butterflyoffire
1026d99025 Translated using Weblate (Arabic)
Currently translated at 81.2% (317 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-10-13 11:43:24 +00:00
ihor_ck
3fb947fbfe Translated using Weblate (Ukrainian)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-13 11:43:24 +00:00
David Lapshin
f097506d49 Translated using Weblate (Russian)
Currently translated at 99.7% (389 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-10-13 11:43:24 +00:00
alextecplayz
3df7d74fbf Translated using Weblate (Romanian)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-10-13 11:43:24 +00:00
Oliebol
3fc17eb9c9 Translated using Weblate (Dutch)
Currently translated at 81.0% (316 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-10-13 11:43:24 +00:00
Linerly
67ee74c1a0 Translated using Weblate (Indonesian)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-10-13 11:43:24 +00:00
Espasant3
bcebba8896 Translated using Weblate (Galician)
Currently translated at 97.9% (382 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-10-13 11:43:24 +00:00
Choukajohn
451b0c1ac5 Translated using Weblate (French)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-13 11:43:24 +00:00
butterflyoffire
5d08b4923b Translated using Weblate (French)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-13 11:43:24 +00:00
kallekn
9ba948c721 Translated using Weblate (Finnish)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fi/
2023-10-13 11:43:24 +00:00
gallegonovato
23e7513133 Translated using Weblate (Spanish)
Currently translated at 100.0% (390 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-10-13 11:43:24 +00:00
poesty
aaed8e53c8 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (389 of 390 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-13 11:43:24 +00:00
sk
f2754cc5c9 bump version 2023-10-13 13:43:08 +02:00
sk
6a65edd089 fix draft/schedule view for large display sizes
cc @LucasGGamerM
see 77e19b4d6f
2023-10-13 13:39:51 +02:00
LucasGGamerM
4d49b10585 fix(compose): make 'This post will be saved as a draft' view work better on smaller screens
cc: @sk22
2023-10-13 13:27:13 +02:00
LucasGGamerM
767312f71c feat: only spin favorite button if its a star 2023-10-13 08:21:05 -03:00
LucasGGamerM
045668d67b fix(nord): use nord colors for like (heart) and favorite (star) 2023-10-13 08:16:11 -03:00
Gregory K
f5b98009dd Merge pull request #712 from mastodon/remove-funding
Delete .github/FUNDING.yml
2023-10-13 14:08:47 +03:00
Renaud Chaput
cf0b66d852 Delete .github/FUNDING.yml
We now have an organisation-wide funding configuration, this one is no longer needed
2023-10-13 10:45:53 +02:00
LucasGGamerM
8f80668e0f fix: always show hashtag pin menu item as action 2023-10-12 22:10:23 -03:00
LucasGGamerM
b1754846f8 fix: this fixes an issue where the app wouldn't want to compile because of the StringRes at the title and CharSequence as the subtitle
This addresses that by resolving the StringRes and making the app compile again

cc: @sk22
2023-10-12 22:02:38 -03:00
LucasGGamerM
681ee1c211 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java
2023-10-12 21:41:18 -03:00
LucasGGamerM
714ec9692c fix: this fixes a compilation bug. I am not sure how, but yes 2023-10-12 21:35:47 -03:00
sk
86bfab81bd lucas is right
closes sk22#857
2023-10-13 02:27:34 +02:00
LucasGGamerM
07d2ad749e Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java
2023-10-12 21:25:16 -03:00
sk
50e313cff0 fix gap max_id when post before gap is filtered
hopefully fixes sk22#856
2023-10-13 02:22:03 +02:00
LucasGGamerM
1e7e4810f1 fix: make favorite button spin again 2023-10-12 21:16:21 -03:00
sk
5d07cde6dd fix gap not filtering posts 2023-10-13 01:48:45 +02:00
sk
f84a923102 implement global default color
closes sk22#853
2023-10-13 01:28:59 +02:00
LucasGGamerM
9964236616 fix: make notification header timestamp ellipsizeable
cc: @sk22
2023-10-12 20:16:50 -03:00
sk
c9eac34ae6 fix gap item alpha not being reset
closes sk22#851
2023-10-13 00:27:35 +02:00
sk
181a0577c8 fix missing gap item when status removed 2023-10-13 00:24:32 +02:00
sk
0426084194 fix changes breaking off-screen bound holders
i hope i didn't break everything lol
2023-10-13 00:14:29 +02:00
LucasGGamerM
26579abe6a fix: make notification header background work well when hovered upon 2023-10-12 19:10:36 -03:00
LucasGGamerM
d829c659bd chore: make app compile again 2023-10-12 15:34:32 -03:00
LucasGGamerM
ce3ca42c7a Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/CustomEmojiPopupKeyboard.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/NotificationHeaderStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/ColorPalette.java
#	mastodon/src/main/res/color/bookmark_icon.xml
#	mastodon/src/main/res/color/boost_icon.xml
#	mastodon/src/main/res/color/favorite_icon.xml
#	mastodon/src/main/res/layout/display_item_footer.xml
#	mastodon/src/main/res/layout/tab_bar.xml
#	mastodon/src/main/res/values/palettes.xml
#	mastodon/src/main/res/values/styles.xml
#	metadata/uk/changelogs/51.txt
#	metadata/uk/changelogs/56.txt
#	metadata/uk/changelogs/61.txt
#	metadata/uk/full_description.txt
2023-10-12 14:58:23 -03:00
Eugen Rochko
197d0caf44 New translations strings.xml (Japanese) 2023-10-11 18:34:49 +02:00
Eugen Rochko
a4a082f76a New translations strings.xml (Japanese) 2023-10-11 17:14:57 +02:00
Grishka
84026afb92 A bunch of minor RTL fixes 2023-10-11 00:49:40 +03:00
Grishka
4dea7d2a52 Retain CWs but expand them when showCWs setting is off
#323, closes #87
2023-10-11 00:27:26 +03:00
Grishka
2df1b7dd61 Remove repeating logging code from MastodonAPIController 2023-10-10 23:47:36 +03:00
Grishka
89042113a5 Fix default server loading timeout 2023-10-10 23:43:44 +03:00
Grishka
48665ebcce Scroll search to top on query change 2023-10-10 23:28:09 +03:00
Eugen Rochko
2528d48010 New translations strings.xml (Icelandic) 2023-10-10 18:10:41 +02:00
Eugen Rochko
5456d71979 New translations strings.xml (Chinese Traditional) 2023-10-10 10:25:33 +02:00
Eugen Rochko
e36aae3cf3 New translations strings.xml (Thai) 2023-10-09 20:31:09 +02:00
Grishka
d6040c0895 Hide the FAB while editing profile 2023-10-09 19:42:54 +02:00
Eugen Rochko
6d12e2dd72 New translations strings.xml (Thai) 2023-10-09 19:20:32 +02:00
sk
ff47e6edba remove unused method 2023-10-09 18:46:52 +02:00
sk
327ceb04d4 fix deleting notifications
closes sk22#677
closes sk22#633
2023-10-09 18:35:27 +02:00
sk
40a34b07de add option to disable underlined links
closes sk22#847
2023-10-09 18:07:19 +02:00
Eugen Rochko
f117249bb5 New translations strings.xml (Thai) 2023-10-09 18:02:31 +02:00
sk
1c90164ece fix broken hashtag links
closes sk22#848
2023-10-09 17:59:22 +02:00
sk
0d5fb250bc migrate theme colors
closes sk22#849
2023-10-09 17:54:54 +02:00
sk
e9bd5a373a remove unused icons and replace some wrong ones 2023-10-09 17:39:18 +02:00
sk
c494d283ba remove duplicate section, replace icons
closes sk22#850
2023-10-09 17:38:55 +02:00
Eugen Rochko
cf1d537367 New translations strings.xml (Vietnamese) 2023-10-09 06:03:28 +02:00
Eugen Rochko
517d13b400 New translations strings.xml (Vietnamese) 2023-10-09 05:07:06 +02:00
Grishka
103aaafff1 Hide the FAB while editing profile
closes #709
2023-10-08 22:31:27 +03:00
Eugen Rochko
fae870c93a New translations strings.xml (Urdu (India)) 2023-10-08 21:13:23 +02:00
Eugen Rochko
f8e00dcc80 New translations strings.xml (Kabyle) 2023-10-08 21:13:22 +02:00
Eugen Rochko
5fdbb597bb New translations strings.xml (Igbo) 2023-10-08 21:13:21 +02:00
Eugen Rochko
d74b286a9d New translations strings.xml (Occitan) 2023-10-08 21:13:20 +02:00
Eugen Rochko
ecb3c521ff New translations strings.xml (Scottish Gaelic) 2023-10-08 21:13:19 +02:00
Eugen Rochko
1d093ce928 New translations strings.xml (Sinhala) 2023-10-08 21:13:18 +02:00
Eugen Rochko
46b711af2e New translations strings.xml (Bosnian) 2023-10-08 21:13:17 +02:00
Eugen Rochko
772e6ddb5d New translations strings.xml (Filipino) 2023-10-08 21:13:16 +02:00
Eugen Rochko
f84e8443d2 New translations strings.xml (Burmese) 2023-10-08 21:13:15 +02:00
Eugen Rochko
250c18ebf1 New translations strings.xml (Hindi) 2023-10-08 21:13:14 +02:00
Eugen Rochko
e3d0f38b79 New translations strings.xml (Croatian) 2023-10-08 21:13:14 +02:00
Eugen Rochko
c512f97783 New translations strings.xml (Bengali) 2023-10-08 21:13:13 +02:00
Eugen Rochko
0594680775 New translations strings.xml (Persian) 2023-10-08 21:13:12 +02:00
Eugen Rochko
f999881f59 New translations strings.xml (Indonesian) 2023-10-08 21:13:11 +02:00
Eugen Rochko
4fe9192ac6 New translations strings.xml (Portuguese, Brazilian) 2023-10-08 21:13:10 +02:00
Eugen Rochko
d936702fa9 New translations strings.xml (Galician) 2023-10-08 21:13:08 +02:00
Eugen Rochko
74e284b0de New translations strings.xml (Vietnamese) 2023-10-08 21:13:07 +02:00
Eugen Rochko
4c42b72ed8 New translations strings.xml (Chinese Simplified) 2023-10-08 21:13:06 +02:00
Eugen Rochko
0e0046df65 New translations strings.xml (Turkish) 2023-10-08 21:13:05 +02:00
Eugen Rochko
c80d1d10c2 New translations strings.xml (Slovenian) 2023-10-08 21:13:05 +02:00
Eugen Rochko
da97971011 New translations strings.xml (Portuguese) 2023-10-08 21:13:04 +02:00
Eugen Rochko
700447dbe7 New translations strings.xml (Polish) 2023-10-08 21:13:03 +02:00
Eugen Rochko
37e7b5ee93 New translations strings.xml (Norwegian) 2023-10-08 21:13:02 +02:00
Eugen Rochko
1265afa93f New translations strings.xml (Dutch) 2023-10-08 21:13:01 +02:00
Eugen Rochko
1e09481b02 New translations strings.xml (Korean) 2023-10-08 21:13:00 +02:00
Eugen Rochko
9996a5a05e New translations strings.xml (Armenian) 2023-10-08 21:12:59 +02:00
Eugen Rochko
f20aac7c81 New translations strings.xml (Hungarian) 2023-10-08 21:12:58 +02:00
Eugen Rochko
98f7b0bacd New translations strings.xml (Hebrew) 2023-10-08 21:12:57 +02:00
Eugen Rochko
3f6d3fb3a2 New translations strings.xml (Irish) 2023-10-08 21:12:56 +02:00
Eugen Rochko
663b49c76b New translations strings.xml (Basque) 2023-10-08 21:12:55 +02:00
Eugen Rochko
16e38f2541 New translations strings.xml (Greek) 2023-10-08 21:12:54 +02:00
Eugen Rochko
842cc55e47 New translations strings.xml (German) 2023-10-08 21:12:53 +02:00
Eugen Rochko
72db099e6f New translations strings.xml (Danish) 2023-10-08 21:12:52 +02:00
Eugen Rochko
be130bc3a7 New translations strings.xml (Czech) 2023-10-08 21:12:51 +02:00
Eugen Rochko
42253336e1 New translations strings.xml (Catalan) 2023-10-08 21:12:50 +02:00
Eugen Rochko
572631e1d7 New translations strings.xml (Belarusian) 2023-10-08 21:12:49 +02:00
Eugen Rochko
723777a800 New translations strings.xml (Arabic) 2023-10-08 21:12:48 +02:00
Eugen Rochko
b825d534c1 New translations strings.xml (Spanish) 2023-10-08 21:12:47 +02:00
Eugen Rochko
b9749620a8 New translations strings.xml (French) 2023-10-08 21:12:46 +02:00
Eugen Rochko
70ea9989aa New translations strings.xml (Romanian) 2023-10-08 21:12:45 +02:00
Eugen Rochko
b3ec9c981c New translations strings.xml (Chinese Traditional) 2023-10-08 21:12:44 +02:00
Eugen Rochko
bf72085abb New translations strings.xml (Finnish) 2023-10-08 21:12:43 +02:00
Eugen Rochko
64dd416b59 New translations strings.xml (Russian) 2023-10-08 21:12:42 +02:00
Eugen Rochko
ab2a920455 New translations strings.xml (Swedish) 2023-10-08 21:12:41 +02:00
Eugen Rochko
7580446d60 New translations strings.xml (Italian) 2023-10-08 21:12:40 +02:00
Eugen Rochko
ade18ac6fc New translations strings.xml (Ukrainian) 2023-10-08 21:12:39 +02:00
Eugen Rochko
005c851d72 New translations strings.xml (Japanese) 2023-10-08 21:12:38 +02:00
Eugen Rochko
0f1d46c765 New translations strings.xml (Icelandic) 2023-10-08 21:12:36 +02:00
Eugen Rochko
21fbb07b1d New translations strings.xml (Thai) 2023-10-08 21:12:36 +02:00
Grishka
dff2217e80 Lists
closes #89, closes #279
2023-10-08 22:03:16 +03:00
sk
58e0ce3970 add tooltips 2023-10-08 16:36:56 +02:00
sk
139a7d7c98 change settings order 2023-10-08 16:36:48 +02:00
sk
a1c81e89e8 don't show labels per default 2023-10-08 16:36:41 +02:00
sk
a5c197b496 hearts everywhere
closes sk22#846
2023-10-08 13:04:26 +02:00
sk
df49ef9d58 redder red 2023-10-08 12:54:59 +02:00
sk
f747d4c979 toggle label font weight according to m3 2023-10-08 12:49:29 +02:00
sk
98677cd307 on page change listener may also load first page 2023-10-08 11:48:59 +02:00
sk
cd3de97d55 Merge remote-tracking branch 'upstream/l10n_master' 2023-10-07 23:47:36 +02:00
sk
4853a25710 bump version 2023-10-07 23:47:01 +02:00
SomeTr
eba9a1da7b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-10-07 21:46:18 +00:00
SomeTr
068c62b060 Translated using Weblate (Ukrainian)
Currently translated at 98.9% (385 of 389 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-07 21:46:18 +00:00
David Lapshin
5d7f06eba0 Translated using Weblate (Russian)
Currently translated at 97.6% (380 of 389 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-10-07 21:46:18 +00:00
Linerly
fed9dec33a Translated using Weblate (Indonesian)
Currently translated at 100.0% (389 of 389 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-10-07 21:46:18 +00:00
Choukajohn
7973914a5f Translated using Weblate (French)
Currently translated at 100.0% (389 of 389 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-07 21:46:18 +00:00
kallekn
35efb3f047 Translated using Weblate (Finnish)
Currently translated at 100.0% (389 of 389 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fi/
2023-10-07 21:46:18 +00:00
sk
0a4ed50904 i think i fixed the offset issue 2023-10-07 23:45:33 +02:00
sk
002c66174a fix type filter, wrong max id, refactor max id 2023-10-07 23:32:10 +02:00
Eugen Rochko
22aac3d943 New translations strings.xml (Finnish) 2023-10-07 22:49:20 +02:00
sk
872f47305a Revert "temporary fix for pre-release users"
This reverts commit 2314871246.
2023-10-07 22:38:42 +02:00
sk
75d5332411 don't add existing posts to timeline 2023-10-07 22:38:30 +02:00
sk
035da8a517 update blocks and languages 2023-10-07 22:17:36 +02:00
sk
4c2c877d41 bump version 2023-10-07 22:11:35 +02:00
sk
0cc8cddfc3 support recently used emoji
closes sk22#832
2023-10-07 22:10:52 +02:00
sk
4428ef7ac2 display recent languages
closes sk22#654
2023-10-07 19:47:49 +02:00
sk
44912b7982 display timestamp in notification header
closes sk22#668
2023-10-07 19:17:45 +02:00
sk
c930db6068 refactor filtering
hopefully also fixes sk22#816
2023-10-07 18:25:37 +02:00
sk
d96d4dd581 unselect add button on bind
closes sk22#830
2023-10-07 17:52:39 +02:00
sk
67e3a5bb47 fix warning not clickable if main status 2023-10-07 17:50:24 +02:00
sk22
b0a5aa93e1 Translated using Weblate (German)
Currently translated at 100.0% (389 of 389 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-10-07 15:39:39 +00:00
sk22
0bc1459898 Translated using Weblate (English)
Currently translated at 100.0% (389 of 389 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/en/
2023-10-07 15:39:38 +00:00
sk
fae25e93a5 Merge remote-tracking branch 'weblate/main' 2023-10-07 17:29:18 +02:00
kallekn
c0c121050c Translated using Weblate (Finnish)
Currently translated at 100.0% (388 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fi/
2023-10-07 15:29:11 +00:00
teknopata
afe572ca7f Translated using Weblate (Basque)
Currently translated at 78.6% (305 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/eu/
2023-10-07 15:29:11 +00:00
sk
8cb4db5fcf Merge remote-tracking branch 'weblate/main' 2023-10-07 17:28:51 +02:00
sk
de235ec7cc use upstream filtering for warn item 2023-10-07 17:21:20 +02:00
sk
140c2a7b9d remove status filter predicate from thread 2023-10-07 17:04:55 +02:00
sk
c833513344 use same filtering function everywhere 2023-10-07 17:02:03 +02:00
sk
1e95536208 fix issue with loading more 2023-10-07 17:00:16 +02:00
sk
b58fda9795 remove unused method 2023-10-07 16:12:35 +02:00
sk
3135aef398 fix wrong overflow button style 2023-10-07 16:03:30 +02:00
sk
c83dc51322 re-add ripple to filter warning 2023-10-07 15:43:16 +02:00
sk
9cfaed89e6 fix filter warning background color
closes sk22#823
2023-10-07 15:26:10 +02:00
sk
5374ac766c prettier visibility toggle animation
maybe fix sk22#845
2023-10-07 15:21:06 +02:00
sk
98596e77f2 fix interaction button colors
closes sk22#841
2023-10-07 12:44:39 +02:00
sk
54aa89c7f8 OOPS 2023-10-07 12:44:33 +02:00
sk
f59157b160 fix text not selectable
closes sk22#824
2023-10-07 12:33:14 +02:00
sk
6b38db9607 add heart symbol to extended footer 2023-10-07 12:08:37 +02:00
Eugen Rochko
53afc120f3 New translations strings.xml (Chinese Traditional) 2023-10-07 03:46:12 +02:00
Xen4n
3c603d5dbc Fix empty hashtag as search result for '@' query for Pleroma 2023-10-06 22:21:10 +02:00
sk
c10cdfd795 fix color inheritance issue 2023-10-06 18:44:15 +02:00
sk
c2184e7bd8 don't dismiss when restarting activity 2023-10-06 18:00:59 +02:00
sk
baf756e163 allow using heart as fav icon ❤️
closes sk22#81
2023-10-06 17:53:43 +02:00
sk
efc67fd7e8 remove inset poll styles
closes sk22#801
2023-10-06 17:26:59 +02:00
sk
43e737425a fix null reference 2023-10-06 17:20:49 +02:00
sk
b5b3cb42a1 re-implement missing "translate opened only" 2023-10-06 17:09:57 +02:00
sk
f72f7cb831 hide username wrap in edit mode
closes sk22#828
2023-10-06 17:02:52 +02:00
sk
f86d60be23 fix color palette dialog title 2023-10-06 17:00:09 +02:00
sk
7c8624bd53 fix alpha animations
closes sk22#839
2023-10-06 16:55:02 +02:00
Eugen Rochko
a75ce70615 New translations strings.xml (Finnish) 2023-10-06 16:38:40 +02:00
sk
331548b38d fix crash 2023-10-06 16:20:16 +02:00
sk
8b8f192dfa fix posts/bubble banner not disappearing
closes sk22#833
2023-10-06 16:17:18 +02:00
sk
061b2ee3de per-account color palette preference 2023-10-06 16:13:38 +02:00
sk
5ea2864bd5 Merge remote-tracking branch 'upstream/master' 2023-10-06 15:17:49 +02:00
LucasGGamerM
ee2b4b6a1f fix(hashtag-fragment): fix crash when opening some hashtags (#842) 2023-10-06 15:04:02 +02:00
LucasGGamerM
697f801c1a fix(hashtags): fix crash when hashtag is null (#844) 2023-10-06 15:03:25 +02:00
sk
ebb49c44fe refactor code 2023-10-06 15:01:46 +02:00
LucasGGamerM
bc4619e6b1 fix(translations): fix crash when status language is null 2023-10-06 15:01:46 +02:00
Eugen Rochko
4a3b948760 New translations strings.xml (Finnish) 2023-10-05 22:23:12 +02:00
Eugen Rochko
f81283c892 New translations strings.xml (Italian) 2023-10-05 21:19:39 +02:00
Eugen Rochko
7eae879037 New translations strings.xml (Russian) 2023-10-05 16:06:00 +02:00
Eugen Rochko
1b0ce5d893 New translations strings.xml (Swedish) 2023-10-05 12:02:19 +02:00
butterflyoffire
5d26ea85e9 Translated using Weblate (Arabic)
Currently translated at 80.1% (311 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-10-05 07:53:14 +00:00
ihor_ck
6efe263dd8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (388 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-10-05 07:53:14 +00:00
David Lapshin
0379347f2d Translated using Weblate (Russian)
Currently translated at 87.1% (338 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-10-05 07:53:14 +00:00
alextecplayz
1299b2ad42 Translated using Weblate (Romanian)
Currently translated at 100.0% (388 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-10-05 07:53:14 +00:00
Linerly
f3b3bcaa0a Translated using Weblate (Indonesian)
Currently translated at 100.0% (388 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-10-05 07:53:14 +00:00
Choukajohn
b1bec870c5 Translated using Weblate (French)
Currently translated at 100.0% (388 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-05 07:53:13 +00:00
butterflyoffire
36e05a6d14 Translated using Weblate (French)
Currently translated at 100.0% (388 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-10-05 07:53:13 +00:00
gallegonovato
2e11f78e9d Translated using Weblate (Spanish)
Currently translated at 100.0% (388 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-10-05 07:53:13 +00:00
poesty
9fcfbe5593 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (387 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-05 07:53:12 +00:00
Eugen Rochko
c17745368d New translations strings.xml (Italian) 2023-10-04 22:24:20 +02:00
Eugen Rochko
e78b518654 New translations strings.xml (Ukrainian) 2023-10-04 18:44:26 +02:00
Eugen Rochko
55a8634be2 New translations strings.xml (Japanese) 2023-10-04 16:52:54 +02:00
Eugen Rochko
ac891eea53 New translations strings.xml (Icelandic) 2023-10-04 15:28:52 +02:00
LucasGGamerM
6096857613 docs: add 102 changelog 2023-10-04 10:08:30 -03:00
LucasGGamerM
20c2681872 fix(account-card-on-suggestions-fragment): fix account card progress bar 2023-10-04 08:51:08 -03:00
LucasGGamerM
6778e302b4 fix(account-card-on-discover-fragment): fix account card progress bar 2023-10-04 08:35:02 -03:00
LucasGGamerM
6f981a1c7d fix(account-card): fix account card progress bar 2023-10-04 08:26:47 -03:00
LucasGGamerM
d07ec7f1af build: enable gradle configuration cache 2023-10-04 07:37:19 -03:00
LucasGGamerM
391a44c0e5 fix(instance-info): use normalized Uri instead of parsing the uri 2023-10-04 07:37:00 -03:00
Eugen Rochko
74fa2a3081 New translations strings.xml (Thai) 2023-10-03 21:27:27 +02:00
LucasGGamerM
a849ced203 fix(notifications-banner): fix banner layout 2023-10-03 07:26:18 -03:00
Grishka
6c1c5b7759 Merge branch 'l10n_master' 2023-10-03 03:53:50 +03:00
LucasGGamerM
f2030a8c26 Update nightly-builds.yml 2023-10-02 21:20:22 -03:00
Grishka
1f4152b588 Fix #705 and improve handling of unknown attachment dimensions 2023-10-03 02:52:07 +03:00
Grishka
70386ea1b2 Update appkit to finally fix that ViewPager2 crash 2023-10-03 02:11:04 +03:00
LucasGGamerM
02f97cd142 fix(hashtags): hashtag filter name being wonky 2023-10-02 17:35:37 -03:00
LucasGGamerM
76e0c5bc4d fix(hashtags): fix crash when hashtag is null 2023-10-02 17:27:44 -03:00
LucasGGamerM
084bbc18cf fix(translations): fix crash when status language is null 2023-10-02 16:42:04 -03:00
Eugen Rochko
cbce90c461 New translations strings.xml (Sinhala) 2023-10-02 21:16:49 +02:00
LucasGGamerM
fb8a38e77b fix(instance-info-fragment): readd three dot menu 2023-10-02 12:18:43 -03:00
LucasGGamerM
2e184589e6 fix(hashtag-fragment): fix crash when opening some hashtags
cc: @sk22
2023-10-02 11:20:53 -03:00
LucasGGamerM
f13b6bd4b4 fix: fix crash when opening some hashtags 2023-10-02 07:45:59 -03:00
Eugen Rochko
74ae3bf706 New translations strings.xml (Armenian) 2023-10-02 07:26:27 +02:00
Grishka
1feccdc26d Fixes 2023-10-01 23:11:33 +03:00
LucasGGamerM
3f253c1211 fix(polls): fix poll options being wonky 2023-10-01 16:10:50 -03:00
LucasGGamerM
584e2c0daa fix(emoji-popup-keyboard): increase emoji size 2023-10-01 16:05:00 -03:00
LucasGGamerM
6f180c0451 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/ui/CustomEmojiPopupKeyboard.java
#	mastodon/src/main/res/values-pt-rBR/strings_sk.xml
#	mastodon/src/main/res/values-ro-rRO/strings_sk.xml
2023-10-01 14:26:38 -03:00
LucasGGamerM
c7017c12d9 feat(notification-filters): treat reactions as favorites when filtering 2023-10-01 14:19:45 -03:00
LucasGGamerM
df2211fa15 feat(notification-filters): implement logic and save notification filters when they are cleared 2023-10-01 13:56:58 -03:00
Eugen Rochko
c38c2a425b New translations strings.xml (Indonesian) 2023-10-01 17:30:46 +02:00
LucasGGamerM
8adb1d569e feat(notification-filters): add new settings entry for notification filters. No logic implemented just yet 2023-10-01 12:21:40 -03:00
LucasGGamerM
9e27e21e78 fix(settings): remove duplicate filters item and use fluent icon for Privacy and reach settings 2023-10-01 12:01:48 -03:00
Eugen Rochko
f43352b790 New translations strings.xml (Indonesian) 2023-10-01 16:30:51 +02:00
Grishka
c5b52b2781 Fix default server not loading sometimes 2023-10-01 12:17:21 +03:00
Grishka
b91840fb95 Another attempt to fix ZoomPanView crash 2023-10-01 07:16:21 +03:00
LucasGGamerM
2f8b6acd58 fix(notifications-badge): use correct coloring for inner number 2023-09-30 19:23:01 -03:00
LucasGGamerM
ce9dda4e6e Merge pull request #268 from Henry-Hiles/master
Change badge color and add manual workflow
2023-09-30 19:12:50 -03:00
LucasGGamerM
63338a139a refactor(filter-predicate): move client filters to its own function. Also allows for Client filters to completely hide posts 2023-09-30 19:05:50 -03:00
LucasGGamerM
49c2365120 feat(notification-filter): only show on everything tab 2023-09-30 19:05:50 -03:00
Henry Hiles
df06cdb0bb Update nightly-builds.yml 2023-09-30 15:37:42 -04:00
Henry Hiles
fda89a8d36 Change color 2023-09-30 15:35:21 -04:00
sk
1988849b26 Merge remote-tracking branch 'upstream/master' 2023-09-30 21:21:47 +02:00
Grishka
fc10fbffb0 Clear fragment stack instead of restarting activity
grishka/appkit#13
2023-09-30 21:53:02 +03:00
LucasGGamerM
3b74a183f8 Merge pull request #267 from Henry-Hiles/patch-1
Fix typo
2023-09-30 15:51:10 -03:00
Henry Hiles
41a639bb02 Fix typo 2023-09-30 14:33:21 -04:00
Eugen Rochko
e40841c128 New translations strings.xml (Portuguese, Brazilian) 2023-09-30 20:26:54 +02:00
sk
a21a74a8e7 bonk version 2023-09-30 19:26:15 +02:00
LucasGGamerM
8a6c9881e4 fix(translate-button): fix button opacity when loading translation
cc: @sk22
2023-09-30 14:25:41 -03:00
sk
20e5d2a545 Merge remote-tracking branch 'upstream/l10n_master' 2023-09-30 19:24:52 +02:00
butterflyoffire
7da363fb87 Translated using Weblate (Arabic)
Currently translated at 80.3% (310 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-09-30 17:23:53 +00:00
sk
c46f78272d Merge remote-tracking branch 'weblate/main' 2023-09-30 19:23:42 +02:00
Tyler Baker
95685d4de8 Feature: Support filtering custom emoji in reaction view (#836)
* Support filtering custom emojis in reaction keyboard.

* Move creation of EditText to conditional block.

* Clear unused comment

* Update requests variable when publishing filter results so the images displayed will be correct.

* Combine text fields in emoji reaction keyboard, create new initializer for EmojiCategory so it can be copied properly.

* Performance optimization and fixed a typo in filter.

* improve layout

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-09-30 19:21:36 +02:00
LucasGGamerM
d0c5a2ed2e Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
#	mastodon/src/main/res/layout/display_item_poll_option.xml
#	mastodon/src/main/res/menu/profile_own.xml
#	mastodon/src/main/res/values-pt-rBR/strings_sk.xml
#	mastodon/src/main/res/values/strings_sk.xml
2023-09-30 14:11:39 -03:00
LucasGGamerM
3fceeec362 fix: make hashtag timeline header work properly 2023-09-30 14:10:12 -03:00
FineFindus
cbee0fe72e fix: show individual chips (#838) 2023-09-30 19:04:04 +02:00
FineFindus
6d085ae6f0 fix: show multiline poll options (#837)
* fix: show multiline poll options

* fix resources not found exception

* don't force height on poll options

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-09-30 19:03:17 +02:00
LucasGGamerM
12cbb7518f fix: make app not crash when opening a hashtag 2023-09-30 13:44:49 -03:00
LucasGGamerM
5220a98082 fix: make app compile again 2023-09-30 13:39:54 -03:00
LucasGGamerM
b9bf1e6601 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/TranslateStatus.java
#	mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
#	mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/FeaturedHashtagsListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/SearchFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsMainFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/model/Status.java
#	mastodon/src/main/java/org/joinmastodon/android/model/TranslatedStatus.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/viewcontrollers/ComposePollViewController.java
#	mastodon/src/main/res/layout/display_item_text.xml
#	mastodon/src/main/res/menu/hashtag_timeline.xml
#	mastodon/src/main/res/values/styles.xml
2023-09-30 13:32:50 -03:00
LucasGGamerM
4de7211523 Fix notifications replies visibility/language not being consistent with replied status (#831)
* fix(notifications): make reply visibility consistent with status being replied to

* fix(notifications): make reply language consistent with status being replied to
2023-09-30 18:30:53 +02:00
FineFindus
05f7a44bd5 fix(timeline/gap): use plurals for time (#829)
Co-authored-by: sk <sk22@mailbox.org>
2023-09-30 18:30:18 +02:00
Jacoco
1079f600bc Revert "Fix media layout with unknown sizes" (#827)
This reverts commit a014fe9443.
2023-09-30 18:25:52 +02:00
FineFindus
72580dadd0 feat: display blocks and mutes list (#821)
* feat: add mutes fragment

* feat: add blocks fragment

* refactor: add query params

* rename "mutes" and "blocks"

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-09-30 18:24:24 +02:00
LucasGGamerM
16e04a5a0e feat(notification-filters): basically done with the UI
Its still missing the under the hood implementation though
2023-09-30 13:08:10 -03:00
Eugen Rochko
98a02e874b New translations strings.xml (Vietnamese) 2023-09-30 15:05:49 +02:00
butterflyoffire
d219d7aa4b Translated using Weblate (Arabic)
Currently translated at 78.2% (302 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-09-30 03:45:04 +00:00
alextecplayz
68a9fe8376 Translated using Weblate (Romanian)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-09-30 03:45:04 +00:00
LucasGGamerM
098b0885a2 feat(notification-filters): start implementing this
i am pushing to master because I am THUG LIFE. Jk, its because I wanna throw these strings on weblate
2023-09-29 21:47:34 -03:00
LucasGGamerM
e8631e432a fix: fix toolbar oopsie 2023-09-29 19:15:53 -03:00
sk
f2f8620312 fix menu item icons and state 2023-09-29 21:45:41 +02:00
sk
4ee229ea79 Merge remote-tracking branch 'upstream/master' into try-to-merge-upstream 2023-09-29 20:59:12 +02:00
sk
c261214e49 implement new translation 2023-09-29 18:46:26 +02:00
Eugen Rochko
b06df8c3d0 New translations strings.xml (Galician) 2023-09-29 07:53:31 +02:00
828 changed files with 24300 additions and 7488 deletions

View File

@@ -3,6 +3,7 @@ name: Nightly builds
on: on:
push: push:
branches: [ "master" ] branches: [ "master" ]
workflow_dispatch:
jobs: jobs:
build: build:
@@ -10,27 +11,27 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Appkit Repo # - name: Checkout Appkit Repo
uses: actions/checkout@v3 # uses: actions/checkout@v3
with: # with:
repository: LucasGGamerM/appkit # repository: grishka/appkit
#
- name: set up JDK 17 # - name: set up JDK 17
uses: actions/setup-java@v3 # uses: actions/setup-java@v3
with: # with:
java-version: '17' # java-version: '17'
distribution: 'corretto' # distribution: 'corretto'
cache: gradle # cache: gradle
#
- name: Comment out signing config in appkits gradle file # - name: Comment out signing config in appkits gradle file
run: | # run: |
sed -i 's/sign publishing\.publications\.release/\/\/ sign publishing.publications.release/' appkit/maven-push.gradle # sed -i 's/sign publishing\.publications\.release/\/\/ sign publishing.publications.release/' appkit/maven-push.gradle
#
- name: Grant execute permission for gradlew for Appkit # - name: Grant execute permission for gradlew for Appkit
run: chmod +x gradlew # run: chmod +x gradlew
#
- name: Compile appkit # - name: Compile appkit
run: ./gradlew publishToMavenLocal # run: ./gradlew publishToMavenLocal
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: set up JDK 17 - name: set up JDK 17

2
FAQ.md
View File

@@ -4,6 +4,6 @@ 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. 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? Q: Will there ever be a version of Moshidon for iOS?
A: No. As android and iOS apps do not share code, it is incredibly hard to port. A: No. As android and iOS apps do not share code, it is incredibly hard to port.

View File

@@ -16,6 +16,8 @@
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.moshinda"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a> <a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.moshinda"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
&nbsp; &nbsp;
<a href="https://f-droid.org/pt_BR/packages/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a>
&nbsp;
<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!

View File

@@ -19,4 +19,5 @@ android.useAndroidX=true
android.enableJetifier=false android.enableJetifier=false
android.defaults.buildfeatures.buildconfig=true android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.nonFinalResIds=false android.nonFinalResIds=false
org.gradle.configuration-cache=true

0
gradlew vendored Normal file → Executable file
View File

View File

@@ -16,8 +16,8 @@ android {
applicationId "org.joinmastodon.android.moshinda" applicationId "org.joinmastodon.android.moshinda"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 102 versionCode 105
versionName "2.1.4+fork.102.moshinda" versionName "2.3.0+fork.105.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']
} }
@@ -44,6 +44,28 @@ android {
keyPassword = properties.getProperty('SIGNING_KEY_PASSWORD') keyPassword = properties.getProperty('SIGNING_KEY_PASSWORD')
} }
} }
// release{
// storeFile = file("keystore/release_keystore.jks")
// storePassword System.getenv("RELEASE_SIGNING_STORE_PASSWORD")
// if (storePassword == null) {
// Properties properties = new Properties()
// properties.load(project.rootProject.file('local.properties').newDataInputStream())
// storePassword = properties.getProperty('RELEASE_SIGNING_STORE_PASSWORD')
// }
// keyAlias System.getenv("RELEASE_SIGNING_KEY_ALIAS")
// if (keyAlias == null) {
// Properties properties = new Properties()
// properties.load(project.rootProject.file('local.properties').newDataInputStream())
// keyAlias = properties.getProperty('RELEASE_SIGNING_KEY_ALIAS')
// }
// keyPassword System.getenv("RELEASE_SIGNING_KEY_PASSWORD")
// if (keyPassword == null) {
// Properties properties = new Properties()
// properties.load(project.rootProject.file('local.properties').newDataInputStream())
// keyPassword = properties.getProperty('RELEASE_SIGNING_KEY_PASSWORD')
// }
// }
} }
buildTypes { buildTypes {
@@ -81,8 +103,10 @@ android {
versionNameSuffix '-play' versionNameSuffix '-play'
} }
githubRelease { initWith release } githubRelease { initWith release }
playRelease { initWith release } fdroidRelease {
fdroidRelease { initWith release } initWith release
// signingConfig signingConfigs.release
}
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_17
@@ -118,7 +142,7 @@ dependencies {
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.litex:palette:1.0.0' implementation 'me.grishka.litex:palette:1.0.0'
implementation 'me.grishka.appkit:appkit:1.2.14' implementation 'me.grishka.appkit:appkit:1.2.16'
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'

View File

@@ -257,5 +257,9 @@ public class UiUtilsTest {
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount( assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "-- * (asterisk) --") makeField("pronouns", "-- * (asterisk) --")
)).orElseThrow()); )).orElseThrow());
assertEquals("they/(she?)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "they/(she?)...")
)).orElseThrow());
} }
} }

View File

@@ -4,6 +4,9 @@ import static org.joinmastodon.android.model.FilterAction.*;
import static org.joinmastodon.android.model.FilterContext.*; import static org.joinmastodon.android.model.FilterContext.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import android.graphics.drawable.ColorDrawable;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.LegacyFilter; import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.junit.Test; import org.junit.Test;
@@ -32,11 +35,11 @@ public class StatusFilterPredicateTest {
warnMeFilter.filterAction = WARN; warnMeFilter.filterAction = WARN;
warnMeFilter.context = EnumSet.of(PUBLIC, HOME); warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
noAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable()); // noAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable());
withAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable()); // withAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable());
for (Attachment mediaAttachment : withAltText.mediaAttachments) { // for (Attachment mediaAttachment : withAltText.mediaAttachments) {
mediaAttachment.description = "Alt Text"; // mediaAttachment.description = "Alt Text";
} // }
} }
@Test @Test

View File

@@ -25,12 +25,17 @@
<intent> <intent>
<action android:name="android.intent.action.TRANSLATE" /> <action android:name="android.intent.action.TRANSLATE" />
</intent> </intent>
<intent>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="http"/>
</intent>
</queries> </queries>
<application <application
android:name=".MastodonApp" android:name=".MastodonApp"
android:allowBackup="true" android:allowBackup="true"
android:label="@string/mo_app_name" android:label="@string/mo_app_name"
android:dataExtractionRules="@xml/backup_rules"
android:supportsRtl="true" android:supportsRtl="true"
android:localeConfig="@xml/locales_config" android:localeConfig="@xml/locales_config"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@@ -1,56 +1,43 @@
13bells.com 13bells.com
1611.social 1611.social
4aem.com 4aem.com
5dollah.click
adachi.party adachi.party
anime.website adtension.com
annihilation.social annihilation.social
anon-kenkai.com anon-kenkai.com
asbestos.cafe asbestos.cafe
bae.st bae.st
bajax.us
banepo.st banepo.st
baraag.net
bassam.social bassam.social
battlepenguin.video
beefyboys.win beefyboys.win
beepboop.ga
berserker.town
bikeshed.party
boks.moe
boymoder.biz boymoder.biz
brainsoap.net brainsoap.net
breastmilk.club breastmilk.club
brighteon.social brighteon.social
bungle.online cachapa.xyz
canary.fedinuke.example.com
catgirl.life
cawfee.club cawfee.club
childlove.space
clew.lol clew.lol
clubcyberia.co clubcyberia.co
collapsitarian.io
comfyboy.club
contrapointsfan.club contrapointsfan.club
crucible.world
cum.camp cum.camp
cum.salon cum.salon
darknight-coffee.org
decayable.ink decayable.ink
dembased.xyz dembased.xyz
desupost.soy
detroitriotcity.com detroitriotcity.com
eatthebugs.social djsumdog.com
eientei.org eientei.org
elementality.org
eveningzoo.club eveningzoo.club
firedragonstudios.com
firefaithfellowship.com
fluf.club fluf.club
foxfam.club
freak.university freak.university
freeatlantis.com freeatlantis.com
freedomstrike.org
freesoftwareextremist.com
freespeech.group
freespeechextremist.com freespeechextremist.com
freetalklive.com
froth.zone froth.zone
fulltermprivacy.com
gameliberty.club gameliberty.club
gearlandia.haus gearlandia.haus
genderheretics.xyz genderheretics.xyz
@@ -59,42 +46,34 @@ gleasonator.com
glee.li glee.li
glindr.org glindr.org
goyim.app goyim.app
goyslop.cafe h5q.net
haeder.net haeder.net
handholding.io handholding.io
hitchhiker.social hitchhiker.social
hunk.city
iddqd.social iddqd.social
intkos.link
justicewarrior.social
kawa-kun.com
kitsunemimi.club kitsunemimi.club
kiwifarms.cc kiwifarms.cc
kompost.cz
kurosawa.moe kurosawa.moe
kyaruc.moe
leafposter.club leafposter.club
leftychan.net
lewdieheaven.com lewdieheaven.com
liberdon.com liberdon.com
ligma.pro ligma.pro
lolicon.rocks lolicon.rocks
lolison.network
lolison.top lolison.top
lovingexpressions.net lovingexpressions.net
mahodou.moe
makemysarcophagus.com makemysarcophagus.com
maladaptive.art
marsey.moe marsey.moe
masochi.st
mastinator.com mastinator.com
merovingian.club merovingian.club
midwaytrades.com midwaytrades.com
mirr0r.city mirr0r.city
moa.st morale.ch
mouse.services mouse.services
mugicha.club mugicha.club
narrativerry.xyz narrativerry.xyz
natehiggers.online natehiggers.online
neckbeard.xyz
needs.vodka needs.vodka
neenster.org neenster.org
nicecrew.digital nicecrew.digital
@@ -103,18 +82,18 @@ noagendasocial.com
noagendasocial.nl noagendasocial.nl
noagendatube.com noagendatube.com
nobodyhasthe.biz nobodyhasthe.biz
nukem.biz norwoodzero.net
obo.sh nyanide.com
onionfarms.org onionfarms.org
pawlicker.com pawlicker.com
pawoo.net pawoo.net
pedo.school pedo.school
peervideo.club
piazza.today piazza.today
pibvt.net pibvt.net
pieville.net pieville.net
pisskey.io pisskey.io
plagu.ee plagu.ee
pmth.us
poa.st poa.st
poast.org poast.org
poast.tv poast.tv
@@ -123,17 +102,18 @@ prospeech.space
quodverum.com quodverum.com
r18.social r18.social
rakket.app rakket.app
rapemeat.express
rapemeat.solutions rapemeat.solutions
rdrama.cc rayci.st
rebelbase.site rebelbase.site
retardedniggers.forsale
rojogato.com
ryona.agency ryona.agency
sad.cab
schwartzwelt.xyz schwartzwelt.xyz
seal.cafe seal.cafe
shaw.app
shigusegubu.club shigusegubu.club
shitpost.cloud shitpost.cloud
shota.house shortstacksran.ch
silliness.observer silliness.observer
skinheads.eu skinheads.eu
skinheads.io skinheads.io
@@ -148,23 +128,20 @@ sneed.social
sonichu.com sonichu.com
spinster.xyz spinster.xyz
springbo.cc springbo.cc
starnix.network
strelizia.net strelizia.net
syspxl.xyz
tastingtraffic.net tastingtraffic.net
teci.world teci.world
theapex.social theapex.social
thechimp.zone
thenobody.club
thepostearthdestination.com thepostearthdestination.com
tkammer.de tkammer.de
trumpislovetrumpis.life trumpislovetrumpis.life
truthsocial.co.in truthsocial.co.in
urchan.org usualsuspects.lol
varishangout.net varishangout.net
whinge.house vtuberfan.social
whinge.town
wideboys.org
wolfgirl.bar wolfgirl.bar
xn--p1abe3d.xn--80asehdb xn--p1abe3d.xn--80asehdb
yggdrasil.social yggdrasil.social
youjo.love youjo.love
zztails.gay

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

File diff suppressed because it is too large Load Diff

View File

@@ -266,7 +266,7 @@ public class AudioPlayerService extends Service{
private void updateNotification(boolean dismissable, boolean removeNotification){ private void updateNotification(boolean dismissable, boolean removeNotification){
Notification.Builder bldr=new Notification.Builder(this) Notification.Builder bldr=new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_ntf_logo) .setSmallIcon(R.drawable.ic_ntf_logo)
.setContentTitle(status.account.displayName) .setContentTitle(status.account.getDisplayName())
.setContentText(HtmlParser.strip(status.content)) .setContentText(HtmlParser.strip(status.content))
.setOngoing(!dismissable) .setOngoing(!dismissable)
.setShowWhen(false) .setShowWhen(false)
@@ -281,7 +281,7 @@ public class AudioPlayerService extends Service{
if(playerReady){ if(playerReady){
boolean isPlaying=player.isPlaying(); boolean isPlaying=player.isPlaying();
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_pause_24 : R.drawable.ic_play_24), bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_fluent_pause_24_filled : R.drawable.ic_fluent_play_24_filled),
getString(isPlaying ? R.string.pause : R.string.play), getString(isPlaying ? R.string.pause : R.string.play),
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE)) PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
.build()); .build());

View File

@@ -13,7 +13,7 @@ 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;
import org.joinmastodon.android.ui.AccountSwitcherSheet; import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.jsoup.internal.StringUtil; import org.jsoup.internal.StringUtil;
@@ -32,10 +32,9 @@ public class ExternalShareActivity extends FragmentStackActivity{
UiUtils.setUserPreferredTheme(this); UiUtils.setUserPreferredTheme(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if(savedInstanceState==null){ if(savedInstanceState==null){
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT)); Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle); Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
boolean isFediUrl = text.map(UiUtils::looksLikeMastodonUrl).orElse(false); boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
boolean isOpenable = isFediUrl || fediHandle.isPresent(); boolean isOpenable = isFediUrl || fediHandle.isPresent();
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts(); List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android; package org.joinmastodon.android;
import static org.joinmastodon.android.api.MastodonAPIController.gson; import static org.joinmastodon.android.api.MastodonAPIController.gson;
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.MATERIAL3;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@@ -13,6 +14,7 @@ import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
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.model.ContentType; import org.joinmastodon.android.model.ContentType;
@@ -26,7 +28,8 @@ import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import androidx.annotation.StringRes; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
public class GlobalUserPreferences{ public class GlobalUserPreferences{
private static final String TAG="GlobalUserPreferences"; private static final String TAG="GlobalUserPreferences";
@@ -42,7 +45,6 @@ public class GlobalUserPreferences{
public static boolean showNewPostsButton; public static boolean showNewPostsButton;
public static boolean toolbarMarquee; public static boolean toolbarMarquee;
public static boolean disableSwipe; public static boolean disableSwipe;
public static boolean voteButtonForSingleChoice;
public static boolean enableDeleteNotifications; public static boolean enableDeleteNotifications;
public static boolean translateButtonOpenedOnly; public static boolean translateButtonOpenedOnly;
public static boolean uniformNotificationIcon; public static boolean uniformNotificationIcon;
@@ -54,16 +56,17 @@ public class GlobalUserPreferences{
public static boolean collapseLongPosts; public static boolean collapseLongPosts;
public static boolean spectatorMode; public static boolean spectatorMode;
public static boolean autoHideFab; public static boolean autoHideFab;
public static boolean compactReblogReplyLine;
public static boolean allowRemoteLoading; public static boolean allowRemoteLoading;
public static boolean forwardReportDefault; public static boolean forwardReportDefault;
public static AutoRevealMode autoRevealEqualSpoilers; public static AutoRevealMode autoRevealEqualSpoilers;
public static ColorPreference color;
public static boolean disableM3PillActiveIndicator; public static boolean disableM3PillActiveIndicator;
public static boolean showNavigationLabels; public static boolean showNavigationLabels;
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings; public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
public static boolean overlayMedia; public static boolean overlayMedia;
public static boolean showSuicideHelp; public static boolean showSuicideHelp;
public static boolean underlinedLinks;
public static ColorPreference color;
public static boolean likeIcon;
// MOSHIDON // MOSHIDON
public static boolean showDividers; public static boolean showDividers;
@@ -84,6 +87,11 @@ public class GlobalUserPreferences{
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE); return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
} }
private static SharedPreferences getPreReplyPrefs(){
return MastodonApp.context.getSharedPreferences("pre_reply_sheets", Context.MODE_PRIVATE);
}
public static <T> T fromJson(String json, Type type, T orElse){ public static <T> T fromJson(String json, Type type, T orElse){
if(json==null) return orElse; if(json==null) return orElse;
try{ try{
@@ -116,7 +124,6 @@ public class GlobalUserPreferences{
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true); showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
toolbarMarquee=prefs.getBoolean("toolbarMarquee", true); toolbarMarquee=prefs.getBoolean("toolbarMarquee", true);
disableSwipe=prefs.getBoolean("disableSwipe", false); disableSwipe=prefs.getBoolean("disableSwipe", false);
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false); enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false); translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false); uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
@@ -128,7 +135,6 @@ public class GlobalUserPreferences{
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true); collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
spectatorMode=prefs.getBoolean("spectatorMode", false); spectatorMode=prefs.getBoolean("spectatorMode", false);
autoHideFab=prefs.getBoolean("autoHideFab", true); autoHideFab=prefs.getBoolean("autoHideFab", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true); allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name())); autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true); forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
@@ -139,12 +145,14 @@ public class GlobalUserPreferences{
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true); displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
overlayMedia=prefs.getBoolean("overlayMedia", false); overlayMedia=prefs.getBoolean("overlayMedia", false);
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true); showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
underlinedLinks=prefs.getBoolean("underlinedLinks", true);
color=ColorPreference.valueOf(prefs.getString("color", MATERIAL3.name()));
likeIcon=prefs.getBoolean("likeIcon", false);
// MOSHIDON // MOSHIDON
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false); uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
showDividers =prefs.getBoolean("showDividers", false); showDividers =prefs.getBoolean("showDividers", false);
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true); relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false); defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
doubleTapToSearch =prefs.getBoolean("doubleTapToSearch", true); doubleTapToSearch =prefs.getBoolean("doubleTapToSearch", true);
doubleTapToSwipe =prefs.getBoolean("doubleTapToSwipe", true); doubleTapToSwipe =prefs.getBoolean("doubleTapToSwipe", true);
@@ -169,18 +177,11 @@ public class GlobalUserPreferences{
.apply(); .apply();
} }
try { int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE);
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ if(migrationLevel < 61)
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.MATERIAL3.name())); migrateToUpstreamVersion61();
}else{ if(migrationLevel < BuildConfig.VERSION_CODE)
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PURPLE.name())); prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply();
}
} catch (IllegalArgumentException|ClassCastException ignored) {
// invalid color name or color was previously saved as integer
color=ColorPreference.PURPLE;
}
if(prefs.getInt("migrationLevel", 0) < 61) migrateToUpstreamVersion61();
} }
public static void save(){ public static void save(){
@@ -210,8 +211,6 @@ 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("color", color.name())
.putBoolean("allowRemoteLoading", allowRemoteLoading) .putBoolean("allowRemoteLoading", allowRemoteLoading)
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name()) .putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
.putBoolean("forwardReportDefault", forwardReportDefault) .putBoolean("forwardReportDefault", forwardReportDefault)
@@ -222,12 +221,14 @@ public class GlobalUserPreferences{
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings) .putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
.putBoolean("overlayMedia", overlayMedia) .putBoolean("overlayMedia", overlayMedia)
.putBoolean("showSuicideHelp", showSuicideHelp) .putBoolean("showSuicideHelp", showSuicideHelp)
.putBoolean("underlinedLinks", underlinedLinks)
.putString("color", color.name())
.putBoolean("likeIcon", likeIcon)
// MOSHIDON // MOSHIDON
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies) .putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
.putBoolean("doubleTapToSearch", doubleTapToSearch) .putBoolean("doubleTapToSearch", doubleTapToSearch)
.putBoolean("doubleTapToSwipe", doubleTapToSwipe) .putBoolean("doubleTapToSwipe", doubleTapToSwipe)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putBoolean("replyLineAboveHeader", replyLineAboveHeader) .putBoolean("replyLineAboveHeader", replyLineAboveHeader)
.putBoolean("confirmBeforeReblog", confirmBeforeReblog) .putBoolean("confirmBeforeReblog", confirmBeforeReblog)
.putBoolean("swapBookmarkWithBoostAction", swapBookmarkWithBoostAction) .putBoolean("swapBookmarkWithBoostAction", swapBookmarkWithBoostAction)
@@ -240,11 +241,59 @@ public class GlobalUserPreferences{
.putBoolean("showPostsWithoutAlt", showPostsWithoutAlt) .putBoolean("showPostsWithoutAlt", showPostsWithoutAlt)
.putBoolean("showMediaPreview", showMediaPreview) .putBoolean("showMediaPreview", showMediaPreview)
.putInt("theme", theme.ordinal())
.apply(); .apply();
} }
public static boolean isOptedOutOfPreReplySheet(PreReplySheetType type, Account account, String accountID){
if(getPreReplyPrefs().getBoolean("opt_out_"+type, false))
return true;
if(account==null)
return false;
String accountKey=account.acct;
if(!accountKey.contains("@"))
accountKey+="@"+AccountSessionManager.get(accountID).domain;
return getPreReplyPrefs().getBoolean("opt_out_"+type+"_"+accountKey.toLowerCase(), false);
}
public static void optOutOfPreReplySheet(PreReplySheetType type, Account account, String accountID){
String key;
if(account==null){
key="opt_out_"+type;
}else{
String accountKey=account.acct;
if(!accountKey.contains("@"))
accountKey+="@"+AccountSessionManager.get(accountID).domain;
key="opt_out_"+type+"_"+accountKey.toLowerCase();
}
getPreReplyPrefs().edit().putBoolean(key, true).apply();
}
public enum ThemePreference{
AUTO,
LIGHT,
DARK
}
public enum PreReplySheetType{
OLD_POST,
NON_MUTUAL
}
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
//region preferences migrations
private static void migrateToUpstreamVersion61(){ private static void migrateToUpstreamVersion61(){
Log.d(TAG, "Migrating preferences to upstream version 61!!"); Log.d(TAG, "Migrating preferences to upstream version 61!!");
@@ -291,53 +340,7 @@ public class GlobalUserPreferences{
localPrefs.save(); localPrefs.save();
} }
prefs.edit().putInt("migrationLevel", 61).apply();
} }
public enum ColorPreference{ //endregion
MATERIAL3,
PURPLE,
PINK,
GREEN,
BLUE,
BROWN,
RED,
YELLOW,
NORD,
WHITE;
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
case NORD -> R.string.mo_color_palette_nord;
case WHITE -> R.string.mo_color_palette_black_and_white;
};
}
}
public enum ThemePreference{
AUTO,
LIGHT,
DARK
}
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
} }

View File

@@ -12,6 +12,7 @@ import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.net.Uri; import android.net.Uri;
import android.os.BadParcelableException;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; import android.provider.MediaStore;
@@ -39,62 +40,46 @@ import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels; import org.parceler.Parcels;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Instant;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent { public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
private static final String TAG="MainActivity";
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState){ protected void onCreate(@Nullable Bundle savedInstanceState){
UiUtils.setUserPreferredTheme(this); AccountSession session=getCurrentSession();
UiUtils.setUserPreferredTheme(this, session);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if(savedInstanceState==null){ Thread.UncaughtExceptionHandler defaultHandler=Thread.getDefaultUncaughtExceptionHandler();
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){ Thread.setDefaultUncaughtExceptionHandler((t, e)->{
showFragmentClearingBackStack(new CustomWelcomeFragment()); File file=new File(MastodonApp.context.getFilesDir(), "crash.log");
}else{ try(FileOutputStream out=new FileOutputStream(file)){
AccountSession session; PrintWriter writer=new PrintWriter(out);
Bundle args=new Bundle(); writer.println(BuildConfig.VERSION_NAME+" ("+BuildConfig.VERSION_CODE+")");
Intent intent=getIntent(); writer.println(Instant.now().toString());
if(intent.hasExtra("fromExternalShare")) { writer.println();
AccountSessionManager.getInstance() e.printStackTrace(writer);
.setLastActiveAccountID(intent.getStringExtra("account")); writer.flush();
AccountSessionManager.getInstance().maybeUpdateLocalInfo( }catch(IOException x){
AccountSessionManager.getInstance().getLastActiveAccount()); Log.e(TAG, "Error writing crash.log", x);
showFragmentForExternalShare(intent.getExtras()); }finally{
return; defaultHandler.uncaughtException(t, e);
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID());
} else if (intent.getBooleanExtra("compose", false)){
showCompose();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
} else {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
} }
});
if(savedInstanceState==null){
restartHomeFragment();
} }
if(GithubSelfUpdater.needSelfUpdating()){ if(GithubSelfUpdater.needSelfUpdating()){
@@ -147,11 +132,11 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
session=AccountSessionManager.get(accountID); session=AccountSessionManager.get(accountID);
if(session==null || !session.activated) if(session==null || !session.activated)
return; return;
openSearchQuery(uri.toString(), session.getID(), R.string.opening_link, false); openSearchQuery(uri.toString(), session.getID(), R.string.opening_link, false, null);
} }
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){ public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch, GetSearchResults.Type type){
new GetSearchResults(q, null, true, null, 0, 0) new GetSearchResults(q, type, true, null, 0, 0)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(SearchResults result){ public void onSuccess(SearchResults result){
@@ -285,4 +270,87 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
Fragment fragment = getCurrentFragment(); Fragment fragment = getCurrentFragment();
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent); if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
} }
public AccountSession getCurrentSession(){
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
return AccountSessionManager.getInstance()
.getAccount(intent.getStringExtra("account"));
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
return session;
}
public void restartActivity(){
finish();
startActivity(new Intent(this, MainActivity.class));
}
public void restartHomeFragment(){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSession session;
Bundle args=new Bundle();
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 hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
// Parcelables might not be compatible across app versions so this protects against possible crashes
// when a notification was received, then the app was updated, and then the user opened the notification
try{
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID());
}catch(BadParcelableException x){
Log.w(TAG, x);
}
} else if (intent.getBooleanExtra("compose", false)){
showCompose();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
} else {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
}
}
} }

View File

@@ -17,7 +17,6 @@ import android.graphics.drawable.Drawable;
import android.opengl.Visibility; import android.opengl.Visibility;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@@ -38,7 +37,6 @@ import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -135,7 +133,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if(intent.hasExtra("notification")){ if(intent.hasExtra("notification")){
org.joinmastodon.android.model.Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification")); org.joinmastodon.android.model.Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
String statusID=notification.status.id;
String statusID = null;
if(notification != null && notification.status != null)
statusID=notification.status.id;
if (statusID != null) { if (statusID != null) {
AccountSessionManager accountSessionManager = AccountSessionManager.getInstance(); AccountSessionManager accountSessionManager = AccountSessionManager.getInstance();
Preferences preferences = accountSessionManager.getAccount(accountID).preferences; Preferences preferences = accountSessionManager.getAccount(accountID).preferences;
@@ -156,9 +158,9 @@ public class PushNotificationReceiver extends BroadcastReceiver{
} }
} }
public void notifyUnifiedPush(Context context, String accountID, org.joinmastodon.android.model.Notification notification) { public void notifyUnifiedPush(Context context, AccountSession account, org.joinmastodon.android.model.Notification notification) {
// push notifications are only created from the official push notification, so we create a fake from by transforming the notification // push notifications are only created from the official push notification, so we create a fake from by transforming the notification
PushNotificationReceiver.this.notify(context, PushNotification.fromNotification(context, notification), accountID, notification); PushNotificationReceiver.this.notify(context, PushNotification.fromNotification(context, account, notification), account.getID(), notification);
} }
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){ private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
@@ -218,7 +220,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if (!GlobalUserPreferences.uniformNotificationIcon) { if (!GlobalUserPreferences.uniformNotificationIcon) {
builder.setSmallIcon(switch (pn.notificationType) { builder.setSmallIcon(switch (pn.notificationType) {
case FAVORITE -> R.drawable.ic_fluent_star_24_filled; case FAVORITE -> GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled;
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled; case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled; case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
case MENTION -> R.drawable.ic_fluent_mention_24_filled; case MENTION -> R.drawable.ic_fluent_mention_24_filled;

View File

@@ -72,7 +72,7 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
result.items result.items
.stream() .stream()
.findFirst() .findFirst()
.ifPresent(value->MastodonAPIController.runInBackground(()->new PushNotificationReceiver().notifyUnifiedPush(context, instance, value))); .ifPresent(value->MastodonAPIController.runInBackground(()->new PushNotificationReceiver().notifyUnifiedPush(context, account, value)));
} }
@Override @Override

View File

@@ -9,23 +9,35 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.Log; import android.util.Log;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.notifications.GetNotifications; import org.joinmastodon.android.api.requests.notifications.GetNotifications;
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;
import org.joinmastodon.android.model.CacheablePaginatedResponse; import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FollowList;
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.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.ui.utils.UiUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -43,6 +55,7 @@ public class CacheController{
private final Runnable databaseCloseRunnable=this::closeDatabase; private final Runnable databaseCloseRunnable=this::closeDatabase;
private boolean loadingNotifications; private boolean loadingNotifications;
private final ArrayList<Callback<PaginatedResponse<List<Notification>>>> pendingNotificationsCallbacks=new ArrayList<>(); private final ArrayList<Callback<PaginatedResponse<List<Notification>>>> pendingNotificationsCallbacks=new ArrayList<>();
private List<FollowList> lists;
private static final int POST_FLAG_GAP_AFTER=1; private static final int POST_FLAG_GAP_AFTER=1;
@@ -69,12 +82,11 @@ public class CacheController{
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class); Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
status.postprocess(); status.postprocess();
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) ? status.id : null;
newMaxID=status.id; newMaxID=status.id;
result.add(status); result.add(status);
}while(cursor.moveToNext()); }while(cursor.moveToNext());
String _newMaxID=newMaxID; String _newMaxID=newMaxID;
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true))); uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
return; return;
} }
@@ -86,9 +98,7 @@ public class CacheController{
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
ArrayList<Status> filtered=new ArrayList<>(result); callback.onSuccess(new CacheablePaginatedResponse<>(result, result.isEmpty() ? null : result.get(result.size()-1).id, false));
AccountSessionManager.get(accountID).filterStatuses(filtered, FilterContext.HOME);
callback.onSuccess(new CacheablePaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id, false));
putHomeTimeline(result, maxID==null); putHomeTimeline(result, maxID==null);
} }
@@ -116,12 +126,14 @@ public class CacheController{
values.put("id", s.id); values.put("id", s.id);
values.put("json", MastodonAPIController.gson.toJson(s)); values.put("json", MastodonAPIController.gson.toJson(s));
int flags=0; int flags=0;
if(s.hasGapAfter) if(Objects.equals(s.hasGapAfter, s.id))
flags|=POST_FLAG_GAP_AFTER; flags|=POST_FLAG_GAP_AFTER;
values.put("flags", flags); values.put("flags", flags);
values.put("time", s.createdAt.getEpochSecond()); values.put("time", s.createdAt.getEpochSecond());
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE); db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
} }
if(!clear)
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"1000"});
}); });
} }
@@ -273,6 +285,28 @@ public class CacheController{
public void deleteStatus(String id){ public void deleteStatus(String id){
runOnDbThread((db)->{ runOnDbThread((db)->{
String gapId=null;
int gapFlags=0;
// select to-be-removed and newer row
try(Cursor cursor=db.query("home_timeline", new String[]{"id", "flags"}, "`time`>=(SELECT `time` FROM `home_timeline` WHERE `id`=?)", new String[]{id}, null, null, "`time` ASC", "2")){
boolean hadGapAfter=false;
// always either one or two iterations (only one if there's no newer post)
while(cursor.moveToNext()){
String currentId=cursor.getString(0);
int currentFlags=cursor.getInt(1);
if(currentId.equals(id)){
hadGapAfter=((currentFlags & POST_FLAG_GAP_AFTER)!=0);
}else if(hadGapAfter){
gapFlags=currentFlags|POST_FLAG_GAP_AFTER;
gapId=currentId;
}
}
}
if(gapId!=null){
ContentValues values=new ContentValues();
values.put("flags", gapFlags);
db.update("home_timeline", values, "`id`=?", new String[]{gapId});
}
db.delete("home_timeline", "`id`=?", new String[]{id}); db.delete("home_timeline", "`id`=?", new String[]{id});
}); });
} }
@@ -326,6 +360,99 @@ public class CacheController{
}, 0); }, 0);
} }
public void reloadLists(Callback<List<FollowList>> callback){
new GetLists()
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<FollowList> result){
result.sort(Comparator.comparing(l->l.title));
lists=result;
if(callback!=null)
callback.onSuccess(result);
writeListsToFile();
}
@Override
public void onError(ErrorResponse error){
if(callback!=null)
callback.onError(error);
}
})
.exec(accountID);
}
private List<FollowList> loadListsFromFile(){
File file=getListsFile();
if(!file.exists())
return null;
try(InputStreamReader in=new InputStreamReader(new FileInputStream(file))){
return MastodonAPIController.gson.fromJson(in, new TypeToken<List<FollowList>>(){}.getType());
}catch(Exception x){
Log.w(TAG, "failed to read lists from cache file", x);
return null;
}
}
private void writeListsToFile(){
databaseThread.postRunnable(()->{
try(OutputStreamWriter out=new OutputStreamWriter(new FileOutputStream(getListsFile()))){
MastodonAPIController.gson.toJson(lists, out);
}catch(IOException x){
Log.w(TAG, "failed to write lists to cache file", x);
}
}, 0);
}
public void getLists(Callback<List<FollowList>> callback){
if(lists!=null){
if(callback!=null)
callback.onSuccess(lists);
return;
}
databaseThread.postRunnable(()->{
List<FollowList> lists=loadListsFromFile();
if(lists!=null){
this.lists=lists;
if(callback!=null)
uiHandler.post(()->callback.onSuccess(lists));
return;
}
reloadLists(callback);
}, 0);
}
public File getListsFile(){
return new File(MastodonApp.context.getFilesDir(), "lists_"+accountID+".json");
}
public void addList(FollowList list){
if(lists==null)
return;
lists.add(list);
lists.sort(Comparator.comparing(l->l.title));
writeListsToFile();
}
public void deleteList(String id){
if(lists==null)
return;
lists.removeIf(l->l.id.equals(id));
writeListsToFile();
}
public void updateList(FollowList list){
if(lists==null)
return;
for(int i=0;i<lists.size();i++){
if(lists.get(i).id.equals(list.id)){
lists.set(i, list);
lists.sort(Comparator.comparing(l->l.title));
writeListsToFile();
break;
}
}
}
private class DatabaseHelper extends SQLiteOpenHelper{ private class DatabaseHelper extends SQLiteOpenHelper{
public DatabaseHelper(){ public DatabaseHelper(){

View File

@@ -54,7 +54,9 @@ public class MastodonAPIController{
.create(); .create();
private static WorkerThread thread=new WorkerThread("MastodonAPIController"); private static WorkerThread thread=new WorkerThread("MastodonAPIController");
private static OkHttpClient httpClient=new OkHttpClient.Builder() private static OkHttpClient httpClient=new OkHttpClient.Builder()
.readTimeout(5, TimeUnit.MINUTES) .connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build(); .build();
private AccountSession session; private AccountSession session;
@@ -113,24 +115,24 @@ public class MastodonAPIController{
} }
Request hreq=builder.build(); Request hreq=builder.build();
Call call=httpClient.newCall(hreq); OkHttpClient client=req.timeout>0
? httpClient.newBuilder().readTimeout(req.timeout, TimeUnit.MILLISECONDS).build()
: httpClient;
Call call=client.newCall(hreq);
synchronized(req){ synchronized(req){
req.okhttpCall=call; req.okhttpCall=call;
} }
if(req.timeout>0){
call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS);
}
if(BuildConfig.DEBUG) if(BuildConfig.DEBUG)
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq); Log.d(TAG, logTag(session)+"Sending request: "+hreq);
call.enqueue(new Callback(){ call.enqueue(new Callback(){
@Override @Override
public void onFailure(@NonNull Call call, @NonNull IOException e){ public void onFailure(@NonNull Call call, @NonNull IOException e){
if(call.isCanceled()) if(req.canceled)
return; return;
if(BuildConfig.DEBUG) if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" failed", e); Log.w(TAG, logTag(session)+""+hreq+" failed", e);
synchronized(req){ synchronized(req){
req.okhttpCall=null; req.okhttpCall=null;
} }
@@ -139,10 +141,10 @@ public class MastodonAPIController{
@Override @Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{
if(call.isCanceled()) if(req.canceled)
return; return;
if(BuildConfig.DEBUG) if(BuildConfig.DEBUG)
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+hreq+" received response: "+response); Log.d(TAG, logTag(session)+hreq+" received response: "+response);
synchronized(req){ synchronized(req){
req.okhttpCall=null; req.okhttpCall=null;
} }
@@ -153,7 +155,7 @@ public class MastodonAPIController{
try{ try{
if(BuildConfig.DEBUG){ if(BuildConfig.DEBUG){
JsonElement respJson=JsonParser.parseReader(reader); JsonElement respJson=JsonParser.parseReader(reader);
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] response body: "+respJson); Log.d(TAG, logTag(session)+"response body: "+respJson);
if(req.respTypeToken!=null) if(req.respTypeToken!=null)
respObj=gson.fromJson(respJson, req.respTypeToken.getType()); respObj=gson.fromJson(respJson, req.respTypeToken.getType());
else if(req.respClass!=null) else if(req.respClass!=null)
@@ -175,7 +177,7 @@ public class MastodonAPIController{
return; 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, logTag(session)+response+" error parsing or reading body", x);
req.onError(x.getLocalizedMessage(), response.code(), x); req.onError(x.getLocalizedMessage(), response.code(), x);
return; return;
} }
@@ -184,19 +186,19 @@ public class MastodonAPIController{
req.validateAndPostprocessResponse(respObj, response); req.validateAndPostprocessResponse(respObj, response);
}catch(IOException x){ }catch(IOException x){
if(BuildConfig.DEBUG) if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error post-processing or validating response", x); Log.w(TAG, logTag(session)+response+" error post-processing or validating response", x);
req.onError(x.getLocalizedMessage(), response.code(), x); req.onError(x.getLocalizedMessage(), response.code(), x);
return; return;
} }
if(BuildConfig.DEBUG) if(BuildConfig.DEBUG)
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" parsed successfully: "+respObj); Log.d(TAG, logTag(session)+response+" parsed successfully: "+respObj);
req.onSuccess(respObj); req.onSuccess(respObj);
}else{ }else{
try{ try{
JsonObject error=JsonParser.parseReader(reader).getAsJsonObject(); JsonObject error=JsonParser.parseReader(reader).getAsJsonObject();
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" received error: "+error); Log.w(TAG, logTag(session)+response+" received error: "+error);
if(error.has("details")){ if(error.has("details")){
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code(), null); MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code(), null);
HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>(); HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>();
@@ -231,7 +233,7 @@ public class MastodonAPIController{
}); });
}catch(Exception x){ }catch(Exception x){
if(BuildConfig.DEBUG) if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] error creating and sending http request", x); Log.w(TAG, logTag(session)+"error creating and sending http request", x);
req.onError(x.getLocalizedMessage(), 0, x); req.onError(x.getLocalizedMessage(), 0, x);
} }
}, 0); }, 0);
@@ -244,4 +246,8 @@ public class MastodonAPIController{
public static OkHttpClient getHttpClient(){ public static OkHttpClient getHttpClient(){
return httpClient; return httpClient;
} }
private static String logTag(AccountSession session){
return "["+(session==null ? "no-auth" : session.getID())+"] ";
}
} }

View File

@@ -153,8 +153,9 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
headers.put(key, value); headers.put(key, value);
} }
protected void setTimeout(long timeout){ public MastodonAPIRequest<T> setTimeout(long timeout){
this.timeout=timeout; this.timeout=timeout;
return this;
} }
protected String getPathPrefix(){ protected String getPathPrefix(){
@@ -179,6 +180,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
} }
public RequestBody getRequestBody() throws IOException{ public RequestBody getRequestBody() throws IOException{
if(requestBody instanceof RequestBody rb)
return rb;
return requestBody==null ? null : new JsonObjectRequestBody(requestBody); return requestBody==null ? null : new JsonObjectRequestBody(requestBody);
} }

View File

@@ -97,7 +97,7 @@ public class PushSubscriptionManager{
deviceToken=getPrefs().getString("deviceToken", null); deviceToken=getPrefs().getString("deviceToken", null);
int tokenVersion=getPrefs().getInt("version", 0); int tokenVersion=getPrefs().getInt("version", 0);
if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){ if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){
registerAllAccountsForPush(true); // TODO: revert this before release registerAllAccountsForPush(false);
return; return;
} }
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token..."); Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");

View File

@@ -8,7 +8,10 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited; import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted; import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged; import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
@@ -50,7 +53,7 @@ public class StatusInteractionController{
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));
} }
@Override @Override
@@ -59,13 +62,13 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context); error.showToast(MastodonApp.context);
status.favourited=!favorited; status.favourited=!favorited;
cb.accept(status); cb.accept(status);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
} }
}) })
.exec(accountID); .exec(accountID);
runningFavoriteRequests.put(status.id, req); runningFavoriteRequests.put(status.id, req);
status.favourited=favorited; status.favourited=favorited;
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
} }
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){ public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
@@ -80,11 +83,15 @@ public class StatusInteractionController{
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
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));
if(reblogged) E.post(new StatusCreatedEvent(reblog, accountID));
else E.post(new ReblogDeletedEvent(status.id, accountID));
}
} }
@Override @Override
@@ -93,13 +100,13 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context); error.showToast(MastodonApp.context);
status.reblogged=!reblogged; status.reblogged=!reblogged;
cb.accept(status); cb.accept(status);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
} }
}) })
.exec(accountID); .exec(accountID);
runningReblogRequests.put(status.id, req); runningReblogRequests.put(status.id, req);
status.reblogged=reblogged; status.reblogged=reblogged;
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
} }
public void setBookmarked(Status status, boolean bookmarked){ public void setBookmarked(Status status, boolean bookmarked){
@@ -120,7 +127,7 @@ public class StatusInteractionController{
public void onSuccess(Status result){ public void onSuccess(Status result){
runningBookmarkRequests.remove(status.id); runningBookmarkRequests.remove(status.id);
cb.accept(result); cb.accept(result);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
} }
@Override @Override
@@ -129,12 +136,12 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context); error.showToast(MastodonApp.context);
status.bookmarked=!bookmarked; status.bookmarked=!bookmarked;
cb.accept(status); cb.accept(status);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
} }
}) })
.exec(accountID); .exec(accountID);
runningBookmarkRequests.put(status.id, req); runningBookmarkRequests.put(status.id, req);
status.bookmarked=bookmarked; status.bookmarked=bookmarked;
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status)); if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
} }
} }

View File

@@ -0,0 +1,22 @@
package org.joinmastodon.android.api.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.model.BaseModel;
public class CheckInviteLink extends MastodonAPIRequest<CheckInviteLink.Response>{
public CheckInviteLink(String path){
super(HttpMethod.GET, path, Response.class);
addHeader("Accept", "application/json");
}
@Override
protected String getPathPrefix(){
return "";
}
public static class Response extends BaseModel{
@RequiredField
public String inviteCode;
}
}

View File

@@ -0,0 +1,14 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.FollowList;
import java.util.List;
public class GetAccountLists extends MastodonAPIRequest<List<FollowList>>{
public GetAccountLists(String id){
super(HttpMethod.GET, "/accounts/"+id+"/lists", new TypeToken<>(){});
}
}

View File

@@ -4,22 +4,23 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Token; import org.joinmastodon.android.model.Token;
public class RegisterAccount extends MastodonAPIRequest<Token>{ public class RegisterAccount extends MastodonAPIRequest<Token>{
public RegisterAccount(String username, String email, String password, String locale, String reason, String timezone){ public RegisterAccount(String username, String email, String password, String locale, String reason, String timezone, String inviteCode){
super(HttpMethod.POST, "/accounts", Token.class); super(HttpMethod.POST, "/accounts", Token.class);
setRequestBody(new Body(username, email, password, locale, reason, timezone)); setRequestBody(new Body(username, email, password, locale, reason, timezone, inviteCode));
} }
private static class Body{ private static class Body{
public String username, email, password, locale, reason, timeZone; public String username, email, password, locale, reason, timeZone, inviteCode;
public boolean agreement=true; public boolean agreement=true;
public Body(String username, String email, String password, String locale, String reason, String timeZone){ public Body(String username, String email, String password, String locale, String reason, String timeZone, String inviteCode){
this.username=username; this.username=username;
this.email=email; this.email=email;
this.password=password; this.password=password;
this.locale=locale; this.locale=locale;
this.reason=reason; this.reason=reason;
this.timeZone=timeZone; this.timeZone=timeZone;
this.inviteCode=inviteCode;
} }
} }
} }

View File

@@ -0,0 +1,23 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Account;
import java.util.List;
public class SearchAccounts extends MastodonAPIRequest<List<Account>>{
public SearchAccounts(String q, int limit, int offset, boolean resolve, boolean following){
super(HttpMethod.GET, "/accounts/search", new TypeToken<>(){});
addQueryParameter("q", q);
if(limit>0)
addQueryParameter("limit", limit+"");
if(offset>0)
addQueryParameter("offset", offset+"");
if(resolve)
addQueryParameter("resolve", "true");
if(following)
addQueryParameter("following", "true");
}
}

View File

@@ -4,15 +4,21 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
public class SetAccountMuted extends MastodonAPIRequest<Relationship>{ public class SetAccountMuted extends MastodonAPIRequest<Relationship>{
public SetAccountMuted(String id, boolean muted, long duration){ public SetAccountMuted(String id, boolean muted, long duration, boolean muteNotifications){
super(HttpMethod.POST, "/accounts/"+id+"/"+(muted ? "mute" : "unmute"), Relationship.class); super(HttpMethod.POST, "/accounts/"+id+"/"+(muted ? "mute" : "unmute"), Relationship.class);
setRequestBody(new Request(duration)); if(muted)
setRequestBody(new Request(duration, muteNotifications));
else{
setRequestBody(new Object());
}
} }
private static class Request{ private static class Request{
public long duration; public long duration;
public Request(long duration){ public boolean muteNotifications;
public Request(long duration, boolean muteNotifications){
this.duration=duration; this.duration=duration;
this.muteNotifications=muteNotifications;
} }
} }
} }

View File

@@ -22,6 +22,7 @@ public class UpdateAccountCredentials extends MastodonAPIRequest<Account>{
private Uri avatar, cover; private Uri avatar, cover;
private File avatarFile, coverFile; private File avatarFile, coverFile;
private List<AccountField> fields; private List<AccountField> fields;
private Boolean discoverable, indexable;
public UpdateAccountCredentials(String displayName, String bio, Uri avatar, Uri cover, List<AccountField> fields){ public UpdateAccountCredentials(String displayName, String bio, Uri avatar, Uri cover, List<AccountField> fields){
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class); super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
@@ -41,6 +42,12 @@ public class UpdateAccountCredentials extends MastodonAPIRequest<Account>{
this.fields=fields; this.fields=fields;
} }
public UpdateAccountCredentials setDiscoverableIndexable(boolean discoverable, boolean indexable){
this.discoverable=discoverable;
this.indexable=indexable;
return this;
}
@Override @Override
public RequestBody getRequestBody() throws IOException{ public RequestBody getRequestBody() throws IOException{
MultipartBody.Builder bldr=new MultipartBody.Builder() MultipartBody.Builder bldr=new MultipartBody.Builder()
@@ -58,15 +65,21 @@ public class UpdateAccountCredentials extends MastodonAPIRequest<Account>{
}else if(coverFile!=null){ }else if(coverFile!=null){
bldr.addFormDataPart("header", coverFile.getName(), new ResizedImageRequestBody(Uri.fromFile(coverFile), 1500*500, null)); bldr.addFormDataPart("header", coverFile.getName(), new ResizedImageRequestBody(Uri.fromFile(coverFile), 1500*500, null));
} }
if(fields.isEmpty()){ if(fields!=null){
bldr.addFormDataPart("fields_attributes[0][name]", "").addFormDataPart("fields_attributes[0][value]", ""); if(fields.isEmpty()){
}else{ bldr.addFormDataPart("fields_attributes[0][name]", "").addFormDataPart("fields_attributes[0][value]", "");
int i=0; }else{
for(AccountField field:fields){ int i=0;
bldr.addFormDataPart("fields_attributes["+i+"][name]", field.name).addFormDataPart("fields_attributes["+i+"][value]", field.value); for(AccountField field:fields){
i++; bldr.addFormDataPart("fields_attributes["+i+"][name]", field.name).addFormDataPart("fields_attributes["+i+"][value]", field.value);
i++;
}
} }
} }
if(discoverable!=null)
bldr.addFormDataPart("discoverable", discoverable.toString());
if(indexable!=null)
bldr.addFormDataPart("indexable", indexable.toString());
return bldr.build(); return bldr.build();
} }

View File

@@ -2,6 +2,9 @@ package org.joinmastodon.android.api.requests.filters;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import androidx.annotation.Keep;
@Keep
class KeywordAttribute{ class KeywordAttribute{
public String id; public String id;
@SerializedName("_destroy") @SerializedName("_destroy")

View File

@@ -1,17 +1,19 @@
package org.joinmastodon.android.api.requests.lists; package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
import java.util.List;
public class AddAccountsToList extends MastodonAPIRequest<Object> { import java.nio.charset.StandardCharsets;
public AddAccountsToList(String listId, List<String> accountIds){ import java.util.Collection;
super(HttpMethod.POST, "/lists/"+listId+"/accounts", Object.class);
Request req = new Request();
req.accountIds = accountIds;
setRequestBody(req);
}
public static class Request{ import okhttp3.FormBody;
public List<String> accountIds;
} public class AddAccountsToList extends ResultlessMastodonAPIRequest{
public AddAccountsToList(String listID, Collection<String> accountIDs){
super(HttpMethod.POST, "/lists/"+listID+"/accounts");
FormBody.Builder builder=new FormBody.Builder(StandardCharsets.UTF_8);
for(String id:accountIDs){
builder.add("account_ids[]", id);
}
setRequestBody(builder.build());
}
} }

View File

@@ -1,21 +1,23 @@
package org.joinmastodon.android.api.requests.lists; package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.FollowList;
public class CreateList extends MastodonAPIRequest<ListTimeline> { public class CreateList extends MastodonAPIRequest<FollowList>{
public CreateList(String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) { public CreateList(String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
super(HttpMethod.POST, "/lists", ListTimeline.class); super(HttpMethod.POST, "/lists", FollowList.class);
Request req = new Request(); setRequestBody(new Request(title, repliesPolicy, exclusive));
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
} }
public static class Request { private static class Request{
public String title; public String title;
public FollowList.RepliesPolicy repliesPolicy;
public boolean exclusive; public boolean exclusive;
public ListTimeline.RepliesPolicy repliesPolicy;
public Request(String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
this.title=title;
this.repliesPolicy=repliesPolicy;
this.exclusive=exclusive;
}
} }
} }

View File

@@ -1,10 +1,9 @@
package org.joinmastodon.android.api.requests.lists; package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
public class DeleteList extends MastodonAPIRequest<Object> { public class DeleteList extends ResultlessMastodonAPIRequest{
public DeleteList(String id) { public DeleteList(String id){
super(HttpMethod.DELETE, "/lists/" + id, Object.class); super(HttpMethod.DELETE, "/lists/"+id);
} }
} }

View File

@@ -1,10 +1,10 @@
package org.joinmastodon.android.api.requests.lists; package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.FollowList;
public class GetList extends MastodonAPIRequest<ListTimeline> { public class GetList extends MastodonAPIRequest<FollowList> {
public GetList(String id) { public GetList(String id) {
super(HttpMethod.GET, "/lists/" + id, ListTimeline.class); super(HttpMethod.GET, "/lists/" + id, FollowList.class);
} }
} }

View File

@@ -0,0 +1,17 @@
package org.joinmastodon.android.api.requests.lists;
import android.text.TextUtils;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetListAccounts extends HeaderPaginationRequest<Account>{
public GetListAccounts(String listID, String maxID, int limit){
super(HttpMethod.GET, "/lists/"+listID+"/accounts", new TypeToken<>(){});
if(!TextUtils.isEmpty(maxID))
addQueryParameter("max_id", maxID);
addQueryParameter("limit", String.valueOf(limit));
}
}

View File

@@ -3,11 +3,11 @@ package org.joinmastodon.android.api.requests.lists;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.FollowList;
import java.util.List; import java.util.List;
public class GetLists extends MastodonAPIRequest<List<ListTimeline>>{ public class GetLists extends MastodonAPIRequest<List<FollowList>>{
public GetLists() { public GetLists() {
super(HttpMethod.GET, "/lists", new TypeToken<>(){}); super(HttpMethod.GET, "/lists", new TypeToken<>(){});
} }

View File

@@ -1,17 +1,19 @@
package org.joinmastodon.android.api.requests.lists; package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
import java.util.List;
public class RemoveAccountsFromList extends MastodonAPIRequest<Object> { import java.nio.charset.StandardCharsets;
public RemoveAccountsFromList(String listId, List<String> accountIds){ import java.util.Collection;
super(HttpMethod.DELETE, "/lists/"+listId+"/accounts", Object.class);
Request req = new Request();
req.accountIds = accountIds;
setRequestBody(req);
}
public static class Request{ import okhttp3.FormBody;
public List<String> accountIds;
} public class RemoveAccountsFromList extends ResultlessMastodonAPIRequest{
public RemoveAccountsFromList(String listID, Collection<String> accountIDs){
super(HttpMethod.DELETE, "/lists/"+listID+"/accounts");
FormBody.Builder builder=new FormBody.Builder(StandardCharsets.UTF_8);
for(String id:accountIDs){
builder.add("account_ids[]", id);
}
setRequestBody(builder.build());
}
} }

View File

@@ -1,15 +1,23 @@
package org.joinmastodon.android.api.requests.lists; package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.FollowList;
public class UpdateList extends MastodonAPIRequest<ListTimeline> { public class UpdateList extends MastodonAPIRequest<FollowList>{
public UpdateList(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) { public UpdateList(String listID, String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class); super(HttpMethod.PUT, "/lists/"+listID, FollowList.class);
CreateList.Request req = new CreateList.Request(); setRequestBody(new Request(title, repliesPolicy, exclusive));
req.title = title; }
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy; private static class Request{
setRequestBody(req); public String title;
public FollowList.RepliesPolicy repliesPolicy;
public boolean exclusive;
public Request(String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
this.title=title;
this.repliesPolicy=repliesPolicy;
this.exclusive=exclusive;
}
} }
} }

View File

@@ -13,7 +13,7 @@ import okhttp3.MultipartBody;
import okhttp3.RequestBody; import okhttp3.RequestBody;
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> { public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
private String maxID; private final String maxID;
public PleromaMarkNotificationsRead(String maxID) { public PleromaMarkNotificationsRead(String maxID) {
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){}); super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
this.maxID = maxID; this.maxID = maxID;

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.AkkomaTranslation;
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
public AkkomaTranslateStatus(String id, String lang){
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
}
}

View File

@@ -48,6 +48,8 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public String quoteId; public String quoteId;
public ContentType contentType; public ContentType contentType;
public boolean preview;
public static class Poll{ public static class Poll{
public ArrayList<String> options=new ArrayList<>(); public ArrayList<String> options=new ArrayList<>();
public int expiresIn; public int expiresIn;

View File

@@ -26,6 +26,11 @@ public class GetStatusEditHistory extends MastodonAPIRequest<List<Status>>{
s.visibility=StatusPrivacy.PUBLIC; s.visibility=StatusPrivacy.PUBLIC;
s.mentions=Collections.emptyList(); s.mentions=Collections.emptyList();
s.tags=Collections.emptyList(); s.tags=Collections.emptyList();
if(s.poll!=null){
s.poll.id="fakeID"+i;
s.poll.emojis=Collections.emptyList();
s.poll.ownVotes=Collections.emptyList();
}
i++; i++;
} }
super.validateAndPostprocessResponse(respObj, httpResponse); super.validateAndPostprocessResponse(respObj, httpResponse);

View File

@@ -1,11 +1,13 @@
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.TranslatedStatus; import org.joinmastodon.android.model.Translation;
public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> { import java.util.Map;
public TranslateStatus(String id) {
super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class); public class TranslateStatus extends MastodonAPIRequest<Translation>{
setRequestBody(new Object()); public TranslateStatus(String id, String lang){
} super(HttpMethod.POST, "/statuses/"+id+"/translate", Translation.class);
setRequestBody(Map.of("lang", lang));
}
} }

View File

@@ -0,0 +1,16 @@
package org.joinmastodon.android.api.requests.tags;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Hashtag;
public class GetFollowedTags extends HeaderPaginationRequest<Hashtag>{
public GetFollowedTags(String maxID, int limit){
super(HttpMethod.GET, "/followed_tags", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
}
}

View File

@@ -10,7 +10,7 @@ import org.joinmastodon.android.model.Status;
import java.util.List; import java.util.List;
public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
public GetPublicTimeline(boolean local, boolean remote, String maxID, int limit, String replyVisibility){ public GetPublicTimeline(boolean local, boolean remote, String maxID, String minID, int limit, String sinceID, String replyVisibility){
super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){}); super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){});
if(local) if(local)
addQueryParameter("local", "true"); addQueryParameter("local", "true");
@@ -18,6 +18,10 @@ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
addQueryParameter("remote", "true"); addQueryParameter("remote", "true");
if(!TextUtils.isEmpty(maxID)) if(!TextUtils.isEmpty(maxID))
addQueryParameter("max_id", maxID); addQueryParameter("max_id", maxID);
if(!TextUtils.isEmpty(minID))
addQueryParameter("min_id", minID);
if(!TextUtils.isEmpty(sinceID))
addQueryParameter("since_id", sinceID);
if(limit>0) if(limit>0)
addQueryParameter("limit", limit+""); addQueryParameter("limit", limit+"");
if(replyVisibility != null) if(replyVisibility != null)

View File

@@ -6,10 +6,16 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import androidx.annotation.StringRes;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@@ -40,16 +46,21 @@ public class AccountLocalPreferences{
public String publishButtonText; public String publishButtonText;
public String timelineReplyVisibility; // akkoma-only public String timelineReplyVisibility; // akkoma-only
public boolean keepOnlyLatestNotification; public boolean keepOnlyLatestNotification;
public boolean emojiReactionsEnabled; public boolean emojiReactionsEnabled;
public ShowEmojiReactions showEmojiReactions; public ShowEmojiReactions showEmojiReactions;
public ColorPreference color;
public ArrayList<Emoji> recentCustomEmoji;
public boolean preReplySheet;
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType(); private final static Type recentLanguagesType=new TypeToken<ArrayList<String>>() {}.getType();
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType(); private final static Type timelinesType=new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
private final static Type recentCustomEmojiType=new TypeToken<ArrayList<Emoji>>() {}.getType();
// MOSHIDON // MOSHIDON
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType(); // private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
public Map<String, Integer> recentEmojis; // public Map<String, Integer> recentEmojis;
private final static Type notificationFiltersType = new TypeToken<PushSubscription.Alerts>() {}.getType();
public PushSubscription.Alerts notificationFilters;
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){ public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
this.prefs=prefs; this.prefs=prefs;
@@ -58,6 +69,7 @@ public class AccountLocalPreferences{
revealCWs=prefs.getBoolean("revealCWs", false); revealCWs=prefs.getBoolean("revealCWs", false);
hideSensitiveMedia=prefs.getBoolean("hideSensitive", true); hideSensitiveMedia=prefs.getBoolean("hideSensitive", true);
serverSideFiltersSupported=prefs.getBoolean("serverSideFilters", false); serverSideFiltersSupported=prefs.getBoolean("serverSideFilters", false);
// preReplySheet=prefs.getBoolean("preReplySheet", false);
// MEGALODON // MEGALODON
showReplies=prefs.getBoolean("showReplies", true); showReplies=prefs.getBoolean("showReplies", true);
@@ -74,9 +86,12 @@ public class AccountLocalPreferences{
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false); keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma()); emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name())); showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
// MOSHIDON // MOSHIDON
recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>()); // recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
notificationFilters=fromJson(prefs.getString("notificationFilters", gson.toJson(PushSubscription.Alerts.ofAll())), notificationFiltersType, PushSubscription.Alerts.ofAll());
} }
public long getNotificationsPauseEndTime(){ public long getNotificationsPauseEndTime(){
@@ -87,6 +102,10 @@ public class AccountLocalPreferences{
prefs.edit().putLong("notificationsPauseTime", time).apply(); prefs.edit().putLong("notificationsPauseTime", time).apply();
} }
public ColorPreference getCurrentColor(){
return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3;
}
public void save(){ public void save(){
prefs.edit() prefs.edit()
.putBoolean("interactionCounts", showInteractionCounts) .putBoolean("interactionCounts", showInteractionCounts)
@@ -95,6 +114,9 @@ public class AccountLocalPreferences{
.putBoolean("hideSensitive", hideSensitiveMedia) .putBoolean("hideSensitive", hideSensitiveMedia)
.putBoolean("serverSideFilters", serverSideFiltersSupported) .putBoolean("serverSideFilters", serverSideFiltersSupported)
//TODO figure this stuff out
// .putBoolean("preReplySheet", preReplySheet)
// MEGALODON // MEGALODON
.putBoolean("showReplies", showReplies) .putBoolean("showReplies", showReplies)
.putBoolean("showBoosts", showBoosts) .putBoolean("showBoosts", showBoosts)
@@ -110,12 +132,43 @@ public class AccountLocalPreferences{
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification) .putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled) .putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
.putString("showEmojiReactions", showEmojiReactions.name()) .putString("showEmojiReactions", showEmojiReactions.name())
.putString("color", color!=null ? color.name() : null)
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))
// MOSHIDON // MOSHIDON
.putString("recentEmojis", gson.toJson(recentEmojis)) // .putString("recentEmojis", gson.toJson(recentEmojis))
.putString("notificationFilters", gson.toJson(notificationFilters))
.apply(); .apply();
} }
public enum ColorPreference{
MATERIAL3,
PURPLE,
PINK,
GREEN,
BLUE,
BROWN,
RED,
YELLOW,
NORD,
WHITE;
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
case NORD -> R.string.mo_color_palette_nord;
case WHITE -> R.string.mo_color_palette_black_and_white;
};
}
}
public enum ShowEmojiReactions{ public enum ShowEmojiReactions{
HIDE_EMPTY, HIDE_EMPTY,
ONLY_OPENED, ONLY_OPENED,

View File

@@ -26,6 +26,7 @@ import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterResult; import org.joinmastodon.android.model.FilterResult;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.LegacyFilter; import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Preferences; import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushSubscription; import org.joinmastodon.android.model.PushSubscription;
@@ -34,13 +35,13 @@ import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.model.Token; import org.joinmastodon.android.model.Token;
import org.joinmastodon.android.utils.ObjectIdComparator; import org.joinmastodon.android.utils.ObjectIdComparator;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@@ -71,6 +72,7 @@ public class AccountSession{
private transient SharedPreferences prefs; private transient SharedPreferences prefs;
private transient boolean preferencesNeedSaving; private transient boolean preferencesNeedSaving;
private transient AccountLocalPreferences localPreferences; private transient AccountLocalPreferences localPreferences;
private transient List<FollowList> lists;
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){ AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
this.token=token; this.token=token;
@@ -219,7 +221,7 @@ public class AccountSession{
public void savePreferencesIfPending(){ public void savePreferencesIfPending(){
if(preferencesNeedSaving){ if(preferencesNeedSaving){
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable) new UpdateAccountCredentialsPreferences(preferences, self.locked, self.discoverable, self.source.indexable)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Account result){ public void onSuccess(Account result){
@@ -255,52 +257,77 @@ public class AccountSession{
filterStatusContainingObjects(objects, extractor, context, null); filterStatusContainingObjects(objects, extractor, context, null);
} }
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){ private boolean statusIsOnOwnProfile(Status s, Account profile){
Predicate<Status> statusIsOnOwnProfile = (s) -> self != null && profile != null && s.account != null return self != null && profile != null && s.account != null
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id); && Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
}
if(getLocalPreferences().serverSideFiltersSupported){ private boolean isFilteredType(Status s){
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them AccountLocalPreferences localPreferences = getLocalPreferences();
objects.removeIf(o->{ return (!localPreferences.showReplies && s.inReplyToId != null)
Status s=extractor.apply(o); || (!localPreferences.showBoosts && s.reblog != null);
if(s==null) }
return false;
if(s.filtered==null) public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
return false; AccountLocalPreferences localPreferences = getLocalPreferences();
// don't hide own posts in own profile if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
if (statusIsOnOwnProfile.test(s))
return false;
for(FilterResult filter:s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;
}
return false;
});
return;
}
if(wordFilters==null)
return;
for(T obj:objects){
Status s=extractor.apply(obj); Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){ if(s!=null && s.filtered!=null){
getLocalPreferences().serverSideFiltersSupported=true; localPreferences.serverSideFiltersSupported=true;
getLocalPreferences().save(); localPreferences.save();
return; break;
} }
} }
objects.removeIf(o->{
Status s=extractor.apply(o); List<T> removeUs=new ArrayList<>();
if(s==null) for(int i=0; i<objects.size(); i++){
return false; T o=objects.get(i);
// don't hide own posts in own profile if(filterStatusContainingObject(o, extractor, context, profile)){
if (statusIsOnOwnProfile.test(s)) Status s=extractor.apply(o);
return false; removeUs.add(o);
for(LegacyFilter filter:wordFilters){ if(s!=null && s.hasGapAfter!=null && i>0){
// oops, we're about to remove an item that has a gap after...
// gotta find the previous status that's not also about to be removed
for(int j=i-1; j>=0; j--){
T p=objects.get(j);
Status prev=extractor.apply(objects.get(j));
if(prev!=null && !removeUs.contains(p)){
prev.hasGapAfter=s.hasGapAfter;
break;
}
}
}
}
}
objects.removeAll(removeUs);
}
public <T> boolean filterStatusContainingObject(T object, Function<T, Status> extractor, FilterContext context, Account profile){
Status s=extractor.apply(object);
if(s==null)
return false;
// don't hide own posts in own profile
if(statusIsOnOwnProfile(s, profile))
return false;
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
return true;
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
if(getLocalPreferences().serverSideFiltersSupported){
for(FilterResult filter : s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;
}
}else if(wordFilters!=null){
for(LegacyFilter filter : wordFilters){
if(filter.context.contains(context) && filter.matches(s) && filter.isActive()) if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
return true; return true;
} }
return false; }
}); return false;
}
public void updateAccountInfo(){
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
} }
public Optional<Instance> getInstance() { public Optional<Instance> getInstance() {
@@ -320,7 +347,11 @@ public class AccountSession{
.orElse(""); .orElse("");
} }
public void updateAccountInfo(){ public boolean isNotificationsMentionsOnly(){
AccountSessionManager.getInstance().updateSessionLocalInfo(this); return getRawLocalPreferences().getBoolean("notificationsMentionsOnly", false);
}
public void setNotificationsMentionsOnly(boolean mentionsOnly){
getRawLocalPreferences().edit().putBoolean("notificationsMentionsOnly", mentionsOnly).apply();
} }
} }

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android.api.session; package org.joinmastodon.android.api.session;
import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
import android.app.Activity; import android.app.Activity;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.ComponentName; import android.content.ComponentName;
@@ -34,6 +36,7 @@ import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.LegacyFilter; import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Token; import org.joinmastodon.android.model.Token;
import org.unifiedpush.android.connector.UnifiedPush;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@@ -93,6 +96,7 @@ public class AccountSessionManager{
private AccountSessionManager(){ private AccountSessionManager(){
prefs=MastodonApp.context.getSharedPreferences("account_manager", Context.MODE_PRIVATE); prefs=MastodonApp.context.getSharedPreferences("account_manager", Context.MODE_PRIVATE);
// This file should not be backed up, otherwise the app may start with accounts already logged in. See res/xml/backup_rules.xml
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json"); File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
if(!file.exists()) if(!file.exists())
return; return;
@@ -112,6 +116,7 @@ public class AccountSessionManager{
} }
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){ public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
Context context = MastodonApp.context;
instances.put(instance.uri, instance); instances.put(instance.uri, instance);
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo); AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
sessions.put(session.getID(), session); sessions.put(session.getID(), session);
@@ -124,7 +129,14 @@ public class AccountSessionManager{
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri)); MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
updateMoreInstanceInfo(instance, instance.uri); updateMoreInstanceInfo(instance, instance.uri);
if(PushSubscriptionManager.arePushNotificationsAvailable()){ if (!UnifiedPush.getDistributor(context).isEmpty()) {
UnifiedPush.registerApp(
context,
session.getID(),
new ArrayList<>(),
context.getPackageName()
);
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null); session.getPushSubscriptionManager().registerAccountForPush(null);
} }
maybeUpdateShortcuts(); maybeUpdateShortcuts();
@@ -205,12 +217,17 @@ public class AccountSessionManager{
public void removeAccount(String id){ public void removeAccount(String id){
AccountSession session=getAccount(id); AccountSession session=getAccount(id);
session.getCacheController().closeDatabase(); session.getCacheController().closeDatabase();
session.getCacheController().getListsFile().delete();
MastodonApp.context.deleteDatabase(id+".db"); MastodonApp.context.deleteDatabase(id+".db");
MastodonApp.context.getSharedPreferences(id, 0).edit().clear().commit(); MastodonApp.context.getSharedPreferences(id, 0).edit().clear().commit();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
MastodonApp.context.deleteSharedPreferences(id); MastodonApp.context.deleteSharedPreferences(id);
}else{ }else{
new File(MastodonApp.context.getDir("shared_prefs", Context.MODE_PRIVATE), id+".xml").delete(); String dataDir=MastodonApp.context.getApplicationInfo().dataDir;
if(dataDir!=null){
File prefsDir=new File(dataDir, "shared_prefs");
new File(prefsDir, id+".xml").delete();
}
} }
sessions.remove(id); sessions.remove(id);
if(lastActiveAccountID.equals(id)){ if(lastActiveAccountID.equals(id)){
@@ -314,8 +331,7 @@ public class AccountSessionManager{
} }
} }
/*package*/ void updateSessionLocalInfo(AccountSession session){
public void updateSessionLocalInfo(AccountSession session){
new GetOwnAccount() new GetOwnAccount()
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override

View File

@@ -0,0 +1,15 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.model.Account;
public class AccountAddedToListEvent{
public final String accountID;
public final String listID;
public final Account account;
public AccountAddedToListEvent(String accountID, String listID, Account account){
this.accountID=accountID;
this.listID=listID;
this.account=account;
}
}

View File

@@ -0,0 +1,13 @@
package org.joinmastodon.android.events;
public class AccountRemovedFromListEvent{
public final String accountID;
public final String listID;
public final String targetAccountID;
public AccountRemovedFromListEvent(String accountID, String listID, String targetAccountID){
this.accountID=accountID;
this.listID=listID;
this.targetAccountID=targetAccountID;
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.events;
public class FinishListCreationFragmentEvent{
public final String accountID;
public final String listID;
public FinishListCreationFragmentEvent(String accountID, String listID){
this.accountID=accountID;
this.listID=listID;
}
}

View File

@@ -0,0 +1,13 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.model.FollowList;
public class ListCreatedEvent{
public final String accountID;
public final FollowList list;
public ListCreatedEvent(String accountID, FollowList list){
this.accountID=accountID;
this.list=list;
}
}

View File

@@ -1,9 +1,11 @@
package org.joinmastodon.android.events; package org.joinmastodon.android.events;
public class ListDeletedEvent { public class ListDeletedEvent{
public final String id; public final String accountID;
public final String listID;
public ListDeletedEvent(String id) { public ListDeletedEvent(String accountID, String listID){
this.id = id; this.accountID=accountID;
this.listID=listID;
} }
} }

View File

@@ -1,14 +1,14 @@
package org.joinmastodon.android.events; package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.FollowList;
public class ListUpdatedCreatedEvent { public class ListUpdatedCreatedEvent {
public final String id; public final String id;
public final String title; public final String title;
public final ListTimeline.RepliesPolicy repliesPolicy; public final FollowList.RepliesPolicy repliesPolicy;
public final boolean exclusive; public final boolean exclusive;
public ListUpdatedCreatedEvent(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) { public ListUpdatedCreatedEvent(String id, String title, boolean exclusive, FollowList.RepliesPolicy repliesPolicy) {
this.id = id; this.id = id;
this.title = title; this.title = title;
this.exclusive = exclusive; this.exclusive = exclusive;

View File

@@ -0,0 +1,13 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.model.FollowList;
public class ListUpdatedEvent{
public final String accountID;
public final FollowList list;
public ListUpdatedEvent(String accountID, FollowList list){
this.accountID=accountID;
this.list=list;
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.events;
public class ReblogDeletedEvent{
public final String statusID;
public final String accountID;
public ReblogDeletedEvent(String statusID, String accountID){
this.statusID=statusID;
this.accountID=accountID;
}
}

View File

@@ -1,7 +1,5 @@
package org.joinmastodon.android.events; package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ScheduledStatus;
public class ScheduledStatusDeletedEvent{ public class ScheduledStatusDeletedEvent{
public final String id; public final String id;
public final String accountID; public final String accountID;

View File

@@ -9,5 +9,6 @@ public class StatusCreatedEvent{
public StatusCreatedEvent(Status status, String accountID){ public StatusCreatedEvent(Status status, String accountID){
this.status=status; this.status=status;
this.accountID=accountID; this.accountID=accountID;
status.fromStatusCreated=true;
} }
} }

View File

@@ -9,19 +9,16 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent; import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; 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.Optional;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
@@ -55,15 +52,14 @@ public class AccountTimelineFragment extends StatusListFragment{
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter) currentRequest=new GetAccountStatuses(user.id, getMaxID(), null, count, filter)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return; if(getActivity()==null) return;
AccountSessionManager asm = AccountSessionManager.getInstance(); boolean more=applyMaxID(result);
boolean empty=result.isEmpty();
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user); AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
onDataLoaded(result, !empty); onDataLoaded(result, more);
} }
}) })
.exec(accountID); .exec(accountID);

View File

@@ -0,0 +1,114 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.widget.TextView;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountLists;
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.AccountAddedToListEvent;
import org.joinmastodon.android.events.AccountRemovedFromListEvent;
import org.joinmastodon.android.fragments.settings.BaseSettingsFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
public class AddAccountToListsFragment extends BaseSettingsFragment<FollowList>{
private Account account;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.add_user_to_list_title);
account=Parcels.unwrap(getArguments().getParcelable("targetAccount"));
loadData();
}
@Override
protected void doLoadData(int offset, int count){
AccountSessionManager.get(accountID).getCacheController().getLists(new SimpleCallback<>(this){
@Override
public void onSuccess(List<FollowList> allLists){
if(getActivity()==null)
return;
loadAccountLists(allLists);
}
});
}
private void loadAccountLists(final List<FollowList> allLists){
currentRequest=new GetAccountLists(account.id)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<FollowList> result){
Set<String> lists=result.stream().map(l->l.id).collect(Collectors.toSet());
onDataLoaded(allLists.stream()
.map(l->new CheckableListItem<>(l.title, null, CheckableListItem.Style.CHECKBOX, lists.contains(l.id),
R.drawable.ic_list_alt_24px, AddAccountToListsFragment.this::onItemClick, l))
.collect(Collectors.toList()), false);
}
})
.exec(accountID);
}
@Override
protected int indexOfItemsAdapter(){
return 1;
}
@Override
protected RecyclerView.Adapter<?> getAdapter(){
TextView topText=new TextView(getActivity());
topText.setTextAppearance(R.style.m3_body_medium);
topText.setTextColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurface));
topText.setPadding(V.dp(16), V.dp(8), V.dp(16), V.dp(8));
topText.setText(getString(R.string.manage_user_lists, account.getDisplayUsername()));
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(topText));
mergeAdapter.addAdapter(super.getAdapter());
return mergeAdapter;
}
private void onItemClick(CheckableListItem<FollowList> item){
boolean add=!item.checked;
ResultlessMastodonAPIRequest req=add ? new AddAccountsToList(item.parentObject.id, Set.of(account.id)) : new RemoveAccountsFromList(item.parentObject.id, Set.of(account.id));
req.setCallback(new Callback<>(){
@Override
public void onSuccess(Void result){
item.checked=add;
rebindItem(item);
if(add){
E.post(new AccountAddedToListEvent(accountID, item.parentObject.id, account));
}else{
E.post(new AccountRemovedFromListEvent(accountID, item.parentObject.id, account.id));
}
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, false)
.exec(accountID);
}
}

View File

@@ -97,7 +97,7 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Announcement> result){ public void onSuccess(List<Announcement> result){
if (getActivity() == null) return; if(getActivity()==null) return;
// get unread items first // get unread items first
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList()); List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList());

View File

@@ -0,0 +1,176 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.DeleteList;
import org.joinmastodon.android.api.requests.lists.GetListAccounts;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.fragments.settings.BaseSettingsFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.viewmodel.AvatarPileListItem;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.List;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
public abstract class BaseEditListFragment extends BaseSettingsFragment<Void>{
protected FollowList followList;
protected AvatarPileListItem<Void> membersItem;
protected CheckableListItem<Void> exclusiveItem;
protected FloatingHintEditTextLayout titleEditLayout;
protected EditText titleEdit;
protected Spinner showRepliesSpinner;
private APIRequest<?> getMembersRequest;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
followList=Parcels.unwrap(getArguments().getParcelable("list"));
membersItem=new AvatarPileListItem<>(getString(R.string.list_members), null, List.of(), 0, i->onMembersClick(), null, false);
List<ListItem<Void>> items=new ArrayList<>();
if(followList!=null){
items.add(membersItem);
}
exclusiveItem=new CheckableListItem<>(R.string.list_exclusive, R.string.list_exclusive_subtitle, CheckableListItem.Style.SWITCH, followList!=null && followList.exclusive, this::toggleCheckableItem);
items.add(exclusiveItem);
onDataLoaded(items);
}
@Override
public void onDestroy(){
super.onDestroy();
if(getMembersRequest!=null)
getMembersRequest.cancel();
}
@Override
protected void doLoadData(int offset, int count){}
@Override
protected RecyclerView.Adapter<?> getAdapter(){
LinearLayout topView=new LinearLayout(getActivity());
topView.setOrientation(LinearLayout.VERTICAL);
titleEditLayout=(FloatingHintEditTextLayout) getActivity().getLayoutInflater().inflate(R.layout.floating_hint_edit_text, topView, false);
titleEdit=titleEditLayout.findViewById(R.id.edit);
titleEdit.setHint(R.string.list_name);
titleEditLayout.updateHint();
if(followList!=null)
titleEdit.setText(followList.title);
topView.addView(titleEditLayout);
FloatingHintEditTextLayout showRepliesLayout=(FloatingHintEditTextLayout) getActivity().getLayoutInflater().inflate(R.layout.floating_hint_spinner, topView, false);
showRepliesSpinner=showRepliesLayout.findViewById(R.id.spinner);
showRepliesLayout.setHint(R.string.list_show_replies_to);
topView.addView(showRepliesLayout);
ArrayAdapter<String> spinnerAdapter=new ArrayAdapter<>(getActivity(), R.layout.item_spinner, List.of(
getString(R.string.list_replies_no_one),
getString(R.string.list_replies_members),
getString(R.string.list_replies_anyone)
));
showRepliesSpinner.setAdapter(spinnerAdapter);
showRepliesSpinner.setSelection(switch(followList!=null ? followList.repliesPolicy : FollowList.RepliesPolicy.LIST){
case FOLLOWED -> 2;
case LIST -> 1;
case NONE -> 0;
});
ViewGroup.MarginLayoutParams llp=(ViewGroup.MarginLayoutParams)showRepliesLayout.getLabel().getLayoutParams();
llp.setMarginStart(llp.getMarginStart()+V.dp(16));
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
adapter.addAdapter(new SingleViewRecyclerAdapter(topView));
adapter.addAdapter(super.getAdapter());
return adapter;
}
@Override
protected int indexOfItemsAdapter(){
return 1;
}
protected void doDeleteList(){
new DeleteList(followList.id)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Void result){
AccountSessionManager.get(accountID).getCacheController().deleteList(followList.id);
E.post(new ListDeletedEvent(accountID, followList.id));
Nav.finish(BaseEditListFragment.this);
}
@Override
public void onError(ErrorResponse error){
Activity activity=getActivity();
if(activity==null)
return;
error.showToast(activity);
}
})
.wrapProgress(getActivity(), R.string.loading, true)
.exec(accountID);
}
private void onMembersClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("list", Parcels.wrap(followList));
Nav.go(getActivity(), ListMembersFragment.class, args);
}
protected void loadMembers(){
getMembersRequest=new GetListAccounts(followList.id, null, 3)
.setCallback(new Callback<>(){
@Override
public void onSuccess(HeaderPaginationList<Account> result){
getMembersRequest=null;
membersItem.avatars=new ArrayList<>();
for(int i=0;i<Math.min(3, result.size());i++){
Account acc=result.get(i);
membersItem.avatars.add(new UrlImageLoaderRequest(acc.avatarStatic, V.dp(32), V.dp(32)));
}
rebindItem(membersItem);
imgLoader.updateImages();
}
@Override
public void onError(ErrorResponse error){
getMembersRequest=null;
}
})
.exec(accountID);
}
protected FollowList.RepliesPolicy getSelectedRepliesPolicy(){
return switch(showRepliesSpinner.getSelectedItemPosition()){
case 0 -> FollowList.RepliesPolicy.NONE;
case 1 -> FollowList.RepliesPolicy.LIST;
case 2 -> FollowList.RepliesPolicy.FOLLOWED;
default -> throw new IllegalStateException("Unexpected value: "+showRepliesSpinner.getSelectedItemPosition());
};
}
}

View File

@@ -9,7 +9,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
@@ -24,18 +23,28 @@ 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.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
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.requests.statuses.AkkomaTranslateStatus;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.session.AccountSessionManager; 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.AkkomaTranslation;
import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.NonMutualPreReplySheet;
import org.joinmastodon.android.ui.OldPostPreReplySheet;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
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.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
@@ -51,18 +60,23 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer; 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.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController; import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController; import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController;
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.ProvidesAssistContent;
import org.joinmastodon.android.utils.TypedObjectPool; import org.joinmastodon.android.utils.TypedObjectPool;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -95,6 +109,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, PreviewlessMediaAttachmentViewController> previewlessAttachmentViewsPool=new TypedObjectPool<>(this::makeNewPreviewlessMediaAttachmentView); protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, PreviewlessMediaAttachmentViewController> previewlessAttachmentViewsPool=new TypedObjectPool<>(this::makeNewPreviewlessMediaAttachmentView);
protected boolean currentlyScrolling; protected boolean currentlyScrolling;
protected String maxID;
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
@@ -136,6 +151,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
for(T s:items){ for(T s:items){
displayItems.addAll(buildDisplayItems(s)); displayItems.addAll(buildDisplayItems(s));
} }
loadRelationships(items.stream().map(DisplayItemsParent::getAccountID).filter(Objects::nonNull).collect(Collectors.toSet()));
} }
@Override @Override
@@ -157,10 +173,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
if(notify) if(notify)
adapter.notifyItemRangeInserted(0, offset); adapter.notifyItemRangeInserted(0, offset);
loadRelationships(items.stream().map(DisplayItemsParent::getAccountID).filter(Objects::nonNull).collect(Collectors.toSet()));
return offset; return offset;
} }
protected String getMaxID(){ protected String getMaxID(){
if(refreshing) return null;
if(maxID!=null) return maxID;
if(!preloadedData.isEmpty()) if(!preloadedData.isEmpty())
return preloadedData.get(preloadedData.size()-1).getID(); return preloadedData.get(preloadedData.size()-1).getID();
else if(!data.isEmpty()) else if(!data.isEmpty())
@@ -169,6 +188,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null; return null;
} }
protected boolean applyMaxID(List<Status> result){
boolean empty=result.isEmpty();
if(!empty) maxID=result.get(result.size()-1).id;
return !empty;
}
protected abstract List<StatusDisplayItem> buildDisplayItems(T s); protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
protected abstract void addAccountToKnown(T s); protected abstract void addAccountToKnown(T s);
@@ -205,7 +230,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override @Override
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){ public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
final Status status=_status.getContentStatus(); final Status status=_status.getContentStatus();
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){ currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, status, accountID, new PhotoViewer.Listener(){
private MediaAttachmentViewController transitioningHolder; private MediaAttachmentViewController transitioningHolder;
@Override @Override
@@ -271,6 +296,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override @Override
public void photoViewerDismissed(){ public void photoViewerDismissed(){
currentPhotoViewer=null; currentPhotoViewer=null;
gridHolder.itemView.setHasTransientState(false);
} }
@Override @Override
@@ -282,12 +308,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return gridHolder.getViewController(index); return gridHolder.getViewController(index);
} }
}); });
gridHolder.itemView.setHasTransientState(true);
} }
public void openPreviewlessMediaPhotoViewer(String parentID, Status _status, int attachmentIndex, PreviewlessMediaGridStatusDisplayItem.Holder gridHolder){ public void openPreviewlessMediaPhotoViewer(String parentID, Status _status, int attachmentIndex, PreviewlessMediaGridStatusDisplayItem.Holder gridHolder){
final Status status=_status.getContentStatus(); final Status status=_status.getContentStatus();
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){ currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, status, accountID, new PhotoViewer.Listener(){
private PreviewlessMediaAttachmentViewController transitioningHolder; private PreviewlessMediaAttachmentViewController transitioningHolder;
@Override @Override
@@ -429,12 +456,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
}); });
list.addItemDecoration(new StatusListItemDecoration()); list.addItemDecoration(new StatusListItemDecoration());
list.addItemDecoration(new InsetStatusItemDecoration(this));
((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; if(list!=view.getParent()) return;
int lastIndex = -1, firstIndex = -1; boolean hasDescendant=false, hasAncestor=false, isWarning=false;
int lastIndex=-1, firstIndex=-1;
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){ if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
list.getDecoratedBoundsWithMargins(view, outRect); list.getDecoratedBoundsWithMargins(view, outRect);
}else{ }else{
@@ -539,10 +568,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected void updatePoll(String itemID, Status status, Poll poll){ protected void updatePoll(String itemID, Status status, Poll poll){
status.poll=poll; status.poll=poll;
int firstOptionIndex=-1, footerIndex=-1; int firstOptionIndex=-1, footerIndex=-1;
int spoilerFirstOptionIndex=-1, spoilerFooterIndex=-1;
SpoilerStatusDisplayItem spoilerItem=null;
int i=0; int i=0;
for(StatusDisplayItem item:displayItems){ for(StatusDisplayItem item:displayItems){
if(item.parentID.equals(itemID)){ if(item.parentID.equals(itemID)){
if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){ if(item instanceof SpoilerStatusDisplayItem){
spoilerItem=(SpoilerStatusDisplayItem) item;
}else if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
firstOptionIndex=i; firstOptionIndex=i;
}else if(item instanceof PollFooterStatusDisplayItem){ }else if(item instanceof PollFooterStatusDisplayItem){
footerIndex=i; footerIndex=i;
@@ -551,12 +584,39 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
i++; i++;
} }
// This is a temporary measure to deal with the app crashing when the poll isn't updated.
// This is needed because of a possible id mismatch that screws with things
if(firstOptionIndex==-1 || footerIndex==-1){
for(StatusDisplayItem item:displayItems){
if(status.id.equals(itemID)){
if(item instanceof SpoilerStatusDisplayItem){
spoilerItem=(SpoilerStatusDisplayItem) item;
}else if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
firstOptionIndex=i;
}else if(item instanceof PollFooterStatusDisplayItem){
footerIndex=i;
break;
}
}
i++;
}
}
if(firstOptionIndex==-1 || footerIndex==-1) if(firstOptionIndex==-1 || footerIndex==-1)
throw new IllegalStateException("Can't find all poll items in displayItems"); throw new IllegalStateException("Can't find all poll items in displayItems");
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1); List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
int prevSize=pollItems.size(); int prevSize=pollItems.size();
if(spoilerItem!=null){
spoilerFirstOptionIndex=spoilerItem.contentItems.indexOf(pollItems.get(0));
spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1));
}
pollItems.clear(); pollItems.clear();
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems, status); StatusDisplayItem.buildPollItems(itemID, this, poll, status, pollItems);
if(spoilerItem!=null){
spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear();
spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems);
}
if(prevSize!=pollItems.size()){ if(prevSize!=pollItems.size()){
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize); adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size()); adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
@@ -568,7 +628,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onPollOptionClick(PollOptionStatusDisplayItem.Holder holder){ public void onPollOptionClick(PollOptionStatusDisplayItem.Holder holder){
Poll poll=holder.getItem().poll; Poll poll=holder.getItem().poll;
Poll.Option option=holder.getItem().option; Poll.Option option=holder.getItem().option;
if(poll.multiple || GlobalUserPreferences.voteButtonForSingleChoice){ // MEGALODON: always show vote button
// if(poll.multiple){
if(poll.selectedOptions==null) if(poll.selectedOptions==null)
poll.selectedOptions=new ArrayList<>(); poll.selectedOptions=new ArrayList<>();
boolean optionContained=poll.selectedOptions.contains(option); boolean optionContained=poll.selectedOptions.contains(option);
@@ -583,7 +644,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
for(int i=0;i<list.getChildCount();i++){ for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder vh=list.getChildViewHolder(list.getChildAt(i)); RecyclerView.ViewHolder vh=list.getChildViewHolder(list.getChildAt(i));
if(!poll.multiple && vh instanceof PollOptionStatusDisplayItem.Holder item){ if(!poll.multiple && vh instanceof PollOptionStatusDisplayItem.Holder item){
if (item != holder) item.itemView.setSelected(false); if(item!=holder) item.itemView.setSelected(false);
} }
if(vh instanceof PollFooterStatusDisplayItem.Holder footer){ if(vh instanceof PollFooterStatusDisplayItem.Holder footer){
if(footer.getItemID().equals(holder.getItemID())){ if(footer.getItemID().equals(holder.getItemID())){
@@ -592,9 +653,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
} }
} }
}else{ // }else{
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){
@@ -602,6 +663,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
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()));
} }
public void onPollViewResultsButtonClick(PollFooterStatusDisplayItem.Holder holder, boolean shown){
for(int i=0;i<list.getChildCount();i++){
if(list.getChildViewHolder(list.getChildAt(i)) instanceof PollOptionStatusDisplayItem.Holder item && item.getItemID().equals(holder.getItemID())){
item.showResults(shown);
}
}
}
protected void submitPollVote(String parentID, String pollID, List<Integer> choices){ protected void submitPollVote(String parentID, String pollID, List<Integer> choices){
if(refreshing) if(refreshing)
return; return;
@@ -623,37 +692,39 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){ public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
Status status=holder.getItem().status; Status status=holder.getItem().status;
toggleSpoiler(status, holder.getItemID()); boolean isForQuote=holder.getItem().isForQuote;
toggleSpoiler(status, isForQuote, holder.getItemID());
} }
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) { public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
Status status = holder.getItem().status; Status status = holder.getItem().status;
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class); if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false);
if (mediaGrid != null) { MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
if (!status.sensitiveRevealed) mediaGrid.revealSensitive(); if(mediaGrid!=null){
if(!status.sensitiveRevealed) mediaGrid.revealSensitive();
else mediaGrid.hideSensitive(); else mediaGrid.hideSensitive();
} else { }else{
// media grid's methods normally change the status' state - we still want to be able status.sensitiveRevealed=false;
// to do this if the media grid is not bound, tho - so, doing it ourselves here notifyItemChangedAfter(holder.getItem(), MediaGridStatusDisplayItem.class);
status.sensitiveRevealed = !status.sensitiveRevealed;
} }
holder.rebind();
} }
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) { public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header != null) header.rebind(); if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true);
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
} }
protected void toggleSpoiler(Status status, String itemID){ protected void toggleSpoiler(Status status, boolean isForQuote, String itemID){
status.spoilerRevealed=!status.spoilerRevealed; status.spoilerRevealed=!status.spoilerRevealed;
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
status.sensitiveRevealed = false; status.sensitiveRevealed = false;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class); List<SpoilerStatusDisplayItem.Holder> spoilers=findAllHoldersOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null) SpoilerStatusDisplayItem.Holder spoiler=spoilers.size() > 1 && isForQuote ? spoilers.get(1) : spoilers.get(0);
spoiler.rebind(); if(spoiler!=null) spoiler.rebind();
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class)); else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(spoiler.getItem());
int index=displayItems.indexOf(spoilerItem); int index=displayItems.indexOf(spoilerItem);
if(status.spoilerRevealed){ if(status.spoilerRevealed){
@@ -664,50 +735,42 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size()); adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
} }
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); notifyItemChanged(itemID, TextStatusDisplayItem.class);
if(text!=null)
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) if(header!=null) header.rebind();
header.rebind(); else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
list.invalidateItemDecorations(); list.invalidateItemDecorations();
} }
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) { public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
if (holder.getItem().status.textExpandable != expandable && list != null) { Status s=holder.getItem().status;
holder.getItem().status.textExpandable = expandable; if(s.textExpandable!=expandable && list!=null) {
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); s.textExpandable=expandable;
if (header != null) header.rebind(); HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.bindCollapseButton();
} }
} }
public void onToggleExpanded(Status status, String itemID) { public void onToggleExpanded(Status status, String itemID) {
status.textExpanded = !status.textExpanded; status.textExpanded = !status.textExpanded;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); notifyItemChanged(itemID, TextStatusDisplayItem.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if (text != null) text.rebind(); if(header!=null) header.animateExpandToggle();
if (header != null) header.rebind(); else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
}
public void updateEmojiReactions(Status status, String itemID){
EmojiReactionsStatusDisplayItem.Holder reactions=findHolderOfType(itemID, EmojiReactionsStatusDisplayItem.Holder.class);
if(reactions != null){
reactions.getItem().status.reactions.clear();
reactions.getItem().status.reactions.addAll(status.reactions);
reactions.rebind();
}
} }
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){} public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){ public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
int startPos = warning.getAbsoluteAdapterPosition(); WarningFilteredStatusDisplayItem filterItem=findItemOfType(warning.getItemID(), WarningFilteredStatusDisplayItem.class);
int startPos=displayItems.indexOf(filterItem);
displayItems.remove(startPos); displayItems.remove(startPos);
displayItems.addAll(startPos, warning.filteredItems); displayItems.addAll(startPos, warning.filteredItems);
adapter.notifyItemRangeInserted(startPos, warning.filteredItems.size() - 1); adapter.notifyItemRangeInserted(startPos, warning.filteredItems.size() - 1);
if (startPos == 0) scrollToTop(); if (startPos == 0) scrollToTop();
warning.getItem().status.filterRevealed = true; warning.getItem().status.filterRevealed = true;
list.invalidateItemDecorations();
} }
@Override @Override
@@ -724,6 +787,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
protected void loadRelationships(Set<String> ids){ protected void loadRelationships(Set<String> ids){
if(ids.isEmpty())
return;
ids=ids.stream().filter(id->!relationships.containsKey(id)).collect(Collectors.toSet());
if(ids.isEmpty()) if(ids.isEmpty())
return; return;
// TODO somehow manage these and cancel outstanding requests on refresh // TODO somehow manage these and cancel outstanding requests on refresh
@@ -755,9 +821,61 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null; return null;
} }
/**
* Use this as a fallback if findHolderOfType fails to find the ViewHolder.
* It might still be bound but off-screen and therefore not a child of the RecyclerView -
* resulting in the ViewHolder displaying an outdated state once scrolled back into view.
*/
protected <I extends StatusDisplayItem> int notifyItemChanged(String id, Class<I> type){
boolean encounteredParent=false;
for(int i=0; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
boolean idEquals=id.equals(item.parentID);
if(!encounteredParent && idEquals) encounteredParent=true; // reached top of the parent
else if(encounteredParent && !idEquals) break; // passed by bottom of the parent. man muss ja wissen wann schluss is
if(idEquals && type.isInstance(item)){
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedAfter(StatusDisplayItem afterThis, Class<I> type){
int startIndex=displayItems.indexOf(afterThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem");
String parentID=afterThis.parentID;
for(int i=startIndex; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class<I> type){
int startIndex=displayItems.indexOf(beforeThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem");
String parentID=beforeThis.parentID;
for(int i=startIndex; i>=0; i--){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
@Nullable @Nullable
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){ protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
for(int i=0;i<list.getChildCount();i++){ for(int i=0; i<list.getChildCount(); i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i)); RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder)) if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
return type.cast(holder); return type.cast(holder);
@@ -862,6 +980,99 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon())); assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
} }
public void togglePostTranslation(Status status, String itemID){
switch(status.translationState){
case LOADING -> {
return;
}
case SHOWN -> {
status.translationState=Status.TranslationState.HIDDEN;
}
case HIDDEN -> {
if(status.translation!=null){
status.translationState=Status.TranslationState.SHOWN;
}else{
status.translationState=Status.TranslationState.LOADING;
Consumer<Translation> successCallback=(result)->{
status.translation=result;
status.translationState=Status.TranslationState.SHOWN;
updateTranslation(itemID);
};
MastodonAPIRequest<?> req=isInstanceAkkoma()
? new AkkomaTranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
@Override
public void onSuccess(AkkomaTranslation result){
if(getActivity()!=null) successCallback.accept(result.toTranslation());
}
@Override
public void onError(ErrorResponse error){
if(getActivity()!=null) translationCallbackError(status, itemID);
}
})
: new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()!=null) successCallback.accept(result);
}
@Override
public void onError(ErrorResponse error){
if(getActivity()!=null) translationCallbackError(status, itemID);
}
});
// 1 minute
req.setTimeout(60000).exec(accountID);
}
}
}
updateTranslation(itemID);
}
private void translationCallbackError(Status status, String itemID) {
status.translationState=Status.TranslationState.HIDDEN;
updateTranslation(itemID);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.translation_failed)
.setPositiveButton(R.string.ok, null)
.show();
}
private void updateTranslation(String itemID) {
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
if(isInstanceAkkoma())
return;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null){
spoiler.rebind();
}
MediaGridStatusDisplayItem.Holder media=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
if (media!=null) {
media.rebind();
}
PreviewlessMediaGridStatusDisplayItem.Holder previewLessMedia=findHolderOfType(itemID, PreviewlessMediaGridStatusDisplayItem.Holder.class);
if (previewLessMedia!=null) {
previewLessMedia.rebind();
}
for(int i=0;i<list.getChildCount();i++){
if(list.getChildViewHolder(list.getChildAt(i)) instanceof PollOptionStatusDisplayItem.Holder item){
item.rebind();
}
}
}
public void rebuildAllDisplayItems(){ public void rebuildAllDisplayItems(){
displayItems.clear(); displayItems.clear();
for(T item:data){ for(T item:data){
@@ -870,6 +1081,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }
public void maybeShowPreReplySheet(Status status, Runnable proceed){
Relationship rel=getRelationship(status.account.id);
if(!GlobalUserPreferences.isOptedOutOfPreReplySheet(GlobalUserPreferences.PreReplySheetType.NON_MUTUAL, status.account, accountID) &&
!status.account.id.equals(AccountSessionManager.get(accountID).self.id) && rel!=null && !rel.followedBy && status.account.followingCount>=1){
new NonMutualPreReplySheet(getActivity(), notAgain->{
GlobalUserPreferences.optOutOfPreReplySheet(GlobalUserPreferences.PreReplySheetType.NON_MUTUAL, notAgain ? null : status.account, accountID);
proceed.run();
}, status.account, accountID).show();
}else if(!GlobalUserPreferences.isOptedOutOfPreReplySheet(GlobalUserPreferences.PreReplySheetType.OLD_POST, null, null) &&
status.createdAt.isBefore(Instant.now().minus(90, ChronoUnit.DAYS))){
new OldPostPreReplySheet(getActivity(), notAgain->{
if(notAgain)
GlobalUserPreferences.optOutOfPreReplySheet(GlobalUserPreferences.PreReplySheetType.OLD_POST, null, null);
proceed.run();
}, status).show();
}else{
proceed.run();
}
}
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){} protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){}
@Override @Override
@@ -877,7 +1108,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(getContext()==null) return; if(getContext()==null) return;
super.onDataLoaded(d, more); super.onDataLoaded(d, more);
// more available, but the page isn't even full yet? seems wrong, let's load some more // more available, but the page isn't even full yet? seems wrong, let's load some more
if(more && d.size() < itemsPerPage){ if(more && data.size() < itemsPerPage){
preloader.onScrolledToLastItem(); preloader.onScrolledToLastItem();
} }
} }

View File

@@ -28,7 +28,7 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Status> result){ public void onSuccess(HeaderPaginationList<Status> result){
if (getActivity() == null) return; if(getActivity()==null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else

View File

@@ -7,6 +7,9 @@ import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDra
import android.Manifest; import android.Manifest;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.DatePickerDialog; import android.app.DatePickerDialog;
@@ -75,7 +78,7 @@ import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent; import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.fragments.account_list.ComposeAccountSearchFragment; import org.joinmastodon.android.fragments.account_list.AccountSearchFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
@@ -123,11 +126,11 @@ import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle; import java.time.format.FormatStyle;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -135,12 +138,14 @@ import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.CustomTransitionsFragment;
import me.grishka.appkit.fragments.OnBackPressedListener; import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener, HasAccountID { public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener, HasAccountID, CustomTransitionsFragment {
private static final int MEDIA_RESULT=717; private static final int MEDIA_RESULT=717;
public static final int IMAGE_DESCRIPTION_RESULT=363; public static final int IMAGE_DESCRIPTION_RESULT=363;
@@ -165,7 +170,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public LinearLayout mainLayout; public LinearLayout mainLayout;
private SizeListenerLinearLayout contentView; private SizeListenerLinearLayout contentView;
private TextView selfName, selfUsername, selfExtraText, extraText, pronouns; private TextView selfName, selfUsername, selfExtraText, extraText;
private ImageView selfAvatar; private ImageView selfAvatar;
private Account self; private Account self;
private String instanceDomain; private String instanceDomain;
@@ -176,7 +181,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private int charCount, charLimit, trimmedCharCount; private int charCount, charLimit, trimmedCharCount;
private Button publishButton, languageButton, scheduleTimeBtn; private Button publishButton, languageButton, scheduleTimeBtn;
private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup; private PopupMenu contentTypePopup, visibilityPopup, draftOptionsPopup;
private ImageButton publishButtonRelocated, mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn; private ImageButton publishButtonRelocated, mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn;
private View sensitiveBtn; private View sensitiveBtn;
private TextView replyText; private TextView replyText;
@@ -215,7 +220,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public Instance instance; public Instance instance;
public Status editingStatus; public Status editingStatus;
private ScheduledStatus scheduledStatus; public ScheduledStatus scheduledStatus;
private boolean redraftStatus; private boolean redraftStatus;
private Uri photoUri; private Uri photoUri;
@@ -312,7 +317,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override @Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
creatingView=true; creatingView=true;
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), customEmojis, instanceDomain, getAccountID()); emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), accountID, customEmojis, instanceDomain);
emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){ emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){
@Override @Override
public void onEmojiSelected(Emoji emoji){ public void onEmojiSelected(Emoji emoji){
@@ -364,7 +369,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
selfUsername=view.findViewById(R.id.self_username); selfUsername=view.findViewById(R.id.self_username);
selfAvatar=view.findViewById(R.id.self_avatar); selfAvatar=view.findViewById(R.id.self_avatar);
selfExtraText=view.findViewById(R.id.self_extra_text); selfExtraText=view.findViewById(R.id.self_extra_text);
HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis); HtmlParser.setTextWithCustomEmoji(selfName, self.getDisplayName(), self.emojis);
selfUsername.setText('@'+self.username+'@'+instanceDomain); selfUsername.setText('@'+self.username+'@'+instanceDomain);
if(self.avatar!=null) if(self.avatar!=null)
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar)); ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
@@ -527,7 +532,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void onLaunchAccountSearch(){ public void onLaunchAccountSearch(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
Nav.goForResult(getActivity(), ComposeAccountSearchFragment.class, args, AUTOCOMPLETE_ACCOUNT_RESULT, ComposeFragment.this); Nav.goForResult(getActivity(), AccountSearchFragment.class, args, AUTOCOMPLETE_ACCOUNT_RESULT, ComposeFragment.this);
} }
}); });
View autocompleteView=autocompleteViewController.getView(); View autocompleteView=autocompleteViewController.getView();
@@ -685,7 +690,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}); });
View originalPost=view.findViewById(R.id.original_post); View originalPost=view.findViewById(R.id.original_post);
extraText=view.findViewById(R.id.extra_text); extraText=view.findViewById(R.id.extra_text);
pronouns=view.findViewById(R.id.pronouns);
originalPost.setVisibility(View.VISIBLE); originalPost.setVisibility(View.VISIBLE);
originalPost.setOnClickListener(v->{ originalPost.setOnClickListener(v->{
Bundle args=new Bundle(); Bundle args=new Bundle();
@@ -725,7 +729,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
moreBtn.setBackground(null); moreBtn.setBackground(null);
TextView name = view.findViewById(R.id.name); TextView name = view.findViewById(R.id.name);
name.setText(HtmlParser.parseCustomEmoji(status.account.displayName, status.account.emojis)); name.setText(HtmlParser.parseCustomEmoji(status.account.getDisplayName(), status.account.emojis));
UiUtils.loadCustomEmojiInTextView(name); UiUtils.loadCustomEmojiInTextView(name);
String time = status==null || status.editedAt==null String time = status==null || status.editedAt==null
@@ -759,7 +763,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16))); .setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
} }
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.displayName)); replyText.setText(HtmlParser.parseCustomEmoji(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.getDisplayName()), status.account.emojis));
UiUtils.loadCustomEmojiInTextView(replyText);
int visibilityNameRes = switch (status.visibility) { int visibilityNameRes = switch (status.visibility) {
case PUBLIC -> R.string.visibility_public; case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted; case UNLISTED -> R.string.sk_visibility_unlisted;
@@ -767,7 +772,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
case DIRECT -> R.string.visibility_private; case DIRECT -> R.string.visibility_private;
case LOCAL -> R.string.sk_local_only; case LOCAL -> R.string.sk_local_only;
}; };
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.displayName) + ", " + getString(visibilityNameRes)); replyText.setContentDescription(getString(R.string.in_reply_to, status.account.getDisplayName()) + ", " + getString(visibilityNameRes));
replyText.setOnClickListener(v->{ replyText.setOnClickListener(v->{
scrollView.smoothScrollTo(0, 0); scrollView.smoothScrollTo(0, 0);
}); });
@@ -801,7 +806,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id))) || (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
&& !status.spoilerText.startsWith("re: ") ? "re: " : ""; && !status.spoilerText.startsWith("re: ") ? "re: " : "";
spoilerEdit.setText(prefix + replyTo.spoilerText); spoilerEdit.setText(prefix + status.spoilerText);
spoilerBtn.setSelected(true); spoilerBtn.setSelected(true);
} }
if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language); if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language);
@@ -862,6 +867,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
} }
@SuppressLint("ClickableViewAccessibility")
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu); inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
@@ -889,19 +895,22 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
charCounter.setText(String.valueOf(charLimit)); charCounter.setText(String.valueOf(charLimit));
} }
// draftsBtn = wrap.findViewById(R.id.drafts_btn); // draftsBtn=wrap.findViewById(R.id.drafts_btn);
draftOptionsPopup = new PopupMenu(getContext(), draftsBtn); draftOptionsPopup=new PopupMenu(getContext(), draftsBtn);
draftOptionsPopup.inflate(R.menu.compose_more); draftOptionsPopup.inflate(R.menu.compose_more);
draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft); Menu draftOptionsMenu=draftOptionsPopup.getMenu();
undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft); draftMenuItem=draftOptionsMenu.findItem(R.id.draft);
scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule); undraftMenuItem=draftOptionsMenu.findItem(R.id.undraft);
unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule); scheduleMenuItem=draftOptionsMenu.findItem(R.id.schedule);
unscheduleMenuItem=draftOptionsMenu.findItem(R.id.unschedule);
draftOptionsMenu.findItem(R.id.preview).setVisible(isInstanceAkkoma());
draftOptionsPopup.setOnMenuItemClickListener(i->{ draftOptionsPopup.setOnMenuItemClickListener(i->{
int id = i.getItemId(); int id=i.getItemId();
if (id == R.id.draft) updateScheduledAt(getDraftInstant()); if(id==R.id.draft) updateScheduledAt(getDraftInstant());
else if (id == R.id.schedule) pickScheduledDateTime(); else if(id==R.id.schedule) pickScheduledDateTime();
else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null); else if(id==R.id.unschedule || id==R.id.undraft) updateScheduledAt(null);
else navigateToUnsentPosts(); else if(id==R.id.drafts) navigateToUnsentPosts();
else if(id==R.id.preview) publish(true);
return true; return true;
}); });
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup); UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
@@ -909,23 +918,37 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
languageButton = wrap.findViewById(R.id.language_btn); languageButton = wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert()); languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
if(!getLocalPrefs().bottomEncoding){
getLocalPrefs().bottomEncoding=true;
getLocalPrefs().save();
}
return false;
});
if (!GlobalUserPreferences.relocatePublishButton)
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
if(GlobalUserPreferences.relocatePublishButton){ (GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setOnClickListener(v->{
publishButtonRelocated.setOnClickListener(v -> { Consumer<Boolean> draftCheckComplete=(isDraft)->{
if(GlobalUserPreferences.altTextReminders && editingStatus==null) if(GlobalUserPreferences.altTextReminders && !isDraft) checkAltTextsAndPublish();
checkAltTextsAndPublish(); else publish();
else };
publish();
});
} else {
publishButton.setOnClickListener(v -> {
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
checkAltTextsAndPublish();
else
publish();
});
}
boolean isAlreadyDraft=scheduledAt!=null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
if(editingStatus!=null && scheduledAt!=null && isAlreadyDraft) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w)->draftCheckComplete.accept(isAlreadyDraft))
.setNegativeButton(R.string.publish, (d, w)->{
updateScheduledAt(null);
draftCheckComplete.accept(false);
})
.show();
}else{
draftCheckComplete.accept(isAlreadyDraft);
}
});
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show()); draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener()); draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null); updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
@@ -936,8 +959,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
? languageResolver.fromOrFallback(prefs.postingDefaultLanguage) ? languageResolver.fromOrFallback(prefs.postingDefaultLanguage)
: languageResolver.getDefault()); : languageResolver.getDefault());
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE); if(isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) { if(isInstancePixelfed() || (editingStatus!=null && !redraftStatus)) {
// editing an already published post // editing an already published post
draftsBtn.setVisibility(View.GONE); draftsBtn.setVisibility(View.GONE);
} }
@@ -1068,7 +1091,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override @Override
protected int getNavigationIconDrawableResource(){ protected int getNavigationIconDrawableResource(){
return R.drawable.ic_baseline_close_24; return R.drawable.ic_fluent_dismiss_24_regular;
} }
@Override @Override
@@ -1127,6 +1150,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void publish(){ private void publish(){
publish(false);
}
private void publish(boolean preview){
sendingOverlay=new View(getActivity()); sendingOverlay=new View(getActivity());
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams(); WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@@ -1137,31 +1164,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
overlayParams.token=mainEditText.getWindowToken(); overlayParams.token=mainEditText.getWindowToken();
wm.addView(sendingOverlay, overlayParams); wm.addView(sendingOverlay, overlayParams);
if(GlobalUserPreferences.relocatePublishButton){ (GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(false);
publishButtonRelocated.setEnabled(false);
} else {
publishButton.setEnabled(false);
}
V.setVisibilityAnimated(sendProgress, View.VISIBLE); V.setVisibilityAnimated(sendProgress, View.VISIBLE);
mediaViewController.saveAltTextsBeforePublishing(this::actuallyPublish, this::handlePublishError); mediaViewController.saveAltTextsBeforePublishing(
()->actuallyPublish(preview),
this::handlePublishError);
} }
private void actuallyPublish(){ private void actuallyPublish(boolean preview){
actuallyPublish(false);
}
private void actuallyPublish(boolean force){
String text=mainEditText.getText().toString(); String text=mainEditText.getText().toString();
CreateStatus.Request req=new CreateStatus.Request(); CreateStatus.Request req=new CreateStatus.Request();
if ("bottom".equals(postLang.encoding)) { if("bottom".equals(postLang.encoding)){
text = new StatusTextEncoder(Bottom::encode).encode(text); text=new StatusTextEncoder(Bottom::encode).encode(text);
req.spoilerText = "bottom-encoded emoji spam"; req.spoilerText="bottom-encoded emoji spam";
} }
if (localOnly && if(localOnly &&
AccountSessionManager.get(accountID).getLocalPreferences().glitchInstance && AccountSessionManager.get(accountID).getLocalPreferences().glitchInstance &&
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()) { !GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()){
text += " " + GLITCH_LOCAL_ONLY_SUFFIX; text+=" "+GLITCH_LOCAL_ONLY_SUFFIX;
} }
req.status=text; req.status=text;
req.localOnly=localOnly; req.localOnly=localOnly;
@@ -1169,25 +1190,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.sensitive=sensitive; req.sensitive=sensitive;
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType; req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
req.scheduledAt=scheduledAt; req.scheduledAt=scheduledAt;
req.preview=preview;
if(!mediaViewController.isEmpty()){ if(!mediaViewController.isEmpty()){
req.mediaIds=mediaViewController.getAttachmentIDs(); req.mediaIds=mediaViewController.getAttachmentIDs();
if(editingStatus != null){ if(editingStatus != null){
req.mediaAttributes=mediaViewController.getAttachmentAttributes(); req.mediaAttributes=mediaViewController.getAttachmentAttributes();
} }
} }
// ask whether to publish now when editing an existing draft
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w) -> actuallyPublish(true))
.setNegativeButton(R.string.publish, (d, w) -> {
updateScheduledAt(null);
actuallyPublish();
})
.show();
return;
}
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){ if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id; req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
} }
@@ -1209,7 +1218,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Callback<Status> resCallback=new Callback<>(){ Callback<Status> resCallback=new Callback<>(){
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
maybeDeleteScheduledPost(() -> { if(preview){
openPreview(result);
return;
}
maybeDeleteScheduledPost(()->{
wm.removeView(sendingOverlay); wm.removeView(sendingOverlay);
sendingOverlay=null; sendingOverlay=null;
if(editingStatus==null || redraftStatus){ if(editingStatus==null || redraftStatus){
@@ -1231,10 +1245,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
E.post(new StatusUpdatedEvent(editedStatus)); 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);
} }
if (getArguments().getBoolean("navigateToStatus", false)) { if(getArguments().getBoolean("navigateToStatus", false)){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result)); args.putParcelable("status", Parcels.wrap(result));
@@ -1250,11 +1264,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
}; };
if(editingStatus!=null && !redraftStatus){ if(editingStatus!=null && !redraftStatus && !preview){
new EditStatus(req, editingStatus.id) new EditStatus(req, editingStatus.id)
.setCallback(resCallback) .setCallback(resCallback)
.exec(accountID); .exec(accountID);
}else if(req.scheduledAt == null){ }else if(req.scheduledAt == null || preview){
new CreateStatus(req, uuid) new CreateStatus(req, uuid)
.setCallback(resCallback) .setCallback(resCallback)
.exec(accountID); .exec(accountID);
@@ -1284,7 +1298,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
.setPositiveButton(R.string.ok, (a, b)->{}) .setPositiveButton(R.string.ok, (a, b)->{})
.show(); .show();
handlePublishError(null); handlePublishError(null);
publishButton.setEnabled(false); (GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(false);
} }
if (replyTo == null) updateRecentLanguages(); if (replyTo == null) updateRecentLanguages();
@@ -1294,13 +1308,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
wm.removeView(sendingOverlay); wm.removeView(sendingOverlay);
sendingOverlay=null; sendingOverlay=null;
V.setVisibilityAnimated(sendProgress, View.GONE); V.setVisibilityAnimated(sendProgress, View.GONE);
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(true);
if(GlobalUserPreferences.relocatePublishButton) {
publishButtonRelocated.setEnabled(true);
} else {
publishButton.setEnabled(true);
}
if(error instanceof MastodonErrorResponse me){ if(error instanceof MastodonErrorResponse me){
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.post_failed) .setTitle(R.string.post_failed)
@@ -1313,6 +1321,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
} }
private void openPreview(Status result){
result.preview=true;
wm.removeView(sendingOverlay);
sendingOverlay=null;
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(true);
V.setVisibilityAnimated(sendProgress, View.GONE);
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(contentView.getWindowToken(), 0);
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
if(replyTo!=null){
args.putParcelable("inReplyTo", Parcels.wrap(replyTo));
args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo.account));
}
Nav.go(getActivity(), ThreadFragment.class, args);
}
private void updateRecentLanguages() { private void updateRecentLanguages() {
if (postLang == null || postLang.language == null) return; if (postLang == null || postLang.language == null) return;
String language = postLang.language.getLanguage(); String language = postLang.language.getLanguage();
@@ -1388,20 +1415,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void confirmDiscardDraftAndFinish(){ private void confirmDiscardDraftAndFinish(){
boolean attachmentsPending = mediaViewController.areAnyAttachmentsNotDone(); boolean attachmentsPending=mediaViewController.areAnyAttachmentsNotDone();
if (attachmentsPending) new M3AlertDialogBuilder(getActivity()) if(attachmentsPending) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_unfinished_attachments) .setTitle(R.string.sk_unfinished_attachments)
.setMessage(R.string.sk_unfinished_attachments_message) .setMessage(R.string.sk_unfinished_attachments_message)
.setPositiveButton(R.string.edit, (d, w) -> {}) .setPositiveButton(R.string.ok, (d, w)->{})
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this)) .setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
.show(); .show();
else new M3AlertDialogBuilder(getActivity()) else new M3AlertDialogBuilder(getActivity())
.setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft) .setTitle(editingStatus!=null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
.setPositiveButton(R.string.save, (d, w) -> { .setPositiveButton(R.string.save, (d, w)->{
updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt); updateScheduledAt(scheduledAt==null ? getDraftInstant() : scheduledAt);
publish(); publish();
}) })
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this)) .setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
.show(); .show();
} }
@@ -1528,7 +1555,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void updateSensitive() { public void updateSensitive() {
sensitiveBtn.setVisibility(View.GONE); sensitiveBtn.setVisibility(View.GONE);
if (!mediaViewController.isEmpty() && !hasSpoiler) sensitiveBtn.setVisibility(View.VISIBLE); if (!mediaViewController.isEmpty()) sensitiveBtn.setVisibility(View.VISIBLE);
if (mediaViewController.isEmpty()) sensitive = false; if (mediaViewController.isEmpty()) sensitive = false;
} }
@@ -1539,8 +1566,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
.withMinute(0); .withMinute(0);
new DatePickerDialog(getActivity(), (datePicker, year, arrayMonth, dayOfMonth) -> { new DatePickerDialog(getActivity(), (datePicker, year, arrayMonth, dayOfMonth) -> {
new TimePickerDialog(getActivity(), (timePicker, hour, minute) -> { new TimePickerDialog(getActivity(), (timePicker, hour, minute) -> {
updateScheduledAt(LocalDateTime.of(year, arrayMonth + 1, dayOfMonth, hour, minute) LocalDateTime at=LocalDateTime.of(year, arrayMonth + 1, dayOfMonth, hour, minute);
.toInstant(OffsetDateTime.now().getOffset())); updateScheduledAt(at.toInstant(ZoneId.systemDefault().getRules().getOffset(at)));
}, soon.getHour(), soon.getMinute(), DateFormat.is24HourFormat(getActivity())).show(); }, soon.getHour(), soon.getMinute(), DateFormat.is24HourFormat(getActivity())).show();
}, soon.getYear(), soon.getMonthValue() - 1, soon.getDayOfMonth()).show(); }, soon.getYear(), soon.getMonthValue() - 1, soon.getDayOfMonth()).show();
} }
@@ -1606,8 +1633,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void updateHeaders() { private void updateHeaders() {
UiUtils.setExtraTextInfo(getContext(), selfExtraText, null, false, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null); UiUtils.setExtraTextInfo(getContext(), selfExtraText, false, false, localOnly, null);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, pronouns, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account); if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
} }
private void buildVisibilityPopup(View v){ private void buildVisibilityPopup(View v){
@@ -1635,7 +1662,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
} }
UiUtils.enablePopupMenuIcons(getActivity(), visibilityPopup); UiUtils.enablePopupMenuIcons(getActivity(), visibilityPopup);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P) m.setGroupDividerEnabled(true); if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI()) m.setGroupDividerEnabled(true);
visibilityPopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ visibilityPopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
@Override @Override
public boolean onMenuItemClick(MenuItem item){ public boolean onMenuItemClick(MenuItem item){
@@ -1679,6 +1706,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
contentTypePopup.setOnMenuItemClickListener(i->{ contentTypePopup.setOnMenuItemClickListener(i->{
uuid=null;
int index=i.getItemId(); int index=i.getItemId();
contentType=ContentType.values()[index]; contentType=ContentType.values()[index];
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal()); btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());
@@ -1783,8 +1811,26 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
return new String[]{"image/jpeg", "image/gif", "image/png", "video/mp4"}; return new String[]{"image/jpeg", "image/gif", "image/png", "video/mp4"};
} }
private String sanitizeMediaDescription(String description){
if(description == null){
return null;
}
// The Gboard android keyboard attaches this text whenever the user
// pastes something from the keyboard's suggestion bar.
// Due to different end user locales, the exact text may vary, but at
// least in version 13.4.08, all of the translations contained the
// string "Gboard".
if (description.contains("Gboard")){
return null;
}
return description;
}
@Override @Override
public boolean onAddMediaAttachmentFromEditText(Uri uri, String description){ public boolean onAddMediaAttachmentFromEditText(Uri uri, String description){
description = sanitizeMediaDescription(description);
return mediaViewController.addMediaAttachment(uri, description); return mediaViewController.addMediaAttachment(uri, description);
} }
@@ -1827,6 +1873,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Editable e=mainEditText.getText(); Editable e=mainEditText.getText();
int start=e.getSpanStart(currentAutocompleteSpan); int start=e.getSpanStart(currentAutocompleteSpan);
int end=e.getSpanEnd(currentAutocompleteSpan); int end=e.getSpanEnd(currentAutocompleteSpan);
if(start==-1 || end==-1)
return;
e.replace(start, end, text+" "); e.replace(start, end, text+" ");
finishAutocomplete(); finishAutocomplete();
InputConnection conn=mainEditText.getCurrentInputConnection(); InputConnection conn=mainEditText.getCurrentInputConnection();
@@ -1895,4 +1943,35 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
languageButton.setText(opt.language.getLanguageName()); languageButton.setText(opt.language.getLanguageName());
languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, opt.language.getDefaultName())); languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, opt.language.getDefaultName()));
} }
@Override
public Animator onCreateEnterTransition(View prev, View container){
AnimatorSet anim=new AnimatorSet();
if(getArguments().getBoolean("fromThreadFragment")){
anim.playTogether(
ObjectAnimator.ofFloat(container, View.ALPHA, 0f, 1f),
ObjectAnimator.ofFloat(container, View.TRANSLATION_Y, V.dp(200), 0)
);
}else{
anim.playTogether(
ObjectAnimator.ofFloat(container, View.ALPHA, 0f, 1f),
ObjectAnimator.ofFloat(container, View.TRANSLATION_X, V.dp(100), 0)
);
}
anim.setDuration(300);
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
return anim;
}
@Override
public Animator onCreateExitTransition(View prev, View container){
AnimatorSet anim=new AnimatorSet();
anim.playTogether(
ObjectAnimator.ofFloat(container, View.TRANSLATION_X, V.dp(100)),
ObjectAnimator.ofFloat(container, View.ALPHA, 0)
);
anim.setDuration(200);
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
return anim;
}
} }

View File

@@ -7,10 +7,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.MediaMetadataRetriever; import android.media.MediaMetadataRetriever;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.style.BulletSpan;
import android.util.Log; import android.util.Log;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -26,12 +23,12 @@ import android.widget.ImageView;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.utils.ColorPalette; import org.joinmastodon.android.ui.utils.ColorPalette;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
import java.util.Collections; import java.util.Collections;
@@ -54,16 +51,17 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment");
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@Override @Override
public void onAttach(Activity activity){ public void onAttach(Activity activity){
super.onAttach(activity); super.onAttach(activity);
accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment");
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark); themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
ColorPalette.palettes.get(GlobalUserPreferences.color).apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK); ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().getCurrentColor())
.apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
setTitle(R.string.add_alt_text); setTitle(R.string.add_alt_text);
} }
@@ -134,20 +132,9 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
@Override @Override
public boolean onOptionsItemSelected(MenuItem item){ public boolean onOptionsItemSelected(MenuItem item){
if(item.getItemId()==R.id.help){ if(item.getItemId()==R.id.help){
SpannableStringBuilder msg=new SpannableStringBuilder(getText(R.string.alt_text_help));
BulletSpan[] spans=msg.getSpans(0, msg.length(), BulletSpan.class);
for(BulletSpan span:spans){
BulletSpan betterSpan;
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface));
else
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface), V.dp(1.5f));
msg.setSpan(betterSpan, msg.getSpanStart(span), msg.getSpanEnd(span), msg.getSpanFlags(span));
msg.removeSpan(span);
}
new M3AlertDialogBuilder(themeWrapper) new M3AlertDialogBuilder(themeWrapper)
.setTitle(R.string.what_is_alt_text) .setTitle(R.string.what_is_alt_text)
.setMessage(msg) .setMessage(UiUtils.fixBulletListInString(themeWrapper, R.string.alt_text_help))
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show(); .show();
} }
@@ -184,7 +171,7 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
fakeAttachment.meta.width=width; fakeAttachment.meta.width=width;
fakeAttachment.meta.height=height; fakeAttachment.meta.height=height;
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, new PhotoViewer.Listener(){ photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, null, accountID, new PhotoViewer.Listener(){
@Override @Override
public void setPhotoViewVisibility(int index, boolean visible){ public void setPhotoViewVisibility(int index, boolean visible){
image.setAlpha(visible ? 1f : 0f); image.setAlpha(visible ? 1f : 0f);

View File

@@ -0,0 +1,330 @@
package org.joinmastodon.android.fragments;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Bundle;
import android.view.Gravity;
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.ViewStub;
import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
import org.joinmastodon.android.api.requests.lists.GetListAccounts;
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
import org.joinmastodon.android.events.FinishListCreationFragmentEvent;
import org.joinmastodon.android.fragments.account_list.AddNewListMembersFragment;
import org.joinmastodon.android.fragments.account_list.BaseAccountListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.joinmastodon.android.ui.views.CurlyArrowEmptyView;
import org.parceler.Parcels;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.fragments.WindowInsetsAwareFragment;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
public class CreateListAddMembersFragment extends BaseAccountListFragment implements OnBackPressedListener, AddNewListMembersFragment.Listener{
private FollowList followList;
private Button nextButton;
private View buttonBar;
private FragmentRootLinearLayout rootView;
private FrameLayout searchFragmentContainer;
private FrameLayout fragmentContentWrap;
private AddNewListMembersFragment searchFragment;
private WindowInsets lastInsets;
private boolean dismissingSearchFragment;
private HashSet<String> accountIDsInList=new HashSet<>();
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.manage_list_members);
setSubtitle(getString(R.string.step_x_of_y, 2, 2));
setLayout(R.layout.fragment_login);
setEmptyText(R.string.list_no_members);
setHasOptionsMenu(true);
followList=Parcels.unwrap(getArguments().getParcelable("list"));
if(savedInstanceState!=null || getArguments().getBoolean("needLoadMembers", false)){
loadData();
}else{
onDataLoaded(List.of());
}
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetListAccounts(followList.id, null, 0)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(HeaderPaginationList<Account> result){
for(Account acc:result)
accountIDsInList.add(acc.id);
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()));
}
})
.exec(accountID);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view=super.onCreateView(inflater, container, savedInstanceState);
FrameLayout wrapper=new FrameLayout(getActivity());
wrapper.addView(view);
rootView=(FragmentRootLinearLayout) view;
fragmentContentWrap=wrapper;
return wrapper;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
nextButton=view.findViewById(R.id.btn_next);
nextButton.setOnClickListener(this::onNextClick);
nextButton.setText(R.string.done);
buttonBar=view.findViewById(R.id.button_bar);
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
lastInsets=insets;
if(searchFragment!=null)
searchFragment.onApplyWindowInsets(insets);
insets=UiUtils.applyBottomInsetToFixedView(buttonBar, insets);
rootView.dispatchApplyWindowInsets(insets);
}
@Override
protected List<View> getViewsForElevationEffect(){
return List.of(getToolbar(), buttonBar);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
MenuItem item=menu.add(R.string.add_list_member);
item.setIcon(R.drawable.ic_fluent_add_24_regular);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
if(searchFragmentContainer!=null)
return true;
searchFragmentContainer=new FrameLayout(getActivity());
searchFragmentContainer.setId(R.id.search_fragment);
fragmentContentWrap.addView(searchFragmentContainer);
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("list", Parcels.wrap(followList));
args.putBoolean("_can_go_back", true);
searchFragment=new AddNewListMembersFragment(this);
searchFragment.setArguments(args);
getChildFragmentManager().beginTransaction().add(R.id.search_fragment, searchFragment).commit();
getChildFragmentManager().executePendingTransactions();
if(lastInsets!=null)
searchFragment.onApplyWindowInsets(lastInsets);
searchFragmentContainer.setTranslationX(V.dp(100));
searchFragmentContainer.setAlpha(0f);
searchFragmentContainer.animate().translationX(0).alpha(1).setDuration(300).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
rootView.setVisibility(View.GONE);
}).start();
return true;
}
@Override
protected void initializeEmptyView(View contentView){
ViewStub emptyStub=contentView.findViewById(R.id.empty);
emptyStub.setLayoutResource(R.layout.empty_with_arrow);
super.initializeEmptyView(contentView);
TextView emptySecondary=contentView.findViewById(R.id.empty_text_secondary);
emptySecondary.setText(R.string.list_find_users);
CurlyArrowEmptyView arrowView=(CurlyArrowEmptyView) emptyView;
arrowView.setGravityAndOffsets(Gravity.TOP | Gravity.END, 24, 2);
}
@Override
protected void setStatusBarColor(int color){
rootView.setStatusBarColor(color);
}
@Override
protected void setNavigationBarColor(int color){
rootView.setNavigationBarColor(color);
}
private void dismissSearchFragment(){
if(searchFragment==null || dismissingSearchFragment)
return;
dismissingSearchFragment=true;
rootView.setVisibility(View.VISIBLE);
searchFragmentContainer.animate().translationX(V.dp(100)).alpha(0).setDuration(200).withLayer().setInterpolator(CubicBezierInterpolator.DEFAULT).withEndAction(()->{
getChildFragmentManager().beginTransaction().remove(searchFragment).commit();
getChildFragmentManager().executePendingTransactions();
fragmentContentWrap.removeView(searchFragmentContainer);
searchFragmentContainer=null;
searchFragment=null;
dismissingSearchFragment=false;
}).start();
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
}
private void onNextClick(View v){
E.post(new FinishListCreationFragmentEvent(accountID, followList.id));
Nav.finish(this);
}
@Override
public boolean onBackPressed(){
if(searchFragment!=null){
dismissSearchFragment();
return true;
}
return false;
}
@Override
public boolean isAccountInList(AccountViewModel account){
return accountIDsInList.contains(account.account.id);
}
@Override
public void addAccountToList(AccountViewModel account, Runnable onDone){
new AddAccountsToList(followList.id, Set.of(account.account.id))
.setCallback(new Callback<>(){
@Override
public void onSuccess(Void result){
accountIDsInList.add(account.account.id);
if(onDone!=null)
onDone.run();
int i=0;
for(AccountViewModel acc:data){
if(acc.account.id.equals(account.account.id)){
list.getAdapter().notifyItemChanged(i);
return;
}
i++;
}
int pos=data.size();
data.add(account);
list.getAdapter().notifyItemInserted(pos);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.exec(accountID);
}
@Override
public void removeAccountAccountFromList(AccountViewModel account, Runnable onDone){
new RemoveAccountsFromList(followList.id, Set.of(account.account.id))
.setCallback(new Callback<>(){
@Override
public void onSuccess(Void result){
accountIDsInList.remove(account.account.id);
if(onDone!=null)
onDone.run();
int i=0;
for(AccountViewModel acc:data){
if(acc.account.id.equals(account.account.id)){
list.getAdapter().notifyItemChanged(i);
return;
}
i++;
}
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.exec(accountID);
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
holder.setStyle(AccountViewHolder.AccessoryType.CUSTOM_BUTTON, false);
holder.setOnLongClickListener(vh->false);
Button button=holder.getButton();
button.setPadding(V.dp(24), 0, V.dp(24), 0);
button.setMinimumWidth(0);
button.setMinWidth(0);
button.setOnClickListener(v->{
holder.setActionProgressVisible(true);
holder.itemView.setHasTransientState(true);
Runnable onDone=()->{
holder.setActionProgressVisible(false);
holder.itemView.setHasTransientState(false);
};
AccountViewModel account=holder.getItem();
if(isAccountInList(account)){
removeAccountAccountFromList(account, onDone);
}else{
addAccountToList(account, onDone);
}
});
}
@Override
protected void onBindViewHolder(AccountViewHolder holder){
Button button=holder.getButton();
int textRes, styleRes;
if(isAccountInList(holder.getItem())){
textRes=R.string.remove;
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal_Error;
}else{
textRes=R.string.add;
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
}
button.setText(textRes);
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
button.setBackground(ta.getDrawable(0));
ta.recycle();
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
button.setTextColor(ta.getColorStateList(0));
ta.recycle();
}
@Override
protected void loadRelationships(List<AccountViewModel> accounts){
// no-op
}
@Override
public Uri getWebUri(Uri.Builder base){
// TODO this
return null;
}
}

View File

@@ -0,0 +1,149 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.CreateList;
import org.joinmastodon.android.api.requests.lists.UpdateList;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.FinishListCreationFragmentEvent;
import org.joinmastodon.android.events.ListCreatedEvent;
import org.joinmastodon.android.events.ListUpdatedEvent;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class CreateListFragment extends BaseEditListFragment{
private Button nextButton;
private View buttonBar;
private FollowList followList;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.create_list);
setSubtitle(getString(R.string.step_x_of_y, 1, 2));
setLayout(R.layout.fragment_login);
if(savedInstanceState!=null)
followList=Parcels.unwrap(savedInstanceState.getParcelable("list"));
E.register(this);
}
@Override
public void onDestroy(){
super.onDestroy();
E.unregister(this);
}
@Override
protected int getNavigationIconDrawableResource(){
return R.drawable.ic_baseline_arrow_drop_down_18;
}
@Override
public boolean wantsCustomNavigationIcon(){
return true;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
nextButton=view.findViewById(R.id.btn_next);
nextButton.setOnClickListener(this::onNextClick);
nextButton.setText(R.string.create);
buttonBar=view.findViewById(R.id.button_bar);
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets));
}
@Override
protected List<View> getViewsForElevationEffect(){
return List.of(getToolbar(), buttonBar);
}
@Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putParcelable("list", Parcels.wrap(followList));
}
private void onNextClick(View v){
String title=titleEdit.getText().toString().trim();
if(TextUtils.isEmpty(title)){
titleEditLayout.setErrorState(getString(R.string.required_form_field_blank));
return;
}
if(followList==null){
new CreateList(title, getSelectedRepliesPolicy(), exclusiveItem.checked)
.setCallback(new Callback<>(){
@Override
public void onSuccess(FollowList result){
followList=result;
proceed(false);
E.post(new ListCreatedEvent(accountID, result));
AccountSessionManager.get(accountID).getCacheController().addList(result);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, true)
.exec(accountID);
}else if(!title.equals(followList.title) || getSelectedRepliesPolicy()!=followList.repliesPolicy || exclusiveItem.checked!=followList.exclusive){
new UpdateList(followList.id, title, getSelectedRepliesPolicy(), exclusiveItem.checked)
.setCallback(new Callback<>(){
@Override
public void onSuccess(FollowList result){
followList=result;
proceed(true);
E.post(new ListUpdatedEvent(accountID, result));
AccountSessionManager.get(accountID).getCacheController().updateList(result);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, true)
.exec(accountID);
}else{
proceed(true);
}
}
private void proceed(boolean needLoadMembers){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("list", Parcels.wrap(followList));
args.putBoolean("needLoadMembers", needLoadMembers);
Nav.go(getActivity(), CreateListAddMembersFragment.class, args);
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
}
@Subscribe
public void onFinishListCreationFragment(FinishListCreationFragmentEvent ev){
if(ev.accountID.equals(accountID) && followList!=null && ev.listID.equals(followList.id)){
Nav.finish(this);
}
}
}

View File

@@ -46,7 +46,7 @@ public class CustomLocalTimelineFragment extends PinnableStatusListFragment impl
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, null, count, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){

View File

@@ -0,0 +1,67 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.UpdateList;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.ListUpdatedEvent;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class EditListFragment extends BaseEditListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.edit_list);
loadMembers();
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
menu.add(R.string.delete_list);
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.delete_list)
.setMessage(getString(R.string.delete_list_confirm, followList.title))
.setPositiveButton(R.string.delete, (dlg, which)->doDeleteList())
.setNegativeButton(R.string.cancel, null)
.show();
return true;
}
@Override
public void onDestroy(){
super.onDestroy();
String newTitle=titleEdit.getText().toString();
FollowList.RepliesPolicy newRepliesPolicy=getSelectedRepliesPolicy();
boolean newExclusive=exclusiveItem.checked;
if(!newTitle.equals(followList.title) || newRepliesPolicy!=followList.repliesPolicy || newExclusive!=followList.exclusive){
new UpdateList(followList.id, newTitle, newRepliesPolicy, newExclusive)
.setCallback(new Callback<>(){
@Override
public void onSuccess(FollowList result){
AccountSessionManager.get(accountID).getCacheController().updateList(result);
E.post(new ListUpdatedEvent(accountID, result));
}
@Override
public void onError(ErrorResponse error){
// TODO handle errors somehow
}
})
.exec(accountID);
}
}
}

View File

@@ -41,17 +41,14 @@ 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.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
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.FollowList;
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.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;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -67,86 +64,86 @@ 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 EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop { public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop{
private String accountID; private String accountID;
private TimelinesAdapter adapter; private TimelinesAdapter adapter;
private final ItemTouchHelper itemTouchHelper; private final ItemTouchHelper itemTouchHelper;
private Menu optionsMenu; private Menu optionsMenu;
private boolean updated; private boolean updated;
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>(); private final Map<MenuItem, TimelineDefinition> timelineByMenuItem=new HashMap<>();
private final List<ListTimeline> listTimelines = new ArrayList<>(); private final List<FollowList> followLists =new ArrayList<>();
private final List<Hashtag> hashtags = new ArrayList<>(); private final List<Hashtag> hashtags=new ArrayList<>();
private MenuItem addHashtagItem; private MenuItem addHashtagItem;
private final List<CustomLocalTimeline> localTimelines = new ArrayList<>(); private final List<CustomLocalTimeline> localTimelines = new ArrayList<>();
public EditTimelinesFragment() { public EditTimelinesFragment(){
super(10); super(10);
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ; ItemTouchHelper.SimpleCallback itemTouchCallback=new ItemTouchHelperCallback();
itemTouchHelper = new ItemTouchHelper(itemTouchCallback); itemTouchHelper=new ItemTouchHelper(itemTouchCallback);
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
setTitle(R.string.sk_timelines); setTitle(R.string.sk_timelines);
accountID = getArguments().getString("account"); accountID=getArguments().getString("account");
new GetLists().setCallback(new Callback<>() { new GetLists().setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<ListTimeline> result) { public void onSuccess(List<FollowList> result){
listTimelines.addAll(result); followLists.addAll(result);
updateOptionsMenu(); updateOptionsMenu();
} }
@Override @Override
public void onError(ErrorResponse error) { public void onError(ErrorResponse error){
error.showToast(getContext()); error.showToast(getContext());
} }
}).exec(accountID); }).exec(accountID);
new GetFollowedHashtags().setCallback(new Callback<>() { new GetFollowedHashtags().setCallback(new Callback<>(){
@Override @Override
public void onSuccess(HeaderPaginationList<Hashtag> result) { public void onSuccess(HeaderPaginationList<Hashtag> result){
hashtags.addAll(result); hashtags.addAll(result);
updateOptionsMenu(); updateOptionsMenu();
} }
@Override @Override
public void onError(ErrorResponse error) { public void onError(ErrorResponse error){
error.showToast(getContext()); error.showToast(getContext());
} }
}).exec(accountID); }).exec(accountID);
} }
@Override @Override
protected void onShown(){ protected void onShown(){
super.onShown(); super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData(); if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
} }
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
itemTouchHelper.attachToRecyclerView(list); itemTouchHelper.attachToRecyclerView(list);
refreshLayout.setEnabled(false); refreshLayout.setEnabled(false);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16)); list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
this.optionsMenu = menu; this.optionsMenu=menu;
updateOptionsMenu(); updateOptionsMenu();
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item){
if (item.getItemId() == R.id.menu_back) { if(item.getItemId()==R.id.menu_back){
updateOptionsMenu(); updateOptionsMenu();
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0); optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
return true; return true;
} }
if (item.getItemId() == R.id.menu_add_local_timelines) { if (item.getItemId() == R.id.menu_add_local_timelines) {
addNewLocalTimeline(); addNewLocalTimeline();
return true; return true;
} }
@@ -161,14 +158,14 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
return true; return true;
} }
private void addTimeline(TimelineDefinition tl) { private void addTimeline(TimelineDefinition tl){
data.add(tl.copy()); data.add(tl.copy());
adapter.notifyItemInserted(data.size()); adapter.notifyItemInserted(data.size());
saveTimelines(); saveTimelines();
updateOptionsMenu(); updateOptionsMenu();
} }
private void addNewLocalTimeline() { private void addNewLocalTimeline() {
FrameLayout inputWrap = new FrameLayout(getContext()); FrameLayout inputWrap = new FrameLayout(getContext());
EditText input = new EditText(getContext()); EditText input = new EditText(getContext());
input.setHint(R.string.sk_example_domain); input.setHint(R.string.sk_example_domain);
@@ -194,92 +191,92 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
timelineByMenuItem.put(item, tl); timelineByMenuItem.put(item, tl);
} }
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon) { private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon){
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, name); MenuItem item=menu.add(0, View.generateViewId(), Menu.NONE, name);
item.setIcon(icon); item.setIcon(icon);
return item; return item;
} }
private void updateOptionsMenu() { private void updateOptionsMenu(){
if (getActivity() == null) return; if(getActivity()==null) return;
optionsMenu.clear(); optionsMenu.clear();
timelineByMenuItem.clear(); timelineByMenuItem.clear();
SubMenu menu = optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add); SubMenu menu=optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular); menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline); SubMenu timelinesMenu=menu.addSubMenu(R.string.sk_timeline);
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular); timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list); SubMenu listsMenu=menu.addSubMenu(R.string.sk_list);
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular); listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag); SubMenu hashtagsMenu=menu.addSubMenu(R.string.sk_hashtag);
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular); hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
MenuItem addLocalTimelines = menu.add(0, R.id.menu_add_local_timelines, NONE, R.string.local_timeline); MenuItem addLocalTimelines = menu.add(0, R.id.menu_add_local_timelines, NONE, R.string.local_timeline);
addLocalTimelines.setIcon(R.drawable.ic_fluent_add_24_regular); addLocalTimelines.setIcon(R.drawable.ic_fluent_add_24_regular);
makeBackItem(timelinesMenu); makeBackItem(timelinesMenu);
makeBackItem(listsMenu); makeBackItem(listsMenu);
makeBackItem(hashtagsMenu); makeBackItem(hashtagsMenu);
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl -> addTimelineToOptions(tl, timelinesMenu)); TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl->addTimelineToOptions(tl, timelinesMenu));
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu)); followLists.stream().map(TimelineDefinition::ofList).forEach(tl->addTimelineToOptions(tl, listsMenu));
addHashtagItem = addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular); addHashtagItem=addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu)); hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl->addTimelineToOptions(tl, hashtagsMenu));
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0); timelinesMenu.getItem().setVisible(timelinesMenu.size()>0);
listsMenu.getItem().setVisible(listsMenu.size() > 0); listsMenu.getItem().setVisible(listsMenu.size()>0);
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0); hashtagsMenu.getItem().setVisible(hashtagsMenu.size()>0);
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline); UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
} }
private void saveTimelines() { private void saveTimelines(){
updated=true; updated=true;
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences(); AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE); if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
prefs.timelines=data; prefs.timelines=data;
prefs.save(); prefs.save();
} }
private void removeTimeline(int position) { private void removeTimeline(int position){
data.remove(position); data.remove(position);
adapter.notifyItemRemoved(position); adapter.notifyItemRemoved(position);
saveTimelines(); saveTimelines();
updateOptionsMenu(); updateOptionsMenu();
} }
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
onDataLoaded(AccountSessionManager.get(accountID).getLocalPreferences().timelines); onDataLoaded(AccountSessionManager.get(accountID).getLocalPreferences().timelines);
updateOptionsMenu(); updateOptionsMenu();
} }
@Override @Override
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() { protected RecyclerView.Adapter<TimelineViewHolder> getAdapter(){
return adapter = new TimelinesAdapter(); return adapter=new TimelinesAdapter();
} }
@Override @Override
public void scrollToTop() { public void scrollToTop(){
smoothScrollRecyclerViewToTop(list); smoothScrollRecyclerViewToTop(list);
} }
@Override @Override
public void onDestroy() { public void onDestroy(){
super.onDestroy(); super.onDestroy();
if (updated) UiUtils.restartApp(); if(updated) UiUtils.restartApp();
} }
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) { private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags){
if (tags == null || tags.isEmpty()) return false; if(tags==null || tags.isEmpty()) return false;
editText.setText(tags); editText.setText(tags);
editText.chipifyAllUnterminatedTokens(); editText.chipifyAllUnterminatedTokens();
return true; return true;
} }
private NachoTextView prepareChipTextView(NachoTextView nacho) { private NachoTextView prepareChipTextView(NachoTextView nacho){
//Ill Be Back //Ill Be Back
nacho.setChipTerminators( nacho.setChipTerminators(
Map.of( Map.of(
@@ -289,223 +286,228 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
';', BEHAVIOR_CHIPIFY_ALL ';', BEHAVIOR_CHIPIFY_ALL
) )
); );
nacho.enableEditChipOnTouch(true, true); nacho.enableEditChipOnTouch(true, true);
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens()); nacho.setOnFocusChangeListener((v, hasFocus)->nacho.chipifyAllUnterminatedTokens());
return nacho; return nacho;
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove) { protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove){
Context ctx = getContext(); Context ctx=getContext();
View view = getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false); View view=getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
View divider = view.findViewById(R.id.divider); View divider=view.findViewById(R.id.divider);
Button advancedBtn = view.findViewById(R.id.advanced); Button advancedBtn=view.findViewById(R.id.advanced);
EditText editText = view.findViewById(R.id.input); EditText editText=view.findViewById(R.id.input);
if (item != null) editText.setText(item.getCustomTitle()); if(item!=null) editText.setText(item.getCustomTitle());
editText.setHint(item != null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag)); editText.setHint(item!=null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap); LinearLayout tagWrap=view.findViewById(R.id.tag_wrap);
boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG; boolean hashtagOptionsAvailable=item==null || item.getType()==TimelineDefinition.TimelineType.HASHTAG;
advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE); advancedBtn.setVisibility(hashtagOptionsAvailable ? View.VISIBLE : View.GONE);
advancedBtn.setOnClickListener(l -> { advancedBtn.setOnClickListener(l->{
advancedBtn.setSelected(!advancedBtn.isSelected()); advancedBtn.setSelected(!advancedBtn.isSelected());
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show); advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE); divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE); tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
UiUtils.beginLayoutTransition((ViewGroup) view); UiUtils.beginLayoutTransition((ViewGroup) view);
}); });
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch); Switch localOnlySwitch=view.findViewById(R.id.local_only_switch);
view.findViewById(R.id.local_only) view.findViewById(R.id.local_only).setOnClickListener(l->localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
.setOnClickListener(l -> localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
EditText tagMain = view.findViewById(R.id.tag_main); EditText tagMain=view.findViewById(R.id.tag_main);
NachoTextView tagsAny = prepareChipTextView(view.findViewById(R.id.tags_any)); NachoTextView tagsAny=prepareChipTextView(view.findViewById(R.id.tags_any));
NachoTextView tagsAll = prepareChipTextView(view.findViewById(R.id.tags_all)); NachoTextView tagsAll=prepareChipTextView(view.findViewById(R.id.tags_all));
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none)); NachoTextView tagsNone=prepareChipTextView(view.findViewById(R.id.tags_none));
if (item != null) {
tagMain.setText(item.getHashtagName()); if(item!=null && hashtagOptionsAvailable){
boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle()); tagMain.setText(item.getHashtagName());
hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced; boolean hasAdvanced=!TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
hasAdvanced = setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced; hasAdvanced=setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
hasAdvanced = setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced; hasAdvanced=setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
if (item.isHashtagLocalOnly()) { hasAdvanced=setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
localOnlySwitch.setChecked(true); if(item.isHashtagLocalOnly()){
hasAdvanced = true; localOnlySwitch.setChecked(true);
} hasAdvanced=true;
if (hasAdvanced) { }
advancedBtn.setSelected(true); if(hasAdvanced){
advancedBtn.setText(R.string.sk_advanced_options_hide); advancedBtn.setSelected(true);
advancedBtn.setText(R.string.sk_advanced_options_hide);
tagWrap.setVisibility(View.VISIBLE); tagWrap.setVisibility(View.VISIBLE);
divider.setVisibility(View.VISIBLE); divider.setVisibility(View.VISIBLE);
} }
} }
ImageButton btn = view.findViewById(R.id.button); ImageButton btn=view.findViewById(R.id.button);
PopupMenu popup = new PopupMenu(ctx, btn); PopupMenu popup=new PopupMenu(ctx, btn);
TimelineDefinition.Icon currentIcon = item != null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG; TimelineDefinition.Icon currentIcon=item!=null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
btn.setImageResource(currentIcon.iconRes); btn.setImageResource(currentIcon.iconRes);
btn.setTag(currentIcon.ordinal()); btn.setTag(currentIcon.ordinal());
btn.setContentDescription(ctx.getString(currentIcon.nameRes)); btn.setContentDescription(ctx.getString(currentIcon.nameRes));
btn.setOnTouchListener(popup.getDragToOpenListener()); btn.setOnTouchListener(popup.getDragToOpenListener());
btn.setOnClickListener(l -> popup.show()); btn.setOnClickListener(l->popup.show());
Menu menu = popup.getMenu(); Menu menu=popup.getMenu();
TimelineDefinition.Icon defaultIcon = item != null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG; TimelineDefinition.Icon defaultIcon=item!=null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes); menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
if (!currentIcon.equals(defaultIcon)) { if(!currentIcon.equals(defaultIcon)){
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes); menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
} }
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) { for(TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()){
if (icon.hidden || icon.ordinal() == (int) btn.getTag()) continue; if(icon.hidden || icon.ordinal()==(int) btn.getTag()) continue;
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes); menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
} }
UiUtils.enablePopupMenuIcons(ctx, popup); UiUtils.enablePopupMenuIcons(ctx, popup);
popup.setOnMenuItemClickListener(menuItem -> { popup.setOnMenuItemClickListener(menuItem->{
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()]; TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[menuItem.getItemId()];
btn.setImageResource(icon.iconRes); btn.setImageResource(icon.iconRes);
btn.setTag(menuItem.getItemId()); btn.setTag(menuItem.getItemId());
btn.setContentDescription(ctx.getString(icon.nameRes)); btn.setContentDescription(ctx.getString(icon.nameRes));
return true; return true;
}); });
AlertDialog.Builder builder = new M3AlertDialogBuilder(ctx) AlertDialog.Builder builder=new M3AlertDialogBuilder(ctx)
.setTitle(item == null ? R.string.sk_add_timeline : R.string.sk_edit_timeline) .setTitle(item==null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
.setView(view) .setView(view)
.setPositiveButton(R.string.save, (d, which) -> { .setPositiveButton(R.string.save, (d, which)->{
tagsAny.chipifyAllUnterminatedTokens(); String name=editText.getText().toString().trim();
tagsAll.chipifyAllUnterminatedTokens();
tagsNone.chipifyAllUnterminatedTokens();
String name = editText.getText().toString().trim();
String mainHashtag = tagMain.getText().toString().trim();
if (TextUtils.isEmpty(mainHashtag)) {
mainHashtag = name;
name = null;
}
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) {
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(null);
return;
}
TimelineDefinition tl = item != null ? item : TimelineDefinition.ofHashtag(name); String mainHashtag=tagMain.getText().toString().trim();
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[(int) btn.getTag()]; if(item != null && item.getType()==TimelineDefinition.TimelineType.HASHTAG){
tl.setIcon(icon); tagsAny.chipifyAllUnterminatedTokens();
tl.setTitle(name); tagsAll.chipifyAllUnterminatedTokens();
tl.setTagOptions( tagsNone.chipifyAllUnterminatedTokens();
mainHashtag, if(TextUtils.isEmpty(mainHashtag)){
tagsAny.getChipValues(), mainHashtag=name;
tagsAll.getChipValues(), name=null;
tagsNone.getChipValues(), }
localOnlySwitch.isChecked() if(TextUtils.isEmpty(mainHashtag) && (item!=null && item.getType()==TimelineDefinition.TimelineType.HASHTAG)){
); Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(tl); onSave.accept(null);
}) return;
.setNegativeButton(R.string.cancel, (d, which) -> {}); }
}
if (onRemove != null) builder.setNeutralButton(R.string.sk_remove, (d, which) -> onRemove.run()); TimelineDefinition tl=item!=null ? item : TimelineDefinition.ofHashtag(name);
TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[(int) btn.getTag()];
tl.setIcon(icon);
tl.setTitle(name);
if(item == null || item.getType()==TimelineDefinition.TimelineType.HASHTAG){
tl.setTagOptions(
mainHashtag,
tagsAny.getChipValues(),
tagsAll.getChipValues(),
tagsNone.getChipValues(),
localOnlySwitch.isChecked()
);
}
onSave.accept(tl);
})
.setNegativeButton(R.string.cancel, (d, which)->{});
builder.show(); if(onRemove!=null) builder.setNeutralButton(R.string.sk_remove, (d, which)->onRemove.run());
btn.requestFocus();
}
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{ builder.show();
@NonNull btn.requestFocus();
@Override }
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new TimelineViewHolder();
}
@Override private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) { @NonNull
holder.bind(data.get(position)); @Override
} public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new TimelineViewHolder();
}
@Override @Override
public int getItemCount() { public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position){
return data.size(); holder.bind(data.get(position));
} }
}
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{ @Override
private final TextView title; public int getItemCount(){
private final ImageView dragger; return data.size();
}
}
public TimelineViewHolder(){ private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
super(getActivity(), R.layout.item_text, list); private final TextView title;
title=findViewById(R.id.title); private final ImageView dragger;
dragger=findViewById(R.id.dragger_thingy);
}
@SuppressLint("ClickableViewAccessibility") public TimelineViewHolder(){
@Override super(getActivity(), R.layout.item_text, list);
public void onBind(TimelineDefinition item) { title=findViewById(R.id.title);
title.setText(item.getTitle(getContext())); dragger=findViewById(R.id.dragger_thingy);
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null); }
dragger.setVisibility(View.VISIBLE);
dragger.setOnTouchListener((View v, MotionEvent event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
itemTouchHelper.startDrag(this);
return true;
}
return false;
});
}
private void onSave(TimelineDefinition tl) { @SuppressLint("ClickableViewAccessibility")
saveTimelines(); @Override
rebind(); public void onBind(TimelineDefinition item){
} title.setText(item.getTitle(getContext()));
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
dragger.setVisibility(View.VISIBLE);
dragger.setOnTouchListener((View v, MotionEvent event)->{
if(event.getAction()==MotionEvent.ACTION_DOWN){
itemTouchHelper.startDrag(this);
return true;
}
return false;
});
}
private void onRemove() { private void onSave(TimelineDefinition tl){
removeTimeline(getAbsoluteAdapterPosition()); saveTimelines();
} rebind();
}
@SuppressLint("ClickableViewAccessibility") private void onRemove(){
@Override removeTimeline(getAbsoluteAdapterPosition());
public void onClick() { }
makeTimelineEditor(item, this::onSave, this::onRemove);
}
}
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback { @SuppressLint("ClickableViewAccessibility")
public ItemTouchHelperCallback() { @Override
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT); public void onClick(){
} makeTimelineEditor(item, this::onSave, this::onRemove);
}
}
@Override private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback{
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { public ItemTouchHelperCallback(){
int fromPosition = viewHolder.getAbsoluteAdapterPosition(); super(ItemTouchHelper.UP|ItemTouchHelper.DOWN, ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT);
int toPosition = target.getAbsoluteAdapterPosition(); }
if (Math.max(fromPosition, toPosition) >= data.size() || Math.min(fromPosition, toPosition) < 0) {
return false;
} else {
Collections.swap(data, fromPosition, toPosition);
adapter.notifyItemMoved(fromPosition, toPosition);
saveTimelines();
return true;
}
}
@Override @Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) { public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target){
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) { int fromPosition=viewHolder.getAbsoluteAdapterPosition();
viewHolder.itemView.animate().alpha(0.65f); int toPosition=target.getAbsoluteAdapterPosition();
} if(Math.max(fromPosition, toPosition)>=data.size() || Math.min(fromPosition, toPosition)<0){
} return false;
}else{
Collections.swap(data, fromPosition, toPosition);
adapter.notifyItemMoved(fromPosition, toPosition);
saveTimelines();
return true;
}
}
@Override @Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
super.clearView(recyclerView, viewHolder); if(actionState==ItemTouchHelper.ACTION_STATE_DRAG && viewHolder!=null){
viewHolder.itemView.animate().alpha(1f); viewHolder.itemView.animate().alpha(0.65f);
} }
}
@Override @Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
int position = viewHolder.getAbsoluteAdapterPosition(); super.clearView(recyclerView, viewHolder);
removeTimeline(position); viewHolder.itemView.animate().alpha(1f);
} }
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction){
int position=viewHolder.getAbsoluteAdapterPosition();
removeTimeline(position);
}
}
} }

View File

@@ -27,7 +27,7 @@ public class FavoritedStatusListFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Status> result){ public void onSuccess(HeaderPaginationList<Status> result){
if (getActivity() == null) return; if(getActivity()==null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else

View File

@@ -45,8 +45,8 @@ public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag
} }
@Override @Override
public void onItemClick(String hashtag){ public void onItemClick(String id){
UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag, data.stream().filter(h -> Objects.equals(h.name, hashtag)).findAny().map(h -> h.following).orElse(null)); UiUtils.openHashtagTimeline(getActivity(), accountID, Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag);
} }
@Override @Override

View File

@@ -20,7 +20,6 @@ import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
@@ -83,7 +82,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Account> result){ public void onSuccess(HeaderPaginationList<Account> result){
if (getActivity() == null) return; if(getActivity()==null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else
@@ -271,7 +270,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id); relationship=relationships.get(item.account.id);
UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account); UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account);
if(relationship==null || !relationship.followedBy){ if(relationship==null || !relationship.followedBy){
actionWrap.setVisibility(View.GONE); actionWrap.setVisibility(View.GONE);
@@ -366,9 +365,9 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000); coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(account.emojis.isEmpty()){ if(account.emojis.isEmpty()){
parsedName=account.displayName; parsedName= account.getDisplayName();
}else{ }else{
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis); parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis);
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio)); emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
} }
} }

View File

@@ -56,7 +56,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Hashtag> result){ public void onSuccess(HeaderPaginationList<Hashtag> result){
if (getActivity() == null) return; if(getActivity()==null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else
@@ -122,7 +122,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
@Override @Override
public void onClick() { public void onClick() {
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following); UiUtils.openHashtagTimeline(getActivity(), accountID, item.name);
} }
} }
} }

View File

@@ -1,35 +1,45 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.content.res.TypedArray;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
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 android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.E; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.filters.CreateFilter; import org.joinmastodon.android.api.requests.filters.CreateFilter;
import org.joinmastodon.android.api.requests.filters.DeleteFilter; import org.joinmastodon.android.api.requests.filters.DeleteFilter;
import org.joinmastodon.android.api.requests.filters.GetFilters; import org.joinmastodon.android.api.requests.filters.GetFilters;
import org.joinmastodon.android.api.requests.tags.GetHashtag; import org.joinmastodon.android.api.requests.tags.GetTag;
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed; import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline; import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.events.HashtagUpdatedEvent;
import org.joinmastodon.android.fragments.settings.EditFilterFragment;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.FilterAction; import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterKeyword; import org.joinmastodon.android.model.FilterKeyword;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.text.SpacerSpan;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.StatusFilterPredicate; import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
@@ -42,17 +52,29 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class HashtagTimelineFragment extends PinnableStatusListFragment { public class HashtagTimelineFragment extends PinnableStatusListFragment{
private String hashtag; private Hashtag hashtag;
private String hashtagName;
private TextView headerTitle, headerSubtitle;
private ProgressBarButton followButton;
private ProgressBar followProgress;
private MenuItem followMenuItem, pinMenuItem, muteMenuItem;
private boolean followRequestRunning;
private boolean toolbarContentVisible;
private String maxID;
private List<String> any; private List<String> any;
private List<String> all; private List<String> all;
private List<String> none; private List<String> none;
private boolean following; private boolean following;
private boolean localOnly; private boolean localOnly;
private MenuItem followButton; private Menu optionsMenu;
private MenuItem muteButton; private MenuInflater optionsMenuInflater;
private Optional<Filter> filter = Optional.empty(); private Optional<Filter> filter = Optional.empty();
@Override @Override
@@ -60,100 +82,27 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
return true; return true;
} }
@Override @Override
public void onAttach(Activity activity){ public void onAttach(Activity activity){
super.onAttach(activity); super.onAttach(activity);
updateTitle(getArguments().getString("hashtag"));
following=getArguments().getBoolean("following", false); following=getArguments().getBoolean("following", false);
localOnly=getArguments().getBoolean("localOnly", false); localOnly=getArguments().getBoolean("localOnly", false);
any=getArguments().getStringArrayList("any"); any=getArguments().getStringArrayList("any");
all=getArguments().getStringArrayList("all"); all=getArguments().getStringArrayList("all");
none=getArguments().getStringArrayList("none"); none=getArguments().getStringArrayList("none");
if(getArguments().containsKey("hashtag")){
hashtag=Parcels.unwrap(getArguments().getParcelable("hashtag"));
hashtagName=hashtag.name;
}else{
hashtagName=getArguments().getString("hashtagName");
}
setTitle('#'+hashtagName);
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
private void updateTitle(String hashtagName) {
hashtag = hashtagName;
setTitle('#'+hashtag);
}
private void updateFollowingState(boolean newFollowing) {
this.following = newFollowing;
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
E.post(new HashtagUpdatedEvent(hashtag, following));
}
private void updateMuteState(boolean newMute) { private void updateMuteState(boolean newMute) {
muteButton.setTitle(getString(newMute ? R.string.unmute_user : R.string.mute_user, "#" + hashtag)); muteMenuItem.setTitle(getString(newMute ? R.string.unmute_user : R.string.mute_user, "#" + hashtag));
muteButton.setIcon(newMute ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular); muteMenuItem.setIcon(newMute ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
followButton = menu.findItem(R.id.follow_hashtag);
updateFollowingState(following);
muteButton = menu.findItem(R.id.mute_hashtag);
updateMuteState(filter.isPresent());
new GetHashtag(hashtag).setCallback(new Callback<>() {
@Override
public void onSuccess(Hashtag hashtag) {
if (getActivity() == null) return;
updateTitle(hashtag.name);
updateFollowingState(hashtag.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
new GetFilters().setCallback(new Callback<>() {
@Override
public void onSuccess(List<Filter> filters) {
if (getActivity() == null) return;
filter=filters.stream().filter(filter->filter.title.equals("#"+hashtag)).findAny();
updateMuteState(filter.isPresent());
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.follow_hashtag) {
updateFollowingState(!following);
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
@Override
public void onSuccess(Hashtag i) {
if (getActivity() == null) return;
if (i.following == following) Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show();
updateFollowingState(i.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
updateFollowingState(!following);
}
}).exec(accountID);
return true;
} else if (item.getItemId() == R.id.mute_hashtag) {
showMuteDialog(filter.isPresent());
return true;
}
return false;
} }
private void showMuteDialog(boolean mute) { private void showMuteDialog(boolean mute) {
@@ -184,8 +133,8 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
private void muteHashtag() { private void muteHashtag() {
FilterKeyword hashtagFilter=new FilterKeyword(); FilterKeyword hashtagFilter=new FilterKeyword();
hashtagFilter.wholeWord=true; hashtagFilter.wholeWord=true;
hashtagFilter.keyword="#"+hashtag; hashtagFilter.keyword="#"+hashtagName;
new CreateFilter("#"+hashtag, EnumSet.of(FilterContext.HOME), FilterAction.HIDE, 0 , List.of(hashtagFilter)).setCallback(new Callback<>(){ new CreateFilter("#"+hashtagName, EnumSet.of(FilterContext.HOME), FilterAction.HIDE, 0 , List.of(hashtagFilter)).setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Filter result){ public void onSuccess(Filter result){
filter=Optional.of(result); filter=Optional.of(result);
@@ -203,18 +152,19 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
@Override @Override
protected TimelineDefinition makeTimelineDefinition() { protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofHashtag(hashtag); return TimelineDefinition.ofHashtag(hashtagName);
} }
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetHashtagTimeline(hashtagName, getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@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, getFilterContext())).collect(Collectors.toList()); boolean more=applyMaxID(result);
onDataLoaded(result, !result.isEmpty()); AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
} }
}) })
.exec(accountID); .exec(accountID);
@@ -227,16 +177,46 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
loadData(); loadData();
} }
@Override
public void loadData(){
reloadTag();
super.loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
if(getParentFragment() instanceof HomeTabFragment) return;
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
View topChild=recyclerView.getChildAt(0);
int firstChildPos=recyclerView.getChildAdapterPosition(topChild);
float newAlpha=firstChildPos>0 ? 1f : Math.min(1f, -topChild.getTop()/(float)headerTitle.getHeight());
toolbarTitleView.setAlpha(newAlpha);
boolean newToolbarVisibility=newAlpha>0.5f;
if(newToolbarVisibility!=toolbarContentVisible){
toolbarContentVisible=newToolbarVisibility;
createOptionsMenu();
}
}
});
}
@Override @Override
public boolean onFabLongClick(View v) { public boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '); return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtagName+' ');
} }
@Override @Override
public void onFabClick(View v){ public void onFabClick(View v){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putString("prefilledText", '#'+hashtag+' '); args.putString("prefilledText", '#'+hashtagName+' ');
Nav.go(getActivity(), ComposeFragment.class, args); Nav.go(getActivity(), ComposeFragment.class, args);
} }
@@ -252,6 +232,219 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
@Override @Override
public Uri getWebUri(Uri.Builder base) { public Uri getWebUri(Uri.Builder base) {
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build(); return base.path((isInstanceAkkoma() ? "/tag/" : "/tags/") + hashtag).build();
}
@Override
protected RecyclerView.Adapter getAdapter(){
View header=getActivity().getLayoutInflater().inflate(R.layout.header_hashtag_timeline, list, false);
headerTitle=header.findViewById(R.id.title);
headerSubtitle=header.findViewById(R.id.subtitle);
followButton=header.findViewById(R.id.profile_action_btn);
followProgress=header.findViewById(R.id.action_progress);
headerTitle.setText("#"+hashtagName);
followButton.setVisibility(View.GONE);
followButton.setOnClickListener(v->{
if(hashtag==null)
return;
setFollowed(!hashtag.following);
});
followButton.setOnLongClickListener(v->{
if(hashtag==null) return false;
UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> {
new SetTagFollowed(hashtagName, true).setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag hashtag) {
Toast.makeText(
getActivity(),
getString(R.string.sk_followed_as, session.self.getShortUsername()),
Toast.LENGTH_SHORT
).show();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(session.getID());
}, null);
return true;
});
updateHeader();
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
if(!(getParentFragment() instanceof HomeTabFragment)){
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(header));
}
mergeAdapter.addAdapter(super.getAdapter());
return mergeAdapter;
}
@Override
protected int getMainAdapterOffset(){
return 1;
}
private void createOptionsMenu(){
optionsMenu.clear();
optionsMenuInflater.inflate(R.menu.hashtag_timeline, optionsMenu);
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
pinMenuItem=optionsMenu.findItem(R.id.pin);
followMenuItem.setVisible(toolbarContentVisible);
// pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
super.updatePinButton(pinMenuItem);
muteMenuItem = optionsMenu.findItem(R.id.mute_hashtag);
updateMuteState(filter.isPresent());
new GetFilters().setCallback(new Callback<>() {
@Override
public void onSuccess(List<Filter> filters) {
if (getActivity() == null) return;
filter=filters.stream().filter(filter->filter.title.equals("#"+hashtagName)).findAny();
updateMuteState(filter.isPresent());
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
@Override
public void updatePinButton(MenuItem pin){
super.updatePinButton(pin);
if(toolbarContentVisible) UiUtils.insetPopupMenuIcon(getContext(), pin);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
optionsMenu=menu;
optionsMenuInflater=inflater;
createOptionsMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) {
setFollowed(!hashtag.following);
} else if (item.getItemId() == R.id.mute_hashtag) {
showMuteDialog(filter.isPresent());
return true;
}
return true;
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
toolbarTitleView.setAlpha(toolbarContentVisible ? 1f : 0f);
createOptionsMenu();
}
private void updateHeader(){
if(hashtag==null || getActivity()==null)
return;
if(hashtag.history!=null && !hashtag.history.isEmpty()){
int weekPosts=hashtag.history.stream().mapToInt(h->h.uses).sum();
int todayPosts=hashtag.history.get(0).uses;
int numAccounts=hashtag.history.stream().mapToInt(h->h.accounts).sum();
int hSpace=V.dp(8);
SpannableStringBuilder ssb=new SpannableStringBuilder();
ssb.append(getResources().getQuantityString(R.plurals.x_posts, weekPosts, weekPosts));
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append('·');
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append(getResources().getQuantityString(R.plurals.x_participants, numAccounts, numAccounts));
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append('·');
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append(getResources().getQuantityString(R.plurals.x_posts_today, todayPosts, todayPosts));
headerSubtitle.setText(ssb);
}
int styleRes;
followButton.setVisibility(View.VISIBLE);
if(hashtag.following){
followButton.setText(R.string.button_following);
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal;
}else{
followButton.setText(R.string.button_follow);
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
}
TypedArray ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
followButton.setBackground(ta.getDrawable(0));
ta.recycle();
ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
followButton.setTextColor(ta.getColorStateList(0));
followProgress.setIndeterminateTintList(ta.getColorStateList(0));
ta.recycle();
followButton.setTextVisible(true);
followProgress.setVisibility(View.GONE);
if(followMenuItem!=null){
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
}
if(muteMenuItem!=null){
muteMenuItem.setTitle(getString(filter.isPresent() ? R.string.unmute_user : R.string.mute_user, "#" + hashtag));
muteMenuItem.setIcon(filter.isPresent() ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
}
}
private void reloadTag(){
new GetTag(hashtagName)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
hashtag=result;
updateHeader();
}
@Override
public void onError(ErrorResponse error){
}
})
.exec(accountID);
}
private void setFollowed(boolean followed){
if(followRequestRunning)
return;
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
followButton.setTextVisible(false);
followProgress.setVisibility(View.VISIBLE);
followRequestRunning=true;
new SetTagFollowed(hashtagName, followed)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
if(getActivity()==null)
return;
hashtag=result;
updateHeader();
followRequestRunning=false;
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
if(error instanceof MastodonErrorResponse er && "Duplicate record".equals(er.error)){
hashtag.following=true;
}else{
error.showToast(getActivity());
}
updateHeader();
followRequestRunning=false;
}
})
.exec(accountID);
} }
} }

View File

@@ -5,8 +5,6 @@ import android.app.Fragment;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.assist.AssistContent; import android.app.assist.AssistContent;
import android.graphics.drawable.RippleDrawable; import android.graphics.drawable.RippleDrawable;
import android.content.Intent;
import android.graphics.Outline;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
@@ -35,10 +33,9 @@ import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
import org.joinmastodon.android.fragments.discover.DiscoverFragment; import org.joinmastodon.android.fragments.discover.DiscoverFragment;
import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestionsFragment; import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestionsFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
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.PaginatedResponse;
import org.joinmastodon.android.ui.AccountSwitcherSheet; import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
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.TabBar; import org.joinmastodon.android.ui.views.TabBar;
@@ -75,7 +72,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
private TextView notificationsBadge; private TextView notificationsBadge;
private String accountID; private String accountID;
private boolean isAkkoma;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -83,8 +79,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
setTitle(R.string.mo_app_name); setTitle(R.string.mo_app_name);
isAkkoma = 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);
@@ -94,7 +88,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
homeTabFragment=new HomeTabFragment(); homeTabFragment=new HomeTabFragment();
homeTabFragment.setArguments(args); homeTabFragment.setArguments(args);
args=new Bundle(args); args=new Bundle(args);
args.putBoolean("disableDiscover", isAkkoma);
args.putBoolean("noAutoLoad", true); args.putBoolean("noAutoLoad", true);
discoverFragment=new DiscoverFragment(); discoverFragment=new DiscoverFragment();
discoverFragment.setArguments(args); discoverFragment.setArguments(args);
@@ -304,7 +297,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
if(tab==R.id.tab_profile){ if(tab==R.id.tab_profile){
ArrayList<String> options=new ArrayList<>(); ArrayList<String> options=new ArrayList<>();
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.getDisplayName()+"\n("+session.self.username+"@"+session.domain+")");
} }
new AccountSwitcherSheet(getActivity(), this).show(); new AccountSwitcherSheet(getActivity(), this).show();
return true; return true;
@@ -317,11 +310,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
discoverFragment.openSearch(); discoverFragment.openSearch();
return true; return true;
} }
if(tab==R.id.tab_home){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
}
return false; return false;
} }

View File

@@ -53,7 +53,7 @@ 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;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.TimelineDefinition; 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;
@@ -95,7 +95,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
private ImageView collapsedChevron; private ImageView collapsedChevron;
private TextView timelineTitle; private TextView timelineTitle;
private PopupMenu switcherPopup; private PopupMenu switcherPopup;
private final Map<Integer, ListTimeline> listItems = new HashMap<>(); private final Map<Integer, FollowList> listItems = new HashMap<>();
private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>(); private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>();
private List<TimelineDefinition> timelinesList; private List<TimelineDefinition> timelinesList;
private int count; private int count;
@@ -237,21 +237,25 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
ViewTreeObserver vto = getToolbar().getViewTreeObserver(); ViewTreeObserver vto = getToolbar().getViewTreeObserver();
if (vto.isAlive()) { if (vto.isAlive()) {
vto.addOnGlobalLayoutListener(() -> { vto.addOnGlobalLayoutListener(()->{
Toolbar t = getToolbar(); Toolbar t=getToolbar();
if (t == null) return; if(t==null) return;
int toolbarWidth = t.getWidth(); int toolbarWidth=t.getWidth();
if (toolbarWidth == 0) return; if(toolbarWidth==0) return;
int toolbarFrameWidth = toolbarFrame.getWidth(); int toolbarFrameWidth=toolbarFrame.getWidth();
int padding = toolbarWidth - toolbarFrameWidth; int actionsWidth=toolbarWidth-toolbarFrameWidth;
FrameLayout parent = ((FrameLayout) toolbarShowNewPostsBtn.getParent()); // margin (4) + padding (12) + icon (24) + margin (8) + chevron (16) + padding (12)
if (padding == parent.getPaddingStart()) return; int switcherWidth=V.dp(76);
FrameLayout parent=((FrameLayout) toolbarShowNewPostsBtn.getParent());
if(actionsWidth==parent.getPaddingStart()) return;
int paddingMax=Math.max(actionsWidth, switcherWidth);
int paddingEnd=(Math.max(0, switcherWidth-actionsWidth));
// toolbar frame goes from screen edge to beginning of right-aligned option buttons. // toolbar frame goes from screen edge to beginning of right-aligned option buttons.
// centering button by applying the same space on the left // centering button by applying the same space on the left
parent.setPaddingRelative(padding, 0, 0, 0); parent.setPaddingRelative(paddingMax, 0, paddingEnd, 0);
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth - padding * 2); toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth-paddingMax*2);
switcher.setPivotX(V.dp(28)); // padding + half of icon switcher.setPivotX(V.dp(28)); // padding + half of icon
switcher.setPivotY(switcher.getHeight() / 2f); switcher.setPivotY(switcher.getHeight() / 2f);
@@ -266,7 +270,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
new GetLists().setCallback(new Callback<>() { new GetLists().setCallback(new Callback<>() {
@Override @Override
public void onSuccess(List<ListTimeline> lists) { public void onSuccess(List<FollowList> lists) {
updateList(lists, listItems); updateList(lists, listItems);
} }
@@ -291,7 +295,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
new GetAnnouncements(false).setCallback(new Callback<>() { new GetAnnouncements(false).setCallback(new Callback<>() {
@Override @Override
public void onSuccess(List<Announcement> result) { public void onSuccess(List<Announcement> result) {
if (getActivity() == null) return; if(getActivity()==null) return;
if (result.stream().anyMatch(a -> !a.read)) { if (result.stream().anyMatch(a -> !a.read)) {
announcementsBadged = true; announcementsBadged = true;
announcements.setVisible(false); announcements.setVisible(false);
@@ -385,7 +389,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
} }
private void updateOverflowMenu() { private void updateOverflowMenu() {
if (getActivity() == null) return; if(getActivity()==null) return;
Menu m = overflowPopup.getMenu(); Menu m = overflowPopup.getMenu();
m.clear(); m.clear();
overflowPopup.inflate(R.menu.home_overflow); overflowPopup.inflate(R.menu.home_overflow);
@@ -404,9 +408,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
addListsToOverflowMenu(); addListsToOverflowMenu();
addHashtagsToOverflowMenu(); addHashtagsToOverflowMenu();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) { if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI())
m.setGroupDividerEnabled(true); m.setGroupDividerEnabled(true);
}
} }
@Override @Override
@@ -509,7 +512,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
int id = item.getItemId(); int id = item.getItemId();
ListTimeline list; FollowList list;
Hashtag hashtag; Hashtag hashtag;
if (item.getItemId() == R.id.menu_back) { if (item.getItemId() == R.id.menu_back) {
@@ -528,9 +531,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal()); if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args); Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) { } else if ((hashtag = hashtagsItems.get(id)) != null) {
args.putString("hashtag", hashtag.name); UiUtils.openHashtagTimeline(getContext(), accountID, hashtag);
args.putBoolean("following", hashtag.following);
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
} }
return true; return true;
} }
@@ -700,13 +701,13 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Subscribe @Subscribe
public void onListDeletedEvent(ListDeletedEvent event) { public void onListDeletedEvent(ListDeletedEvent event) {
handleListEvent(listItems, l -> l.id.equals(event.id), false, null); handleListEvent(listItems, l -> l.id.equals(event.listID), false, null);
} }
@Subscribe @Subscribe
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) { public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
handleListEvent(listItems, l -> l.id.equals(event.id), true, () -> { handleListEvent(listItems, l -> l.id.equals(event.id), true, () -> {
ListTimeline list = new ListTimeline(); FollowList list = new FollowList();
list.id = event.id; list.id = event.id;
list.title = event.title; list.title = event.title;
list.repliesPolicy = event.repliesPolicy; list.repliesPolicy = event.repliesPolicy;

View File

@@ -11,7 +11,6 @@ import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
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.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.CacheablePaginatedResponse; import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
@@ -20,6 +19,7 @@ import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -49,16 +49,6 @@ public class HomeTimelineFragment extends StatusListFragment {
loadData(); loadData();
} }
private boolean typeFilterPredicate(Status s) {
AccountLocalPreferences lp=getLocalPrefs();
return (lp.showReplies || s.inReplyToId == null) &&
(lp.showBoosts || s.reblog == null);
}
private List<Status> filterPosts(List<Status> items) {
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
}
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance() AccountSessionManager.getInstance()
@@ -66,11 +56,12 @@ public class HomeTimelineFragment extends StatusListFragment {
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){ .getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
@Override @Override
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){ public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
if (getActivity() == null) return; if(getActivity()==null) return;
List<Status> filteredItems = filterPosts(result.items); boolean empty=result.items.isEmpty();
maxID=result.maxID; maxID=result.maxID;
onDataLoaded(filteredItems, !result.items.isEmpty()); AccountSessionManager.get(accountID).filterStatuses(result.items, getFilterContext());
if(result.isFromCache()) onDataLoaded(result.items, !empty);
if(result.isFromCache() && GlobalUserPreferences.loadNewPosts)
loadNewPosts(); loadNewPosts();
} }
}); });
@@ -83,7 +74,7 @@ public class HomeTimelineFragment extends StatusListFragment {
list.addOnScrollListener(new RecyclerView.OnScrollListener(){ list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override @Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){ if(parent!=null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
parent.hideNewPostsButton(); parent.hideNewPostsButton();
} }
} }
@@ -96,7 +87,7 @@ public class HomeTimelineFragment extends StatusListFragment {
if(!getArguments().getBoolean("noAutoLoad")){ if(!getArguments().getBoolean("noAutoLoad")){
if(!loaded && !dataLoading){ if(!loaded && !dataLoading){
loadData(); loadData();
}else if(!dataLoading){ }else if(!dataLoading && GlobalUserPreferences.loadNewPosts){
loadNewPosts(); loadNewPosts();
} }
} }
@@ -126,38 +117,46 @@ public class HomeTimelineFragment extends StatusListFragment {
} }
public void onStatusCreated(Status status){ public void onStatusCreated(Status status){
if(status.reblog!=null) return;
prependItems(Collections.singletonList(status), true); prependItems(Collections.singletonList(status), true);
} }
private void loadNewPosts(){ private void loadNewPosts(){
if (!GlobalUserPreferences.loadNewPosts) return;
dataLoading=true; dataLoading=true;
// we only care about the data that was actually retrieved from the timeline api since
// user-created statuses are probably in the wrong position
List<Status> dataFromTimeline=data.stream().filter(s->!s.fromStatusCreated).collect(Collectors.toList());
// The idea here is that we request the timeline such that if there are fewer than `limit` posts, // The idea here is that we request the timeline such that if there are fewer than `limit` posts,
// we'll get the currently topmost post as last in the response. This way we know there's no gap // we'll get the currently topmost post as last in the response. This way we know there's no gap
// between the existing and newly loaded parts of the timeline. // between the existing and newly loaded parts of the timeline.
String sinceID=data.size()>1 ? data.get(1).id : "1"; String sinceID=dataFromTimeline.size()>1 ? dataFromTimeline.get(1).id : "1";
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
currentRequest=null; currentRequest=null;
dataLoading=false; dataLoading=false;
result = filterPosts(result); refreshDone();
if(result.isEmpty() || getActivity()==null) if(result.isEmpty() || getActivity()==null)
return; return;
Status last=result.get(result.size()-1); Status last=result.get(result.size()-1);
List<Status> toAdd; List<Status> toAdd;
if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one if(!dataFromTimeline.isEmpty() && last.id.equals(dataFromTimeline.get(0).id)){ // This part intersects with the existing one
toAdd=result.subList(0, result.size()-1); // Remove the already known last post toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post
}else{ }else{
result.get(result.size()-1).hasGapAfter=true; last.hasGapAfter=last.id;
toAdd=result; toAdd=result;
} }
if(!toAdd.isEmpty())
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(new ArrayList<>(toAdd), false);
// removing statuses that come up as duplicates (hopefully only posts and boosts that were locally created
// and thus were already prepended to the timeline earlier)
List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList());
toAdd.removeIf(s->existingIds.contains(s.getID()));
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext()); AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
if(!toAdd.isEmpty()){ if(!toAdd.isEmpty()){
prependItems(toAdd, true); prependItems(toAdd, true);
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton(); if(parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
} }
} }
@@ -165,6 +164,7 @@ public class HomeTimelineFragment extends StatusListFragment {
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
currentRequest=null; currentRequest=null;
dataLoading=false; dataLoading=false;
refreshDone();
} }
}) })
.exec(accountID); .exec(accountID);
@@ -182,10 +182,10 @@ public class HomeTimelineFragment extends StatusListFragment {
gap.loading=true; gap.loading=true;
dataLoading=true; dataLoading=true;
String maxID = null; String maxID=null;
String minID = null; String minID=null;
if (downwards) { if (downwards) {
maxID = item.getItemID(); maxID=item.getItem().getMaxID();
} else { } else {
int gapPos=displayItems.indexOf(gap); int gapPos=displayItems.indexOf(gap);
StatusDisplayItem nextItem=displayItems.get(gapPos + 1); StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
@@ -202,15 +202,17 @@ public class HomeTimelineFragment extends StatusListFragment {
int gapPos=displayItems.indexOf(gap); int gapPos=displayItems.indexOf(gap);
if(gapPos==-1) if(gapPos==-1)
return; return;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
if(result.isEmpty()){ if(result.isEmpty()){
displayItems.remove(gapPos); displayItems.remove(gapPos);
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos); adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
Status gapStatus=getStatusByID(gap.parentID); Status gapStatus=getStatusByID(gap.parentID);
if(gapStatus!=null){ if(gapStatus!=null){
gapStatus.hasGapAfter=false; gapStatus.hasGapAfter=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
} }
}else{ }else{
// TODO: refactor this code. it's too long. incomprehensible, even
if(downwards) { if(downwards) {
Set<String> idsBelowGap=new HashSet<>(); Set<String> idsBelowGap=new HashSet<>();
boolean belowGap=false; boolean belowGap=false;
@@ -220,7 +222,7 @@ public class HomeTimelineFragment extends StatusListFragment {
idsBelowGap.add(s.id); idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){ }else if(s.id.equals(gap.parentID)){
belowGap=true; belowGap=true;
s.hasGapAfter=false; s.hasGapAfter=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{ }else{
gapPostIndex++; gapPostIndex++;
@@ -233,7 +235,8 @@ public class HomeTimelineFragment extends StatusListFragment {
break; break;
} }
if(endIndex==result.size()){ if(endIndex==result.size()){
result.get(result.size()-1).hasGapAfter=true; Status last=result.get(result.size()-1);
last.hasGapAfter=last.id;
}else{ }else{
result=result.subList(0, endIndex); result=result.subList(0, endIndex);
} }
@@ -278,7 +281,7 @@ public class HomeTimelineFragment extends StatusListFragment {
.filter(s->Objects.equals(s.id, gap.parentID)) .filter(s->Objects.equals(s.id, gap.parentID))
.findFirst(); .findFirst();
if (gapStatus.isPresent()) { if (gapStatus.isPresent()) {
gapStatus.get().hasGapAfter=false; gapStatus.get().hasGapAfter=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
} }
targetList.clear(); targetList.clear();
@@ -329,7 +332,7 @@ public class HomeTimelineFragment extends StatusListFragment {
currentRequest=null; currentRequest=null;
dataLoading=false; dataLoading=false;
} }
if (parent != null) parent.hideNewPostsButton(); if(parent!=null) parent.hideNewPostsButton();
super.onRefresh(); super.onRefresh();
} }

View File

@@ -0,0 +1,324 @@
package org.joinmastodon.android.fragments;
import android.net.Uri;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowInsets;
import android.widget.ImageButton;
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.HeaderPaginationRequest;
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
import org.joinmastodon.android.api.requests.lists.GetListAccounts;
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
import org.joinmastodon.android.events.AccountAddedToListEvent;
import org.joinmastodon.android.events.AccountRemovedFromListEvent;
import org.joinmastodon.android.fragments.account_list.AddListMembersFragment;
import org.joinmastodon.android.fragments.account_list.PaginatedAccountListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.ActionModeHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.utils.V;
public class ListMembersFragment extends PaginatedAccountListFragment{
private static final int ADD_MEMBER_RESULT=600;
private ImageButton fab;
private FollowList followList;
private boolean inSelectionMode;
private Set<String> selectedAccounts=new HashSet<>();
private ActionMode actionMode;
private MenuItem deleteItem;
public ListMembersFragment(){
setListLayoutId(R.layout.recycler_fragment_with_fab);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
followList=Parcels.unwrap(getArguments().getParcelable("list"));
setTitle(R.string.list_members);
setHasOptionsMenu(true);
E.register(this);
}
@Override
public void onDestroy(){
super.onDestroy();
E.unregister(this);
}
@Override
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetListAccounts(followList.id, maxID, count);
}
@Override
protected MastodonAPIRequest loadRemoteInfo(){
return null;
}
@Override
public Object getCurrentInfo(){
return null;
}
@Override
public String getRemoteDomain(){
return null;
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(inSelectionMode ? AccountViewHolder.AccessoryType.CHECKBOX : AccountViewHolder.AccessoryType.MENU, false);
holder.setOnClickListener(this::onItemClick);
holder.setOnLongClickListener(this::onItemLongClick);
holder.getContextMenu().getMenu().add(0, R.id.remove_from_list, 0, R.string.remove_from_list);
holder.setOnCustomMenuItemSelectedListener(item->onItemMenuItemSelected(holder, item));
}
@Override
protected void onBindViewHolder(AccountViewHolder holder){
super.onBindViewHolder(holder);
holder.setStyle(inSelectionMode ? AccountViewHolder.AccessoryType.CHECKBOX : AccountViewHolder.AccessoryType.MENU, false);
if(inSelectionMode){
holder.setChecked(selectedAccounts.contains(holder.getItem().account.id));
}
}
@Override
public boolean wantsLightStatusBar(){
if(actionMode!=null)
return UiUtils.isDarkTheme();
return super.wantsLightStatusBar();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.selectable_list, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
int id=item.getItemId();
if(id==R.id.select){
enterSelectionMode();
}else if(id==R.id.select_all){
for(AccountViewModel a:(ArrayList<AccountViewModel>)data){
selectedAccounts.add(a.account.id);
}
enterSelectionMode();
}
return true;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setImageResource(R.drawable.ic_fluent_add_24_regular);
fab.setContentDescription(getString(R.string.add_list_member));
fab.setOnClickListener(v->onFabClick());
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
super.onApplyWindowInsets(insets);
UiUtils.applyBottomInsetToFAB(fab, insets);
}
@Override
public void onFragmentResult(int reqCode, boolean success, Bundle result){
if(reqCode==ADD_MEMBER_RESULT && success){
Account acc=Objects.requireNonNull(Parcels.unwrap(result.getParcelable("selectedAccount")));
addAccounts(List.of(acc));
}
}
@Subscribe
public void onAccountRemovedFromList(AccountRemovedFromListEvent ev){
if(ev.accountID.equals(accountID) && ev.listID.equals(followList.id)){
removeAccountRows(Set.of(ev.targetAccountID));
}
}
@Subscribe
public void onAccountAddedToList(AccountAddedToListEvent ev){
if(ev.accountID.equals(accountID) && ev.listID.equals(followList.id)){
data.add(new AccountViewModel(ev.account, accountID));
list.getAdapter().notifyItemInserted(data.size()-1);
}
}
private void onFabClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.goForResult(getActivity(), AddListMembersFragment.class, args, ADD_MEMBER_RESULT, this);
}
private void onItemClick(AccountViewHolder holder){
if(inSelectionMode){
String id=holder.getItem().account.id;
if(selectedAccounts.contains(id)){
selectedAccounts.remove(id);
holder.setChecked(false);
}else{
selectedAccounts.add(id);
holder.setChecked(true);
}
updateActionModeTitle();
deleteItem.setEnabled(!selectedAccounts.isEmpty());
return;
}
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(holder.getItem().account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
private boolean onItemLongClick(AccountViewHolder holder){
if(inSelectionMode)
return false;
selectedAccounts.add(holder.getItem().account.id);
enterSelectionMode();
return true;
}
private void onItemMenuItemSelected(AccountViewHolder holder, MenuItem item){
int id=item.getItemId();
if(id==R.id.remove_from_list){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.confirm_remove_list_member)
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(Set.of(holder.getItem().account.id)))
.setNegativeButton(R.string.cancel, null)
.show();
}
}
private void updateItemsForSelectionModeTransition(){
list.getAdapter().notifyItemRangeChanged(0, data.size());
}
private void enterSelectionMode(){
inSelectionMode=true;
updateItemsForSelectionModeTransition();
V.setVisibilityAnimated(fab, View.INVISIBLE);
actionMode=ActionModeHelper.startActionMode(this, ()->elevationOnScrollListener.getCurrentStatusBarColor(), new ActionMode.Callback(){
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu){
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
mode.getMenuInflater().inflate(R.menu.settings_filter_words_action_mode, menu);
deleteItem=menu.findItem(R.id.delete);
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.confirm_remove_list_members)
.setPositiveButton(R.string.remove, (dlg, which)->removeAccounts(new HashSet<>(selectedAccounts)))
.setNegativeButton(R.string.cancel, null)
.show();
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode){
actionMode=null;
inSelectionMode=false;
selectedAccounts.clear();
updateItemsForSelectionModeTransition();
V.setVisibilityAnimated(fab, View.VISIBLE);
}
});
updateActionModeTitle();
}
private void updateActionModeTitle(){
actionMode.setTitle(getResources().getQuantityString(R.plurals.x_items_selected, selectedAccounts.size(), selectedAccounts.size()));
}
private void removeAccounts(Set<String> ids){
new RemoveAccountsFromList(followList.id, ids)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Void result){
if(inSelectionMode)
actionMode.finish();
removeAccountRows(ids);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, true)
.exec(accountID);
}
private void addAccounts(Collection<Account> accounts){
new AddAccountsToList(followList.id, accounts.stream().map(a->a.id).collect(Collectors.toSet()))
.setCallback(new Callback<>(){
@Override
public void onSuccess(Void result){
for(Account acc:accounts){
data.add(new AccountViewModel(acc, accountID));
}
list.getAdapter().notifyItemRangeInserted(data.size()-accounts.size(), accounts.size());
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, true)
.exec(accountID);
}
private void removeAccountRows(Set<String> ids){
for(int i=data.size()-1;i>=0;i--){
if(ids.contains(((ArrayList<AccountViewModel>)data).get(i).account.id)){
data.remove(i);
list.getAdapter().notifyItemRemoved(i);
}
}
}
@Override
public Uri getWebUri(Uri.Builder base){
return null;
}
}

View File

@@ -16,19 +16,18 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetList; import org.joinmastodon.android.api.requests.lists.GetList;
import org.joinmastodon.android.api.requests.lists.UpdateList; import org.joinmastodon.android.api.requests.lists.UpdateList;
import org.joinmastodon.android.api.requests.timelines.GetListTimeline; import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
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.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ListEditor; import org.joinmastodon.android.ui.views.ListEditor;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -40,7 +39,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
private String listID; private String listID;
private String listTitle; private String listTitle;
@Nullable @Nullable
private ListTimeline.RepliesPolicy repliesPolicy; private FollowList.RepliesPolicy repliesPolicy;
private boolean exclusive; private boolean exclusive;
@Override @Override
@@ -55,19 +54,19 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
listID = args.getString("listID"); listID = args.getString("listID");
listTitle = args.getString("listTitle"); listTitle = args.getString("listTitle");
exclusive = args.getBoolean("listIsExclusive"); exclusive = args.getBoolean("listIsExclusive");
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)]; repliesPolicy = FollowList.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
setTitle(listTitle); setTitle(listTitle);
setHasOptionsMenu(true); setHasOptionsMenu(true);
new GetList(listID).setCallback(new Callback<>() { new GetList(listID).setCallback(new Callback<>() {
@Override @Override
public void onSuccess(ListTimeline listTimeline) { public void onSuccess(FollowList followList) {
if (getActivity() == null) return; if(getActivity()==null) return;
// TODO: save updated info // TODO: save updated info
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title); if (!followList.title.equals(listTitle)) setTitle(followList.title);
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) { if (followList.repliesPolicy != null && !followList.repliesPolicy.equals(repliesPolicy)) {
repliesPolicy = listTimeline.repliesPolicy; repliesPolicy = followList.repliesPolicy;
} }
} }
@@ -98,10 +97,10 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
.setPositiveButton(R.string.save, (d, which) -> { .setPositiveButton(R.string.save, (d, which) -> {
String newTitle = editor.getTitle().trim(); String newTitle = editor.getTitle().trim();
setTitle(newTitle); setTitle(newTitle);
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() { new UpdateList(listID, newTitle, editor.getRepliesPolicy(), editor.isExclusive()).setCallback(new Callback<>() {
@Override @Override
public void onSuccess(ListTimeline list) { public void onSuccess(FollowList list) {
if (getActivity() == null) return; if(getActivity()==null) return;
setTitle(list.title); setTitle(list.title);
listTitle = list.title; listTitle = list.title;
repliesPolicy = list.repliesPolicy; repliesPolicy = list.repliesPolicy;
@@ -120,7 +119,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
.show(); .show();
} else if (item.getItemId() == R.id.delete) { } else if (item.getItemId() == R.id.delete) {
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> { UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
E.post(new ListDeletedEvent(listID)); E.post(new ListDeletedEvent(accountID, listID));
Nav.finish(this); Nav.finish(this);
}); });
} }
@@ -134,13 +133,14 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
@Override @Override
protected void doLoadData(int offset, int count) { protected void doLoadData(int offset, int count) {
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetListTimeline(listID, getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this) { .setCallback(new SimpleCallback<>(this) {
@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, getFilterContext())).collect(Collectors.toList()); boolean more=applyMaxID(result);
onDataLoaded(result, !result.isEmpty()); AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
} }
}) })
.exec(accountID); .exec(accountID);

View File

@@ -18,13 +18,14 @@ 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.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
import org.joinmastodon.android.api.requests.lists.AddAccountsToList; import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
import org.joinmastodon.android.api.requests.lists.CreateList; import org.joinmastodon.android.api.requests.lists.CreateList;
import org.joinmastodon.android.api.requests.lists.GetLists; import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList; import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
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.model.ListTimeline; import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.views.ListEditor; import org.joinmastodon.android.ui.views.ListEditor;
@@ -42,7 +43,7 @@ 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 ListsFragment extends MastodonRecyclerFragment<ListTimeline> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri { public class ListsFragment extends MastodonRecyclerFragment<FollowList> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
private String accountID; private String accountID;
private String profileAccountId; private String profileAccountId;
private final HashMap<String, Boolean> userInListBefore = new HashMap<>(); private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
@@ -97,9 +98,9 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
.setIcon(R.drawable.ic_fluent_people_add_28_regular) .setIcon(R.drawable.ic_fluent_people_add_28_regular)
.setView(editor) .setView(editor)
.setPositiveButton(R.string.sk_create, (d, which) -> .setPositiveButton(R.string.sk_create, (d, which) ->
new CreateList(editor.getTitle(), editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() { new CreateList(editor.getTitle(), editor.getRepliesPolicy(), editor.isExclusive()).setCallback(new Callback<>() {
@Override @Override
public void onSuccess(ListTimeline list) { public void onSuccess(FollowList list) {
data.add(0, list); data.add(0, list);
adapter.notifyItemRangeInserted(0, 1); adapter.notifyItemRangeInserted(0, 1);
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.exclusive, list.repliesPolicy)); E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.exclusive, list.repliesPolicy));
@@ -120,10 +121,10 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
private void saveListMembership(String listId, boolean isMember) { private void saveListMembership(String listId, boolean isMember) {
userInList.put(listId, isMember); userInList.put(listId, isMember);
List<String> accountIdList = Collections.singletonList(profileAccountId); List<String> accountIdList = Collections.singletonList(profileAccountId);
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList); ResultlessMastodonAPIRequest req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
req.setCallback(new Callback<>() { req.setCallback(new Callback<>() {
@Override @Override
public void onSuccess(Object o) {} public void onSuccess(Void o) {}
@Override @Override
public void onError(ErrorResponse error) { public void onError(ErrorResponse error) {
@@ -139,19 +140,19 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists()) currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
.setCallback(new SimpleCallback<>(this) { .setCallback(new SimpleCallback<>(this) {
@Override @Override
public void onSuccess(List<ListTimeline> lists) { public void onSuccess(List<FollowList> lists) {
if (getActivity() == null) return; if(getActivity()==null) return;
for (ListTimeline l : lists) userInListBefore.put(l.id, true); for (FollowList l : lists) userInListBefore.put(l.id, true);
userInList.putAll(userInListBefore); userInList.putAll(userInListBefore);
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false); if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
if (profileAccountId == null) return; if (profileAccountId == null) return;
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) { currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
@Override @Override
public void onSuccess(List<ListTimeline> allLists) { public void onSuccess(List<FollowList> allLists) {
if (getActivity() == null) return; if(getActivity()==null) return;
List<ListTimeline> newLists = new ArrayList<>(); List<FollowList> newLists = new ArrayList<>();
for (ListTimeline l : allLists) { for (FollowList l : allLists) {
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l); if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
if (!userInListBefore.containsKey(l.id)) { if (!userInListBefore.containsKey(l.id)) {
userInListBefore.put(l.id, false); userInListBefore.put(l.id, false);
@@ -169,8 +170,8 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
@Subscribe @Subscribe
public void onListDeletedEvent(ListDeletedEvent event) { public void onListDeletedEvent(ListDeletedEvent event) {
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i); FollowList item = data.get(i);
if (item.id.equals(event.id)) { if (item.id.equals(event.listID)) {
data.remove(i); data.remove(i);
adapter.notifyItemRemoved(i); adapter.notifyItemRemoved(i);
break; break;
@@ -181,7 +182,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
@Subscribe @Subscribe
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) { public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
for (int i = 0; i < data.size(); i++) { for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i); FollowList item = data.get(i);
if (item.id.equals(event.id)) { if (item.id.equals(event.id)) {
item.title = event.title; item.title = event.title;
item.repliesPolicy = event.repliesPolicy; item.repliesPolicy = event.repliesPolicy;
@@ -230,7 +231,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
} }
} }
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{ private class ListViewHolder extends BindableViewHolder<FollowList> implements UsableRecyclerView.Clickable{
private final TextView title; private final TextView title;
private final CheckBox listToggle; private final CheckBox listToggle;
@@ -241,7 +242,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
} }
@Override @Override
public void onBind(ListTimeline item) { public void onBind(FollowList item) {
title.setText(item.title); title.setText(item.title);
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable( title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(
item.exclusive ? R.drawable.ic_fluent_rss_24_regular : R.drawable.ic_fluent_people_24_regular item.exclusive ? R.drawable.ic_fluent_rss_24_regular : R.drawable.ic_fluent_people_24_regular

View File

@@ -0,0 +1,95 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.tags.GetFollowedTags;
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
import org.joinmastodon.android.fragments.settings.BaseSettingsFragment;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.viewmodel.ListItemWithOptionsMenu;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
public class ManageFollowedHashtagsFragment extends BaseSettingsFragment<Hashtag> implements ListItemWithOptionsMenu.OptionsMenuListener<Hashtag>{
private String maxID;
public ManageFollowedHashtagsFragment(){
super(100);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.manage_hashtags);
loadData();
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetFollowedTags(offset>0 ? maxID : null, count)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(HeaderPaginationList<Hashtag> result){
maxID=null;
if(result.nextPageUri!=null)
maxID=result.nextPageUri.getQueryParameter("max_id");
onDataLoaded(result.stream().map(t->{
int posts=t.getWeekPosts();
return new ListItemWithOptionsMenu<>(t.name, getResources().getQuantityString(R.plurals.x_posts_recently, posts, posts), ManageFollowedHashtagsFragment.this,
R.drawable.ic_fluent_tag_24_regular, ManageFollowedHashtagsFragment.this::onItemClick, t, false);
}).collect(Collectors.toList()), maxID!=null);
}
})
.exec(accountID);
}
@Override
public void onConfigureListItemOptionsMenu(ListItemWithOptionsMenu<Hashtag> item, Menu menu){
menu.clear();
menu.add(getString(R.string.unfollow_user, "#"+item.parentObject.name));
}
@Override
public void onListItemOptionSelected(ListItemWithOptionsMenu<Hashtag> item, MenuItem menuItem){
new M3AlertDialogBuilder(getActivity())
.setTitle(getString(R.string.unfollow_confirmation, "#"+item.parentObject.name))
.setPositiveButton(R.string.unfollow, (dlg, which)->doUnfollow(item))
.setNegativeButton(R.string.cancel, null)
.show();
}
private void onItemClick(ListItemWithOptionsMenu<Hashtag> item){
UiUtils.openHashtagTimeline(getActivity(), accountID, item.parentObject);
}
private void doUnfollow(ListItemWithOptionsMenu<Hashtag> item){
new SetTagFollowed(item.parentObject.name, false)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
int index=data.indexOf(item);
if(index==-1)
return;
data.remove(index);
list.getAdapter().notifyItemRemoved(index);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, true)
.exec(accountID);
}
}

View File

@@ -0,0 +1,199 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowInsets;
import android.widget.ImageButton;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.DeleteList;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.ListCreatedEvent;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedEvent;
import org.joinmastodon.android.fragments.settings.BaseSettingsFragment;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.model.viewmodel.ListItemWithOptionsMenu;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
public class ManageListsFragment extends BaseSettingsFragment<FollowList> implements ListItemWithOptionsMenu.OptionsMenuListener<FollowList>{
private ImageButton fab;
public ManageListsFragment(){
setListLayoutId(R.layout.recycler_fragment_with_fab);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.manage_lists);
loadData();
setRefreshEnabled(true);
E.register(this);
}
@Override
public void onDestroy(){
super.onDestroy();
E.unregister(this);
}
@Override
protected void doLoadData(int offset, int count){
Callback<List<FollowList>> callback=new SimpleCallback<>(this){
@Override
public void onSuccess(List<FollowList> result){
onDataLoaded(result.stream().map(ManageListsFragment.this::makeItem).collect(Collectors.toList()), false);
}
};
if(refreshing){
AccountSessionManager.get(accountID)
.getCacheController()
.reloadLists(callback);
}else{
AccountSessionManager.get(accountID)
.getCacheController()
.getLists(callback);
}
}
private ListItem<FollowList> makeItem(FollowList l){
return new ListItemWithOptionsMenu<>(l.title, null, ManageListsFragment.this, R.drawable.ic_list_alt_24px, ManageListsFragment.this::onListClick, l, false);
}
private void onListClick(ListItemWithOptionsMenu<FollowList> item){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("list", Parcels.wrap(item.parentObject));
Nav.go(getActivity(), ListTimelineFragment.class, args);
}
@Override
public void onConfigureListItemOptionsMenu(ListItemWithOptionsMenu<FollowList> item, Menu menu){
menu.add(0, R.id.edit, 0, R.string.edit_list);
menu.add(0, R.id.delete, 1, R.string.delete_list);
}
@Override
public void onListItemOptionSelected(ListItemWithOptionsMenu<FollowList> item, MenuItem menuItem){
int id=menuItem.getItemId();
if(id==R.id.edit){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("list", Parcels.wrap(item.parentObject));
Nav.go(getActivity(), EditListFragment.class, args);
}else if(id==R.id.delete){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.delete_list)
.setMessage(getString(R.string.delete_list_confirm, item.parentObject.title))
.setPositiveButton(R.string.delete, (dlg, which)->doDeleteList(item.parentObject))
.setNegativeButton(R.string.cancel, null)
.show();
}
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setImageResource(R.drawable.ic_fluent_add_24_regular);
fab.setContentDescription(getString(R.string.create_list));
fab.setOnClickListener(v->onFabClick());
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
super.onApplyWindowInsets(insets);
UiUtils.applyBottomInsetToFAB(fab, insets);
}
private void doDeleteList(FollowList list){
new DeleteList(list.id)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Void result){
for(int i=0;i<data.size();i++){
if(data.get(i).parentObject==list){
data.remove(i);
itemsAdapter.notifyItemRemoved(i);
AccountSessionManager.get(accountID).getCacheController().deleteList(list.id);
break;
}
}
}
@Override
public void onError(ErrorResponse error){
Activity activity=getActivity();
if(activity==null)
return;
error.showToast(activity);
}
})
.wrapProgress(getActivity(), R.string.loading, true)
.exec(accountID);
}
@Subscribe
public void onListUpdated(ListUpdatedEvent ev){
if(!ev.accountID.equals(accountID))
return;
for(ListItem<FollowList> item:data){
if(item.parentObject.id.equals(ev.list.id)){
item.parentObject=ev.list;
item.title=ev.list.title;
rebindItem(item);
break;
}
}
}
@Subscribe
public void onListDeleted(ListDeletedEvent ev){
if(!ev.accountID.equals(accountID))
return;
int i=0;
for(ListItem<FollowList> item:data){
if(item.parentObject.id.equals(ev.listID)){
data.remove(i);
itemsAdapter.notifyItemRemoved(i);
break;
}
i++;
}
}
@Subscribe
public void onListCreated(ListCreatedEvent ev){
if(!ev.accountID.equals(accountID))
return;
ListItem<FollowList> item=makeItem(ev.list);
data.add(item);
((List<ListItem<FollowList>>)data).sort(Comparator.comparing(l->l.parentObject.title));
itemsAdapter.notifyItemInserted(data.indexOf(item));
}
private void onFabClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), CreateListFragment.class, args);
}
}

View File

@@ -7,6 +7,7 @@ import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ElevationOnScrollListener; import org.joinmastodon.android.utils.ElevationOnScrollListener;
@@ -40,11 +41,13 @@ public abstract class MastodonRecyclerFragment<T> extends BaseRecyclerFragment<T
@CallSuper @CallSuper
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
if(wantsElevationOnScrollEffect()) if (getParentFragment() instanceof HasElevationOnScrollListener elevator)
list.addOnScrollListener(elevator.getElevationOnScrollListener());
else if(wantsElevationOnScrollEffect())
list.addOnScrollListener(elevationOnScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, getViewsForElevationEffect())); list.addOnScrollListener(elevationOnScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, getViewsForElevationEffect()));
if(refreshLayout!=null){ if(refreshLayout!=null)
setRefreshLayoutColors(refreshLayout); setRefreshLayoutColors(refreshLayout);
}
} }
@Override @Override

View File

@@ -3,6 +3,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.app.assist.AssistContent;
import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@@ -15,10 +16,6 @@ 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 com.squareup.otto.Subscribe;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
@@ -31,6 +28,8 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.FollowRequestHandledEvent; import org.joinmastodon.android.events.FollowRequestHandledEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.SimpleViewHolder; 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;
@@ -39,6 +38,11 @@ import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.joinmastodon.android.utils.ObjectIdComparator; import org.joinmastodon.android.utils.ObjectIdComparator;
import org.joinmastodon.android.utils.ProvidesAssistContent; import org.joinmastodon.android.utils.ProvidesAssistContent;
import java.util.Arrays;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@@ -46,7 +50,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
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 NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent, HasElevationOnScrollListener { public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent, HasElevationOnScrollListener, HasAccountID {
TabLayout tabLayout; TabLayout tabLayout;
private ViewPager2 pager; private ViewPager2 pager;
@@ -54,7 +58,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
private View tabsDivider; private View tabsDivider;
private TabLayoutMediator tabLayoutMediator; private TabLayoutMediator tabLayoutMediator;
String unreadMarker, realUnreadMarker; String unreadMarker, realUnreadMarker;
private MenuItem markAllReadItem; private MenuItem markAllReadItem, filterItem;
private NotificationsListFragment allNotificationsFragment, mentionsFragment; private NotificationsListFragment allNotificationsFragment, mentionsFragment;
private ElevationOnScrollListener elevationOnScrollListener; private ElevationOnScrollListener elevationOnScrollListener;
@@ -92,9 +96,10 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.notifications, menu); inflater.inflate(R.menu.notifications, menu);
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications); menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
filterItem=menu.findItem(R.id.filter_notifications).setVisible(true);
markAllReadItem=menu.findItem(R.id.mark_all_read); markAllReadItem=menu.findItem(R.id.mark_all_read);
updateMarkAllReadButton(); updateMarkAllReadButton();
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests, R.id.mark_all_read); UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests, R.id.mark_all_read, R.id.filter_notifications);
} }
@Override @Override
@@ -116,11 +121,61 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
if (getCurrentFragment() instanceof NotificationsListFragment nlf) { if (getCurrentFragment() instanceof NotificationsListFragment nlf) {
nlf.resetUnreadBackground(); nlf.resetUnreadBackground();
} }
return true;
} else if (item.getItemId() == R.id.filter_notifications) {
Context ctx = getToolbarContext();
String[] listItems = {
ctx.getString(R.string.notification_type_mentions_and_replies),
ctx.getString(R.string.notification_type_reblog),
ctx.getString(R.string.notification_type_favorite),
ctx.getString(R.string.notification_type_follow),
ctx.getString(R.string.notification_type_poll),
ctx.getString(R.string.sk_notification_type_update),
ctx.getString(R.string.sk_notification_type_posts)
};
boolean[] checkedItems = {
getLocalPrefs().notificationFilters.mention,
getLocalPrefs().notificationFilters.reblog,
getLocalPrefs().notificationFilters.favourite,
getLocalPrefs().notificationFilters.follow,
getLocalPrefs().notificationFilters.poll,
getLocalPrefs().notificationFilters.update,
getLocalPrefs().notificationFilters.status,
};
M3AlertDialogBuilder dialogBuilder = new M3AlertDialogBuilder(ctx);
dialogBuilder.setTitle(R.string.sk_settings_filters);
dialogBuilder.setMultiChoiceItems(listItems, checkedItems, (dialog, which, isChecked) ->checkedItems[which] = isChecked);
dialogBuilder.setPositiveButton(R.string.save, (d, which) -> {
saveFilters(checkedItems);
this.allNotificationsFragment.reload();
}).setNeutralButton(R.string.mo_notification_filter_reset, (d, which) -> {
Arrays.fill(checkedItems, true);
saveFilters(checkedItems);
this.allNotificationsFragment.reload();
}).setNegativeButton(R.string.cancel, (d, which) -> {});
dialogBuilder.create().show();
return true; return true;
} }
return false; return false;
} }
private void saveFilters(boolean[] checkedItems) {
PushSubscription.Alerts filter = getLocalPrefs().notificationFilters;
filter.mention = checkedItems[0];
filter.reblog = checkedItems[1];
filter.favourite = checkedItems[2];
filter.follow = checkedItems[3];
filter.poll = checkedItems[4];
filter.update = checkedItems[5];
filter.status = checkedItems[6];
getLocalPrefs().save();
}
void markAsRead(){ void markAsRead(){
if(allNotificationsFragment.getData().isEmpty()) return; if(allNotificationsFragment.getData().isEmpty()) return;
String id=allNotificationsFragment.getData().get(0).id; String id=allNotificationsFragment.getData().get(0).id;
@@ -184,6 +239,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
public void onPageSelected(int position){ public void onPageSelected(int position){
if (elevationOnScrollListener != null && getCurrentFragment() instanceof IsOnTop f) if (elevationOnScrollListener != null && getCurrentFragment() instanceof IsOnTop f)
elevationOnScrollListener.handleScroll(getContext(), f.isOnTop()); elevationOnScrollListener.handleScroll(getContext(), f.isOnTop());
filterItem.setVisible(position==0);
if(position==0) if(position==0)
return; return;
Fragment _page=getFragmentForPage(position); Fragment _page=getFragmentForPage(position);
@@ -254,7 +310,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; if(getActivity()==null) return;
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty()); getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
} }
@@ -309,6 +365,11 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent); callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
} }
@Override
public String getAccountID(){
return accountID;
}
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{ private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
@NonNull @NonNull
@Override @Override

View File

@@ -7,6 +7,10 @@ import android.graphics.Rect;
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.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import com.squareup.otto.Subscribe; import com.squareup.otto.Subscribe;
@@ -14,6 +18,7 @@ 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;
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
@@ -47,12 +52,13 @@ import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.MergeRecyclerAdapter;
public class NotificationsListFragment extends BaseStatusListFragment<Notification> { public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
private boolean onlyMentions; private boolean onlyMentions;
private boolean onlyPosts;
private String maxID; private String maxID;
private boolean reloadingFromCache; private boolean reloadingFromCache;
private DiscoverInfoBannerHelper bannerHelper; private DiscoverInfoBannerHelper bannerHelper;
private String unreadMarker, realUnreadMarker;
private MenuItem markAllReadItem;
@Override @Override
protected boolean wantsComposeButton() { protected boolean wantsComposeButton() {
@@ -63,13 +69,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
E.register(this); E.register(this);
if(savedInstanceState!=null){ onlyMentions=getArguments().getBoolean("onlyMentions", false);
onlyMentions=savedInstanceState.getBoolean("onlyMentions", false); setHasOptionsMenu(true);
onlyPosts=savedInstanceState.getBoolean("onlyPosts", false);
}
if (onlyPosts) {
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS, accountID);
}
} }
@Override @Override
@@ -81,13 +82,45 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
public void onAttach(Activity activity){ public void onAttach(Activity activity){
super.onAttach(activity); super.onAttach(activity);
onlyMentions=getArguments().getBoolean("onlyMentions", false);
onlyPosts=getArguments().getBoolean("onlyPosts", false);
setTitle(R.string.notifications); setTitle(R.string.notifications);
} }
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Notification n){ protected List<StatusDisplayItem> buildDisplayItems(Notification n){
if(!onlyMentions){
switch(n.type){
case MENTION -> {
if(!getLocalPrefs().notificationFilters.mention)
return new ArrayList<>();
}
case REBLOG -> {
if(!getLocalPrefs().notificationFilters.reblog)
return new ArrayList<>();
}
case FAVORITE, REACTION -> {
if(!getLocalPrefs().notificationFilters.favourite)
return new ArrayList<>();
}
case FOLLOW, FOLLOW_REQUEST -> {
if(!getLocalPrefs().notificationFilters.follow)
return new ArrayList<>();
}
case POLL -> {
if(!getLocalPrefs().notificationFilters.poll)
return new ArrayList<>();
}
case UPDATE -> {
if(!getLocalPrefs().notificationFilters.update)
return new ArrayList<>();
}
case STATUS -> {
if(!getLocalPrefs().notificationFilters.status)
return new ArrayList<>();
}
default -> {}
}
}
NotificationHeaderStatusDisplayItem titleItem; NotificationHeaderStatusDisplayItem titleItem;
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){ if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
titleItem=null; titleItem=null;
@@ -130,7 +163,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance() AccountSessionManager.getInstance()
.getAccount(accountID).getCacheController() .getAccount(accountID).getCacheController()
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing && !reloadingFromCache, new SimpleCallback<>(this){ .getNotifications(offset>0 ? maxID : null, count, onlyMentions, false, refreshing && !reloadingFromCache, new SimpleCallback<>(this){
@Override @Override
public void onSuccess(PaginatedResponse<List<Notification>> result){ public void onSuccess(PaginatedResponse<List<Notification>> result){
if(getActivity()==null) if(getActivity()==null)
@@ -144,6 +177,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
maxID=result.maxID; maxID=result.maxID;
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty()); onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
if(bannerHelper!=null) bannerHelper.onBannerBecameVisible();
reloadingFromCache=false; reloadingFromCache=false;
if (getParentFragment() instanceof NotificationsFragment nf) { if (getParentFragment() instanceof NotificationsFragment nf) {
nf.updateMarkAllReadButton(); nf.updateMarkAllReadButton();
@@ -151,7 +185,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
} }
}); });
} }
@Override @Override
protected void onRelationshipsLoaded(){ protected void onRelationshipsLoaded(){
if(getActivity()==null) if(getActivity()==null)
@@ -166,13 +200,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
protected void onShown(){ protected void onShown(){
super.onShown(); super.onShown();
if(!dataLoading){ unreadMarker=realUnreadMarker=AccountSessionManager.get(accountID).getLastKnownNotificationsMarker();
if(onlyMentions){ if(!dataLoading && canRefreshWithoutUpsettingUser()){
refresh(); reloadingFromCache=true;
}else{ refresh();
reloadingFromCache=true;
refresh();
}
} }
} }
@@ -194,7 +225,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
list.addItemDecoration(new RecyclerView.ItemDecoration(){ list.addItemDecoration(new RecyclerView.ItemDecoration(){
private Paint paint=new Paint(); private Paint paint=new Paint();
private Rect tmpRect=new Rect(); private Rect tmpRect=new Rect();
@@ -234,13 +264,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
} }
} }
@Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putBoolean("onlyMentions", onlyMentions);
outState.putBoolean("onlyPosts", onlyPosts);
}
private Notification getNotificationByID(String id){ private Notification getNotificationByID(String id){
for(Notification n:data){ for(Notification n:data){
if(n.id.equals(id)) if(n.id.equals(id))
@@ -258,7 +281,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
continue; continue;
Status contentStatus=ntf.status.getContentStatus(); Status contentStatus=ntf.status.getContentStatus();
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){ if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
updatePoll(ntf.id, ntf.status, ev.poll); updatePoll(ntf.id, contentStatus, ev.poll);
} }
} }
} }
@@ -346,11 +369,38 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
adapter.notifyItemRangeRemoved(index, lastIndex-index); adapter.notifyItemRangeRemoved(index, lastIndex-index);
} }
@Override @Override
protected boolean needDividerForExtraItem(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){ protected boolean needDividerForExtraItem(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
return super.needDividerForExtraItem(child, bottomSibling, holder, siblingHolder) || (siblingHolder!=null && siblingHolder.getAbsoluteAdapterPosition()>=adapter.getItemCount()); return super.needDividerForExtraItem(child, bottomSibling, holder, siblingHolder) || (siblingHolder!=null && siblingHolder.getAbsoluteAdapterPosition()>=adapter.getItemCount());
} }
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.notifications, menu);
markAllReadItem=menu.findItem(R.id.mark_all_read);
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
if(item.getItemId()==R.id.mark_all_read){
markAsRead();
resetUnreadBackground();
}
return true;
}
private void markAsRead(){
if(data.isEmpty())
return;
String id=data.get(0).id;
if(ObjectIdComparator.INSTANCE.compare(id, realUnreadMarker)>0){
new SaveMarkers(null, id).exec(accountID);
AccountSessionManager.get(accountID).setNotificationsMarker(id, true);
realUnreadMarker=id;
}
}
void resetUnreadBackground(){ void resetUnreadBackground(){
if (getParentFragment() instanceof NotificationsFragment nf) { if (getParentFragment() instanceof NotificationsFragment nf) {
nf.unreadMarker=nf.realUnreadMarker; nf.unreadMarker=nf.realUnreadMarker;
@@ -362,13 +412,20 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onRefresh(){ public void onRefresh(){
super.onRefresh(); super.onRefresh();
if (getParentFragment() instanceof NotificationsFragment nf) { if (getParentFragment() instanceof NotificationsFragment nf) {
if (!onlyMentions && !onlyPosts) nf.markAsRead(); if (!onlyMentions) nf.markAsRead();
else AccountSessionManager.get(accountID).reloadNotificationsMarker(m->{ else AccountSessionManager.get(accountID).reloadNotificationsMarker(m->{
nf.unreadMarker=nf.realUnreadMarker=m; nf.unreadMarker=nf.realUnreadMarker=m;
nf.updateMarkAllReadButton(); nf.updateMarkAllReadButton();
}); });
} }
resetUnreadBackground(); resetUnreadBackground();
AccountSessionManager.get(accountID).reloadNotificationsMarker(m->{
unreadMarker=realUnreadMarker=m;
});
}
private void updateMarkAllReadButton(){
markAllReadItem.setEnabled(!data.isEmpty() && realUnreadMarker!=null && !realUnreadMarker.equals(data.get(0).id));
} }
@Override @Override
@@ -386,4 +443,20 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
? "/users/" + getSession().self.username + "/interactions" ? "/users/" + getSession().self.username + "/interactions"
: "/notifications").build(); : "/notifications").build();
} }
private boolean canRefreshWithoutUpsettingUser(){
// TODO maybe reload notifications the same way we reload the home timelines, i.e. with gaps and stuff
if(data.size()<=itemsPerPage)
return true;
for(int i=list.getChildCount()-1;i>=0;i--){
if(list.getChildViewHolder(list.getChildAt(i)) instanceof StatusDisplayItem.Holder<?> itemHolder){
String id=itemHolder.getItemID();
for(int j=0;j<data.size();j++){
if(data.get(j).id.equals(id))
return j<itemsPerPage; // Can refresh the list without losing scroll position if it is within the first page
}
}
}
return true;
}
} }

View File

@@ -5,6 +5,7 @@ import android.os.Bundle;
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;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -35,6 +36,8 @@ public class PinnedPostsListFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, false); onDataLoaded(result, false);
} }
}).exec(accountID); }).exec(accountID);

View File

@@ -8,6 +8,8 @@ import android.annotation.SuppressLint;
import android.app.Activity; 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.ClipData;
import android.content.ClipboardManager;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.content.res.Configuration; import android.content.res.Configuration;
@@ -21,9 +23,9 @@ import android.graphics.drawable.LayerDrawable;
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.text.InputType;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.transition.ChangeBounds; import android.transition.ChangeBounds;
import android.transition.Fade; import android.transition.Fade;
import android.transition.TransitionManager; import android.transition.TransitionManager;
@@ -40,8 +42,6 @@ import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
@@ -68,10 +68,10 @@ import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials; import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.requests.instance.GetInstance; import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.account_list.BlocksListFragment; import org.joinmastodon.android.fragments.account_list.BlockedAccountsListFragment;
import org.joinmastodon.android.fragments.account_list.FollowerListFragment; import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
import org.joinmastodon.android.fragments.account_list.FollowingListFragment; import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
import org.joinmastodon.android.fragments.account_list.MutesListFragment; import org.joinmastodon.android.fragments.account_list.MutedAccountsListFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.fragments.settings.SettingsServerFragment; import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
@@ -85,6 +85,7 @@ import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.SimpleViewHolder; import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener; import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.sheets.DecentralizationExplainerSheet;
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.text.CustomEmojiSpan; import org.joinmastodon.android.ui.text.CustomEmojiSpan;
@@ -138,7 +139,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ImageView avatar; private ImageView avatar;
private CoverImageView cover; private CoverImageView cover;
private View avatarBorder; private View avatarBorder;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel; private TextView name, username, usernameDomain, bio, followersCount, followersLabel, followingCount, followingLabel;
private ImageView lockIcon, botIcon; private ImageView lockIcon, botIcon;
private ProgressBarButton actionButton, notifyButton; private ProgressBarButton actionButton, notifyButton;
private ViewPager2 pager; private ViewPager2 pager;
@@ -160,9 +161,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private View actionButtonWrap; private View actionButtonWrap;
private CustomDrawingOrderLinearLayout scrollableContent; private CustomDrawingOrderLinearLayout scrollableContent;
public FrameLayout noteWrap;
public EditText noteEdit;
private String note;
private Account account, remoteAccount; private Account account, remoteAccount;
private String accountID; private String accountID;
private String domain; private String domain;
@@ -193,6 +191,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback()); private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
private ListImageLoaderWrapper imgLoader; private ListImageLoaderWrapper imgLoader;
// profile note
private FrameLayout noteWrap;
private EditText noteEdit;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -213,7 +215,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(!isOwnProfile) if(!isOwnProfile)
loadRelationship(); loadRelationship();
else if (isInstanceAkkoma()) { else if (isInstanceAkkoma()) {
maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields; maxFields = (int) getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
} }
}else{ }else{
profileAccountID=getArguments().getString("profileAccountID"); profileAccountID=getArguments().getString("profileAccountID");
@@ -241,7 +243,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
cover=content.findViewById(R.id.cover); cover=content.findViewById(R.id.cover);
avatarBorder=content.findViewById(R.id.avatar_border); avatarBorder=content.findViewById(R.id.avatar_border);
name=content.findViewById(R.id.name); name=content.findViewById(R.id.name);
usernameWrap=content.findViewById(R.id.username_wrap);
username=content.findViewById(R.id.username); username=content.findViewById(R.id.username);
usernameDomain=content.findViewById(R.id.username_domain);
lockIcon=content.findViewById(R.id.lock_icon); lockIcon=content.findViewById(R.id.lock_icon);
botIcon=content.findViewById(R.id.bot_icon); botIcon=content.findViewById(R.id.bot_icon);
bio=content.findViewById(R.id.bio); bio=content.findViewById(R.id.bio);
@@ -261,7 +265,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bioEdit=content.findViewById(R.id.bio_edit); bioEdit=content.findViewById(R.id.bio_edit);
nameEditWrap=content.findViewById(R.id.name_edit_wrap); nameEditWrap=content.findViewById(R.id.name_edit_wrap);
bioEditWrap=content.findViewById(R.id.bio_edit_wrap); bioEditWrap=content.findViewById(R.id.bio_edit_wrap);
usernameWrap=content.findViewById(R.id.username_wrap);
actionProgress=content.findViewById(R.id.action_progress); actionProgress=content.findViewById(R.id.action_progress);
notifyProgress=content.findViewById(R.id.notify_progress); notifyProgress=content.findViewById(R.id.notify_progress);
fab=content.findViewById(R.id.fab); fab=content.findViewById(R.id.fab);
@@ -278,61 +281,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
avatar.setOutlineProvider(OutlineProviders.roundedRect(24)); avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
avatar.setClipToOutline(true); avatar.setClipToOutline(true);
noteEdit = content.findViewById(R.id.note_edit); noteEdit=content.findViewById(R.id.note_edit);
noteWrap = content.findViewById(R.id.note_edit_wrap); noteWrap=content.findViewById(R.id.note_edit_wrap);
ImageButton noteEditConfirm = content.findViewById(R.id.note_edit_confirm);
noteEditConfirm.setOnClickListener((v -> { noteEdit.setOnFocusChangeListener((v, hasFocus)->{
if (!noteEdit.getText().toString().trim().equals(note)) { if(hasFocus){
savePrivateNote(); hideFab();
} noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE); }else{
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0); showFab();
noteEdit.clearFocus(); savePrivateNote(noteEdit.getText().toString());
}));
noteEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) {
fab.setVisibility(View.INVISIBLE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
0,
fab.getHeight() * 2);
animate.setDuration(300);
fab.startAnimation(animate);
noteEditConfirm.setVisibility(View.VISIBLE);
noteEditConfirm.animate()
.alpha(1.0f)
.setDuration(700);
} else {
fab.setVisibility(View.VISIBLE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
fab.startAnimation(animate);
noteEditConfirm.animate()
.alpha(0.0f)
.setDuration(700);
noteEditConfirm.setVisibility(View.INVISIBLE);
} }
}); });
noteEditConfirm.setOnClickListener((v -> {
if (!noteEdit.getText().toString().trim().equals(note)) {
savePrivateNote();
}
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
noteEdit.clearFocus();
}));
FrameLayout sizeWrapper=new FrameLayout(getActivity()){ FrameLayout sizeWrapper=new FrameLayout(getActivity()){
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
@@ -369,6 +330,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary)); tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
tabbar.setTabTextSize(V.dp(14)); tabbar.setTabTextSize(V.dp(14));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){
case 0 -> R.string.profile_featured;
case 1 -> R.string.profile_timeline;
case 2 -> R.string.profile_about;
default -> throw new IllegalStateException();
}));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){ tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@Override @Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){ public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
@@ -431,28 +398,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick); followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
content.findViewById(R.id.username_wrap).setOnClickListener(v->{ content.findViewById(R.id.username_wrap).setOnClickListener(v->{
try { new DecentralizationExplainerSheet(getActivity(), accountID, account).show();
new GetInstance()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Instance result){
Bundle args = new Bundle();
args.putParcelable("instance", Parcels.wrap(result));
args.putString("account", accountID);
Nav.go(getActivity(), SettingsServerFragment.class, args);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getContext());
}
})
.wrapProgress((Activity) getContext(), R.string.loading, true)
.execRemote(Uri.parse(account.url).getHost());
} catch (NullPointerException ignored) {
// maybe the url was malformed?
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT).show();
}
}); });
content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{ content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{
@@ -460,7 +406,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(!usernameString.contains("@")){ if(!usernameString.contains("@")){
usernameString+="@"+domain; usernameString+="@"+domain;
} }
UiUtils.copyText(username, '@'+usernameString); getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, "@"+usernameString));
UiUtils.maybeShowTextCopiedToast(getActivity());
return true; return true;
}); });
@@ -488,9 +435,49 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
nameEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true)); nameEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
bioEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true)); bioEdit.addTextChangedListener(new SimpleTextWatcher(e->editDirty=true));
// qrCodeButton.setOnClickListener(v->{
// Bundle args=new Bundle();
// args.putString("account", accountID);
// args.putParcelable("targetAccount", Parcels.wrap(account));
// ProfileQrCodeFragment qf=new ProfileQrCodeFragment();
// qf.setArguments(args);
// qf.show(getChildFragmentManager(), "qrDialog");
// });
return sizeWrapper; return sizeWrapper;
} }
private void showPrivateNote(){
noteWrap.setVisibility(View.VISIBLE);
noteEdit.setText(relationship.note);
}
private void hidePrivateNote(){
noteWrap.setVisibility(View.GONE);
noteEdit.setText(null);
}
private void savePrivateNote(String note){
if(note!=null && note.equals(relationship.note)){
updateRelationship();
invalidateOptionsMenu();
return;
}
new SetPrivateNote(profileAccountID, note).setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship result) {
updateRelationship(result);
invalidateOptionsMenu();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountID);
}
private void onAccountLoaded(Account result) { private void onAccountLoaded(Account result) {
account=result; account=result;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account); isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
@@ -517,25 +504,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
V.setVisibilityAnimated(fab, View.VISIBLE); V.setVisibilityAnimated(fab, View.VISIBLE);
} }
public void setNote(String note){
this.note=note;
noteWrap.setVisibility(View.VISIBLE);
noteEdit.setVisibility(View.VISIBLE);
noteEdit.setText(note);
}
private void savePrivateNote(){
new SetPrivateNote(profileAccountID, noteEdit.getText().toString()).setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship result) {}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
@Override @Override
protected void doLoadData(){ protected void doLoadData(){
if (remoteAccount != null) { if (remoteAccount != null) {
@@ -557,7 +525,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.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;
onAccountLoaded(result); onAccountLoaded(result);
} }
}) })
@@ -668,6 +636,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
getChildFragmentManager().putFragment(outState, "pinnedPosts", pinnedPostsFragment); getChildFragmentManager().putFragment(outState, "pinnedPosts", pinnedPostsFragment);
} }
@Override
public void onHidden(){
if (relationship != null && !noteEdit.getText().toString().equals(relationship.note)){
savePrivateNote(noteEdit.getText().toString());
}
}
@Override @Override
public void onConfigurationChanged(Configuration newConfig){ public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
@@ -701,14 +676,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private void bindHeaderView(){ private void bindHeaderView(){
setTitle(account.displayName); setTitle(account.getDisplayName());
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount)); setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest( ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() :
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
V.dp(100), V.dp(100))); V.dp(100), V.dp(100)));
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000)); ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName); SpannableStringBuilder ssb=new SpannableStringBuilder(account.getDisplayName());
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames) if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
HtmlParser.parseCustomEmoji(ssb, account.emojis); HtmlParser.parseCustomEmoji(ssb, account.emojis);
name.setText(ssb); name.setText(ssb);
@@ -730,13 +705,18 @@ 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) // String acct = ((isSelf || account.isRemote)
? account.getFullyQualifiedName() // ? account.getFullyQualifiedName()
: account.acct); // : account.acct);
username.setText('@'+acct); username.setText("@"+account.username);
String domain=account.getDomain();
if(TextUtils.isEmpty(domain))
domain=AccountSessionManager.get(accountID).domain;
usernameDomain.setText(domain);
lockIcon.setVisibility(account.locked ? View.VISIBLE : View.GONE); lockIcon.setVisibility(account.locked ? View.VISIBLE : View.GONE);
lockIcon.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor())); lockIcon.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor()));
@@ -787,7 +767,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
} }
for(AccountField field:account.fields){ for(AccountField field:account.fields){
field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
field.valueEmojis=ssb.getSpans(0, ssb.length(), CustomEmojiSpan.class); field.valueEmojis=ssb.getSpans(0, ssb.length(), CustomEmojiSpan.class);
ssb=new SpannableStringBuilder(field.name); ssb=new SpannableStringBuilder(field.name);
HtmlParser.parseCustomEmoji(ssb, account.emojis); HtmlParser.parseCustomEmoji(ssb, account.emojis);
@@ -832,27 +812,28 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return; return;
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu); inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
if(isOwnProfile){ if(isOwnProfile){
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.scheduled, R.id.bookmarks, R.id.favorites); UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.scheduled, R.id.bookmarks);
}else{ }else{
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags, R.id.favorites, R.id.scheduled); UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.edit_note);
} }
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1; boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
MenuItem openWithAccounts = menu.findItem(R.id.open_with_account); MenuItem openWithAccounts = menu.findItem(R.id.open_with_account);
openWithAccounts.setVisible(hasMultipleAccounts); openWithAccounts.setVisible(hasMultipleAccounts);
SubMenu accountsMenu = openWithAccounts.getSubMenu(); SubMenu accountsMenu=openWithAccounts.getSubMenu();
if (hasMultipleAccounts) { if(hasMultipleAccounts){
accountsMenu.clear(); accountsMenu.clear();
UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL( UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL(
getActivity(), s.getID(), account.url, false getActivity(), s.getID(), account.url, false
)); ));
} }
menu.findItem(R.id.share).setTitle(R.string.share_user);
if(isOwnProfile) { if(isOwnProfile) {
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false); if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
return; return;
} }
MenuItem mute = menu.findItem(R.id.mute); menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
MenuItem mute=menu.findItem(R.id.mute);
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername())); mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular); mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), mute); UiUtils.insetPopupMenuIcon(getContext(), mute);
@@ -860,19 +841,24 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername())); menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following); menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following); menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
if (relationship.following) { if (relationship.following) {
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername())); hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular); hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts); UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername())); hideBoosts.setVisible(true);
} else { } else {
menu.findItem(R.id.hide_boosts).setVisible(false); hideBoosts.setVisible(false);
} }
if(!account.isLocal()) MenuItem blockDomain=menu.findItem(R.id.block_domain);
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain())); if(!account.isLocal()){
else blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
menu.findItem(R.id.block_domain).setVisible(false); blockDomain.setVisible(true);
}else{
blockDomain.setVisible(false);
}
boolean canAddNote = noteWrap.getVisibility()==View.GONE && (relationship.note==null || relationship.note.isEmpty());
menu.findItem(R.id.edit_note).setTitle(canAddNote ? R.string.sk_add_note : R.string.sk_delete_note);
} }
@Override @Override
@@ -884,11 +870,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
intent.putExtra(Intent.EXTRA_TEXT, account.url); intent.putExtra(Intent.EXTRA_TEXT, account.url);
startActivity(Intent.createChooser(intent, item.getTitle())); startActivity(Intent.createChooser(intent, item.getTitle()));
}else if(id==R.id.mute){ }else if(id==R.id.mute){
confirmToggleMuted(); UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
}else if(id==R.id.block){ }else if(id==R.id.block){
confirmToggleBlocked(); UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
}else if(id==R.id.soft_block){ }else if(id==R.id.soft_block){
confirmSoftBlockUser(); UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
}else if(id==R.id.report){ }else if(id==R.id.report){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -933,16 +919,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("profileDisplayUsername", account.getDisplayUsername()); args.putString("profileDisplayUsername", account.getDisplayUsername());
} }
Nav.go(getActivity(), ListsFragment.class, args); Nav.go(getActivity(), ListsFragment.class, args);
}else if(id==R.id.mutes){ }else if(id==R.id.muted_accounts){
final Bundle args=new Bundle(); final Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account)); args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), MutesListFragment.class, args); Nav.go(getActivity(), MutedAccountsListFragment.class, args);
}else if(id==R.id.blocks){ }else if(id==R.id.blocked_accounts){
final Bundle args=new Bundle(); final Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account)); args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), BlocksListFragment.class, args); Nav.go(getActivity(), BlockedAccountsListFragment.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);
@@ -954,6 +940,36 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}else if(id==R.id.save){ }else if(id==R.id.save){
if(isInEditMode) if(isInEditMode)
saveAndExitEditMode(); saveAndExitEditMode();
}else if(id==R.id.edit_note){
if(noteWrap.getVisibility()==View.GONE){
showPrivateNote();
UiUtils.beginLayoutTransition(scrollableContent);
noteEdit.requestFocus();
noteEdit.postDelayed(()->{
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.showSoftInput(noteEdit, 0);
}, 100);
}else if(relationship.note.isEmpty() && noteEdit.getText().toString().isEmpty()){
hidePrivateNote();
noteEdit.clearFocus();
noteEdit.postDelayed(()->{
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(noteEdit.getWindowToken(), 0);
}, 100);
UiUtils.beginLayoutTransition(scrollableContent);
}else{
new M3AlertDialogBuilder(getActivity())
.setMessage(getContext().getString(R.string.sk_private_note_confirm_delete, account.getDisplayUsername()))
.setPositiveButton(R.string.delete, (dlg, btn)->savePrivateNote(null))
.setNegativeButton(R.string.cancel, null)
.show();
}
invalidateOptionsMenu();
}else if(id==R.id.manage_user_lists){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), AddAccountToListsFragment.class, args);
} }
return true; return true;
} }
@@ -978,7 +994,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
} }
private void updateRelationship(){ private void updateRelationship(){
if (getActivity() == null) return; if(getActivity()==null) return;
if(relationship.note!=null && !relationship.note.isEmpty()) showPrivateNote();
else hidePrivateNote();
invalidateOptionsMenu(); invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE); actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE); notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
@@ -988,11 +1006,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE); followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
notifyButton.setSelected(relationship.notifying); notifyButton.setSelected(relationship.notifying);
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username)); notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
if (!isOwnProfile) { UiUtils.beginLayoutTransition(scrollableContent);
setNote(relationship.note);
// aboutFragment.setNote(relationship.note, accountID, profileAccountID);
}
} }
public ImageButton getFab() { public ImageButton getFab() {
@@ -1190,7 +1205,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
updateMetadataHeight(); updateMetadataHeight();
Toolbar toolbar=getToolbar(); Toolbar toolbar=getToolbar();
Drawable close=getToolbarContext().getDrawable(R.drawable.ic_baseline_close_24).mutate(); Drawable close=getToolbarContext().getDrawable(R.drawable.ic_fluent_dismiss_24_regular).mutate();
close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant)); close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant));
toolbar.setNavigationIcon(close); toolbar.setNavigationIcon(close);
toolbar.setNavigationContentDescription(R.string.discard); toolbar.setNavigationContentDescription(R.string.discard);
@@ -1266,6 +1281,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
imm.hideSoftInputFromWindow(content.getWindowToken(), 0); imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
V.setVisibilityAnimated(fab, View.VISIBLE); V.setVisibilityAnimated(fab, View.VISIBLE);
bindHeaderView(); bindHeaderView();
V.setVisibilityAnimated(fab, View.VISIBLE);
} }
private void saveAndExitEditMode(){ private void saveAndExitEditMode(){
@@ -1280,7 +1296,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
savingEdits=false; savingEdits=false;
account=result; account=result;
AccountSessionManager.getInstance().updateAccountInfo(accountID, account); AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
if (getActivity() == null) return; if(getActivity()==null) return;
exitEditMode(); exitEditMode();
setActionProgressVisible(false); setActionProgressVisible(false);
} }
@@ -1295,18 +1311,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.exec(accountID); .exec(accountID);
} }
private void confirmToggleMuted(){
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
}
private void confirmToggleBlocked(){
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
}
private void confirmSoftBlockUser(){
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
}
private void updateRelationship(Relationship r){ private void updateRelationship(Relationship r){
relationship=r; relationship=r;
updateRelationship(); updateRelationship();
@@ -1315,7 +1319,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override @Override
public boolean onBackPressed(){ public boolean onBackPressed(){
if(noteEdit.hasFocus()) { if(noteEdit.hasFocus()) {
savePrivateNote(); savePrivateNote(noteEdit.getText().toString());
} }
if(isInEditMode){ if(isInEditMode){
if(savingEdits) if(savingEdits)
@@ -1357,7 +1361,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return; return;
int radius=V.dp(25); int radius=V.dp(25);
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : account.avatar, ava), 0, currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : account.avatar, ava), 0,
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null)); null, accountID, new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
} }
} }
@@ -1369,7 +1373,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(drawable==null || drawable instanceof ColorDrawable) if(drawable==null || drawable instanceof ColorDrawable)
return; return;
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0, currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.header, drawable), 0,
new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0))); null, accountID, new SingleImagePhotoViewerListener(cover, cover, null, this, ()->currentPhotoViewer=null, ()->drawable, ()->avatarBorder.setTranslationZ(2), ()->avatarBorder.setTranslationZ(0)));
} }
} }
@@ -1576,7 +1580,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
title.setText(item.parsedName); title.setText(item.parsedName);
value.setText(item.parsedValue); value.setText(item.parsedValue);
if(item.verifiedAt!=null){ if(item.verifiedAt!=null){
int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63; int textColor=UiUtils.getThemeColor(getContext(), R.attr.colorM3Success);
value.setTextColor(textColor); value.setTextColor(textColor);
value.setLinkTextColor(textColor); value.setLinkTextColor(textColor);
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate(); Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate();

View File

@@ -120,6 +120,15 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
} }
} }
@Override
protected void onShown(){
super.onShown();
// because, for some reason, when navigating back from compose fragment,
// match_parent would otherwise be incorrect (leaving a gap for the keyboard
// where there is none)
list.post(list::requestLayout);
}
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count) currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
@@ -130,7 +139,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else
nextMaxID=null; nextMaxID=null;
if (getActivity() == null) return; if(getActivity()==null) return;
onDataLoaded(result, nextMaxID!=null); onDataLoaded(result, nextMaxID!=null);
} }
}) })

View File

@@ -1,7 +1,12 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -11,6 +16,8 @@ import android.widget.ProgressBar;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.accounts.CheckInviteLink;
import org.joinmastodon.android.api.requests.catalog.GetCatalogDefaultInstances; import org.joinmastodon.android.api.requests.catalog.GetCatalogDefaultInstances;
import org.joinmastodon.android.api.requests.instance.GetInstance; import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment; import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
@@ -20,6 +27,7 @@ import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.catalog.CatalogDefaultInstance; import org.joinmastodon.android.model.catalog.CatalogDefaultInstance;
import org.joinmastodon.android.ui.InterpolatingMotionEffect; import org.joinmastodon.android.ui.InterpolatingMotionEffect;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
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.ui.views.ProgressBarButton; import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout; import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
@@ -47,13 +55,15 @@ public class SplashFragment extends AppKitFragment{
private ProgressBarButton defaultServerButton; private ProgressBarButton defaultServerButton;
private ProgressBar defaultServerProgress; private ProgressBar defaultServerProgress;
private String chosenDefaultServer=DEFAULT_SERVER; private String chosenDefaultServer=DEFAULT_SERVER;
private boolean loadingDefaultServer; private boolean loadingDefaultServer, loadedDefaultServer;
private Uri currentInviteLink;
private ProgressDialog instanceLoadingProgress;
private String inviteCode;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
motionEffect=new InterpolatingMotionEffect(MastodonApp.context); motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
loadAndChooseDefaultServer();
} }
@Nullable @Nullable
@@ -101,6 +111,8 @@ public class SplashFragment extends AppKitFragment{
}); });
} }
}); });
if(!loadedDefaultServer && !loadingDefaultServer)
loadAndChooseDefaultServer();
return contentView; return contentView;
} }
@@ -109,19 +121,65 @@ public class SplashFragment extends AppKitFragment{
Bundle extras=new Bundle(); Bundle extras=new Bundle();
boolean isSignup=v.getId()==R.id.btn_get_started; boolean isSignup=v.getId()==R.id.btn_get_started;
extras.putBoolean("signup", isSignup); extras.putBoolean("signup", isSignup);
extras.putString("defaultServer", chosenDefaultServer);
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras); Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
} }
private void onJoinDefaultServerClick(View v){ private void onJoinDefaultServerClick(View v){
if(loadingDefaultServer) if(loadingDefaultServer)
return; return;
instanceLoadingProgress=new ProgressDialog(getActivity());
instanceLoadingProgress.setCancelable(false);
instanceLoadingProgress.setMessage(getString(R.string.loading_instance));
instanceLoadingProgress.show();
if(currentInviteLink!=null){
new CheckInviteLink(currentInviteLink.getPath())
.setCallback(new Callback<>(){
@Override
public void onSuccess(CheckInviteLink.Response result){
inviteCode=result.inviteCode;
proceedWithServerDomain(currentInviteLink.getHost());
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
instanceLoadingProgress.dismiss();
instanceLoadingProgress=null;
if(error instanceof MastodonErrorResponse mer){
switch(mer.httpStatus){
case 401 -> new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.expired_invite_link)
.setMessage(getString(R.string.expired_clipboard_invite_link_alert, currentInviteLink.getHost(), chosenDefaultServer))
.setPositiveButton(R.string.ok, null)
.show();
case 404 -> new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.invalid_invite_link)
.setMessage(getString(R.string.invalid_clipboard_invite_link_alert, currentInviteLink.getHost(), chosenDefaultServer))
.setPositiveButton(R.string.ok, null)
.show();
default -> error.showToast(getActivity());
}
}
}
})
.execNoAuth(currentInviteLink.getHost());
return;
}
proceedWithServerDomain(chosenDefaultServer);
}
private void proceedWithServerDomain(String domain){
new GetInstance() new GetInstance()
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Instance result){ public void onSuccess(Instance result){
if(getActivity()==null) if(getActivity()==null)
return; return;
if(!result.registrations){ instanceLoadingProgress.dismiss();
instanceLoadingProgress=null;
if(!result.registrations && TextUtils.isEmpty(inviteCode)){
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.instance_signup_closed) .setMessage(R.string.instance_signup_closed)
@@ -131,6 +189,8 @@ public class SplashFragment extends AppKitFragment{
} }
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(result)); args.putParcelable("instance", Parcels.wrap(result));
if(inviteCode!=null)
args.putString("inviteCode", inviteCode);
Nav.go(getActivity(), InstanceRulesFragment.class, args); Nav.go(getActivity(), InstanceRulesFragment.class, args);
} }
@@ -138,11 +198,12 @@ public class SplashFragment extends AppKitFragment{
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
if(getActivity()==null) if(getActivity()==null)
return; return;
instanceLoadingProgress.dismiss();
instanceLoadingProgress=null;
error.showToast(getActivity()); error.showToast(getActivity());
} }
}) })
.wrapProgress(getActivity(), R.string.loading_instance, true) .execNoAuth(domain);
.execNoAuth(chosenDefaultServer);
} }
private void onLearnMoreClick(View v){ private void onLearnMoreClick(View v){
@@ -197,7 +258,18 @@ public class SplashFragment extends AppKitFragment{
} }
private void loadAndChooseDefaultServer(){ private void loadAndChooseDefaultServer(){
loadingDefaultServer=true; ClipData clipData=getActivity().getSystemService(ClipboardManager.class).getPrimaryClip();
if(clipData!=null && clipData.getItemCount()>0){
CharSequence clipText=clipData.getItemAt(0).coerceToText(getActivity());
if(HtmlParser.INVITE_LINK_PATTERN.matcher(clipText).find()){
currentInviteLink=Uri.parse(clipText.toString());
defaultServerButton.setText(getString(R.string.join_server_x_with_invite, currentInviteLink.getHost()));
}
}else{
loadingDefaultServer=true;
defaultServerButton.setTextVisible(false);
defaultServerProgress.setVisibility(View.VISIBLE);
}
new GetCatalogDefaultInstances() new GetCatalogDefaultInstances()
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
@@ -239,7 +311,8 @@ public class SplashFragment extends AppKitFragment{
private void setChosenDefaultServer(String domain){ private void setChosenDefaultServer(String domain){
chosenDefaultServer=domain; chosenDefaultServer=domain;
loadingDefaultServer=false; loadingDefaultServer=false;
if(defaultServerButton!=null && getActivity()!=null){ loadedDefaultServer=true;
if(defaultServerButton!=null && getActivity()!=null && currentInviteLink==null){
defaultServerButton.setTextVisible(true); defaultServerButton.setTextVisible(true);
defaultServerProgress.setVisibility(View.GONE); defaultServerProgress.setVisibility(View.GONE);
defaultServerButton.setText(getString(R.string.join_default_server, chosenDefaultServer)); defaultServerButton.setText(getString(R.string.join_default_server, chosenDefaultServer));

View File

@@ -3,8 +3,8 @@ package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory; import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
@@ -12,18 +12,23 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; 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.utils.InsetStatusItemDecoration; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
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 name.fraser.neil.plaintext.diff_match_patch;
public class StatusEditHistoryFragment extends StatusListFragment{ public class StatusEditHistoryFragment extends StatusListFragment{
private String id, url; private String id, url;
@@ -48,17 +53,52 @@ public class StatusEditHistoryFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return;
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed()); Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
if (getActivity() == null) return; if(result.size()<=1&& GlobalUserPreferences.allowRemoteLoading) {
//server send only a single edit, which is always the original status
//try to get the complete history from the remote server
loadRemoteData(result);
return;
}
onDataLoaded(result, false); onDataLoaded(result, false);
} }
}) })
.exec(accountID); .exec(accountID);
} }
void loadRemoteData(List<Status> prevData){
String remoteURL = Uri.parse(url).getHost();
String[] parts=url.split("/");
if(parts.length==0||remoteURL==null) {
onDataLoaded(prevData, false);
setSubtitle(getContext().getString(R.string.sk_no_remote_info_hint));
return;
}
new GetStatusEditHistory(parts[parts.length-1])
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
onDataLoaded(result, false);
}
@Override
public void onError(ErrorResponse errorResponse){
//fallback to previously loaded data
onDataLoaded(prevData, false);
setSubtitle(getContext().getString(R.string.sk_no_remote_info_hint));
}
})
.execNoAuth(remoteURL);
}
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS); List<StatusDisplayItem> items=new ArrayList<>();
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()));
@@ -83,8 +123,11 @@ public class StatusEditHistoryFragment extends StatusListFragment{
EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class); EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class);
Status prev=data.get(idx+1); Status prev=data.get(idx+1);
if(!Objects.equals(s.content, prev.content)){ // if only formatting was changed, don't even try to create a diff text
if(!Objects.equals(HtmlParser.text(s.content), HtmlParser.text(prev.content))){
changes.add(StatusEditChangeType.TEXT_CHANGED); changes.add(StatusEditChangeType.TEXT_CHANGED);
//update status content to display a diffs
s.content=createDiffText(prev.content, s.content);
} }
if(!Objects.equals(s.spoilerText, prev.spoilerText)){ if(!Objects.equals(s.spoilerText, prev.spoilerText)){
if(s.spoilerText==null){ if(s.spoilerText==null){
@@ -147,15 +190,10 @@ public class StatusEditHistoryFragment extends StatusListFragment{
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s)); items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s));
items.add(1, new DummyStatusDisplayItem(s.id, this)); items.add(1, new DummyStatusDisplayItem(s.id, this));
} }
items.addAll(StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER|StatusDisplayItem.FLAG_INSET|StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS));
return items; return items;
} }
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
}
@Override @Override
public boolean isItemEnabled(String id){ public boolean isItemEnabled(String id){
return false; return false;
@@ -170,4 +208,28 @@ public class StatusEditHistoryFragment extends StatusListFragment{
public Uri getWebUri(Uri.Builder base) { public Uri getWebUri(Uri.Builder base) {
return Uri.parse(url); return Uri.parse(url);
} }
private String createDiffText(String original, String modified) {
diff_match_patch dmp=new diff_match_patch();
LinkedList<diff_match_patch.Diff> diffs=dmp.diff_main(original, modified);
dmp.diff_cleanupSemantic(diffs);
StringBuilder stringBuilder=new StringBuilder();
for(diff_match_patch.Diff diff : diffs){
switch(diff.operation){
case DELETE->{
stringBuilder.append("<edit-diff-delete>");
stringBuilder.append(diff.text);
stringBuilder.append("</edit-diff-delete>");
}
case INSERT->{
stringBuilder.append("<edit-diff-insert>");
stringBuilder.append(diff.text);
stringBuilder.append("</edit-diff-insert>");
}
default->stringBuilder.append(diff.text);
}
}
return stringBuilder.toString();
}
} }

View File

@@ -9,11 +9,13 @@ 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.api.CacheController;
import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusMuteChangedEvent; import org.joinmastodon.android.events.StatusMuteChangedEvent;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
@@ -25,13 +27,19 @@ import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
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.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
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.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -45,10 +53,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id); boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id);
int flags = 0; int flags = 0;
AccountLocalPreferences lp=getLocalPrefs(); AccountLocalPreferences lp=getLocalPrefs();
if (GlobalUserPreferences.spectatorMode) if(GlobalUserPreferences.spectatorMode)
flags |= StatusDisplayItem.FLAG_NO_FOOTER; flags |= StatusDisplayItem.FLAG_NO_FOOTER;
if (!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED) if(!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS; flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS;
if(GlobalUserPreferences.translateButtonOpenedOnly)
flags |= StatusDisplayItem.FLAG_NO_TRANSLATE;
if(!GlobalUserPreferences.showMediaPreview) if(!GlobalUserPreferences.showMediaPreview)
flags |= StatusDisplayItem.FLAG_NO_MEDIA_PREVIEW; flags |= StatusDisplayItem.FLAG_NO_MEDIA_PREVIEW;
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags); return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags);
@@ -79,8 +89,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
@Override @Override
public void onItemClick(String id){ public void onItemClick(String id){
Status status=getContentStatusByID(id); Status status=getContentStatusByID(id);
if(status==null) if(status==null || status.preview) return;
return;
if(status.isRemote){ if(status.isRemote){
UiUtils.lookupStatus(getContext(), status, accountID, null, status1 -> { UiUtils.lookupStatus(getContext(), status, accountID, null, status1 -> {
status1.filterRevealed = true; status1.filterRevealed = true;
@@ -157,12 +166,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
} }
} }
protected Status getContentStatusByID(String id){ public Status getContentStatusByID(String id){
Status s=getStatusByID(id); Status s=getStatusByID(id);
return s==null ? null : s.getContentStatus(); return s==null ? null : s.getContentStatus();
} }
protected Status getStatusByID(String id){ public Status getStatusByID(String id){
for(Status s:data){ for(Status s:data){
if(s.id.equals(id)){ if(s.id.equals(id)){
return s; return s;
@@ -189,41 +198,73 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
} }
} }
protected void removeStatus(Status status){ private boolean removeStatusDisplayItems(String parentID, int firstIndex, int ancestorFirstIndex, int ancestorLastIndex){
data.remove(status);
preloadedData.remove(status);
int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
for(int i=0;i<displayItems.size();i++){
StatusDisplayItem item = displayItems.get(i);
if(status.id.equals(item.parentID)){
index=i;
break;
}
if (item.parentID.equals(status.inReplyToId)) {
if (ancestorFirstIndex == -1) ancestorFirstIndex = i;
ancestorLastIndex = i;
}
}
// did we find an ancestor that is also the status' neighbor? // did we find an ancestor that is also the status' neighbor?
if (ancestorFirstIndex >= 0 && ancestorLastIndex == index - 1) { if(ancestorFirstIndex>=0 && ancestorLastIndex==firstIndex-1){
for (int i = ancestorFirstIndex; i <= ancestorLastIndex; i++) { // update ancestor to have no descendant anymore
StatusDisplayItem item = displayItems.get(i); displayItems.subList(ancestorFirstIndex, ancestorLastIndex+1).forEach(i->i.hasDescendantNeighbor=false);
// update ancestor to have no descendant anymore adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex-ancestorFirstIndex+1);
if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
}
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
} }
if(index==-1) if(firstIndex==-1) return false;
return; int lastIndex=firstIndex;
int lastIndex; while(lastIndex<displayItems.size()){
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){ StatusDisplayItem item=displayItems.get(lastIndex);
if(!displayItems.get(lastIndex).parentID.equals(status.id)) if(!item.parentID.equals(parentID) || item instanceof GapStatusDisplayItem) break;
break; lastIndex++;
} }
displayItems.subList(index, lastIndex).clear(); int count=lastIndex-firstIndex;
adapter.notifyItemRangeRemoved(index, lastIndex-index); if(count<1) return false;
displayItems.subList(firstIndex, lastIndex).clear();
adapter.notifyItemRangeRemoved(firstIndex, count);
return true;
}
protected void removeStatus(Status status){
final AccountSessionManager asm=AccountSessionManager.getInstance();
final CacheController cache=AccountSessionManager.get(accountID).getCacheController();
final boolean unReblogging=status.reblog!=null && asm.isSelf(accountID, status.account);
final Predicate<Status> isToBeRemovedReblog=item->item!=null && item.reblog!=null
&& item.reblog.id.equals(status.reblog.id)
&& asm.isSelf(accountID, item.account);
final BiPredicate<String, Supplier<String>> isToBeRemovedContent=(parentId, contentIdSupplier)->
parentId.equals(status.id) || contentIdSupplier.get().equals(status.id);
int ancestorFirstIndex=-1, ancestorLastIndex=-1;
for(int i=0;i<displayItems.size();i++){
StatusDisplayItem item=displayItems.get(i);
// we found a status that the to-be-removed status replies to!
// storing indices to maybe update its display items
if(item.parentID.equals(status.inReplyToId)){
if(ancestorFirstIndex==-1) ancestorFirstIndex=i;
ancestorLastIndex=i;
}
// if we're un-reblogging, we compare the reblogged status's id with the current status's
if(unReblogging
? isToBeRemovedReblog.test(getStatusByID(item.parentID))
: isToBeRemovedContent.test(item.parentID, item::getContentStatusID)){
// if statuses are removed from index i, the next iteration should be on the same index again
if(removeStatusDisplayItems(item.parentID, i, ancestorFirstIndex, ancestorLastIndex)) i--;
// resetting in case we find another occurrence of the same status that also has ancestors
// (we won't - unless the timeline is being especially weird)
ancestorFirstIndex=-1; ancestorLastIndex=-1;
}
}
Consumer<List<Status>> removeStatusFromData=(list)->{
Iterator<Status> it=list.iterator();
while(it.hasNext()){
Status s=it.next();
if(unReblogging
? isToBeRemovedReblog.test(s)
: isToBeRemovedContent.test(s.id, s::getContentStatusID)){
it.remove();
cache.deleteStatus(s.id);
}
}
};
removeStatusFromData.accept(data);
removeStatusFromData.accept(preloadedData);
} }
@Override @Override
@@ -314,6 +355,22 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
removeStatus(status); removeStatus(status);
} }
@Subscribe
public void onReblogDeleted(ReblogDeletedEvent ev){
AccountSessionManager asm=AccountSessionManager.getInstance();
if(!ev.accountID.equals(accountID))
return;
for(Status item : data){
boolean itemIsOwnReblog=item.reblog!=null
&& item.getContentStatusID().equals(ev.statusID)
&& asm.isSelf(accountID, item.account);
if(itemIsOwnReblog){
removeStatus(item);
break;
}
}
}
@Subscribe @Subscribe
public void onStatusCreated(StatusCreatedEvent ev){ public void onStatusCreated(StatusCreatedEvent ev){
if(!ev.accountID.equals(accountID)) if(!ev.accountID.equals(accountID))
@@ -334,6 +391,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
Status contentStatus=status.getContentStatus(); Status contentStatus=status.getContentStatus();
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){ if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
updatePoll(status.id, contentStatus, ev.poll); updatePoll(status.id, contentStatus, ev.poll);
AccountSessionManager.get(accountID).getCacheController().updateStatus(contentStatus);
} }
} }
} }

View File

@@ -2,11 +2,19 @@ package org.joinmastodon.android.fragments;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences;
import com.squareup.otto.Subscribe; import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
@@ -23,18 +31,19 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
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.OutlineProviders;
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.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
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.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.ProvidesAssistContent;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@@ -50,25 +59,41 @@ import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent { public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
protected Status mainStatus, updatedStatus; protected Status mainStatus, updatedStatus, replyTo;
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>(); private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
private StatusContext result; private StatusContext result;
protected boolean contextInitiallyRendered, transitionFinished; protected boolean contextInitiallyRendered, transitionFinished, preview;
private FrameLayout replyContainer;
private LinearLayout replyButton;
private ImageView replyButtonAva;
private TextView replyButtonText;
private int lastBottomInset;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setLayout(R.layout.fragment_thread);
mainStatus=Parcels.unwrap(getArguments().getParcelable("status")); mainStatus=Parcels.unwrap(getArguments().getParcelable("status"));
replyTo=Parcels.unwrap(getArguments().getParcelable("inReplyTo"));
Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount")); Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount"));
refreshing=contextInitiallyRendered=getArguments().getBoolean("refresh", false);
if(inReplyToAccount!=null) if(inReplyToAccount!=null)
knownAccounts.put(inReplyToAccount.id, inReplyToAccount); knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
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)); preview=mainStatus.preview;
if(preview) setRefreshEnabled(false);
setTitle(preview ? getString(R.string.sk_post_preview) : HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
transitionFinished = getArguments().getBoolean("noTransition", false); transitionFinished = getArguments().getBoolean("noTransition", false);
E.register(this); E.register(this);
@@ -81,7 +106,7 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
} }
@Subscribe @Subscribe
public void onStatusMuteChaged(StatusMuteChangedEvent ev){ public void onStatusMuteChanged(StatusMuteChangedEvent ev){
for(Status s:data){ for(Status s:data){
s.getContentStatus().update(ev); s.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateStatus(s); AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
@@ -131,12 +156,18 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
text.textSelectable=true; text.textSelectable=true;
else if(item instanceof FooterStatusDisplayItem footer) else if(item instanceof FooterStatusDisplayItem footer)
footer.hideCounts=true; footer.hideCounts=true;
else if(item instanceof SpoilerStatusDisplayItem spoiler){
for(StatusDisplayItem subItem:spoiler.contentItems){
if(subItem instanceof TextStatusDisplayItem text)
text.textSelectable=true;
}
}
} }
} }
for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem); for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem);
if(s.id.equals(mainStatus.id)) { if(s.id.equals(mainStatus.id)) {
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, accountID, s.getContentStatus())); itemsToModify.add(itemsToModify.size()-1, new ExtendedFooterStatusDisplayItem(s.id, this, accountID, s.getContentStatus()));
} }
return items; return items;
} }
@@ -149,11 +180,21 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
if (refreshing) loadMainStatus(); if(preview && replyTo==null){
currentRequest=new GetStatusContext(mainStatus.id) result=new StatusContext();
result.descendants=Collections.emptyList();
result.ancestors=Collections.emptyList();
return;
}
if(refreshing && !preview) loadMainStatus();
currentRequest=new GetStatusContext(preview ? replyTo.id : mainStatus.id)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(StatusContext result){ public void onSuccess(StatusContext result){
if(preview){
result.descendants=Collections.emptyList();
result.ancestors.add(replyTo);
}
ThreadFragment.this.result = result; ThreadFragment.this.result = result;
maybeApplyContext(); maybeApplyContext();
} }
@@ -214,8 +255,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
// TODO: figure out how this code works // TODO: figure out how this code works
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result); if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
result.descendants=filterStatuses(result.descendants); filterStatuses(result.descendants);
result.ancestors=filterStatuses(result.ancestors); filterStatuses(result.ancestors);
restoreStatusStates(result.descendants, oldData); restoreStatusStates(result.descendants, oldData);
restoreStatusStates(result.ancestors, oldData); restoreStatusStates(result.ancestors, oldData);
@@ -295,12 +336,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
// descendant neighbor // descendant neighbor
Optional Optional
.ofNullable(count > index + 1 ? statuses.get(index + 1) : null) .ofNullable(count > index + 1 ? statuses.get(index + 1) : null)
.filter(s -> s.inReplyToId.equals(current.id)) .filter(s -> current.id.equals(s.inReplyToId))
.orElse(null), .orElse(null),
// ancestoring neighbor // ancestoring neighbor
Optional.ofNullable(index > 0 ? ancestry.get(index - 1) : null) Optional.ofNullable(index > 0 ? ancestry.get(index - 1) : null)
.filter(ancestor -> Optional.ofNullable(ancestor.descendantNeighbor) .filter(ancestor -> Optional.ofNullable(ancestor.descendantNeighbor)
.map(ancestorsDescendant -> ancestorsDescendant.id.equals(current.id)) .map(ancestorsDescendant -> current.id.equals(ancestorsDescendant.id))
.orElse(false)) .orElse(false))
.map(a -> a.status) .map(a -> a.status)
.orElse(null) .orElse(null)
@@ -351,11 +392,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private List<Status> filterStatuses(List<Status> statuses){ private void filterStatuses(List<Status> statuses){
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext()); AccountSessionManager.get(accountID).filterStatuses(statuses, getFilterContext());
return statuses.stream()
.filter(statusFilterPredicate)
.collect(Collectors.toList());
} }
@Override @Override
@@ -370,6 +408,22 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
replyContainer=view.findViewById(R.id.reply_button_wrapper);
replyButton=replyContainer.findViewById(R.id.reply_button);
replyButtonText=replyButton.findViewById(R.id.reply_btn_text);
replyButtonAva=replyButton.findViewById(R.id.avatar);
replyButton.setOutlineProvider(OutlineProviders.roundedRect(20));
replyButton.setClipToOutline(true);
replyButtonText.setText(HtmlParser.parseCustomEmoji(getString(R.string.reply_to_user, mainStatus.account.displayName), mainStatus.account.emojis));
UiUtils.loadCustomEmojiInTextView(replyButtonText);
replyButtonAva.setOutlineProvider(OutlineProviders.OVAL);
replyButtonAva.setClipToOutline(true);
replyButton.setOnClickListener(v->openReply());
replyButton.setOnLongClickListener(this::onReplyLongClick);
Account self=AccountSessionManager.get(accountID).self;
if(!TextUtils.isEmpty(self.avatar)){
ViewImageLoader.loadWithoutAnimation(replyButtonAva, getResources().getDrawable(R.drawable.image_placeholder), new UrlImageLoaderRequest(self.avatar, V.dp(24), V.dp(24)));
}
UiUtils.loadCustomEmojiInTextView(toolbarTitleView); UiUtils.loadCustomEmojiInTextView(toolbarTitleView);
showContent(); showContent();
if(!loaded) if(!loaded)
@@ -509,4 +563,35 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
} }
super.onErrorRetryClick(); super.onErrorRetryClick();
} }
@Override
public void onApplyWindowInsets(WindowInsets insets){
lastBottomInset=insets.getSystemWindowInsetBottom();
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(replyContainer, insets));
}
private void openReply(){
maybeShowPreReplySheet(mainStatus, ()->{
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("replyTo", Parcels.wrap(mainStatus));
args.putBoolean("fromThreadFragment", true);
Nav.go(getActivity(), ComposeFragment.class, args);
});
}
private boolean onReplyLongClick(View v) {
if(mainStatus.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickAccount(v.getContext(), accountID, R.string.sk_reply_as, R.drawable.ic_fluent_arrow_reply_28_regular, session -> {
UiUtils.lookupStatus(v.getContext(), mainStatus, accountID, session.getID(), status -> {
if (status == null) return;
openReply();
});
}, null);
return true;
}
public int getSnackbarOffset(){
return replyContainer.getHeight()-lastBottomInset;
}
} }

View File

@@ -6,7 +6,9 @@ import android.text.TextUtils;
import android.view.View; import android.view.View;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.search.GetSearchResults; import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.SearchResults; import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.model.viewmodel.AccountViewModel; import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.SearchViewHelper; import org.joinmastodon.android.ui.SearchViewHelper;
@@ -14,13 +16,14 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder; import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
public class ComposeAccountSearchFragment extends BaseAccountListFragment{ public class AccountSearchFragment extends BaseAccountListFragment{
private String currentQuery; protected String currentQuery;
private boolean resultDelivered; private boolean resultDelivered;
private SearchViewHelper searchViewHelper; private SearchViewHelper searchViewHelper;
@@ -29,12 +32,11 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setRefreshEnabled(false); setRefreshEnabled(false);
setEmptyText(""); setEmptyText("");
dataLoaded();
} }
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
searchViewHelper=new SearchViewHelper(getActivity(), getToolbarContext(), getString(R.string.search_hint)); searchViewHelper=new SearchViewHelper(getActivity(), getToolbarContext(), getSearchViewPlaceholder());
searchViewHelper.setListeners(this::onQueryChanged, null); searchViewHelper.setListeners(this::onQueryChanged, null);
searchViewHelper.addDivider(contentView); searchViewHelper.addDivider(contentView);
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
@@ -52,13 +54,21 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(SearchResults result){ public void onSuccess(SearchResults result){
setEmptyText(R.string.no_search_results); AccountSearchFragment.this.onSuccess(result.accounts);
onDataLoaded(result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), false);
} }
}) })
.exec(accountID); .exec(accountID);
} }
protected void onSuccess(List<Account> result){
setEmptyText(R.string.no_search_results);
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), false);
}
protected String getSearchViewPlaceholder(){
return getString(R.string.search_hint);
}
@Override @Override
protected void onUpdateToolbar(){ protected void onUpdateToolbar(){
super.onUpdateToolbar(); super.onUpdateToolbar();

View File

@@ -0,0 +1,37 @@
package org.joinmastodon.android.fragments.account_list;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.SearchAccounts;
import org.joinmastodon.android.model.Account;
import java.util.List;
import me.grishka.appkit.api.SimpleCallback;
public class AddListMembersFragment extends AccountSearchFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
dataLoaded();
}
@Override
protected void doLoadData(int offset, int count){
refreshing=true;
currentRequest=new SearchAccounts(currentQuery, 0, 0, false, true)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Account> result){
AddListMembersFragment.this.onSuccess(result);
}
})
.exec(accountID);
}
@Override
protected String getSearchViewPlaceholder(){
return getString(R.string.search_among_people_you_follow);
}
}

View File

@@ -0,0 +1,125 @@
package org.joinmastodon.android.fragments.account_list;
import android.annotation.SuppressLint;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Button;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountFollowing;
import org.joinmastodon.android.api.requests.accounts.SearchAccounts;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
@SuppressLint("ValidFragment") // This shouldn't be part of any saved states anyway
public class AddNewListMembersFragment extends AccountSearchFragment{
private Listener listener;
private String maxID;
public AddNewListMembersFragment(Listener listener){
this.listener=listener;
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
loadData();
}
@Override
protected void doLoadData(int offset, int count){
if(TextUtils.isEmpty(currentQuery)){
currentRequest=new GetAccountFollowing(AccountSessionManager.get(accountID).self.id, offset>0 ? maxID : null, count)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(HeaderPaginationList<Account> result){
setEmptyText("");
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), result.nextPageUri!=null);
maxID=result.getNextPageMaxID();
}
})
.exec(accountID);
}else{
refreshing=true;
currentRequest=new SearchAccounts(currentQuery, 0, 0, false, true)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Account> result){
AddNewListMembersFragment.this.onSuccess(result);
}
})
.exec(accountID);
}
}
@Override
protected String getSearchViewPlaceholder(){
return getString(R.string.search_among_people_you_follow);
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
holder.setStyle(AccountViewHolder.AccessoryType.CUSTOM_BUTTON, false);
holder.setOnLongClickListener(vh->false);
Button button=holder.getButton();
button.setPadding(V.dp(24), 0, V.dp(24), 0);
button.setMinimumWidth(0);
button.setMinWidth(0);
button.setOnClickListener(v->{
holder.setActionProgressVisible(true);
holder.itemView.setHasTransientState(true);
Runnable onDone=()->{
holder.setActionProgressVisible(false);
holder.itemView.setHasTransientState(false);
onBindViewHolder(holder);
};
AccountViewModel account=holder.getItem();
if(listener.isAccountInList(account)){
listener.removeAccountAccountFromList(account, onDone);
}else{
listener.addAccountToList(account, onDone);
}
});
}
@Override
protected void onBindViewHolder(AccountViewHolder holder){
Button button=holder.getButton();
int textRes, styleRes;
if(listener.isAccountInList(holder.getItem())){
textRes=R.string.remove;
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal_Error;
}else{
textRes=R.string.add;
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
}
button.setText(textRes);
TypedArray ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
button.setBackground(ta.getDrawable(0));
ta.recycle();
ta=button.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
button.setTextColor(ta.getColorStateList(0));
ta.recycle();
}
@Override
protected void loadRelationships(List<AccountViewModel> accounts){
// no-op
}
public interface Listener{
boolean isAccountInList(AccountViewModel account);
void addAccountToList(AccountViewModel account, Runnable onDone);
void removeAccountAccountFromList(AccountViewModel account, Runnable onDone);
}
}

View File

@@ -39,6 +39,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
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<>();
protected int itemLayoutRes=R.layout.item_account_list;
public BaseAccountListFragment(){ public BaseAccountListFragment(){
super(40); super(40);
@@ -74,6 +75,8 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
protected void loadRelationships(List<AccountViewModel> accounts){ protected void loadRelationships(List<AccountViewModel> accounts){
Set<String> ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet()); Set<String> ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet());
if(ids.isEmpty())
return;
GetAccountRelationships req=new GetAccountRelationships(ids); GetAccountRelationships req=new GetAccountRelationships(ids);
relationshipsRequests.add(req); relationshipsRequests.add(req);
req.setCallback(new Callback<>(){ req.setCallback(new Callback<>(){
@@ -83,7 +86,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
for(Relationship rel:result){ for(Relationship rel:result){
relationships.put(rel.id, rel); relationships.put(rel.id, rel);
} }
if (getActivity() == null) return; if(getActivity()==null) return;
if(list==null) if(list==null)
return; return;
for(int i=0;i<list.getChildCount();i++){ for(int i=0;i<list.getChildCount();i++){
@@ -124,13 +127,6 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
Toolbar toolbar=getToolbar(); Toolbar toolbar=getToolbar();
if(toolbar!=null && toolbar.getNavigationIcon()!=null){ if(toolbar!=null && toolbar.getNavigationIcon()!=null){
toolbar.setNavigationContentDescription(R.string.back); toolbar.setNavigationContentDescription(R.string.back);
if(hasSubtitle()){
toolbar.setTitleTextAppearance(getActivity(), R.style.m3_title_medium);
toolbar.setSubtitleTextAppearance(getActivity(), R.style.m3_body_medium);
int color=UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary);
toolbar.setTitleTextColor(color);
toolbar.setSubtitleTextColor(color);
}
} }
} }
@@ -162,6 +158,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
} }
protected void onConfigureViewHolder(AccountViewHolder holder){} protected void onConfigureViewHolder(AccountViewHolder holder){}
protected void onBindViewHolder(AccountViewHolder holder){}
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{ protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){ public AccountsAdapter(){
@@ -171,7 +168,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
@NonNull @NonNull
@Override @Override
public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ public AccountViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
AccountViewHolder holder=new AccountViewHolder(BaseAccountListFragment.this, parent, relationships); AccountViewHolder holder=new AccountViewHolder(BaseAccountListFragment.this, parent, relationships, itemLayoutRes);
onConfigureViewHolder(holder); onConfigureViewHolder(holder);
return holder; return holder;
} }
@@ -179,6 +176,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
@Override @Override
public void onBindViewHolder(AccountViewHolder holder, int position){ public void onBindViewHolder(AccountViewHolder holder, int position){
holder.bind(data.get(position)); holder.bind(data.get(position));
BaseAccountListFragment.this.onBindViewHolder(holder);
super.onBindViewHolder(holder, position); super.onBindViewHolder(holder, position);
} }

View File

@@ -0,0 +1,36 @@
package org.joinmastodon.android.fragments.account_list;
import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountBlocks;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
public class BlockedAccountsListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.sk_blocked_accounts);
}
@Override
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetAccountBlocks(maxID, count);
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
}
@Override
public Uri getWebUri(Uri.Builder base) {
return super.getWebUri(base).buildUpon()
.appendPath("/blocks").build();
}
}

View File

@@ -0,0 +1,36 @@
package org.joinmastodon.android.fragments.account_list;
import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountMutes;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
public class MutedAccountsListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.sk_muted_accounts);
}
@Override
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetAccountMutes(maxID, count);
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
}
@Override
public Uri getWebUri(Uri.Builder base) {
return super.getWebUri(base).buildUpon()
.appendPath("/mutes").build();
}
}

Some files were not shown because too many files have changed in this diff Show More