Compare commits

..

802 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
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
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
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
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
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
Eugen Rochko
64bbe2c438 New translations strings.xml (Polish) 2023-12-17 21:16:34 +01: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
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
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
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
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
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
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
Eugen Rochko
ff2f1a4955 New translations strings.xml (Dutch) 2023-11-27 21:48:52 +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
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
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
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
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
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
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
Eugen Rochko
37ef67d7ac New translations strings.xml (Italian) 2023-11-15 13:33:31 +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
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
Eugen Rochko
714345a65d New translations strings.xml (French) 2023-11-13 19:16:10 +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
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
Eugen Rochko
4521def103 New translations strings.xml (Norwegian) 2023-11-10 20:08:28 +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
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
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
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
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
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
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
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
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
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
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
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
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
Grishka
66b7b127f9 List creation (AND-98) 2023-10-21 09:13:01 +03: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
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
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
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
Grishka
103aaafff1 Hide the FAB while editing profile
closes #709
2023-10-08 22:31:27 +03:00
Grishka
dff2217e80 Lists
closes #89, closes #279
2023-10-08 22:03:16 +03:00
370 changed files with 13612 additions and 2847 deletions

View File

@@ -16,8 +16,8 @@ android {
applicationId "org.joinmastodon.android.moshinda"
minSdk 23
targetSdk 33
versionCode 104
versionName "2.1.4+fork.104.moshinda"
versionCode 105
versionName "2.3.0+fork.105.moshinda"
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']
}
@@ -142,7 +142,7 @@ dependencies {
implementation 'me.grishka.litex:viewpager:1.0.0'
implementation 'me.grishka.litex:viewpager2: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 'org.jsoup:jsoup:1.14.3'
implementation 'com.squareup:otto:1.3.8'

View File

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

View File

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

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.AccountSessionManager;
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.jsoup.internal.StringUtil;

View File

@@ -28,6 +28,9 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
public class GlobalUserPreferences{
private static final String TAG="GlobalUserPreferences";
@@ -84,6 +87,11 @@ public class GlobalUserPreferences{
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){
if(json==null) return orElse;
try{
@@ -236,12 +244,41 @@ public class GlobalUserPreferences{
.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,
@@ -306,5 +343,4 @@ public class GlobalUserPreferences{
}
//endregion
}

View File

@@ -12,6 +12,7 @@ import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.Uri;
import android.os.BadParcelableException;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
@@ -131,11 +132,11 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
session=AccountSessionManager.get(accountID);
if(session==null || !session.activated)
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){
new GetSearchResults(q, null, true, null, 0, 0)
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch, GetSearchResults.Type type){
new GetSearchResults(q, type, true, null, 0, 0)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
@@ -334,8 +335,14 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
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());
// 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())){

View File

@@ -133,7 +133,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if(intent.hasExtra("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) {
AccountSessionManager accountSessionManager = AccountSessionManager.getInstance();
Preferences preferences = accountSessionManager.getAccount(accountID).preferences;
@@ -154,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
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){

View File

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

View File

@@ -9,21 +9,32 @@ import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.BuildConfig;
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.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.SearchResult;
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.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
@@ -44,6 +55,7 @@ public class CacheController{
private final Runnable databaseCloseRunnable=this::closeDatabase;
private boolean loadingNotifications;
private final ArrayList<Callback<PaginatedResponse<List<Notification>>>> pendingNotificationsCallbacks=new ArrayList<>();
private List<FollowList> lists;
private static final int POST_FLAG_GAP_AFTER=1;
@@ -348,6 +360,99 @@ public class CacheController{
}, 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{
public DatabaseHelper(){

View File

@@ -54,7 +54,9 @@ public class MastodonAPIController{
.create();
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
private static OkHttpClient httpClient=new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build();
private AccountSession session;
@@ -122,15 +124,15 @@ public class MastodonAPIController{
}
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(){
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e){
if(call.isCanceled())
if(req.canceled)
return;
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){
req.okhttpCall=null;
}
@@ -139,10 +141,10 @@ public class MastodonAPIController{
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException{
if(call.isCanceled())
if(req.canceled)
return;
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){
req.okhttpCall=null;
}
@@ -153,7 +155,7 @@ public class MastodonAPIController{
try{
if(BuildConfig.DEBUG){
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)
respObj=gson.fromJson(respJson, req.respTypeToken.getType());
else if(req.respClass!=null)
@@ -175,7 +177,7 @@ public class MastodonAPIController{
return;
}
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);
return;
}
@@ -184,19 +186,19 @@ public class MastodonAPIController{
req.validateAndPostprocessResponse(respObj, response);
}catch(IOException x){
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);
return;
}
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);
}else{
try{
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")){
MastodonDetailedErrorResponse err=new MastodonDetailedErrorResponse(error.get("error").getAsString(), response.code(), null);
HashMap<String, List<MastodonDetailedErrorResponse.FieldError>> details=new HashMap<>();
@@ -231,7 +233,7 @@ public class MastodonAPIController{
});
}catch(Exception x){
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);
}
}, 0);
@@ -244,4 +246,8 @@ public class MastodonAPIController{
public static OkHttpClient getHttpClient(){
return httpClient;
}
private static String logTag(AccountSession session){
return "["+(session==null ? "no-auth" : session.getID())+"] ";
}
}

View File

@@ -180,6 +180,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
}
public RequestBody getRequestBody() throws IOException{
if(requestBody instanceof RequestBody rb)
return rb;
return requestBody==null ? null : new JsonObjectRequestBody(requestBody);
}

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;
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);
setRequestBody(new Body(username, email, password, locale, reason, timezone));
setRequestBody(new Body(username, email, password, locale, reason, timezone, inviteCode));
}
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 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.email=email;
this.password=password;
this.locale=locale;
this.reason=reason;
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;
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);
setRequestBody(new Request(duration));
if(muted)
setRequestBody(new Request(duration, muteNotifications));
else{
setRequestBody(new Object());
}
}
private static class Request{
public long duration;
public Request(long duration){
public boolean muteNotifications;
public Request(long duration, boolean muteNotifications){
this.duration=duration;
this.muteNotifications=muteNotifications;
}
}
}

View File

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

View File

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

View File

@@ -1,17 +1,19 @@
package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
public class AddAccountsToList extends MastodonAPIRequest<Object> {
public AddAccountsToList(String listId, List<String> accountIds){
super(HttpMethod.POST, "/lists/"+listId+"/accounts", Object.class);
Request req = new Request();
req.accountIds = accountIds;
setRequestBody(req);
}
import java.nio.charset.StandardCharsets;
import java.util.Collection;
public static class Request{
public List<String> accountIds;
}
import okhttp3.FormBody;
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;
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 CreateList(String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.POST, "/lists", ListTimeline.class);
Request req = new Request();
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
public class CreateList extends MastodonAPIRequest<FollowList>{
public CreateList(String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
super(HttpMethod.POST, "/lists", FollowList.class);
setRequestBody(new Request(title, repliesPolicy, exclusive));
}
public static class Request {
private static class Request{
public String title;
public FollowList.RepliesPolicy repliesPolicy;
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;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
public class DeleteList extends MastodonAPIRequest<Object> {
public DeleteList(String id) {
super(HttpMethod.DELETE, "/lists/" + id, Object.class);
public class DeleteList extends ResultlessMastodonAPIRequest{
public DeleteList(String id){
super(HttpMethod.DELETE, "/lists/"+id);
}
}

View File

@@ -1,10 +1,10 @@
package org.joinmastodon.android.api.requests.lists;
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) {
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 org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.FollowList;
import java.util.List;
public class GetLists extends MastodonAPIRequest<List<ListTimeline>>{
public class GetLists extends MastodonAPIRequest<List<FollowList>>{
public GetLists() {
super(HttpMethod.GET, "/lists", new TypeToken<>(){});
}

View File

@@ -1,17 +1,19 @@
package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
public class RemoveAccountsFromList extends MastodonAPIRequest<Object> {
public RemoveAccountsFromList(String listId, List<String> accountIds){
super(HttpMethod.DELETE, "/lists/"+listId+"/accounts", Object.class);
Request req = new Request();
req.accountIds = accountIds;
setRequestBody(req);
}
import java.nio.charset.StandardCharsets;
import java.util.Collection;
public static class Request{
public List<String> accountIds;
}
import okhttp3.FormBody;
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;
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 UpdateList(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
CreateList.Request req = new CreateList.Request();
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
public class UpdateList extends MastodonAPIRequest<FollowList>{
public UpdateList(String listID, String title, FollowList.RepliesPolicy repliesPolicy, boolean exclusive){
super(HttpMethod.PUT, "/lists/"+listID, FollowList.class);
setRequestBody(new Request(title, repliesPolicy, exclusive));
}
private static class Request{
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

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

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;
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<>(){});
if(local)
addQueryParameter("local", "true");
@@ -18,6 +18,10 @@ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
addQueryParameter("remote", "true");
if(!TextUtils.isEmpty(maxID))
addQueryParameter("max_id", maxID);
if(!TextUtils.isEmpty(minID))
addQueryParameter("min_id", minID);
if(!TextUtils.isEmpty(sinceID))
addQueryParameter("since_id", sinceID);
if(limit>0)
addQueryParameter("limit", limit+"");
if(replyVisibility != null)

View File

@@ -50,6 +50,7 @@ public class AccountLocalPreferences{
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 timelinesType=new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
@@ -68,6 +69,7 @@ public class AccountLocalPreferences{
revealCWs=prefs.getBoolean("revealCWs", false);
hideSensitiveMedia=prefs.getBoolean("hideSensitive", true);
serverSideFiltersSupported=prefs.getBoolean("serverSideFilters", false);
// preReplySheet=prefs.getBoolean("preReplySheet", false);
// MEGALODON
showReplies=prefs.getBoolean("showReplies", true);
@@ -112,6 +114,9 @@ public class AccountLocalPreferences{
.putBoolean("hideSensitive", hideSensitiveMedia)
.putBoolean("serverSideFilters", serverSideFiltersSupported)
//TODO figure this stuff out
// .putBoolean("preReplySheet", preReplySheet)
// MEGALODON
.putBoolean("showReplies", showReplies)
.putBoolean("showBoosts", showBoosts)

View File

@@ -26,6 +26,7 @@ import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterResult;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushSubscription;
@@ -34,6 +35,7 @@ import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.model.Token;
import org.joinmastodon.android.utils.ObjectIdComparator;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -70,6 +72,7 @@ public class AccountSession{
private transient SharedPreferences prefs;
private transient boolean preferencesNeedSaving;
private transient AccountLocalPreferences localPreferences;
private transient List<FollowList> lists;
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
this.token=token;
@@ -343,4 +346,12 @@ public class AccountSession{
.map(instance->"https://"+domain+(instance.isAkkoma() ? "/images/avi.png" : "/avatars/original/missing.png"))
.orElse("");
}
public boolean isNotificationsMentionsOnly(){
return getRawLocalPreferences().getBoolean("notificationsMentionsOnly", false);
}
public void setNotificationsMentionsOnly(boolean mentionsOnly){
getRawLocalPreferences().edit().putBoolean("notificationsMentionsOnly", mentionsOnly).apply();
}
}

View File

@@ -96,6 +96,7 @@ public class AccountSessionManager{
private AccountSessionManager(){
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");
if(!file.exists())
return;
@@ -216,12 +217,17 @@ public class AccountSessionManager{
public void removeAccount(String id){
AccountSession session=getAccount(id);
session.getCacheController().closeDatabase();
session.getCacheController().getListsFile().delete();
MastodonApp.context.deleteDatabase(id+".db");
MastodonApp.context.getSharedPreferences(id, 0).edit().clear().commit();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
MastodonApp.context.deleteSharedPreferences(id);
}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);
if(lastActiveAccountID.equals(id)){

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;
public class ListDeletedEvent {
public final String id;
public class ListDeletedEvent{
public final String accountID;
public final String listID;
public ListDeletedEvent(String id) {
this.id = id;
public ListDeletedEvent(String accountID, String listID){
this.accountID=accountID;
this.listID=listID;
}
}

View File

@@ -1,14 +1,14 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.FollowList;
public class ListUpdatedCreatedEvent {
public final String id;
public final String title;
public final ListTimeline.RepliesPolicy repliesPolicy;
public final FollowList.RepliesPolicy repliesPolicy;
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.title = title;
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,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

@@ -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

@@ -28,6 +28,8 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
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.events.PollUpdatedEvent;
@@ -40,6 +42,8 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
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.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
@@ -63,6 +67,8 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.joinmastodon.android.utils.TypedObjectPool;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -145,6 +151,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
for(T s:items){
displayItems.addAll(buildDisplayItems(s));
}
loadRelationships(items.stream().map(DisplayItemsParent::getAccountID).filter(Objects::nonNull).collect(Collectors.toSet()));
}
@Override
@@ -166,6 +173,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
if(notify)
adapter.notifyItemRangeInserted(0, offset);
loadRelationships(items.stream().map(DisplayItemsParent::getAccountID).filter(Objects::nonNull).collect(Collectors.toSet()));
return offset;
}
@@ -222,7 +230,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
final Status status=_status.getContentStatus();
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, status, accountID, new PhotoViewer.Listener(){
private MediaAttachmentViewController transitioningHolder;
@Override
@@ -288,6 +296,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override
public void photoViewerDismissed(){
currentPhotoViewer=null;
gridHolder.itemView.setHasTransientState(false);
}
@Override
@@ -299,12 +308,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return gridHolder.getViewController(index);
}
});
gridHolder.itemView.setHasTransientState(true);
}
public void openPreviewlessMediaPhotoViewer(String parentID, Status _status, int attachmentIndex, PreviewlessMediaGridStatusDisplayItem.Holder gridHolder){
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;
@Override
@@ -753,12 +763,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
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.addAll(startPos, warning.filteredItems);
adapter.notifyItemRangeInserted(startPos, warning.filteredItems.size() - 1);
if (startPos == 0) scrollToTop();
warning.getItem().status.filterRevealed = true;
list.invalidateItemDecorations();
}
@Override
@@ -775,6 +787,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
protected void loadRelationships(Set<String> ids){
if(ids.isEmpty())
return;
ids=ids.stream().filter(id->!relationships.containsKey(id)).collect(Collectors.toSet());
if(ids.isEmpty())
return;
// TODO somehow manage these and cancel outstanding requests on refresh
@@ -1066,6 +1081,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
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){}
@Override

View File

@@ -7,6 +7,9 @@ import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDra
import android.Manifest;
import android.animation.ObjectAnimator;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
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.StatusCreatedEvent;
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.ContentType;
import org.joinmastodon.android.model.Emoji;
@@ -135,12 +138,14 @@ 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.fragments.CustomTransitionsFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
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;
public static final int IMAGE_DESCRIPTION_RESULT=363;
@@ -527,7 +532,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void onLaunchAccountSearch(){
Bundle args=new Bundle();
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();
@@ -1657,7 +1662,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
}
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(){
@Override
public boolean onMenuItemClick(MenuItem item){
@@ -1806,8 +1811,26 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
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
public boolean onAddMediaAttachmentFromEditText(Uri uri, String description){
description = sanitizeMediaDescription(description);
return mediaViewController.addMediaAttachment(uri, description);
}
@@ -1850,6 +1873,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Editable e=mainEditText.getText();
int start=e.getSpanStart(currentAutocompleteSpan);
int end=e.getSpanEnd(currentAutocompleteSpan);
if(start==-1 || end==-1)
return;
e.replace(start, end, text+" ");
finishAutocomplete();
InputConnection conn=mainEditText.getCurrentInputConnection();
@@ -1918,4 +1943,35 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
languageButton.setText(opt.language.getLanguageName());
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.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.style.BulletSpan;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -135,20 +132,9 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
@Override
public boolean onOptionsItemSelected(MenuItem item){
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)
.setTitle(R.string.what_is_alt_text)
.setMessage(msg)
.setMessage(UiUtils.fixBulletListInString(themeWrapper, R.string.alt_text_help))
.setPositiveButton(R.string.ok, null)
.show();
}
@@ -185,7 +171,7 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
fakeAttachment.meta.width=width;
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
public void setPhotoViewVisibility(int index, boolean visible){
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
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){
@Override
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.session.AccountLocalPreferences;
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.FollowList;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import java.util.ArrayList;
import java.util.Collections;
@@ -74,7 +71,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
private Menu optionsMenu;
private boolean updated;
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 MenuItem addHashtagItem;
private final List<CustomLocalTimeline> localTimelines = new ArrayList<>();
@@ -94,8 +91,8 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
new GetLists().setCallback(new Callback<>(){
@Override
public void onSuccess(List<ListTimeline> result){
listTimelines.addAll(result);
public void onSuccess(List<FollowList> result){
followLists.addAll(result);
updateOptionsMenu();
}
@@ -224,7 +221,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
makeBackItem(hashtagsMenu);
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);
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl->addTimelineToOptions(tl, hashtagsMenu));

View File

@@ -32,6 +32,8 @@ import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.FilterContext;
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.Status;
import org.joinmastodon.android.model.TimelineDefinition;
@@ -63,6 +65,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
private MenuItem followMenuItem, pinMenuItem, muteMenuItem;
private boolean followRequestRunning;
private boolean toolbarContentVisible;
private String maxID;
private List<String> any;
private List<String> all;

View File

@@ -5,8 +5,6 @@ import android.app.Fragment;
import android.app.NotificationManager;
import android.app.assist.AssistContent;
import android.graphics.drawable.RippleDrawable;
import android.content.Intent;
import android.graphics.Outline;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
@@ -37,7 +35,7 @@ import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestions
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Notification;
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.utils.UiUtils;
import org.joinmastodon.android.ui.views.TabBar;
@@ -312,11 +310,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
discoverFragment.openSearch();
return true;
}
if(tab==R.id.tab_home){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
}
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.Hashtag;
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.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -95,7 +95,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
private ImageView collapsedChevron;
private TextView timelineTitle;
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 List<TimelineDefinition> timelinesList;
private int count;
@@ -270,7 +270,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
new GetLists().setCallback(new Callback<>() {
@Override
public void onSuccess(List<ListTimeline> lists) {
public void onSuccess(List<FollowList> lists) {
updateList(lists, listItems);
}
@@ -512,7 +512,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
Bundle args=new Bundle();
args.putString("account", accountID);
int id = item.getItemId();
ListTimeline list;
FollowList list;
Hashtag hashtag;
if (item.getItemId() == R.id.menu_back) {
@@ -701,13 +701,13 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Subscribe
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
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
handleListEvent(listItems, l -> l.id.equals(event.id), true, () -> {
ListTimeline list = new ListTimeline();
FollowList list = new FollowList();
list.id = event.id;
list.title = event.title;
list.repliesPolicy = event.repliesPolicy;

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

@@ -20,7 +20,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
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.TimelineDefinition;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
@@ -39,7 +39,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
private String listID;
private String listTitle;
@Nullable
private ListTimeline.RepliesPolicy repliesPolicy;
private FollowList.RepliesPolicy repliesPolicy;
private boolean exclusive;
@Override
@@ -54,19 +54,19 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
listID = args.getString("listID");
listTitle = args.getString("listTitle");
exclusive = args.getBoolean("listIsExclusive");
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
repliesPolicy = FollowList.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
setTitle(listTitle);
setHasOptionsMenu(true);
new GetList(listID).setCallback(new Callback<>() {
@Override
public void onSuccess(ListTimeline listTimeline) {
public void onSuccess(FollowList followList) {
if(getActivity()==null) return;
// TODO: save updated info
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
repliesPolicy = listTimeline.repliesPolicy;
if (!followList.title.equals(listTitle)) setTitle(followList.title);
if (followList.repliesPolicy != null && !followList.repliesPolicy.equals(repliesPolicy)) {
repliesPolicy = followList.repliesPolicy;
}
}
@@ -97,9 +97,9 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
.setPositiveButton(R.string.save, (d, which) -> {
String newTitle = editor.getTitle().trim();
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
public void onSuccess(ListTimeline list) {
public void onSuccess(FollowList list) {
if(getActivity()==null) return;
setTitle(list.title);
listTitle = list.title;
@@ -119,7 +119,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
.show();
} else if (item.getItemId() == R.id.delete) {
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
E.post(new ListDeletedEvent(listID));
E.post(new ListDeletedEvent(accountID, listID));
Nav.finish(this);
});
}

View File

@@ -18,13 +18,14 @@ 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.ResultlessMastodonAPIRequest;
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
import org.joinmastodon.android.api.requests.lists.CreateList;
import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.FollowList;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
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.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 profileAccountId;
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)
.setView(editor)
.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
public void onSuccess(ListTimeline list) {
public void onSuccess(FollowList list) {
data.add(0, list);
adapter.notifyItemRangeInserted(0, 1);
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) {
userInList.put(listId, isMember);
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<>() {
@Override
public void onSuccess(Object o) {}
public void onSuccess(Void o) {}
@Override
public void onError(ErrorResponse error) {
@@ -139,19 +140,19 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
.setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(List<ListTimeline> lists) {
public void onSuccess(List<FollowList> lists) {
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);
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
if (profileAccountId == null) return;
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
@Override
public void onSuccess(List<ListTimeline> allLists) {
public void onSuccess(List<FollowList> allLists) {
if(getActivity()==null) return;
List<ListTimeline> newLists = new ArrayList<>();
for (ListTimeline l : allLists) {
List<FollowList> newLists = new ArrayList<>();
for (FollowList l : allLists) {
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
if (!userInListBefore.containsKey(l.id)) {
userInListBefore.put(l.id, false);
@@ -169,8 +170,8 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
@Subscribe
public void onListDeletedEvent(ListDeletedEvent event) {
for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i);
if (item.id.equals(event.id)) {
FollowList item = data.get(i);
if (item.id.equals(event.listID)) {
data.remove(i);
adapter.notifyItemRemoved(i);
break;
@@ -181,7 +182,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
@Subscribe
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i);
FollowList item = data.get(i);
if (item.id.equals(event.id)) {
item.title = event.title;
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 CheckBox listToggle;
@@ -241,7 +242,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
}
@Override
public void onBind(ListTimeline item) {
public void onBind(FollowList item) {
title.setText(item.title);
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(
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 org.joinmastodon.android.R;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ElevationOnScrollListener;

View File

@@ -7,6 +7,10 @@ import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
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 com.squareup.otto.Subscribe;
@@ -14,6 +18,7 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
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.utils.MergeRecyclerAdapter;
public class NotificationsListFragment extends BaseStatusListFragment<Notification> {
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
private boolean onlyMentions;
private boolean onlyPosts;
private String maxID;
private boolean reloadingFromCache;
private DiscoverInfoBannerHelper bannerHelper;
private String unreadMarker, realUnreadMarker;
private MenuItem markAllReadItem;
@Override
protected boolean wantsComposeButton() {
@@ -63,13 +69,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
E.register(this);
if(savedInstanceState!=null){
onlyMentions=savedInstanceState.getBoolean("onlyMentions", false);
onlyPosts=savedInstanceState.getBoolean("onlyPosts", false);
}
if (onlyPosts) {
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS, accountID);
}
onlyMentions=getArguments().getBoolean("onlyMentions", false);
setHasOptionsMenu(true);
}
@Override
@@ -81,14 +82,12 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
onlyMentions=getArguments().getBoolean("onlyMentions", false);
onlyPosts=getArguments().getBoolean("onlyPosts", false);
setTitle(R.string.notifications);
}
@Override
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
if(!onlyMentions && !onlyPosts){
if(!onlyMentions){
switch(n.type){
case MENTION -> {
if(!getLocalPrefs().notificationFilters.mention)
@@ -164,7 +163,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance()
.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
public void onSuccess(PaginatedResponse<List<Notification>> result){
if(getActivity()==null)
@@ -186,7 +185,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
}
});
}
@Override
protected void onRelationshipsLoaded(){
if(getActivity()==null)
@@ -201,13 +200,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override
protected void onShown(){
super.onShown();
if(!dataLoading){
if(onlyMentions){
refresh();
}else{
reloadingFromCache=true;
refresh();
}
unreadMarker=realUnreadMarker=AccountSessionManager.get(accountID).getLastKnownNotificationsMarker();
if(!dataLoading && canRefreshWithoutUpsettingUser()){
reloadingFromCache=true;
refresh();
}
}
@@ -268,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){
for(Notification n:data){
if(n.id.equals(id))
@@ -380,11 +369,38 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
adapter.notifyItemRangeRemoved(index, lastIndex-index);
}
@Override
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());
}
@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(){
if (getParentFragment() instanceof NotificationsFragment nf) {
nf.unreadMarker=nf.realUnreadMarker;
@@ -396,13 +412,20 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onRefresh(){
super.onRefresh();
if (getParentFragment() instanceof NotificationsFragment nf) {
if (!onlyMentions && !onlyPosts) nf.markAsRead();
if (!onlyMentions) nf.markAsRead();
else AccountSessionManager.get(accountID).reloadNotificationsMarker(m->{
nf.unreadMarker=nf.realUnreadMarker=m;
nf.updateMarkAllReadButton();
});
}
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
@@ -420,4 +443,20 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
? "/users/" + getSession().self.username + "/interactions"
: "/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

@@ -8,6 +8,8 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -21,12 +23,9 @@ import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.text.TextWatcher;
import android.transition.ChangeBounds;
import android.transition.Fade;
import android.transition.TransitionManager;
@@ -43,8 +42,6 @@ import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
@@ -72,11 +69,9 @@ import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.account_list.BlockedAccountsListFragment;
import org.joinmastodon.android.fragments.account_list.BlocksListFragment;
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
import org.joinmastodon.android.fragments.account_list.MutedAccountsListFragment;
import org.joinmastodon.android.fragments.account_list.MutesListFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
import org.joinmastodon.android.model.Account;
@@ -90,6 +85,7 @@ import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
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.TabLayoutMediator;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
@@ -143,7 +139,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ImageView avatar;
private CoverImageView cover;
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 ProgressBarButton actionButton, notifyButton;
private ViewPager2 pager;
@@ -249,6 +245,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
name=content.findViewById(R.id.name);
usernameWrap=content.findViewById(R.id.username_wrap);
username=content.findViewById(R.id.username);
usernameDomain=content.findViewById(R.id.username_domain);
lockIcon=content.findViewById(R.id.lock_icon);
botIcon=content.findViewById(R.id.bot_icon);
bio=content.findViewById(R.id.bio);
@@ -401,28 +398,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
content.findViewById(R.id.username_wrap).setOnClickListener(v->{
try {
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();
}
new DecentralizationExplainerSheet(getActivity(), accountID, account).show();
});
content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{
@@ -430,7 +406,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(!usernameString.contains("@")){
usernameString+="@"+domain;
}
UiUtils.copyText(username, '@'+usernameString);
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, "@"+usernameString));
UiUtils.maybeShowTextCopiedToast(getActivity());
return true;
});
@@ -458,6 +435,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
nameEdit.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;
}
@@ -718,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)
? account.getFullyQualifiedName()
: account.acct);
// String acct = ((isSelf || account.isRemote)
// ? account.getFullyQualifiedName()
// : 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.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor()));
@@ -743,7 +735,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, account.followingCount)));
if (account.followersCount < 0) followersBtn.setVisibility(View.GONE);
if (account.followingCount < 0) followingBtn.setVisibility(View.GONE);
if (account.followersCount < 0 || account.followingCount < 0)
@@ -775,7 +767,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
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);
ssb=new SpannableStringBuilder(field.name);
HtmlParser.parseCustomEmoji(ssb, account.emojis);
@@ -973,6 +965,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.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;
}
@@ -1364,7 +1361,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return;
int radius=V.dp(25);
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));
}
}
@@ -1376,7 +1373,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(drawable==null || drawable instanceof ColorDrawable)
return;
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)));
}
}

View File

@@ -1,7 +1,12 @@
package org.joinmastodon.android.fragments;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -11,6 +16,8 @@ import android.widget.ProgressBar;
import org.joinmastodon.android.MastodonApp;
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.instance.GetInstance;
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.ui.InterpolatingMotionEffect;
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.views.ProgressBarButton;
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
@@ -48,6 +56,9 @@ public class SplashFragment extends AppKitFragment{
private ProgressBar defaultServerProgress;
private String chosenDefaultServer=DEFAULT_SERVER;
private boolean loadingDefaultServer, loadedDefaultServer;
private Uri currentInviteLink;
private ProgressDialog instanceLoadingProgress;
private String inviteCode;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -110,19 +121,65 @@ public class SplashFragment extends AppKitFragment{
Bundle extras=new Bundle();
boolean isSignup=v.getId()==R.id.btn_get_started;
extras.putBoolean("signup", isSignup);
extras.putString("defaultServer", chosenDefaultServer);
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
}
private void onJoinDefaultServerClick(View v){
if(loadingDefaultServer)
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()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Instance result){
if(getActivity()==null)
return;
if(!result.registrations){
instanceLoadingProgress.dismiss();
instanceLoadingProgress=null;
if(!result.registrations && TextUtils.isEmpty(inviteCode)){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.instance_signup_closed)
@@ -132,6 +189,8 @@ public class SplashFragment extends AppKitFragment{
}
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(result));
if(inviteCode!=null)
args.putString("inviteCode", inviteCode);
Nav.go(getActivity(), InstanceRulesFragment.class, args);
}
@@ -139,11 +198,12 @@ public class SplashFragment extends AppKitFragment{
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
instanceLoadingProgress.dismiss();
instanceLoadingProgress=null;
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading_instance, true)
.execNoAuth(chosenDefaultServer);
.execNoAuth(domain);
}
private void onLearnMoreClick(View v){
@@ -198,7 +258,18 @@ public class SplashFragment extends AppKitFragment{
}
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()
.setCallback(new Callback<>(){
@Override
@@ -241,7 +312,7 @@ public class SplashFragment extends AppKitFragment{
chosenDefaultServer=domain;
loadingDefaultServer=false;
loadedDefaultServer=true;
if(defaultServerButton!=null && getActivity()!=null){
if(defaultServerButton!=null && getActivity()!=null && currentInviteLink==null){
defaultServerButton.setTextVisible(true);
defaultServerProgress.setVisibility(View.GONE);
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.net.Uri;
import android.os.Bundle;
import android.view.View;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
import org.joinmastodon.android.model.FilterContext;
@@ -13,7 +13,6 @@ import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.time.ZoneId;
@@ -26,6 +25,8 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import name.fraser.neil.plaintext.diff_match_patch;
@@ -54,12 +55,47 @@ public class StatusEditHistoryFragment extends StatusListFragment{
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
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);
}
})
.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
protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=new ArrayList<>();

View File

@@ -2,11 +2,19 @@ package org.joinmastodon.android.fragments;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
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.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
@@ -23,6 +31,7 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusContext;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
@@ -50,7 +59,13 @@ import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
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.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;
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
@@ -58,10 +73,16 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
private StatusContext result;
protected boolean contextInitiallyRendered, transitionFinished, preview;
private FrameLayout replyContainer;
private LinearLayout replyButton;
private ImageView replyButtonAva;
private TextView replyButtonText;
private int lastBottomInset;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setLayout(R.layout.fragment_thread);
mainStatus=Parcels.unwrap(getArguments().getParcelable("status"));
replyTo=Parcels.unwrap(getArguments().getParcelable("inReplyTo"));
Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount"));
@@ -143,10 +164,10 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
}
}
}
for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem);
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;
}
@@ -387,6 +408,22 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
@Override
public void onViewCreated(View view, Bundle 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);
showContent();
if(!loaded)
@@ -526,4 +563,35 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
}
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 org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
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.viewmodel.AccountViewModel;
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.parceler.Parcels;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback;
public class ComposeAccountSearchFragment extends BaseAccountListFragment{
private String currentQuery;
public class AccountSearchFragment extends BaseAccountListFragment{
protected String currentQuery;
private boolean resultDelivered;
private SearchViewHelper searchViewHelper;
@@ -29,12 +32,11 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
super.onCreate(savedInstanceState);
setRefreshEnabled(false);
setEmptyText("");
dataLoaded();
}
@Override
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.addDivider(contentView);
super.onViewCreated(view, savedInstanceState);
@@ -52,13 +54,21 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(SearchResults result){
setEmptyText(R.string.no_search_results);
onDataLoaded(result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), false);
AccountSearchFragment.this.onSuccess(result.accounts);
}
})
.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
protected void 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 String accountID;
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
protected int itemLayoutRes=R.layout.item_account_list;
public BaseAccountListFragment(){
super(40);
@@ -74,6 +75,8 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
protected void loadRelationships(List<AccountViewModel> accounts){
Set<String> ids=accounts.stream().map(ai->ai.account.id).collect(Collectors.toSet());
if(ids.isEmpty())
return;
GetAccountRelationships req=new GetAccountRelationships(ids);
relationshipsRequests.add(req);
req.setCallback(new Callback<>(){
@@ -124,13 +127,6 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
Toolbar toolbar=getToolbar();
if(toolbar!=null && toolbar.getNavigationIcon()!=null){
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 onBindViewHolder(AccountViewHolder holder){}
protected class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){
@@ -171,7 +168,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
@NonNull
@Override
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);
return holder;
}
@@ -179,6 +176,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
@Override
public void onBindViewHolder(AccountViewHolder holder, int position){
holder.bind(data.get(position));
BaseAccountListFragment.this.onBindViewHolder(holder);
super.onBindViewHolder(holder, position);
}

View File

@@ -18,7 +18,7 @@ import me.grishka.appkit.utils.MergeRecyclerAdapter;
public class DiscoverPostsFragment extends StatusListFragment{
private DiscoverInfoBannerHelper bannerHelper;
private int offset;
private int realOffset=0;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -26,26 +26,23 @@ public class DiscoverPostsFragment extends StatusListFragment{
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS, accountID);
}
@Override
protected void doLoadData(int o, int count){
if(refreshing) offset=0;
currentRequest=new GetTrendingStatuses(offset, count)
protected void doLoadData(int offset, int count){
currentRequest=new GetTrendingStatuses(offset==0 ? 0 : realOffset, count)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
boolean empty=result.isEmpty();
offset+=result.size();
realOffset+=result.size();
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, !empty);
onDataLoaded(result, !result.isEmpty());
bannerHelper.onBannerBecameVisible();
}
}).exec(accountID);
}
@Override
protected RecyclerView.Adapter<?> getAdapter(){
protected RecyclerView.Adapter getAdapter(){
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
bannerHelper.maybeAddBanner(list, adapter);
adapter.addAdapter(super.getAdapter());

View File

@@ -29,7 +29,7 @@ public class FederatedTimelineFragment extends StatusListFragment{
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(false, false, getMaxID(), count, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetPublicTimeline(false, false, getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){

View File

@@ -29,7 +29,7 @@ public class LocalTimelineFragment extends StatusListFragment{
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(true, false, getMaxID(), count, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetPublicTimeline(true, false, getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){

View File

@@ -143,7 +143,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}*/
int offset=_offset;
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
.setCallback(new SimpleCallback<>(this){
.setCallback(new SimpleCallback<SearchResults>(this){
@Override
public void onSuccess(SearchResults result){
ArrayList<SearchResult> results=new ArrayList<>();
@@ -164,7 +164,10 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}
prevDisplayItems=new ArrayList<>(displayItems);
unfilteredResults=results;
boolean wasRefreshing=refreshing;
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
if(wasRefreshing)
list.scrollToPosition(0);
}
})
.setTimeout(180000) // 3 minutes (searches can take a long time)

View File

@@ -10,6 +10,7 @@ import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
@@ -26,6 +27,7 @@ import android.widget.Toast;
import android.widget.Toolbar;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -434,7 +436,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}
private void onOpenURLClick(ListItem<?> item_){
UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false);
((MainActivity)getActivity()).handleURL(Uri.parse(searchViewHelper.getQuery()), accountID);
}
private void onGoToHashtagClick(ListItem<?> item_){

View File

@@ -24,7 +24,7 @@ import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;

View File

@@ -137,6 +137,9 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
protected void onButtonClick(){
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
if(getArguments().containsKey("inviteCode")){
args.putString("inviteCode", getArguments().getString("inviteCode"));
}
Nav.goForResult(getActivity(), SignupFragment.class, args, SIGNUP_REQUEST, this);
}

View File

@@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments.onboarding;
import android.app.Activity;
import android.app.ProgressDialog;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
@@ -38,6 +37,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -48,7 +48,6 @@ import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.V;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
@@ -61,6 +60,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
protected EditText searchEdit;
protected Runnable searchDebouncer=this::onSearchChangedDebounced;
protected String currentSearchQuery;
protected String currentSearchQueryButWithCasePreserved;
protected String loadingInstanceDomain;
protected HashMap<String, Instance> instancesCache=new HashMap<>();
protected View buttonBar;
@@ -91,6 +91,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
return true;
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
currentSearchQueryButWithCasePreserved=searchEdit.getText().toString().trim();
updateFilteredList();
searchEdit.removeCallbacks(searchDebouncer);
Instance instance=instancesCache.get(normalizeInstanceDomain(getCurrentSearchQuery()));
@@ -105,6 +106,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
protected void onSearchChangedDebounced(){
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
currentSearchQueryButWithCasePreserved=searchEdit.getText().toString().trim();
updateFilteredList();
loadInstanceInfo(getCurrentSearchQuery(), false);
}
@@ -156,6 +158,10 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
}
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
loadInstanceInfo(_domain, isFromRedirect, null);
}
protected void loadInstanceInfo(String _domain, boolean isFromRedirect, Consumer<Object> onError){
if(TextUtils.isEmpty(_domain))
return;
String domain=normalizeInstanceDomain(_domain);
@@ -180,7 +186,10 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
try{
new URI("https://"+domain+"/api/v1/instance"); // Validate the host by trying to parse the URI
}catch(URISyntaxException x){
showInstanceInfoLoadError(domain, x);
if(onError!=null)
onError.accept(x);
else
showInstanceInfoLoadError(domain, x);
if(fakeInstance!=null){
fakeInstance.description=getString(R.string.error);
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
@@ -200,10 +209,11 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
loadingInstanceDomain=null;
result.uri=domain; // needed for instances that use domain redirection
instancesCache.put(domain, result);
if(instanceProgressDialog!=null || onError!=null)
proceedWithAuthOrSignup(result);
if(instanceProgressDialog!=null){
instanceProgressDialog.dismiss();
instanceProgressDialog=null;
proceedWithAuthOrSignup(result);
}
if(Objects.equals(domain, getCurrentSearchQuery()) || Objects.equals(getCurrentSearchQuery(), redirects.get(domain)) || Objects.equals(getCurrentSearchQuery(), redirectsInverse.get(domain))){
boolean found=false;
@@ -230,11 +240,14 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
public void onError(ErrorResponse error){
loadingInstanceRequest=null;
if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){
fetchDomainFromHostMetaAndMaybeRetry(domain, error);
fetchDomainFromHostMetaAndMaybeRetry(domain, error, onError);
return;
}
loadingInstanceDomain=null;
showInstanceInfoLoadError(domain, error);
if(onError!=null)
onError.accept(error);
else
showInstanceInfoLoadError(domain, error);
if(fakeInstance!=null && getActivity()!=null){
fakeInstance.description=getString(R.string.error);
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
@@ -283,7 +296,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
}
}
private void fetchDomainFromHostMetaAndMaybeRetry(String domain, Object origError){
private void fetchDomainFromHostMetaAndMaybeRetry(String domain, Object origError, Consumer<Object> onError){
String url="https://"+domain+"/.well-known/host-meta";
Request req=new Request.Builder()
.url(url)
@@ -297,7 +310,12 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
Activity a=getActivity();
if(a==null)
return;
a.runOnUiThread(()->showInstanceInfoLoadError(domain, e));
a.runOnUiThread(()->{
if(onError!=null)
onError.accept(e);
else
showInstanceInfoLoadError(domain, e);
});
}
@Override
@@ -309,7 +327,13 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
return;
try(response){
if(!response.isSuccessful()){
a.runOnUiThread(()->showInstanceInfoLoadError(domain, response.code()+" "+response.message()));
a.runOnUiThread(()->{
String err=response.code()+" "+response.message();
if(onError!=null)
onError.accept(err);
else
showInstanceInfoLoadError(domain, err);
});
return;
}
InputSource source=new InputSource(response.body().charStream());
@@ -328,9 +352,19 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
}
}
}
a.runOnUiThread(()->showInstanceInfoLoadError(domain, origError));
a.runOnUiThread(()->{
if(onError!=null)
onError.accept(origError);
else
showInstanceInfoLoadError(domain, origError);
});
}catch(Exception x){
a.runOnUiThread(()->showInstanceInfoLoadError(domain, x));
a.runOnUiThread(()->{
if(onError!=null)
onError.accept(x);
else
showInstanceInfoLoadError(domain, x);
});
}
}
});

View File

@@ -1,8 +1,13 @@
package org.joinmastodon.android.fragments.onboarding;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
@@ -12,6 +17,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.HorizontalScrollView;
import android.widget.ImageButton;
import android.widget.LinearLayout;
@@ -19,9 +26,12 @@ import android.widget.PopupMenu;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.accounts.CheckInviteLink;
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
import org.joinmastodon.android.model.Instance;
@@ -29,6 +39,8 @@ import org.joinmastodon.android.model.catalog.CatalogCategory;
import org.joinmastodon.android.model.catalog.CatalogInstance;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FilterChipView;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
@@ -40,7 +52,9 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
@@ -77,6 +91,9 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
private CatalogInstance.Region chosenRegion;
private CategoryChoice categoryChoice=CategoryChoice.GENERAL;
private String inviteCode, inviteCodeHost;
private AlertDialog currentInviteLinkAlert;
public InstanceCatalogSignupFragment(){
super(R.layout.fragment_onboarding_common, 10);
}
@@ -317,7 +334,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
focusThing=view.findViewById(R.id.focus_thing);
focusThing.requestFocus();
view.findViewById(R.id.btn_random_instance).setOnClickListener(this::onPickRandomInstanceClick);
view.findViewById(R.id.btn_use_invite).setOnClickListener(this::onUseInviteClick);
nextButton.setEnabled(chosenInstance!=null);
}
@@ -351,91 +368,191 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
@Override
protected void proceedWithAuthOrSignup(Instance instance){
if(currentInviteLinkAlert!=null){
currentInviteLinkAlert.dismiss();
}else if(!TextUtils.isEmpty(currentSearchQuery) && HtmlParser.INVITE_LINK_PATTERN.matcher(currentSearchQueryButWithCasePreserved).find()){
if(TextUtils.isEmpty(inviteCode) || !Objects.equals(instance.uri, inviteCodeHost)){
Uri inviteLink=Uri.parse(currentSearchQueryButWithCasePreserved);
new CheckInviteLink(inviteLink.getPath())
.setCallback(new Callback<>(){
@Override
public void onSuccess(CheckInviteLink.Response result){
inviteCodeHost=inviteLink.getHost();
inviteCode=result.inviteCode;
proceedWithAuthOrSignup(instance);
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
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, inviteLink.getHost(), getArguments().getString("defaultServer")))
.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, inviteLink.getHost(), getArguments().getString("defaultServer")))
.setPositiveButton(R.string.ok, null)
.show();
default -> error.showToast(getActivity());
}
}
}
})
.wrapProgress(getActivity(), R.string.loading_instance, true)
.execNoAuth(inviteLink.getHost());
return;
}
}
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
if(!instance.registrations){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.instance_signup_closed)
.setPositiveButton(R.string.ok, null)
.show();
if(!instance.registrations && (TextUtils.isEmpty(inviteCode) || !Objects.equals(instance.uri, inviteCodeHost))){
if(instance.invitesEnabled){
showInviteLinkAlert(instance.uri);
}else{
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.instance_signup_closed)
.setPositiveButton(R.string.ok, null)
.show();
}
return;
}
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
if(!TextUtils.isEmpty(inviteCode) && Objects.equals(instance.uri, inviteCodeHost))
args.putString("inviteCode", inviteCode);
Nav.go(getActivity(), InstanceRulesFragment.class, args);
}
private void onPickRandomInstanceClick(View v){
String lang=Locale.getDefault().getLanguage();
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
if(instances.isEmpty()){
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
}
if(instances.isEmpty()){
instances=data.stream().filter(ci->("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
}
if(instances.isEmpty()){
return;
}
chosenInstance=instances.get(new Random().nextInt(instances.size()));
onNextClick(v);
private void onUseInviteClick(View v){
showInviteLinkAlert(null);
}
// private String getEmojiForCategory(String category){
// return switch(category){
// case "all" -> "💬";
// case "academia" -> "📚";
// case "activism" -> "✊";
// case "food" -> "🍕";
// case "furry" -> "🦁";
// case "games" -> "🕹";
// case "general" -> "🐘";
// case "journalism" -> "📰";
// case "lgbt" -> "🏳️‍🌈";
// case "regional" -> "📍";
// case "art" -> "🎨";
// case "music" -> "🎼";
// case "tech" -> "📱";
// default -> "❓";
// };
// }
private void showInviteLinkAlert(String domain){
AlertDialog alert=new M3AlertDialogBuilder(getActivity())
.setView(R.layout.alert_invite_link)
.setPositiveButton(R.string.next, null)
.setNegativeButton(R.string.cancel, null)
.create();
private int getEmojiForCategory(String category){
return switch(category){
case "all" -> R.drawable.ic_category_all;
case "academia" -> R.drawable.ic_category_academia;
case "activism" -> R.drawable.ic_category_activism;
case "food" -> R.drawable.ic_category_food;
case "furry" -> R.drawable.ic_category_furry;
case "games" -> R.drawable.ic_category_games;
case "general" -> R.drawable.ic_category_general;
case "journalism" -> R.drawable.ic_category_journalism;
case "lgbt" -> R.drawable.ic_category_lgbt;
case "regional" -> R.drawable.ic_category_regional;
case "art" -> R.drawable.ic_category_art;
case "music" -> R.drawable.ic_category_music;
case "tech" -> R.drawable.ic_category_tech;
default -> R.drawable.ic_category_unknown;
};
}
Button next=alert.getButton(AlertDialog.BUTTON_POSITIVE);
EditText edit=alert.findViewById(R.id.edit);
TextView supportingText=alert.findViewById(R.id.supporting_text);
TextView label=alert.findViewById(R.id.label);
TextView subtitle=alert.findViewById(R.id.subtitle);
ImageButton clear=alert.findViewById(R.id.clear);
clear.setVisibility(View.GONE);
private int getTitleForCategory(String category){
return switch(category){
case "all" -> R.string.category_all;
case "academia" -> R.string.category_academia;
case "activism" -> R.string.category_activism;
case "food" -> R.string.category_food;
case "furry" -> R.string.category_furry;
case "games" -> R.string.category_games;
case "general" -> R.string.category_general;
case "journalism" -> R.string.category_journalism;
case "lgbt" -> R.string.category_lgbt;
case "regional" -> R.string.category_regional;
case "art" -> R.string.category_art;
case "music" -> R.string.category_music;
case "tech" -> R.string.category_tech;
default -> 0;
if(TextUtils.isEmpty(domain)){
subtitle.setVisibility(View.GONE);
}else{
subtitle.setText(getString(R.string.need_invite_to_join_server, domain));
}
Consumer<String> errorSetter=err->{
supportingText.setText(err);
int errorColor=UiUtils.getThemeColor(getActivity(), R.attr.colorM3Error);
supportingText.setTextColor(errorColor);
label.setTextColor(errorColor);
edit.setBackgroundResource(R.drawable.bg_m3_filled_text_field_error);
};
next.setOnClickListener(_v->{
Uri inviteLink=Uri.parse(edit.getText().toString());
if(TextUtils.isEmpty(inviteLink.getHost()) || TextUtils.isEmpty(inviteLink.getPath())){
errorSetter.accept(getString(R.string.this_invite_is_invalid));
return;
}
UiUtils.showProgressForAlertButton(next, true);
new CheckInviteLink(inviteLink.getPath())
.setCallback(new Callback<>(){
@Override
public void onSuccess(CheckInviteLink.Response result){
if(getActivity()==null || !alert.isShowing())
return;
String host=inviteLink.getHost();
inviteCode=result.inviteCode;
inviteCodeHost=host;
Instance instance=instancesCache.get(normalizeInstanceDomain(host));
if(instance==null){
loadInstanceInfo(host, false, err->{
String errorStr;
if(err instanceof String str){
errorStr=str;
}else if(err instanceof Throwable x){
errorStr=x.getMessage();
}else if(err instanceof MastodonErrorResponse mer){
errorStr=mer.error;
}else{
errorStr=getString(R.string.error);
}
errorSetter.accept(errorStr);
UiUtils.showProgressForAlertButton(next, false);
});
}else{
proceedWithAuthOrSignup(instance);
}
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null || !alert.isShowing())
return;
UiUtils.showProgressForAlertButton(next, false);
if(error instanceof MastodonErrorResponse mer){
errorSetter.accept(switch(mer.httpStatus){
case 404 -> getString(R.string.this_invite_is_invalid);
case 401 -> getString(R.string.this_invite_has_expired);
default -> mer.error;
});
}
}
})
.execNoAuth(inviteLink.getHost());
});
next.setEnabled(false);
edit.addTextChangedListener(new SimpleTextWatcher(e->{
boolean wasEmpty=!next.isEnabled();
next.setEnabled(e.length()>0);
if(supportingText.length()>0){
supportingText.setText("");
int regularColor=UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant);
supportingText.setTextColor(regularColor);
label.setTextColor(regularColor);
edit.setBackgroundResource(R.drawable.bg_m3_filled_text_field);
}
if(wasEmpty!=(e.length()==0)){
int padEnd;
if(e.length()==0){
clear.setVisibility(View.GONE);
padEnd=V.dp(16);
}else{
clear.setVisibility(View.VISIBLE);
padEnd=V.dp(48);
}
edit.setPaddingRelative(edit.getPaddingStart(), edit.getPaddingTop(), padEnd, edit.getPaddingBottom());
}
}));
clear.setOnClickListener(_v->edit.setText(""));
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()){
edit.setText(clipText);
supportingText.setText(R.string.invite_link_pasted);
}
}
currentInviteLinkAlert=alert;
alert.setOnDismissListener(dialog->currentInviteLinkAlert=null);
alert.show();
}
@Override
@@ -444,8 +561,14 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
filteredData.clear();
if(searchQueryMode){
if(!TextUtils.isEmpty(currentSearchQuery)){
String actualQuery;
if(currentSearchQuery.startsWith("https:")){
actualQuery=Uri.parse(currentSearchQuery).getHost();
}else{
actualQuery=currentSearchQuery;
}
for(CatalogInstance instance:data){
if(instance.domain.contains(currentSearchQuery)){
if(instance.domain.contains(actualQuery)){
filteredData.add(instance);
}
}

View File

@@ -111,6 +111,9 @@ public class InstanceRulesFragment extends ToolbarFragment implements ProvidesAs
protected void onButtonClick(){
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
if(getArguments().containsKey("inviteCode")){
args.putString("inviteCode", getArguments().getString("inviteCode"));
}
Nav.goForResult(getActivity(), GoogleMadeMeAddThisFragment.class, args, RULES_REQUEST, this);
}

View File

@@ -5,6 +5,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
@@ -13,35 +14,38 @@ import org.joinmastodon.android.fragments.account_list.BaseAccountListFragment;
import org.joinmastodon.android.model.FollowSuggestion;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment{
private String accountID;
private View buttonBar;
private ElevationOnScrollListener onScrollListener;
private int numRunningFollowRequests=0;
public OnboardingFollowSuggestionsFragment(){
super(R.layout.fragment_onboarding_follow_suggestions, 40);
itemLayoutRes=R.layout.item_account_list;
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
setTitle(R.string.popular_on_mastodon);
setTitle(R.string.onboarding_recommendations_title);
accountID=getArguments().getString("account");
loadData();
}
@@ -50,7 +54,6 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
buttonBar=view.findViewById(R.id.button_bar);
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
view.findViewById(R.id.btn_next).setOnClickListener(UiUtils.rateLimitedClickListener(this::onFollowAllClick));
// view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
@@ -59,9 +62,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
if(onScrollListener!=null){
onScrollListener.setViews(buttonBar, getToolbar());
}
getToolbar().setContentInsetsRelative(V.dp(56), 0);
}
@Override
@@ -70,7 +71,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<FollowSuggestion> result){
onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID)).collect(Collectors.toList()), false);
onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID).stripLinksFromBio()).collect(Collectors.toList()), false);
}
})
.exec(accountID);
@@ -81,6 +82,20 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets));
}
@Override
protected RecyclerView.Adapter<?> getAdapter(){
// Unused in Moshidon
// TextView introText=new TextView(getActivity());
// introText.setTextAppearance(R.style.m3_body_large);
// introText.setTextColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurface));
// introText.setPaddingRelative(V.dp(56), 0, V.dp(24), V.dp(8));
// introText.setText(R.string.onboarding_recommendations_intro);
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
// mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(introText));
mergeAdapter.addAdapter(super.getAdapter());
return mergeAdapter;
}
private void onFollowAllClick(View v){
if(!loaded || relationships.isEmpty())
return;
@@ -156,6 +171,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(AccountViewHolder.AccessoryType.BUTTON, true);
holder.avatar.setOutlineProvider(OutlineProviders.roundedRect(8));
}
@Override

View File

@@ -11,6 +11,7 @@ import android.view.WindowInsets;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import org.joinmastodon.android.R;
@@ -19,12 +20,17 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import java.util.ArrayList;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
@@ -35,7 +41,7 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
public class OnboardingProfileSetupFragment extends ToolbarFragment implements ReorderableLinearLayout.OnDragListener{
public class OnboardingProfileSetupFragment extends ToolbarFragment{
private Button btn;
private View buttonBar;
private String accountID;
@@ -43,9 +49,9 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment implements R
private ScrollView scroller;
private EditText nameEdit, bioEdit;
private ImageView avaImage, coverImage;
private Button addRow;
private ReorderableLinearLayout profileFieldsLayout;
private Uri avatarUri, coverUri;
private LinearLayout scrollContent;
private CheckableListItem<Void> discoverableItem;
private static final int AVATAR_RESULT=348;
private static final int COVER_RESULT=183;
@@ -73,8 +79,6 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment implements R
bioEdit=view.findViewById(R.id.bio);
avaImage=view.findViewById(R.id.avatar);
coverImage=view.findViewById(R.id.header);
addRow=view.findViewById(R.id.add_row);
profileFieldsLayout=view.findViewById(R.id.profile_fields);
btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick());
@@ -86,31 +90,20 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment implements R
Account account=AccountSessionManager.getInstance().getAccount(accountID).self;
if(savedInstanceState==null){
nameEdit.setText(account.displayName);
makeFieldsRow();
}else{
ArrayList<String> fieldTitles=savedInstanceState.getStringArrayList("fieldTitles");
ArrayList<String> fieldValues=savedInstanceState.getStringArrayList("fieldValues");
for(int i=0;i<fieldTitles.size();i++){
View row=makeFieldsRow();
EditText title=row.findViewById(R.id.title);
EditText content=row.findViewById(R.id.content);
title.setText(fieldTitles.get(i));
content.setText(fieldValues.get(i));
}
if(fieldTitles.size()==4)
addRow.setVisibility(View.GONE);
}
addRow.setOnClickListener(v->{
makeFieldsRow();
if(profileFieldsLayout.getChildCount()==4){
addRow.setVisibility(View.GONE);
}
});
profileFieldsLayout.setDragListener(this);
avaImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), AVATAR_RESULT));
coverImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), COVER_RESULT));
scrollContent=view.findViewById(R.id.scrollable_content);
discoverableItem=new CheckableListItem<>(R.string.make_profile_discoverable, 0, CheckableListItem.Style.SWITCH_SEPARATED, true, R.drawable.ic_campaign_24px, item->showDiscoverabilityAlert());
GenericListItemsAdapter<Void> fakeAdapter=new GenericListItemsAdapter<>(List.of(discoverableItem));
ListItemViewHolder<?> holder=fakeAdapter.onCreateViewHolder(scrollContent, fakeAdapter.getItemViewType(0));
fakeAdapter.bindViewHolder(holder, 0);
holder.itemView.setBackground(UiUtils.getThemeDrawable(getActivity(), android.R.attr.selectableItemBackground));
holder.itemView.setOnClickListener(v->holder.onClick());
scrollContent.addView(holder.itemView);
return view;
}
@@ -129,17 +122,8 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment implements R
}
protected void onButtonClick(){
ArrayList<AccountField> fields=new ArrayList<>();
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
View row=profileFieldsLayout.getChildAt(i);
EditText title=row.findViewById(R.id.title);
EditText content=row.findViewById(R.id.content);
AccountField fld=new AccountField();
fld.name=title.getText().toString();
fld.value=content.getText().toString();
fields.add(fld);
}
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), avatarUri, coverUri, fields)
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), avatarUri, coverUri, null)
.setDiscoverableIndexable(discoverableItem.checked, discoverableItem.checked)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
@@ -163,39 +147,6 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment implements R
super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets));
}
private View makeFieldsRow(){
View view=LayoutInflater.from(getActivity()).inflate(R.layout.onboarding_profile_field, profileFieldsLayout, false);
profileFieldsLayout.addView(view);
view.findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
profileFieldsLayout.startDragging(view);
return true;
});
view.findViewById(R.id.delete).setOnClickListener(v->{
profileFieldsLayout.removeView(view);
if(addRow.getVisibility()==View.GONE)
addRow.setVisibility(View.VISIBLE);
});
return view;
}
@Override
public void onSwapItems(int oldIndex, int newIndex){}
@Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
ArrayList<String> fieldTitles=new ArrayList<>(), fieldValues=new ArrayList<>();
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
View row=profileFieldsLayout.getChildAt(i);
EditText title=row.findViewById(R.id.title);
EditText content=row.findViewById(R.id.content);
fieldTitles.add(title.getText().toString());
fieldValues.add(content.getText().toString());
}
outState.putStringArrayList("fieldTitles", fieldTitles);
outState.putStringArrayList("fieldValues", fieldValues);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(resultCode!=Activity.RESULT_OK)
@@ -215,4 +166,12 @@ public class OnboardingProfileSetupFragment extends ToolbarFragment implements R
img.setForeground(null);
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(uri, size, size));
}
private void showDiscoverabilityAlert(){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.discoverability)
.setMessage(R.string.discoverability_help)
.setPositiveButton(R.string.ok, null)
.show();
}
}

View File

@@ -1,18 +1,15 @@
package org.joinmastodon.android.fragments.onboarding;
import android.app.ProgressDialog;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
import android.text.Editable;
import android.text.Html;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -46,10 +43,13 @@ import org.jsoup.select.NodeVisitor;
import org.parceler.Parcels;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import androidx.annotation.Nullable;
@@ -58,7 +58,6 @@ import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
public class SignupFragment extends ToolbarFragment{
@@ -79,6 +78,7 @@ public class SignupFragment extends ToolbarFragment{
private ProgressDialog progressDialog;
private HashSet<EditText> errorFields=new HashSet<>();
private ElevationOnScrollListener onScrollListener;
private Set<String> serverSupportedTimezones, serverSupportedLocales;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -87,6 +87,8 @@ public class SignupFragment extends ToolbarFragment{
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
createAppAndGetToken();
setTitle(R.string.signup_title);
serverSupportedTimezones=Arrays.stream(getResources().getStringArray(R.array.server_supported_timezones)).collect(Collectors.toSet());
serverSupportedLocales=Arrays.stream(getResources().getStringArray(R.array.server_supported_locales)).collect(Collectors.toSet());
}
@Nullable
@@ -190,7 +192,36 @@ public class SignupFragment extends ToolbarFragment{
edit.setError(null);
}
errorFields.clear();
new RegisterAccount(username, email, password.getText().toString(), getResources().getConfiguration().locale.getLanguage(), reason.getText().toString(), ZoneId.systemDefault().getId())
String locale=null;
String timezone=ZoneId.systemDefault().getId();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
LocaleList localeList=getResources().getConfiguration().getLocales();
for(int i=0;i<localeList.size();i++){
Locale l=localeList.get(i);
if(serverSupportedLocales.contains(l.toLanguageTag())){
locale=l.toLanguageTag();
break;
}else if(serverSupportedLocales.contains(l.getLanguage())){
locale=l.getLanguage();
break;
}
}
}else{
Locale l=getResources().getConfiguration().locale;
if(serverSupportedLocales.contains(l.toLanguageTag())){
locale=l.toLanguageTag();
}else if(serverSupportedLocales.contains(l.getLanguage())){
locale=l.getLanguage();
}
}
if(!serverSupportedTimezones.contains(timezone))
timezone=null;
String inviteCode=getArguments().getString("inviteCode");
new RegisterAccount(username, email, password.getText().toString(), locale, reason.getText().toString(), timezone, inviteCode)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Token result){
@@ -271,7 +302,7 @@ public class SignupFragment extends ToolbarFragment{
@Override
public void tail(Node node, int depth){
if(node instanceof Element){
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

View File

@@ -12,12 +12,10 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
import org.joinmastodon.android.ui.viewholders.CheckableListItemViewHolder;
import org.joinmastodon.android.ui.viewholders.ListItemViewHolder;
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.utils.V;
public abstract class BaseSettingsFragment<T> extends MastodonRecyclerFragment<ListItem<T>>{
protected GenericListItemsAdapter<T> itemsAdapter;
@@ -45,7 +43,7 @@ public abstract class BaseSettingsFragment<T> extends MastodonRecyclerFragment<L
@Override
protected RecyclerView.Adapter<?> getAdapter(){
return itemsAdapter=new GenericListItemsAdapter<T>(data);
return itemsAdapter=new GenericListItemsAdapter<T>(imgLoader, data);
}
@Override
@@ -59,12 +57,13 @@ public abstract class BaseSettingsFragment<T> extends MastodonRecyclerFragment<L
return 0;
}
protected void toggleCheckableItem(CheckableListItem<T> item){
item.toggle();
protected void toggleCheckableItem(ListItem<?> item){
if(item instanceof CheckableListItem<?> checkable)
checkable.toggle();
rebindItem(item);
}
protected void rebindItem(ListItem<T> item){
protected void rebindItem(ListItem<?> item){
if(list==null)
return;
if(list.findViewHolderForAdapterPosition(indexOfItemsAdapter()+data.indexOf(item)) instanceof ListItemViewHolder<?> holder){

View File

@@ -1,6 +1,9 @@
package org.joinmastodon.android.fragments.settings;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
@@ -18,6 +21,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HasAccountID;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.Snackbar;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
@@ -106,6 +110,14 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> impleme
versionInfo.setTextColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Outline));
versionInfo.setGravity(Gravity.CENTER);
versionInfo.setText(getString(R.string.mo_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
versionInfo.setOnClickListener(v->{
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText("", BuildConfig.VERSION_NAME+" ("+BuildConfig.VERSION_CODE+")"));
if(Build.VERSION.SDK_INT<=Build.VERSION_CODES.S_V2){
new Snackbar.Builder(getActivity())
.setText(R.string.app_version_copied)
.show();
}
});
adapter.addAdapter(new SingleViewRecyclerAdapter(versionInfo));
return adapter;

View File

@@ -1,7 +1,17 @@
package org.joinmastodon.android.fragments.settings;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.joinmastodon.android.GlobalUserPreferences;
@@ -24,13 +34,13 @@ import java.util.stream.Stream;
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
private ListItem<Void> languageItem;
private CheckableListItem<Void> altTextItem, playGifsItem, customTabsItem, confirmUnfollowItem, confirmBoostItem, confirmDeleteItem;
private CheckableListItem<Void> altTextItem, playGifsItem, confirmUnfollowItem, confirmBoostItem, confirmDeleteItem;
private MastodonLanguage postLanguage;
private ComposeLanguageAlertViewController.SelectedOption newPostLanguage;
// MEGALODON
private MastodonLanguage.LanguageResolver languageResolver;
private ListItem<Void> prefixRepliesItem, replyVisibilityItem;
private ListItem<Void> prefixRepliesItem, replyVisibilityItem, customTabsItem;
private CheckableListItem<Void> forwardReportsItem, remoteLoadingItem, showBoostsItem, showRepliesItem, loadNewPostsItem, seeNewPostsBtnItem, overlayMediaItem;
// MOSHIDON
@@ -49,11 +59,11 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
List<ListItem<Void>> items = new ArrayList<>(List.of(
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick),
customTabsItem=new ListItem<>(getString(R.string.settings_custom_tabs), getString(GlobalUserPreferences.useCustomTabs ? R.string.in_app_browser : R.string.system_browser), R.drawable.ic_fluent_open_24_regular, this::onCustomTabsClick),
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, i->toggleCheckableItem(altTextItem)),
showPostsWithoutAltItem=new CheckableListItem<>(R.string.mo_settings_show_posts_without_alt, R.string.mo_settings_show_posts_without_alt_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showPostsWithoutAlt, R.drawable.ic_fluent_eye_tracking_on_24_regular, i->toggleCheckableItem(showPostsWithoutAltItem)),
playGifsItem=new CheckableListItem<>(R.string.settings_gif, R.string.mo_setting_play_gif_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, i->toggleCheckableItem(playGifsItem)),
overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, i->toggleCheckableItem(overlayMediaItem)),
customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_fluent_link_24_regular, i->toggleCheckableItem(customTabsItem)),
confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_fluent_person_delete_24_regular, i->toggleCheckableItem(confirmUnfollowItem)),
confirmBoostItem=new CheckableListItem<>(R.string.settings_confirm_boost, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmBoost, R.drawable.ic_fluent_arrow_repeat_all_24_regular, i->toggleCheckableItem(confirmBoostItem)),
confirmDeleteItem=new CheckableListItem<>(R.string.settings_confirm_delete_post, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmDeletePost, R.drawable.ic_fluent_delete_24_regular, i->toggleCheckableItem(confirmDeleteItem)),
@@ -166,12 +176,53 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
rebindItem(seeNewPostsBtnItem);
}
private void onCustomTabsClick(ListItem<?> item){
// GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
Intent intent=new Intent(Intent.ACTION_VIEW, Uri.parse("http://example.com"));
ResolveInfo info=getActivity().getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
final String browserName;
if(info==null){
browserName="??";
}else{
browserName=info.loadLabel(getActivity().getPackageManager()).toString();
}
ArrayAdapter<CharSequence> adapter=new ArrayAdapter<>(getActivity(), R.layout.item_alert_single_choice_2lines_but_different, R.id.text,
new String[]{getString(R.string.in_app_browser), getString(R.string.system_browser)}){
@Override
public boolean hasStableIds(){
return true;
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent){
View view=super.getView(position, convertView, parent);
TextView subtitle=view.findViewById(R.id.subtitle);
if(position==0){
subtitle.setVisibility(View.GONE);
}else{
subtitle.setVisibility(View.VISIBLE);
subtitle.setText(browserName);
}
return view;
}
};
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.settings_custom_tabs)
.setSingleChoiceItems(adapter, GlobalUserPreferences.useCustomTabs ? 0 : 1, (dlg, which)->{
GlobalUserPreferences.useCustomTabs=which==0;
customTabsItem.subtitleRes=GlobalUserPreferences.useCustomTabs ? R.string.in_app_browser : R.string.system_browser;
rebindItem(customTabsItem);
dlg.dismiss();
})
.show();
}
@Override
protected void onHidden(){
super.onHidden();
GlobalUserPreferences.playGifs=playGifsItem.checked;
GlobalUserPreferences.overlayMedia=overlayMediaItem.checked;
GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
GlobalUserPreferences.altTextReminders=altTextItem.checked;
GlobalUserPreferences.confirmUnfollow=confirmUnfollowItem.checked;
GlobalUserPreferences.confirmBoost=confirmBoostItem.checked;

View File

@@ -1,10 +1,9 @@
package org.joinmastodon.android.fragments.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Toast;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.session.AccountActivationInfo;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -28,7 +27,8 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
new ListItem<>("Test email confirmation flow", null, this::onTestEmailConfirmClick),
selfUpdateItem=new ListItem<>("Force self-update", null, this::onForceSelfUpdateClick),
resetUpdateItem=new ListItem<>("Reset self-updater", null, this::onResetUpdaterClick),
new ListItem<>("Reset search info banners", null, this::onResetDiscoverBannersClick)
new ListItem<>("Reset search info banners", null, this::onResetDiscoverBannersClick),
new ListItem<>("Reset pre-reply sheets", null, this::onResetPreReplySheetsClick)
));
if(!GithubSelfUpdater.needSelfUpdating()){
resetUpdateItem.isEnabled=selfUpdateItem.isEnabled=false;
@@ -65,6 +65,12 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
restartUI();
}
private void onResetPreReplySheetsClick(ListItem<?> item){
// TODO fix this
// GlobalUserPreferences.resetPreReplySheets();
Toast.makeText(getActivity(), "Pre-reply sheets were reset", Toast.LENGTH_SHORT).show();
}
private void restartUI(){
Bundle args=new Bundle();
args.putString("account", accountID);

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.fragments.settings;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
@@ -18,7 +17,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;

View File

@@ -83,7 +83,7 @@ public class SettingsServerAboutFragment extends LoaderFragment{
public boolean shouldOverrideUrlLoading(WebView view, String url){
Uri uri=Uri.parse(url);
if(uri.getScheme().equals("http") || uri.getScheme().equals("https")){
UiUtils.launchWebBrowser(getActivity(), url);
UiUtils.openURL(getActivity(),accountID, url);
}else{
Intent intent=new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

View File

@@ -12,6 +12,7 @@ import org.joinmastodon.android.ui.utils.BlurHashDecoder;
import org.joinmastodon.android.ui.utils.BlurHashDrawable;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.List;
@Parcel
@@ -35,11 +36,14 @@ public class Card extends BaseModel{
public String embedUrl;
public String blurhash;
public List<History> history;
public Instant publishedAt;
public transient Drawable blurhashPlaceholder;
@Override
public void postprocess() throws ObjectValidationException{
if(type==null)
type=Type.LINK;
super.postprocess();
if(blurhash!=null){
Bitmap placeholder=BlurHashDecoder.decode(blurhash, 16, 16);
@@ -72,6 +76,7 @@ public class Card extends BaseModel{
", embedUrl='"+embedUrl+'\''+
", blurhash='"+blurhash+'\''+
", history="+history+
", publishedAt="+publishedAt+
'}';
}

View File

@@ -5,4 +5,8 @@ package org.joinmastodon.android.model;
*/
public interface DisplayItemsParent{
String getID();
default String getAccountID(){
return null;
}
}

View File

@@ -0,0 +1,38 @@
package org.joinmastodon.android.model;
import androidx.annotation.NonNull;
import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.api.RequiredField;
import org.parceler.Parcel;
@Parcel
public class FollowList extends BaseModel {
@RequiredField
public String id;
@RequiredField
public String title;
public RepliesPolicy repliesPolicy;
public boolean exclusive;
@NonNull
@Override
public String toString() {
return "List{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", repliesPolicy=" + repliesPolicy +
", exclusive=" + exclusive +
'}';
}
public enum RepliesPolicy{
@SerializedName("followed")
FOLLOWED,
@SerializedName("list")
LIST,
@SerializedName("none")
NONE
}
}

View File

@@ -46,4 +46,8 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
public int hashCode(){
return name.hashCode();
}
public int getWeekPosts(){
return history.stream().mapToInt(h->h.uses).sum();
}
}

View File

@@ -21,4 +21,10 @@ public class HeaderPaginationList<T> extends ArrayList<T>{
public HeaderPaginationList(@NonNull Collection<? extends T> c){
super(c);
}
public String getNextPageMaxID(){
if(nextPageUri==null)
return null;
return nextPageUri.getQueryParameter("max_id");
}
}

View File

@@ -36,6 +36,11 @@ public class Notification extends BaseModel implements DisplayItemsParent{
return id;
}
@Override
public String getAccountID(){
return status!=null ? account.id : null;
}
public enum Type{
@SerializedName("follow")
FOLLOW,

View File

@@ -6,6 +6,7 @@ import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.annotation.StringRes;
@@ -23,7 +24,7 @@ public class PushNotification extends BaseModel{
@RequiredField
public String body;
public static PushNotification fromNotification(Context context, Notification notification){
public static PushNotification fromNotification(Context context, AccountSession account, Notification notification){
PushNotification pushNotification = new PushNotification();
pushNotification.notificationType = switch(notification.type) {
case FOLLOW -> PushNotification.Type.FOLLOW;
@@ -45,6 +46,7 @@ public class PushNotification extends BaseModel{
case REBLOG -> R.string.notification_boosted;
case FAVORITE -> R.string.user_favorited;
case POLL -> R.string.poll_ended;
case STATUS -> R.string.sk_posted;
case UPDATE -> R.string.sk_post_edited;
case SIGN_UP -> R.string.sk_signed_up;
case REPORT -> R.string.sk_reported;
@@ -52,8 +54,12 @@ public class PushNotification extends BaseModel{
});
pushNotification.title = UiUtils.generateFormattedString(notificationTitle, notification.account.displayName).toString();
pushNotification.icon = notification.status.account.avatarStatic;
pushNotification.body = notification.status.getStrippedText();
if (notification.status != null) {
pushNotification.icon = notification.status.account.avatarStatic;
pushNotification.body = notification.status.getStrippedText();
} else {
pushNotification.icon = account.getDefaultAvatarUrl();
}
return pushNotification;
}

View File

@@ -34,10 +34,18 @@ public class SearchResult extends BaseModel implements DisplayItemsParent{
generateID();
}
@Override
public String getID(){
return id;
}
@Override
public String getAccountID(){
if(type==Type.STATUS)
return status.getAccountID();
return null;
}
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();

View File

@@ -55,7 +55,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public StatusPrivacy visibility;
public boolean sensitive;
@RequiredField
public String spoilerText;
public String spoilerText="";
public List<Attachment> mediaAttachments;
public Application application;
@RequiredField
@@ -186,6 +186,11 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
return id;
}
@Override
public String getAccountID(){
return getContentStatus().account.id;
}
public void update(StatusCountersUpdatedEvent ev){
favouritesCount=ev.favorites;
reblogsCount=ev.reblogs;
@@ -225,7 +230,10 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
@NonNull
@Override
public Status clone(){
return (Status) super.clone();
Status copy=(Status) super.clone();
copy.spoilerRevealed=false;
copy.translationState=TranslationState.HIDDEN;
return copy;
}
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");

View File

@@ -9,7 +9,6 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.CustomLocalTimelineFragment;
import org.joinmastodon.android.api.session.AccountSession;
@@ -53,7 +52,7 @@ public class TimelineDefinition {
return def;
}
public static TimelineDefinition ofList(ListTimeline list) {
public static TimelineDefinition ofList(FollowList list) {
return ofList(list.id, list.title, list.exclusive);
}

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.model.viewmodel;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -9,6 +10,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.text.LinkSpan;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import java.util.Collections;
@@ -36,7 +38,7 @@ public class AccountViewModel{
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis);
else
parsedName=account.getDisplayName();
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
ssb.append(parsedBio);
emojiHelper.setText(ssb);
@@ -49,4 +51,13 @@ public class AccountViewModel{
}
this.verifiedLink=verifiedLink;
}
public AccountViewModel stripLinksFromBio(){
if(parsedBio instanceof Spannable spannable){
for(LinkSpan span:spannable.getSpans(0, spannable.length(), LinkSpan.class)){
spannable.removeSpan(span);
}
}
return this;
}
}

View File

@@ -0,0 +1,22 @@
package org.joinmastodon.android.model.viewmodel;
import org.joinmastodon.android.R;
import java.util.List;
import java.util.function.Consumer;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class AvatarPileListItem<T> extends ListItem<T>{
public List<ImageLoaderRequest> avatars;
public AvatarPileListItem(String title, String subtitle, List<ImageLoaderRequest> avatars, int iconRes, Consumer<AvatarPileListItem<T>> onClick, T parentObject, boolean dividerAfter){
super(title, subtitle, iconRes, (Consumer<ListItem<T>>)(Object)onClick, parentObject, 0, dividerAfter);
this.avatars=avatars;
}
@Override
public int getItemViewType(){
return R.id.list_item_avatar_pile;
}
}

View File

@@ -55,6 +55,7 @@ public class CheckableListItem<T> extends ListItem<T>{
case CHECKBOX -> R.id.list_item_checkbox;
case RADIO -> R.id.list_item_radio;
case SWITCH -> R.id.list_item_switch;
case SWITCH_SEPARATED -> R.id.list_item_switch_separated;
};
}
@@ -69,6 +70,7 @@ public class CheckableListItem<T> extends ListItem<T>{
public enum Style{
CHECKBOX,
RADIO,
SWITCH
SWITCH,
SWITCH_SEPARATED
}
}

View File

@@ -0,0 +1,35 @@
package org.joinmastodon.android.model.viewmodel;
import android.view.Menu;
import android.view.MenuItem;
import org.joinmastodon.android.R;
import java.util.function.Consumer;
public class ListItemWithOptionsMenu<T> extends ListItem<T>{
public OptionsMenuListener<T> listener;
public ListItemWithOptionsMenu(String title, String subtitle, OptionsMenuListener<T> listener, int iconRes, Consumer<ListItemWithOptionsMenu<T>> onClick, T parentObject, boolean dividerAfter){
super(title, subtitle, iconRes, (Consumer<ListItem<T>>)(Object)onClick, parentObject, 0, dividerAfter);
this.listener=listener;
}
@Override
public int getItemViewType(){
return R.id.list_item_options;
}
public void performConfigureMenu(Menu menu){
listener.onConfigureListItemOptionsMenu(this, menu);
}
public void performItemSelected(MenuItem item){
listener.onListItemOptionSelected(this, item);
}
public interface OptionsMenuListener<T>{
void onConfigureListItemOptionsMenu(ListItemWithOptionsMenu<T> item, Menu menu);
void onListItemOptionSelected(ListItemWithOptionsMenu<T> item, MenuItem menuItem);
}
}

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