Compare commits

..

581 Commits

Author SHA1 Message Date
LucasGGamerM
c106dc5d19 fix(hashtag-fragment): fix crash when opening some hashtags
cc: @sk22
2023-10-02 11:26:38 -03:00
sk
1988849b26 Merge remote-tracking branch 'upstream/master' 2023-09-30 21:21:47 +02:00
Grishka
fc10fbffb0 Clear fragment stack instead of restarting activity
grishka/appkit#13
2023-09-30 21:53:02 +03:00
sk
a21a74a8e7 bonk version 2023-09-30 19:26:15 +02:00
sk
20e5d2a545 Merge remote-tracking branch 'upstream/l10n_master' 2023-09-30 19:24:52 +02:00
butterflyoffire
7da363fb87 Translated using Weblate (Arabic)
Currently translated at 80.3% (310 of 386 strings)

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

* Move creation of EditText to conditional block.

* Clear unused comment

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

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

* Performance optimization and fixed a typo in filter.

* improve layout

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-09-30 19:21:36 +02:00
FineFindus
cbee0fe72e fix: show individual chips (#838) 2023-09-30 19:04:04 +02:00
FineFindus
6d085ae6f0 fix: show multiline poll options (#837)
* fix: show multiline poll options

* fix resources not found exception

* don't force height on poll options

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-09-30 19:03:17 +02:00
LucasGGamerM
4de7211523 Fix notifications replies visibility/language not being consistent with replied status (#831)
* fix(notifications): make reply visibility consistent with status being replied to

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

* feat: add blocks fragment

* refactor: add query params

* rename "mutes" and "blocks"

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-09-30 18:24:24 +02:00
Eugen Rochko
98a02e874b New translations strings.xml (Vietnamese) 2023-09-30 15:05:49 +02:00
butterflyoffire
d219d7aa4b Translated using Weblate (Arabic)
Currently translated at 78.2% (302 of 386 strings)

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

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-09-30 03:45:04 +00:00
sk
f2f8620312 fix menu item icons and state 2023-09-29 21:45:41 +02:00
sk
4ee229ea79 Merge remote-tracking branch 'upstream/master' into try-to-merge-upstream 2023-09-29 20:59:12 +02:00
sk
c261214e49 implement new translation 2023-09-29 18:46:26 +02:00
Eugen Rochko
b06df8c3d0 New translations strings.xml (Galician) 2023-09-29 07:53:31 +02:00
Grishka
a00afd5d7f Same crash fix in 2 more places ugh 2023-09-29 03:20:58 +03:00
Grishka
9a41a2d6fb Merge branch 'l10n_master' 2023-09-28 20:14:21 +03:00
Grishka
2cd98a6620 More crash fixes 2023-09-28 20:11:43 +03:00
Grishka
283b56be5b Finally fix the mysterious RecyclerView crash 2023-09-28 19:56:25 +03:00
Eugen Rochko
6d56771aba New translations strings.xml (Galician) 2023-09-28 16:51:59 +02:00
Grishka
1724d8a532 Probably fix #703 2023-09-27 19:50:59 +03:00
butterflyoffire
52030b3b2d Translated using Weblate (Arabic)
Currently translated at 76.1% (294 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-09-27 07:53:09 +00:00
Eugen Rochko
b4cdf35d36 New translations strings.xml (Russian) 2023-09-25 23:39:42 +02:00
Eugen Rochko
cad0ad7a59 New translations strings.xml (Ukrainian) 2023-09-25 22:25:17 +02:00
Grishka
ca60003c39 Fix #682 2023-09-25 23:11:10 +03:00
Grishka
0f030e0bac Fix #683 2023-09-25 23:07:34 +03:00
Grishka
6d4f212a18 Probably need to set this too 2023-09-25 23:00:15 +03:00
Grishka
183b39bc24 Specify LED color for notifications
closes #695
2023-09-25 22:58:08 +03:00
Grishka
27ad0c6fcf Crash fixes 2023-09-25 22:52:51 +03:00
Grishka
b5f661f1af I forgot to increment the version code 2023-09-25 19:25:59 +03:00
Grishka
0015f3f0bf Merge branch 'l10n_master' 2023-09-25 19:22:38 +03:00
Eugen Rochko
c5d0fdd645 New translations strings.xml (Turkish) 2023-09-25 18:20:56 +02:00
Grishka
2d09ad44fb Merge branch 'l10n_master' 2023-09-25 19:18:24 +03:00
Eugen Rochko
667fffd124 New translations strings.xml (Chinese Traditional) 2023-09-25 18:18:22 +02:00
Eugen Rochko
699233d8c7 New translations strings.xml (Filipino) 2023-09-25 18:18:05 +02:00
Grishka
56aabdc4a6 Fix empty view text style
closes #701
2023-09-25 19:12:04 +03:00
Grishka
443e2c7a6f Add a tool to detect invalid formatting in localized strings 2023-09-25 18:51:49 +03:00
Eugen Rochko
985b0f6e63 New translations strings.xml (Filipino) 2023-09-25 17:49:57 +02:00
Grishka
cc86edf276 Fix #700 2023-09-25 17:18:42 +03:00
Grishka
4071b9342d Update appkit 2023-09-25 17:13:59 +03:00
Eugen Rochko
f71d1bc5d3 New translations strings.xml (Greek) 2023-09-24 23:27:13 +02:00
Andrewblasco
2b926ffa46 Translated using Weblate (Spanish)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-09-24 08:53:08 +00:00
Eugen Rochko
6bcdbaba34 New translations strings.xml (Turkish) 2023-09-24 08:49:38 +02:00
Eugen Rochko
a2beead3a5 New translations strings.xml (Chinese Simplified) 2023-09-23 18:34:38 +02:00
Eugen Rochko
e7a25e353d New translations strings.xml (Japanese) 2023-09-23 14:15:37 +02:00
Eugen Rochko
af04a01130 New translations strings.xml (Japanese) 2023-09-23 13:16:04 +02:00
Grishka
fe1cfa1d7b Fix indexable setting 2023-09-22 21:33:21 +03:00
Eugen Rochko
b248797bb0 New translations strings.xml (Russian) 2023-09-22 20:28:14 +02:00
Grishka
f24eba08d3 Fix custom emojis in names setting 2023-09-22 21:27:30 +03:00
Eugen Rochko
0e89559a47 New translations strings.xml (Slovenian) 2023-09-22 19:30:53 +02:00
Espasant3
858657799f Translated using Weblate (Galician)
Currently translated at 98.1% (379 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-09-22 12:53:08 +00:00
Eugen Rochko
d02a72e079 New translations strings.xml (Japanese) 2023-09-22 13:01:51 +02:00
Eugen Rochko
3be57d1b0b New translations strings.xml (Japanese) 2023-09-22 11:08:13 +02:00
Eugen Rochko
bed550e97c New translations strings.xml (Russian) 2023-09-22 09:19:11 +02:00
Eugen Rochko
7e2619ea75 New translations strings.xml (Italian) 2023-09-22 01:48:10 +02:00
Eugen Rochko
4b22f1d3a7 New translations strings.xml (Thai) 2023-09-21 21:39:59 +02:00
Eugen Rochko
9dcc7e293f New translations strings.xml (Icelandic) 2023-09-21 15:39:27 +02:00
Eugen Rochko
6a68cf5e41 New translations strings.xml (Persian) 2023-09-21 13:13:31 +02:00
Grishka
29297be4a3 Merge branch 'l10n_master' 2023-09-20 22:43:16 +03:00
Eugen Rochko
90b87529e0 New translations strings.xml (Swedish) 2023-09-20 21:16:11 +02:00
Grishka
39af05524d Privacy settings 2023-09-20 21:44:28 +03:00
Grishka
e3fb2cd03c Scroll profile tab views to top when tab is reselected 2023-09-20 14:47:25 +03:00
Gregory K
90f84d628a Merge pull request #694 from LucasGGamerM/mastodon-android
fix(compose): fix photoPicker not popping up when there is less than 2 spaces available for media
2023-09-20 14:10:26 +03:00
LucasGGamerM
b89e0b5c5a fix(compose): fix photoPicker not popping up when there is less than 2 spaces available for media 2023-09-20 07:55:23 -03:00
Espasant3
f724644d84 Translated using Weblate (Galician)
Currently translated at 89.6% (346 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-09-19 21:53:09 +00:00
Eugen Rochko
aac89c354c New translations full_description.txt (Thai) 2023-09-18 20:35:41 +02:00
Eugen Rochko
a032f9af10 New translations strings.xml (French) 2023-09-18 20:34:32 +02:00
Eugen Rochko
642aaec6da New translations strings.xml (Persian) 2023-09-17 14:08:04 +02:00
Eugen Rochko
ff667d6aed New translations strings.xml (Persian) 2023-09-17 12:27:57 +02:00
Eugen Rochko
5e98496ea6 New translations strings.xml (Vietnamese) 2023-09-17 10:39:46 +02:00
Eugen Rochko
972fe1d15b New translations strings.xml (Italian) 2023-09-17 10:39:45 +02:00
Eugen Rochko
26eaa36faa New translations strings.xml (Finnish) 2023-09-17 10:39:44 +02:00
Grishka
c517f41595 Paginate search results 2023-09-17 11:20:12 +03:00
Grishka
56a6d7243f Crash fixes 2023-09-17 10:49:13 +03:00
Grishka
18e43dfc22 Crash fix 2023-09-17 10:32:59 +03:00
Grishka
816f6370ef Fix #690 2023-09-17 10:26:27 +03:00
butterflyoffire
30866a5292 Translated using Weblate (Arabic)
Currently translated at 71.5% (276 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-09-16 08:53:08 +00:00
Espasant3
3e1403d18a Translated using Weblate (Galician)
Currently translated at 85.2% (329 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-09-16 08:53:08 +00:00
Eugen Rochko
ebc2b2e59d New translations strings.xml (Arabic) 2023-09-15 11:51:46 +02:00
Eugen Rochko
c9a796dbfe New translations strings.xml (Arabic) 2023-09-15 10:16:56 +02:00
Eugen Rochko
1ba185ea9c New translations strings.xml (Vietnamese) 2023-09-15 03:45:44 +02:00
EndermanCo
1a50c3ff5f Translated using Weblate (Persian)
Currently translated at 66.6% (12 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fa/
2023-09-14 17:53:07 +00:00
EndermanCo
35a85c3247 Translated using Weblate (Persian)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-09-14 17:53:07 +00:00
GunChleoc
6a729fa97f Translated using Weblate (Gaelic)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gd/
2023-09-14 17:53:07 +00:00
kallekn
923639a329 Translated using Weblate (Finnish)
Currently translated at 94.5% (365 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fi/
2023-09-14 17:53:07 +00:00
Eugen Rochko
a78be8bc1d New translations strings.xml (Chinese Traditional) 2023-09-14 12:55:27 +02:00
Eugen Rochko
abfb497577 New translations strings.xml (Chinese Traditional) 2023-09-14 11:17:24 +02:00
Eugen Rochko
a10b184508 New translations full_description.txt (Finnish) 2023-09-13 21:32:19 +02:00
Eugen Rochko
f0ea6660e6 New translations strings.xml (Finnish) 2023-09-13 21:32:18 +02:00
Eugen Rochko
a829f25d56 New translations strings.xml (Thai) 2023-09-13 20:36:48 +02:00
Eugen Rochko
deff3dd8e0 New translations strings.xml (Finnish) 2023-09-13 20:36:47 +02:00
EndermanCo
dab596f527 Translated using Weblate (Persian)
Currently translated at 55.5% (10 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fa/
2023-09-13 09:53:07 +00:00
EndermanCo
0c18ab2319 Translated using Weblate (Persian)
Currently translated at 95.5% (369 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-09-13 09:53:07 +00:00
Eugen Rochko
6c5fb5ea09 New translations strings.xml (Dutch) 2023-09-12 12:25:35 +02:00
Eugen Rochko
afe0c9e0db New translations strings.xml (Dutch) 2023-09-12 10:19:31 +02:00
Eugen Rochko
1f2213042f New translations strings.xml (Urdu (India)) 2023-09-12 06:51:43 +02:00
Eugen Rochko
5edd2466f9 New translations strings.xml (Kabyle) 2023-09-12 06:51:42 +02:00
Eugen Rochko
f3b3a1a577 New translations strings.xml (Igbo) 2023-09-12 06:51:41 +02:00
Eugen Rochko
068619b815 New translations strings.xml (Occitan) 2023-09-12 06:51:40 +02:00
Eugen Rochko
f121e94979 New translations strings.xml (Scottish Gaelic) 2023-09-12 06:51:39 +02:00
Eugen Rochko
b5b52529d4 New translations strings.xml (Sinhala) 2023-09-12 06:51:38 +02:00
Eugen Rochko
876bf73454 New translations strings.xml (Bosnian) 2023-09-12 06:51:37 +02:00
Eugen Rochko
522dbf6e4a New translations strings.xml (Filipino) 2023-09-12 06:51:36 +02:00
Eugen Rochko
ae685095ba New translations strings.xml (Burmese) 2023-09-12 06:51:35 +02:00
Eugen Rochko
30d5fe2f12 New translations strings.xml (Hindi) 2023-09-12 06:51:34 +02:00
Eugen Rochko
2bf27c561c New translations strings.xml (Croatian) 2023-09-12 06:51:33 +02:00
Eugen Rochko
bbdc72323d New translations strings.xml (Thai) 2023-09-12 06:51:31 +02:00
Eugen Rochko
6e335930f3 New translations strings.xml (Bengali) 2023-09-12 06:51:31 +02:00
Eugen Rochko
9b309939da New translations strings.xml (Persian) 2023-09-12 06:51:30 +02:00
Eugen Rochko
faf2e5115d New translations strings.xml (Indonesian) 2023-09-12 06:51:28 +02:00
Eugen Rochko
dc5d9412c8 New translations strings.xml (Portuguese, Brazilian) 2023-09-12 06:51:27 +02:00
Eugen Rochko
fc0680d66f New translations strings.xml (Icelandic) 2023-09-12 06:51:26 +02:00
Eugen Rochko
56c9a5433f New translations strings.xml (Galician) 2023-09-12 06:51:25 +02:00
Eugen Rochko
60e473ee55 New translations strings.xml (Vietnamese) 2023-09-12 06:51:24 +02:00
Eugen Rochko
ae34ecd5c3 New translations strings.xml (Chinese Traditional) 2023-09-12 06:51:23 +02:00
Eugen Rochko
fd1caa8729 New translations strings.xml (Chinese Simplified) 2023-09-12 06:51:22 +02:00
Eugen Rochko
1182e5c60c New translations strings.xml (Ukrainian) 2023-09-12 06:51:21 +02:00
Eugen Rochko
d99d515dfa New translations strings.xml (Turkish) 2023-09-12 06:51:20 +02:00
Eugen Rochko
70a15e7d9c New translations strings.xml (Swedish) 2023-09-12 06:51:19 +02:00
Eugen Rochko
1691382369 New translations strings.xml (Slovenian) 2023-09-12 06:51:18 +02:00
Eugen Rochko
b7da9c6d51 New translations strings.xml (Russian) 2023-09-12 06:51:17 +02:00
Eugen Rochko
3426538dca New translations strings.xml (Portuguese) 2023-09-12 06:51:16 +02:00
Eugen Rochko
63de2b200b New translations strings.xml (Polish) 2023-09-12 06:51:15 +02:00
Eugen Rochko
ff1ee766dc New translations strings.xml (Norwegian) 2023-09-12 06:51:14 +02:00
Eugen Rochko
f033411adf New translations strings.xml (Dutch) 2023-09-12 06:51:13 +02:00
Eugen Rochko
a738eaf8c0 New translations strings.xml (Korean) 2023-09-12 06:51:12 +02:00
Eugen Rochko
5074aadd6e New translations strings.xml (Japanese) 2023-09-12 06:51:11 +02:00
Eugen Rochko
0854961470 New translations strings.xml (Italian) 2023-09-12 06:51:10 +02:00
Eugen Rochko
227b077935 New translations strings.xml (Armenian) 2023-09-12 06:51:09 +02:00
Eugen Rochko
1e4358290a New translations strings.xml (Hungarian) 2023-09-12 06:51:08 +02:00
Eugen Rochko
925169eb31 New translations strings.xml (Hebrew) 2023-09-12 06:51:07 +02:00
Eugen Rochko
e1abeb9252 New translations strings.xml (Irish) 2023-09-12 06:51:07 +02:00
Eugen Rochko
cbe0add211 New translations strings.xml (Finnish) 2023-09-12 06:51:06 +02:00
Eugen Rochko
299b524d62 New translations strings.xml (Basque) 2023-09-12 06:51:05 +02:00
Eugen Rochko
31c094e696 New translations strings.xml (Greek) 2023-09-12 06:51:04 +02:00
Eugen Rochko
a8038a2863 New translations strings.xml (German) 2023-09-12 06:51:02 +02:00
Eugen Rochko
29933bb916 New translations strings.xml (Danish) 2023-09-12 06:51:01 +02:00
Eugen Rochko
5ec0c078d8 New translations strings.xml (Czech) 2023-09-12 06:51:00 +02:00
Eugen Rochko
e6287f1ff2 New translations strings.xml (Catalan) 2023-09-12 06:50:59 +02:00
Eugen Rochko
be9caf8905 New translations strings.xml (Belarusian) 2023-09-12 06:50:58 +02:00
Eugen Rochko
f375142084 New translations strings.xml (Arabic) 2023-09-12 06:50:57 +02:00
Eugen Rochko
fd3668d520 New translations strings.xml (Spanish) 2023-09-12 06:50:56 +02:00
Eugen Rochko
d5e03e9d9e New translations strings.xml (French) 2023-09-12 06:50:55 +02:00
Eugen Rochko
d62f094919 New translations strings.xml (Romanian) 2023-09-12 06:50:54 +02:00
Grishka
6d84f28600 Hashtag following
closes #684, closes #233
2023-09-12 07:49:14 +03:00
Grishka
209e603f2c oops 2023-09-12 06:05:45 +03:00
Grishka
1b4dc01c74 Post translation
closes #267, closes #671, closes #502
2023-09-12 06:00:40 +03:00
Andrewblasco
6aab8f6578 Translated using Weblate (Spanish)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-09-12 02:43:45 +00:00
Grishka
645af12c3f Merge branch 'l10n_master' 2023-09-12 02:32:38 +03:00
Grishka
fadc42d72b New version 2023-09-12 02:32:23 +03:00
Eugen Rochko
fc831e7d42 New translations strings.xml (Portuguese, Brazilian) 2023-09-11 22:36:20 +02:00
Eugen Rochko
2998ee9145 New translations strings.xml (Finnish) 2023-09-11 20:09:41 +02:00
Eugen Rochko
971c4e5879 New translations strings.xml (Finnish) 2023-09-11 19:00:28 +02:00
Linerly
48c53ee88b Translated using Weblate (Indonesian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/id/
2023-09-11 14:53:07 +00:00
Linerly
acf1fa15da Translated using Weblate (Indonesian)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-09-11 14:53:07 +00:00
poesty
1c3b28f9d7 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.7% (385 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-09-11 14:53:07 +00:00
Eugen Rochko
b396ee7987 New translations strings.xml (Indonesian) 2023-09-10 15:07:27 +02:00
butterflyoffire
90856a414a Translated using Weblate (Arabic)
Currently translated at 16.6% (3 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ar/
2023-09-09 20:53:07 +00:00
butterflyoffire
ea19925be6 Translated using Weblate (Arabic)
Currently translated at 69.1% (267 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-09-09 20:53:07 +00:00
ihor_ck
03b3775843 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-09-09 20:53:07 +00:00
edxkl
38b39751ae Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.1% (375 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-09-09 20:53:07 +00:00
Oliebol
54a4b0fe41 Translated using Weblate (Dutch)
Currently translated at 81.8% (316 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-09-09 20:53:07 +00:00
Choukajohn
3bf591c944 Translated using Weblate (French)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-09-09 20:53:06 +00:00
gallegonovato
584a6bbfa3 Translated using Weblate (Spanish)
Currently translated at 100.0% (386 of 386 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-09-09 20:53:06 +00:00
Eugen Rochko
0f803cd4fa New translations strings.xml (Finnish) 2023-09-09 20:30:25 +02:00
Eugen Rochko
167a14b8db New translations strings.xml (Finnish) 2023-09-09 19:29:29 +02:00
Eugen Rochko
81cbc2d10c New translations strings.xml (Ukrainian) 2023-09-09 15:40:28 +02:00
Eugen Rochko
9bd8aff99b New translations strings.xml (Finnish) 2023-09-09 12:08:15 +02:00
Eugen Rochko
a770828165 New translations strings.xml (Finnish) 2023-09-09 11:09:40 +02:00
Eugen Rochko
ab457035ff New translations strings.xml (Finnish) 2023-09-09 08:34:27 +02:00
Grishka
f886e4c1d2 Fix #658, fix #620 2023-09-09 03:39:27 +03:00
sk
380e4ff77e remove unused member 2023-09-09 01:48:49 +02:00
sk
58f0c07357 determine next display item using items list
closes sk22#815
2023-09-09 01:27:32 +02:00
sk
77dee59b9c fix string and tweak banner margin
closes sk22#812
2023-09-09 01:24:36 +02:00
edxkl
464dc93d99 Translated using Weblate (Portuguese (Brazil))
Currently translated at 95.8% (368 of 384 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-09-08 20:22:51 +00:00
gallegonovato
dcdfd3e5d3 Translated using Weblate (Spanish)
Currently translated at 100.0% (384 of 384 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-09-08 20:22:51 +00:00
sk
646f83ff0a boop verison 2023-09-08 22:19:41 +02:00
sk
fdf0414698 fix missing banner icons and wrong strings
closes sk22#805
2023-09-08 22:05:06 +02:00
sk
cc699a3f5e fix spacing stuff in report fragment 2023-09-08 21:58:08 +02:00
LucasGGamerM
12eaa8d5f1 fix: fix window insets on ScheduledStatusListFragment
cc: @sk22
2023-09-08 21:49:29 +02:00
sk22
70680e39c6 Translated using Weblate (German)
Currently translated at 100.0% (384 of 384 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-09-08 19:46:45 +00:00
sk
8bd8f90d58 remove unused strings 2023-09-08 21:44:31 +02:00
edxkl
54b53a266e Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.2% (362 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-09-08 19:40:59 +00:00
sk22
66921e3b5a Translated using Weblate (German)
Currently translated at 97.1% (377 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-09-08 19:40:59 +00:00
sk22
9d7af3964b Translated using Weblate (English)
Currently translated at 100.0% (388 of 388 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/en/
2023-09-08 19:40:59 +00:00
ihor_ck
ec73687e9b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (382 of 382 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-09-08 19:17:03 +00:00
Choukajohn
c8af800b88 Translated using Weblate (French)
Currently translated at 100.0% (382 of 382 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-09-08 19:17:02 +00:00
gallegonovato
e74ac5da56 Translated using Weblate (Spanish)
Currently translated at 99.4% (380 of 382 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-09-08 19:17:02 +00:00
sk
efa003a9a5 implement bidirectional missing posts gap 2023-09-08 21:16:42 +02:00
butterflyoffire
de5165434d Translated using Weblate (Arabic)
Currently translated at 16.6% (3 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ar/
2023-09-08 15:43:13 +00:00
sk
be648cc5ab Merge remote-tracking branch 'upstream/l10n_master' 2023-09-08 17:42:47 +02:00
sk
90f1f464dc reset strings.xml to upstream 2023-09-08 17:41:30 +02:00
sk
734aa52816 Merge remote-tracking branch 'weblate/main' 2023-09-08 17:37:31 +02:00
sk
068d42175e Merge remote-tracking branch 'upstream/master' 2023-09-08 17:37:00 +02:00
sk
2314871246 temporary fix for pre-release users 2023-09-08 17:36:17 +02:00
butterflyoffire
e4f13c900b Translated using Weblate (Arabic)
Currently translated at 67.5% (250 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-09-07 19:23:10 +00:00
ppnplus
4ddfa483d4 Translated using Weblate (Thai)
Currently translated at 23.5% (87 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/th/
2023-09-07 19:23:10 +00:00
butterflyoffire
20f41ce7c9 Translated using Weblate (Kabyle)
Currently translated at 0.5% (2 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/kab/
2023-09-07 19:23:10 +00:00
Arkxv
c8df9e085e Translated using Weblate (Japanese)
Currently translated at 78.3% (290 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ja/
2023-09-07 19:23:10 +00:00
GunChleoc
0238aa4375 Translated using Weblate (Gaelic)
Currently translated at 84.3% (312 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gd/
2023-09-07 19:23:09 +00:00
butterflyoffire
79d7873790 Translated using Weblate (French)
Currently translated at 100.0% (370 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-09-07 19:23:09 +00:00
kallekn
996842489d Translated using Weblate (Finnish)
Currently translated at 100.0% (370 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fi/
2023-09-07 19:23:09 +00:00
Andrewblasco
23c2c2b5e7 Translated using Weblate (Spanish)
Currently translated at 100.0% (370 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-09-07 19:23:08 +00:00
sk
9dd694ce2e Merge branch 'merge/load-more-direction' 2023-09-07 18:32:21 +02:00
FineFindus
f51f2a1197 fix: use random account id in notification endpoint (#806)
Closes https://github.com/sk22/megalodon/issues/803. This was due to registering with the actual accounId, whilst saving a random one. Once receiving a notification, the id could not be found.
2023-09-07 18:28:32 +02:00
LucasGGamerM
3020cab243 fix: fixes crashes when currentQuery is null and suicide prevention dialog is enabled (#807)
cc: @FineFindus
2023-09-07 10:03:22 +02:00
Eugen Rochko
be73c9e81c New translations strings.xml (Basque) 2023-09-07 02:28:42 +02:00
Eugen Rochko
1c2183bf1a New translations strings.xml (Slovenian) 2023-09-06 22:29:03 +02:00
Gregory K
1789d90dc3 Merge pull request #685 from LucasGGamerM/mastodon-android
fix(editing-alt-text): fix small oversight on editing existing attachments without alt text
2023-09-06 01:42:41 +03:00
LucasGGamerM
57306ff7fe fix(editing-alt-text): fix small oversight on editing existing attachments without alt text
This makes the implementation hopefully bug free
2023-09-05 19:37:12 -03:00
sk
2aba90f353 add media indicator for spoiler
closes sk22#598
2023-09-05 00:18:47 +02:00
sk
5065c7e7e2 add dummy to add spacing
closes sk22#782
2023-09-04 23:42:00 +02:00
sk
a10e661b21 fix wrong info banner color
closes sk22#790
2023-09-04 23:37:34 +02:00
sk
4975bde76f fix tab layout horizontal paddings 2023-09-04 23:33:35 +02:00
FineFindus
ebbd56e3bc feat(search): show suicide help dialog (#767)
* feat(search): show suicide help dialog

* change wording, add helpline url, change behavior

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-09-04 23:07:24 +02:00
Jacoco
454ec6b4c0 Fix errors when some entries were missing when retrieving account information (#757)
* Handle null avatar

* Handle empty account
2023-09-04 22:24:17 +02:00
FineFindus
10a8b195b1 fix: move tabbar padding to tabbar items (#792)
* feat: increase padding without labels

* fix: move tabbar padding to individual items
2023-09-04 22:19:47 +02:00
sk
6f273df060 use single-line linear layout 2023-09-04 19:54:07 +02:00
sk
7cfade62d3 fix click listener target 2023-09-04 19:53:33 +02:00
starstuff
0a338ad607 Translated using Weblate (Swedish)
Currently translated at 41.6% (154 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/sv/
2023-09-04 08:05:19 +00:00
Arkxv
0cb3e1863e Translated using Weblate (Japanese)
Currently translated at 5.4% (20 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ja/
2023-09-04 08:05:19 +00:00
kallekn
209081f1f0 Translated using Weblate (Finnish)
Currently translated at 0.8% (3 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fi/
2023-09-04 08:05:19 +00:00
Eugen Rochko
f0eb6573f4 New translations strings.xml (Portuguese, Brazilian) 2023-09-04 08:10:24 +02:00
Eugen Rochko
e7f5dd3357 New translations strings.xml (Bengali) 2023-09-04 07:10:06 +02:00
Eugen Rochko
8101bb9ea1 New translations strings.xml (Portuguese, Brazilian) 2023-09-04 07:10:05 +02:00
butterflyoffire
54d48253d5 Translated using Weblate (Arabic)
Currently translated at 12.4% (46 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-09-04 00:53:06 +00:00
ca
3373b2bb04 Translated using Weblate (Catalan)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ca/
2023-09-04 00:53:06 +00:00
ca
a7e23aa228 Translated using Weblate (Catalan)
Currently translated at 100.0% (370 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ca/
2023-09-04 00:53:06 +00:00
FineFindus
8cd55fc365 feat: display icon for bots (#793)
* feat(profile): display bot icon for bots

* feat(status): display bot icon

* use 16sp size and 4sp spacing

* don't add lock/bot icon as image spans

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-09-03 21:34:22 +02:00
LucasGGamerM
f14df2bb0f fix: allows for editing timeline badges again. fixes sk22#800 (#804) 2023-09-03 20:49:29 +02:00
Eugen Rochko
228fdc8ffe New translations strings.xml (Swedish) 2023-09-03 11:26:37 +02:00
Angelo Suzuki
2d24e50ff2 Add a setting for "Load missing posts behavior"
This will make sure that items that are filtered out don't show up on the interface.
Fixes sk22#311
2023-09-01 21:51:14 +02:00
Jacoco
53369eb2d4 Fix unreliable Preferences from Account Source (#798)
* Create empty Preferences object on error

* Update prefs from account when preferences fails
2023-09-01 21:28:02 +02:00
Eugen Rochko
e9df125cde New translations strings.xml (Swedish) 2023-09-01 16:10:32 +02:00
sk
807010893a support static url for emoji reacts and keyboard 2023-09-01 14:38:55 +02:00
sk
ea01b14ffb more options for showing emoji reactions
closes sk22#796
re: sk22#788
2023-09-01 14:23:38 +02:00
arnav
3fd9dc1dcd Translated using Weblate (Hindi)
Currently translated at 4.3% (16 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/hi/
2023-08-31 23:18:52 +00:00
sk
02a4a77885 Merge remote-tracking branch 'weblate/main' 2023-09-01 01:18:30 +02:00
sk
651090a504 Merge remote-tracking branch 'upstream/master' 2023-09-01 01:17:18 +02:00
sk
203254c9f4 fix crash in onEmojiReactionsChanged 2023-09-01 01:16:52 +02:00
Gregory K
16ef577a7a Merge pull request #678 from LucasGGamerM/mastodon-android
fix: fix alt texts not being able to be edited
2023-08-31 20:31:39 +03:00
LucasGGamerM
734b3bced6 fix: fix alt texts not being able to be edited
fixes #70 cc: @sk22
2023-08-31 14:18:32 -03:00
sk
e26c641dc7 fix drafts being all on the same-ish day 2023-08-31 18:07:43 +02:00
sk
9295cf4e9c fix emoji reaction spacing not updating
closes sk22#784
2023-08-31 17:24:29 +02:00
sk
dd9237e9ca enable emojiReactionsInTimelines by default 2023-08-31 17:20:44 +02:00
sk
ea81c1fad6 Merge remote-tracking branch 'upstream/master' 2023-08-31 17:09:18 +02:00
sk
d334703c65 adhere to showEmojiReactionsInLists setting
closes sk22#788
2023-08-31 17:04:22 +02:00
Gregory K
5f6f3c94c9 Merge pull request #677 from tinsukE/gap-local-filter
When loading gap posts, apply filters before building display items.
2023-08-31 15:06:07 +03:00
Angelo Suzuki
09ba42a974 When loading gap posts, apply filters before building display items.
This will make sure that items that are filtered out don't show up on the interface.
Fixes #675
2023-08-31 14:01:58 +02:00
ihor_ck
b1e43d6f97 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (370 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-08-30 18:04:05 +00:00
ppnplus
ea92a61d13 Translated using Weblate (Thai)
Currently translated at 20.5% (76 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/th/
2023-08-30 18:04:05 +00:00
Linerly
7fda69a6aa Translated using Weblate (Indonesian)
Currently translated at 100.0% (370 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-08-30 18:04:05 +00:00
gallegonovato
cf8b9ac649 Translated using Weblate (Spanish)
Currently translated at 100.0% (370 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-08-30 18:04:05 +00:00
ppnplus
3c122b005d Translated using Weblate (Thai)
Currently translated at 3.7% (14 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/th/
2023-08-29 22:37:15 +00:00
edxkl
f9dc6105f4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.5% (361 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-08-29 22:37:15 +00:00
Oliebol
d7fe3c80e6 Translated using Weblate (Dutch)
Currently translated at 82.9% (307 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-08-29 22:37:15 +00:00
Choukajohn
e996deea0b Translated using Weblate (French)
Currently translated at 100.0% (370 of 370 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-08-29 22:37:14 +00:00
S1m
580ae15af6 Fix unifiedpush (#785)
* [UnifiedPush] Register the new endpoint

* [UnifiedPush] Get account linked to instance
2023-08-29 12:22:12 +02:00
Eugen Rochko
d76e823489 New translations strings.xml (Italian) 2023-08-29 11:52:38 +02:00
sk
e66078e52e fix poll options outline provider
closes sk22#702
2023-08-29 00:53:02 +02:00
FineFindus
f84356f5d0 fix: display 'Lists with' with username (#780) 2023-08-29 00:19:34 +02:00
sk
1fa5a8436b highlight verified links again!
closes sk22#713
2023-08-29 00:18:12 +02:00
sk
2a471ffa96 fix broken "edit avatar" overlay.. again!!
closes sk22#727
closes sk22#738
2023-08-28 23:48:18 +02:00
sk
1ee5e1ab3a fix cut-off settings item titles
closes sk22#723
2023-08-28 23:42:34 +02:00
sk
4d90cad034 Merge remote-tracking branch 'weblate/main' 2023-08-28 23:35:18 +02:00
sk
45615b1fc5 Merge remote-tracking branch 'upstream/l10n_master' 2023-08-28 23:35:04 +02:00
sk
9fd0e7fea4 return if fcm device token is empty
closes sk22#779
2023-08-28 23:33:28 +02:00
sk
696016bd8f use hasSpoiler method 2023-08-28 23:29:18 +02:00
sk
cc46e09853 fix issue with visibility button
closes sk22#740
2023-08-28 23:24:56 +02:00
sk
83e84836b5 fix slightly wrong margins
closes sk22#778
2023-08-28 22:47:43 +02:00
sk
14bb544344 replace donation link 2023-08-28 22:35:17 +02:00
sk
ceec18ff5e fix divider not being full-width 2023-08-28 22:33:19 +02:00
sk
d36ad43700 fix reporting crashing the app 2023-08-28 22:26:53 +02:00
sk
fb39f74ba5 fix bottom padding for above emoji reactions 2023-08-28 20:22:16 +02:00
sk
9bfc73d6ee fix wrong bottom padding
hopefully, lol
2023-08-28 20:04:52 +02:00
EndermanCo
efb72eace9 Translated using Weblate (Persian)
Currently translated at 50.0% (9 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fa/
2023-08-28 14:53:05 +00:00
edxkl
84b15f4a49 Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.5% (360 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-08-28 14:53:05 +00:00
ca
2c793fd83b Translated using Weblate (Catalan)
Currently translated at 100.0% (369 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ca/
2023-08-28 14:53:05 +00:00
Eugen Rochko
900b204bb0 New translations strings.xml (Portuguese, Brazilian) 2023-08-27 16:51:00 +02:00
Eugen Rochko
17d679901a New translations strings.xml (Japanese) 2023-08-27 15:42:52 +02:00
sk
c3d9147705 fix wrong time/username reference when replying 2023-08-27 14:46:33 +02:00
Eugen Rochko
d8036779f8 New translations strings.xml (Japanese) 2023-08-27 14:46:15 +02:00
sk
2fafdcc4f8 fix unifiedpush settings item
closes sk22#770
2023-08-27 14:33:57 +02:00
sk
ef3f96ba74 fix timestamp being hidden for long usernames
closes sk22#772
2023-08-27 14:12:23 +02:00
sk
91bf1b277f fix null-pointer exception in filter result
closes sk22#774
2023-08-27 13:52:31 +02:00
sk
c6acd35ceb fix "blocked" button style
closes sk22#775
2023-08-27 13:45:44 +02:00
sk
a85a0b7d78 don't show blocked_by status
restoring fix for sk22#526
2023-08-27 13:38:33 +02:00
0que
3a35674ea2 Translated using Weblate (Russian)
Currently translated at 77.7% (14 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ru/
2023-08-27 05:53:05 +00:00
0que
3235cf1c4f Translated using Weblate (Russian)
Currently translated at 89.7% (331 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-08-27 05:53:04 +00:00
Eugen Rochko
3f6bda28b3 New translations strings.xml (Vietnamese) 2023-08-26 17:43:48 +02:00
Eugen Rochko
c0b4f4dd79 New translations strings.xml (Vietnamese) 2023-08-26 16:06:27 +02:00
sk
ad96031aeb rounded corners for inset images! 2023-08-25 23:26:27 +02:00
sk
aa9e66e6a2 tweak account card design
closes sk22#731
2023-08-25 17:20:54 +02:00
sk
86081654fb remove unused import 2023-08-25 17:19:42 +02:00
sk
cac030ffed fix inconsistent avatar border radius 2023-08-25 17:19:29 +02:00
starstuff
2a913e26e7 Translated using Weblate (Swedish)
Currently translated at 41.4% (153 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/sv/
2023-08-25 14:53:06 +00:00
edxkl
f60375cd5f Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.0% (358 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-08-25 14:53:06 +00:00
poesty
5920270899 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (369 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-08-25 14:53:06 +00:00
Eugen Rochko
f36aee44c6 New translations strings.xml (Scottish Gaelic) 2023-08-25 09:54:23 +02:00
Eugen Rochko
cd24526a9d New translations strings.xml (Swedish) 2023-08-24 17:48:37 +02:00
Eugen Rochko
a345ac1390 New translations strings.xml (Icelandic) 2023-08-24 17:48:36 +02:00
sk
71f4f089b6 scroll to added emoji; improve loading animation 2023-08-24 13:26:42 +02:00
sk
0f72809342 scroll to existing reaction when adding 2023-08-24 13:15:53 +02:00
sk
40a033d692 no emoji reactions for akkoma announcements 2023-08-24 13:03:24 +02:00
sk
d44df2c23c change behavior when adding reactions
closes sk22#769
2023-08-24 13:00:16 +02:00
sk
47fde1e08b undo spacing "fix" 2023-08-24 12:41:17 +02:00
sk
0688521ae8 make emoji reactions not click-through-able 2023-08-24 12:17:15 +02:00
sk
bdf0f21647 fix spacing between text and footer 2023-08-24 12:13:20 +02:00
sk
bb03342ff2 boop versinom 2023-08-24 12:12:55 +02:00
sk
937304f27b improve profile metadata editor styles 2023-08-24 00:05:36 +02:00
sk
6b4ce0ea69 hide roles when in edit mode 2023-08-23 23:48:42 +02:00
sk
7f0c4860f8 clean up code 2023-08-23 23:47:06 +02:00
sk
9b4c70a5ed fix jumping profile layout
closes sk22#656
2023-08-23 23:46:23 +02:00
sk
49137273ae clean up code 2023-08-23 23:38:45 +02:00
sk
647e3e5e85 progress indicator for emoji reactions 2023-08-23 23:38:12 +02:00
sk
4920bf63e3 oops. accidentally removed a setting 2023-08-23 23:12:11 +02:00
sk
0afcdb2cdf emoji reactions for announcements! 2023-08-23 22:56:11 +02:00
sk
d96c3c3c8a fix paddings around dummys, text and spoilers
closes sk22#638
2023-08-23 22:12:14 +02:00
Eugen Rochko
3d987b8e1d New translations strings.xml (Thai) 2023-08-23 21:39:07 +02:00
sk
f5e5408d70 Merge remote-tracking branch 'upstream/master' 2023-08-23 20:02:06 +02:00
sk
d62899c990 fix reaction data and binding inconsistencies 2023-08-23 19:49:07 +02:00
Eugen Rochko
57043912e0 New translations strings.xml (Chinese Traditional) 2023-08-23 19:16:40 +02:00
Eugen Rochko
00aef5ea6b New translations full_description.txt (Arabic) 2023-08-23 13:02:31 +02:00
Eugen Rochko
369b69668c New translations strings.xml (Arabic) 2023-08-23 13:02:29 +02:00
Eugen Rochko
65245f4560 New translations strings.xml (Arabic) 2023-08-23 09:39:50 +02:00
Eugen Rochko
4d4fdc97d4 New translations strings.xml (French) 2023-08-23 09:39:49 +02:00
Eugen Rochko
c96577891c New translations strings.xml (Scottish Gaelic) 2023-08-23 07:00:18 +02:00
Eugen Rochko
f48b2fc9cb New translations strings.xml (Thai) 2023-08-23 07:00:13 +02:00
Eugen Rochko
2fca2580ed New translations strings.xml (Bengali) 2023-08-23 07:00:12 +02:00
Eugen Rochko
7adc1da361 New translations strings.xml (Persian) 2023-08-23 07:00:11 +02:00
Eugen Rochko
6dc24dde43 New translations strings.xml (Indonesian) 2023-08-23 07:00:10 +02:00
Eugen Rochko
4929e0e6ec New translations strings.xml (Portuguese, Brazilian) 2023-08-23 07:00:09 +02:00
Eugen Rochko
16a8b8ed71 New translations strings.xml (Icelandic) 2023-08-23 07:00:08 +02:00
Eugen Rochko
ad1412817e New translations strings.xml (Galician) 2023-08-23 07:00:07 +02:00
Eugen Rochko
d9e6bb3bea New translations strings.xml (Vietnamese) 2023-08-23 07:00:06 +02:00
Eugen Rochko
8970404638 New translations strings.xml (Chinese Traditional) 2023-08-23 07:00:05 +02:00
Eugen Rochko
2a2241d7f9 New translations strings.xml (Chinese Simplified) 2023-08-23 07:00:04 +02:00
Eugen Rochko
db1a47e8eb New translations strings.xml (Ukrainian) 2023-08-23 07:00:03 +02:00
Eugen Rochko
3e57061cef New translations strings.xml (Turkish) 2023-08-23 07:00:02 +02:00
Eugen Rochko
cd200f8450 New translations strings.xml (Slovenian) 2023-08-23 07:00:00 +02:00
Eugen Rochko
782013079f New translations strings.xml (Russian) 2023-08-23 06:59:59 +02:00
Eugen Rochko
e5db8acd66 New translations strings.xml (Polish) 2023-08-23 06:59:57 +02:00
Eugen Rochko
1a6a8019c8 New translations strings.xml (Norwegian) 2023-08-23 06:59:57 +02:00
Eugen Rochko
e935eef29f New translations strings.xml (Dutch) 2023-08-23 06:59:56 +02:00
Eugen Rochko
381defda51 New translations strings.xml (Japanese) 2023-08-23 06:59:54 +02:00
Eugen Rochko
02ae80c204 New translations strings.xml (Italian) 2023-08-23 06:59:53 +02:00
Eugen Rochko
82214b30e8 New translations strings.xml (Armenian) 2023-08-23 06:59:52 +02:00
Eugen Rochko
33a1f48602 New translations strings.xml (Greek) 2023-08-23 06:59:48 +02:00
Eugen Rochko
aee845e5cc New translations strings.xml (German) 2023-08-23 06:59:47 +02:00
Eugen Rochko
cd780f6006 New translations strings.xml (Danish) 2023-08-23 06:59:46 +02:00
Eugen Rochko
d4741fefa0 New translations strings.xml (Czech) 2023-08-23 06:59:46 +02:00
Eugen Rochko
7e1e8a2616 New translations strings.xml (Belarusian) 2023-08-23 06:59:44 +02:00
Eugen Rochko
d73c05cdfc New translations strings.xml (Arabic) 2023-08-23 06:59:43 +02:00
Eugen Rochko
78323023cb New translations strings.xml (Spanish) 2023-08-23 06:59:42 +02:00
Eugen Rochko
2cf084c98f New translations strings.xml (French) 2023-08-23 06:59:41 +02:00
Grishka
e5bdeba1d7 Fix strings 2023-08-23 00:44:40 +03:00
Grishka
8d7db7774f Merge branch 'l10n_master' 2023-08-23 00:40:45 +03:00
Grishka
78d22c670c Fix reporting 2023-08-23 00:40:38 +03:00
Eugen Rochko
0a679109f5 New translations strings.xml (Arabic) 2023-08-22 17:14:41 +02:00
Eugen Rochko
e843142b7e New translations strings.xml (Arabic) 2023-08-22 13:15:25 +02:00
Eugen Rochko
72e728f655 New translations strings.xml (French) 2023-08-22 13:15:23 +02:00
Eugen Rochko
ef56792f56 New translations strings.xml (Arabic) 2023-08-22 11:39:27 +02:00
Eugen Rochko
504a6959e8 New translations strings.xml (French) 2023-08-22 11:39:25 +02:00
sk
6054a3d65c Merge remote-tracking branch 'weblate/main' 2023-08-21 22:43:23 +02:00
sk
f50eac02d8 move adding reactions to reactions item 2023-08-21 22:40:19 +02:00
snerk
9634db9061 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 20.8% (75 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nn/
2023-08-21 22:40:19 +02:00
snerk
97e3e283dd Translated using Weblate (Norwegian Nynorsk)
Currently translated at 18.8% (68 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nn/
2023-08-21 22:40:19 +02:00
snerk
f1e233569b Added translation using Weblate (Norwegian Nynorsk) 2023-08-21 22:40:19 +02:00
alextecplayz
04dd637fa9 Translated using Weblate (Romanian)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-08-21 22:40:19 +02:00
gallegonovato
c48a4105a9 Translated using Weblate (Spanish)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-08-21 22:40:19 +02:00
EndermanCo
aac53d949b Translated using Weblate (Persian)
Currently translated at 38.8% (7 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fa/
2023-08-21 22:40:19 +02:00
EndermanCo
9bb4e5b467 Translated using Weblate (Persian)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-21 22:40:19 +02:00
0eoc
fb0391d5cd Translated using Weblate (Russian)
Currently translated at 61.1% (11 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ru/
2023-08-21 22:40:19 +02:00
ihor_ck
e4d898c903 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-08-21 22:40:19 +02:00
Linerly
da222f75bb Translated using Weblate (Indonesian)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-08-21 22:40:19 +02:00
Choukajohn
25fbd91eb3 Translated using Weblate (French)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-08-21 22:40:19 +02:00
0eoc
d458cca7bf Translated using Weblate (Russian)
Currently translated at 96.1% (323 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-08-21 22:40:19 +02:00
gallegonovato
3933a61b5a Translated using Weblate (Spanish)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-08-21 22:40:19 +02:00
Codeberg Translate
29092bbf36 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/
2023-08-21 22:40:19 +02:00
edxkl
a33d2578c9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.4% (17 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/pt_BR/
2023-08-21 22:40:19 +02:00
edxkl
9afe4b5ac6 Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.3% (327 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-08-21 22:40:19 +02:00
EndermanCo
6782006b05 Translated using Weblate (Persian)
Currently translated at 27.7% (5 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fa/
2023-08-21 22:40:19 +02:00
0eoc
90bdbefd48 Translated using Weblate (Russian)
Currently translated at 61.1% (11 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ru/
2023-08-21 22:40:19 +02:00
gicorada
b7bcf1082e Translated using Weblate (Italian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/it/
2023-08-21 22:40:19 +02:00
EndermanCo
ba9bbc5b6e Translated using Weblate (Persian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-21 22:40:19 +02:00
0eoc
0fecfbd50c Translated using Weblate (Russian)
Currently translated at 81.2% (273 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-08-21 22:40:19 +02:00
gallegonovato
0031dc6119 Translated using Weblate (Spanish)
Currently translated at 99.1% (333 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-08-21 22:40:19 +02:00
EndermanCo
79e606698e Translated using Weblate (Persian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-21 22:40:19 +02:00
EndermanCo
3c9fc43780 Translated using Weblate (Persian)
Currently translated at 98.5% (331 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-21 22:40:19 +02:00
ihor_ck
adb9b7394a Translated using Weblate (Ukrainian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-08-21 22:40:19 +02:00
Eryk Michalak
6191fdfaef Translated using Weblate (Polish)
Currently translated at 91.3% (307 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pl/
2023-08-21 22:40:19 +02:00
Linerly
446754e8a6 Translated using Weblate (Indonesian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-08-21 22:40:19 +02:00
Choukajohn
30c67b0b39 Translated using Weblate (French)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-08-21 22:40:19 +02:00
Hudobni Volk
1043ea7b11 Translated using Weblate (Slovenian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/sl/
2023-08-21 22:40:19 +02:00
alextecplayz
b449bcd006 Translated using Weblate (Romanian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-08-21 22:40:19 +02:00
EndermanCo
a9d513b564 Translated using Weblate (Persian)
Currently translated at 98.5% (330 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-21 22:40:19 +02:00
Hudobni Volk
5cef527810 Translated using Weblate (Slovenian)
Currently translated at 61.1% (11 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/sl/
2023-08-21 22:40:19 +02:00
ganjibaiemade
8bb907747d Translated using Weblate (Chinese (Traditional))
Currently translated at 18.2% (61 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hant/
2023-08-21 22:40:19 +02:00
Hudobni Volk
9c889f8df3 Translated using Weblate (Slovenian)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/sl/
2023-08-21 22:40:19 +02:00
Linerly
cbc164d844 Translated using Weblate (Indonesian)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-08-21 22:40:19 +02:00
Eugen Rochko
b8e3060887 New translations strings.xml (Arabic) 2023-08-21 21:00:00 +02:00
Eugen Rochko
1aa1ede421 New translations strings.xml (Arabic) 2023-08-21 20:03:05 +02:00
Eugen Rochko
480dba7629 New translations strings.xml (Arabic) 2023-08-21 19:05:39 +02:00
Eugen Rochko
9b9c66a149 New translations strings.xml (Arabic) 2023-08-21 17:43:00 +02:00
Eugen Rochko
0f5eb923ee New translations strings.xml (Arabic) 2023-08-21 16:13:06 +02:00
snerk
7f521b3129 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 100.0% (369 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nn/
2023-08-21 09:41:52 +00:00
EndermanCo
9ce217d1f2 Translated using Weblate (Persian)
Currently translated at 100.0% (369 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-21 09:41:51 +00:00
ihor_ck
7b1bd3ccad Translated using Weblate (Ukrainian)
Currently translated at 100.0% (369 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-08-21 09:41:51 +00:00
Oliebol
13b1cbde6b Translated using Weblate (Dutch)
Currently translated at 82.9% (306 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-08-21 09:41:51 +00:00
Linerly
8d898a1a78 Translated using Weblate (Indonesian)
Currently translated at 100.0% (369 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-08-21 09:41:51 +00:00
Choukajohn
51c2890ede Translated using Weblate (French)
Currently translated at 100.0% (369 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-08-21 09:41:51 +00:00
gallegonovato
03642faa9c Translated using Weblate (Spanish)
Currently translated at 100.0% (369 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-08-21 09:41:51 +00:00
sk22
084c6e1e59 Translated using Weblate (German)
Currently translated at 94.5% (349 of 369 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-08-21 09:41:51 +00:00
Eugen Rochko
90ed28e7a0 New translations strings.xml (French) 2023-08-20 17:34:05 +01:00
Eugen Rochko
d2b45c1c84 New translations strings.xml (Swedish) 2023-08-20 12:43:05 +01:00
Eugen Rochko
a119ba5f80 New translations strings.xml (Danish) 2023-08-19 17:08:25 +01:00
Eugen Rochko
8c1191a08f New translations strings.xml (Danish) 2023-08-19 16:09:27 +01:00
Eugen Rochko
4275d596e6 New translations strings.xml (Thai) 2023-08-19 12:46:09 +01:00
snerk
2709d5226d Translated using Weblate (Norwegian Nynorsk)
Currently translated at 21.6% (78 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nn/
2023-08-18 16:18:22 +00:00
snerk
8f613e3255 Translated using Weblate (Norwegian Nynorsk)
Currently translated at 20.8% (75 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nn/
2023-08-18 16:18:22 +00:00
snerk
6831e846cf Translated using Weblate (Norwegian Nynorsk)
Currently translated at 18.8% (68 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nn/
2023-08-18 16:18:22 +00:00
snerk
034eb9427d Added translation using Weblate (Norwegian Nynorsk) 2023-08-18 16:18:22 +00:00
alextecplayz
f73c325db3 Translated using Weblate (Romanian)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-08-18 16:18:22 +00:00
gallegonovato
5e2b11c504 Translated using Weblate (Spanish)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-08-18 16:18:22 +00:00
EndermanCo
ec13133431 Translated using Weblate (Persian)
Currently translated at 38.8% (7 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fa/
2023-08-18 16:18:22 +00:00
EndermanCo
a8a56a3ed8 Translated using Weblate (Persian)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-18 16:18:22 +00:00
0eoc
4e9c7c4de2 Translated using Weblate (Russian)
Currently translated at 61.1% (11 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ru/
2023-08-18 16:18:22 +00:00
ihor_ck
ffb7894098 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-08-18 16:18:22 +00:00
Linerly
0d9520ac45 Translated using Weblate (Indonesian)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-08-18 16:18:22 +00:00
Choukajohn
be852e57df Translated using Weblate (French)
Currently translated at 100.0% (360 of 360 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-08-18 16:18:22 +00:00
0eoc
157b38b8ae Translated using Weblate (Russian)
Currently translated at 96.1% (323 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-08-18 16:18:22 +00:00
gallegonovato
83196a1a0d Translated using Weblate (Spanish)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-08-18 16:18:22 +00:00
Codeberg Translate
306225b054 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/
2023-08-18 16:18:22 +00:00
edxkl
6efc71d8d2 Translated using Weblate (Portuguese (Brazil))
Currently translated at 94.4% (17 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/pt_BR/
2023-08-18 16:18:21 +00:00
edxkl
cc4cd4d3f8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 97.3% (327 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-08-18 16:18:21 +00:00
EndermanCo
00e3292205 Translated using Weblate (Persian)
Currently translated at 27.7% (5 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fa/
2023-08-18 16:18:21 +00:00
0eoc
316952423c Translated using Weblate (Russian)
Currently translated at 61.1% (11 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ru/
2023-08-18 16:18:21 +00:00
gicorada
61c2abd014 Translated using Weblate (Italian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/it/
2023-08-18 16:18:21 +00:00
EndermanCo
ee5f299b90 Translated using Weblate (Persian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-18 16:18:21 +00:00
0eoc
f153846381 Translated using Weblate (Russian)
Currently translated at 81.2% (273 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-08-18 16:18:21 +00:00
gallegonovato
0656db0858 Translated using Weblate (Spanish)
Currently translated at 99.1% (333 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-08-18 16:18:21 +00:00
EndermanCo
7f250cb8df Translated using Weblate (Persian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-18 16:18:21 +00:00
EndermanCo
a1e73eca89 Translated using Weblate (Persian)
Currently translated at 98.5% (331 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-18 16:18:21 +00:00
ihor_ck
1dc6936da6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-08-18 16:18:21 +00:00
Eryk Michalak
0431d80a8d Translated using Weblate (Polish)
Currently translated at 91.3% (307 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pl/
2023-08-18 16:18:21 +00:00
Linerly
eaa78093f7 Translated using Weblate (Indonesian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-08-18 16:18:21 +00:00
Choukajohn
25b7151fde Translated using Weblate (French)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-08-18 16:18:21 +00:00
Hudobni Volk
0438b579b6 Translated using Weblate (Slovenian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/sl/
2023-08-18 16:18:21 +00:00
alextecplayz
afa50a4e8c Translated using Weblate (Romanian)
Currently translated at 100.0% (336 of 336 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-08-18 16:18:21 +00:00
EndermanCo
85bdb0067b Translated using Weblate (Persian)
Currently translated at 98.5% (330 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-08-18 16:18:21 +00:00
Hudobni Volk
760cbc7f9a Translated using Weblate (Slovenian)
Currently translated at 61.1% (11 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/sl/
2023-08-18 16:18:21 +00:00
ganjibaiemade
d0e34fcd90 Translated using Weblate (Chinese (Traditional))
Currently translated at 18.2% (61 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hant/
2023-08-18 16:18:21 +00:00
Hudobni Volk
da434b9a9b Translated using Weblate (Slovenian)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/sl/
2023-08-18 16:18:21 +00:00
Linerly
48863dd510 Translated using Weblate (Indonesian)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-08-18 16:18:21 +00:00
FineFindus
2ca34278f9 build: add F-Droid flavor (#760)
* build: add fdroid flavor

* feat: disable fcm registration in f-droid flavor
2023-08-18 18:15:57 +02:00
Jacoco
a79779f813 Emoji Reactions Support (#645)
* Display Pleroma emoji reactions

* Interact with existing Pleroma emoji reactions

* Setting for emoji reaction support

* Setting for displaying reactions in timelines

* More horizontal padding on reactions display item

* List accounts who reacted

* Arbitrary emoji reaction from status footer

* Hide custom emoji keyboard when emoji is selected

* Clear preferences before applying
All preferences get written anyways so nothing will be lost

* Reset react visibility state on bind

* Fix custom emoji turning black when reacting

* Load reactions when a new one is added

* Emoji reactions grid

* Load custom emoji in reactions list fragment

* New reaction toast messages and Unicode emoji regex

* Make custom emoji picker for reactions scrollable

* Scroll down to show custom emoji picker when reacting

* Divider after reaction custom emoji picker

* Animate react button opacity back in

* fix plural strings

* re-implement reactions using horizontal recycler view

* update reactions with event

* tweak emoji font size

* tweak button styles (a tiny bit)

* change footer react button behavior

* fix emoji reaction status item padding

* move emoji reactions below content items

* add content description and tooltip

* use custom emoji keyboard to enter unicode emoji

* fix reactions clearing on status counter updates

* fix space next to emoji reactions not clickable

* make compatible with glitch-soc

* Remove now unused EmojiReactionsView class

* improve handling of reaction padding

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-08-18 18:14:33 +02:00
Eugen Rochko
cc83f2baf3 New translations strings.xml (Danish) 2023-08-18 01:51:02 +01:00
Eugen Rochko
728496b831 New translations strings.xml (Danish) 2023-08-18 00:06:12 +01:00
Eugen Rochko
bbc99162c6 New translations strings.xml (Danish) 2023-08-17 16:47:55 +01:00
Eugen Rochko
eed3af9e3e New translations strings.xml (Arabic) 2023-08-16 22:54:50 +02:00
Eugen Rochko
50187ff376 New translations strings.xml (Arabic) 2023-08-16 21:48:52 +02:00
Eugen Rochko
5f30919fb4 New translations strings.xml (Arabic) 2023-08-16 18:38:58 +02:00
Eugen Rochko
14c3cfac85 New translations strings.xml (French) 2023-08-16 15:50:45 +02:00
Eugen Rochko
e978f02765 New translations strings.xml (French) 2023-08-16 14:09:02 +02:00
Eugen Rochko
8d877c480f New translations strings.xml (French) 2023-08-16 12:20:32 +02:00
Eugen Rochko
c53efee9a1 New translations strings.xml (Thai) 2023-08-15 22:47:32 +02:00
Eugen Rochko
148c461e86 New translations strings.xml (Danish) 2023-08-15 16:40:55 +02:00
Eugen Rochko
fcadb9883d New translations strings.xml (Danish) 2023-08-15 14:59:58 +02:00
Eugen Rochko
bb6491e10a New translations full_description.txt (Danish) 2023-08-15 14:59:57 +02:00
Eugen Rochko
6248ccf376 New translations full_description.txt (Danish) 2023-08-15 13:53:21 +02:00
Eugen Rochko
c9e08f36fa New translations full_description.txt (Danish) 2023-08-15 03:19:07 +02:00
Eugen Rochko
10b95d753b New translations full_description.txt (Danish) 2023-08-15 02:15:33 +02:00
Eugen Rochko
c3989083cf New translations strings.xml (Portuguese) 2023-08-14 15:33:01 +02:00
Eugen Rochko
01db585094 New translations strings.xml (Basque) 2023-08-14 01:42:56 +02:00
Eugen Rochko
cc67cb330c New translations full_description.txt (Basque) 2023-08-14 00:10:18 +02:00
Eugen Rochko
52ed3c5a04 New translations strings.xml (Basque) 2023-08-14 00:10:17 +02:00
Eugen Rochko
5976f6230a New translations strings.xml (Chinese Simplified) 2023-08-13 18:58:06 +02:00
Eugen Rochko
3553f03a95 New translations strings.xml (Slovenian) 2023-08-13 11:06:06 +02:00
Eugen Rochko
d6e2d889c3 New translations strings.xml (Slovenian) 2023-08-12 16:02:40 +02:00
Eugen Rochko
a777b3b450 New translations strings.xml (Slovenian) 2023-08-12 14:07:49 +02:00
Eugen Rochko
9957efbea0 New translations strings.xml (Norwegian) 2023-08-12 01:06:29 +02:00
Eugen Rochko
22e7b9730f New translations strings.xml (Vietnamese) 2023-08-11 17:50:37 +02:00
Eugen Rochko
91470b8509 New translations strings.xml (Vietnamese) 2023-08-11 16:21:15 +02:00
Eugen Rochko
c9d5327328 New translations strings.xml (Russian) 2023-08-11 12:41:37 +02:00
Eugen Rochko
1aa61b72e5 New translations strings.xml (Russian) 2023-08-11 10:45:11 +02:00
Eugen Rochko
3ca5edc3fc New translations strings.xml (Russian) 2023-08-11 09:19:15 +02:00
Eugen Rochko
a092ebaeb3 New translations strings.xml (Russian) 2023-08-11 08:03:02 +02:00
Eugen Rochko
5b9e84c255 New translations strings.xml (Russian) 2023-08-11 07:07:00 +02:00
Eugen Rochko
9c058b926f New translations strings.xml (Russian) 2023-08-10 12:30:16 +02:00
Eugen Rochko
4f2d2ae6e8 New translations strings.xml (Russian) 2023-08-10 11:10:22 +02:00
Eugen Rochko
75aa26a018 New translations strings.xml (Russian) 2023-08-09 14:50:26 +02:00
Eugen Rochko
0f795254e5 New translations strings.xml (Russian) 2023-08-09 13:28:03 +02:00
Eugen Rochko
33592f0a83 New translations strings.xml (Swedish) 2023-08-07 12:43:38 +02:00
Eugen Rochko
d6fd01eaca New translations strings.xml (Swedish) 2023-08-07 10:56:51 +02:00
Mark Hansen
1cdc58378a Sort Hashtags you follow menu (#695)
* Sort Hashtags you follow menu

* change sorting method

---------

Co-authored-by: Mark Hansen <mhansen@accusoft.com>
Co-authored-by: sk <sk22@mailbox.org>
2023-08-05 20:43:23 +02:00
FineFindus
584b11fce3 feat: allow playing multiple media (#755)
* feat: allow playing multiple media

* replace audio overlay icon

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-08-05 20:35:57 +02:00
FineFindus
fe2039062b fix(remote): send remoteAccount (#754)
closes sk22#664

* fix(remote): send remoteAccount

* fix(server-about): set adming account as remote

* fix(remote): show remote local accounts as remote
2023-08-05 20:30:41 +02:00
FineFindus
0269756b52 feat: change mute duration (#751)
* feat: add mute timer

* fix: missing ressources

* refactor(add-mute-timer): change of the mute timer popup layout

* tweak mute dialog

---------

Co-authored-by: LucasGGamerM <lucassggabriel@gmail.com>
Co-authored-by: sk <sk22@mailbox.org>
2023-08-05 20:17:40 +02:00
FineFindus
df1a6cf764 feat(openURL): open about in app (#750) 2023-08-05 19:44:18 +02:00
FineFindus
6d2385b6b3 feat: support UnifiedPush notifications (#749)
* build: add unified push dependency

* feat(notification): allow arbitrary push notification endpoint

* feat(notification/unified-push): show notification

* refactor(unifiedPush): use more consise null check

* feat(settings/notification): add UnifiedPush toggle

* feat(settings/notification): show no distributor message

* feat(settings/notification): disable unifiedpush when no distributor is available

* change icon name

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-08-05 19:42:10 +02:00
FineFindus
44eaa36cef fix: copy link invisible on EMUI (#753) 2023-08-05 19:33:36 +02:00
sk
50b40c4a07 Merge remote-tracking branch 'upstream/master' 2023-08-05 19:32:28 +02:00
Eugen Rochko
ee6e0ff26c New translations strings.xml (Russian) 2023-08-04 19:26:18 +02:00
Eugen Rochko
4d9574bf38 New translations strings.xml (Russian) 2023-08-04 17:51:30 +02:00
Eugen Rochko
813be9a2be New translations strings.xml (Russian) 2023-08-04 16:30:43 +02:00
Eugen Rochko
cc76ebfafb New translations strings.xml (Russian) 2023-08-04 14:58:02 +02:00
Eugen Rochko
7989ee0243 New translations strings.xml (Russian) 2023-08-04 13:58:08 +02:00
Eugen Rochko
3aa1997cfd New translations strings.xml (Russian) 2023-08-04 12:51:54 +02:00
Grishka
c3da15552e Merge branch 'l10n_master' 2023-08-03 17:22:04 +03:00
Grishka
a014fe9443 Fix media layout with unknown sizes 2023-08-03 17:21:40 +03:00
Eugen Rochko
92551d4ca3 New translations strings.xml (Portuguese, Brazilian) 2023-08-03 03:27:17 +02:00
Eugen Rochko
8010858e85 New translations strings.xml (Portuguese, Brazilian) 2023-08-03 02:26:22 +02:00
Eugen Rochko
4efb4875b0 New translations strings.xml (Swedish) 2023-08-02 09:16:24 +02:00
Eugen Rochko
c5d041e46d New translations strings.xml (Portuguese, Brazilian) 2023-08-01 23:11:09 +02:00
Eugen Rochko
53c2223aae New translations strings.xml (Portuguese, Brazilian) 2023-08-01 21:25:07 +02:00
Eugen Rochko
25034ac0ae New translations strings.xml (Portuguese, Brazilian) 2023-08-01 01:41:52 +02:00
Eugen Rochko
ac9de72b75 New translations strings.xml (Portuguese, Brazilian) 2023-08-01 00:42:41 +02:00
Eugen Rochko
1f48ad93f2 New translations strings.xml (Portuguese, Brazilian) 2023-07-31 23:46:36 +02:00
Eugen Rochko
38f7f7aa00 New translations strings.xml (Czech) 2023-07-31 14:32:20 +02:00
Eugen Rochko
fe8175c63a New translations strings.xml (French) 2023-07-30 19:39:54 +02:00
Eugen Rochko
2d9e01bbc1 New translations strings.xml (French) 2023-07-30 18:44:39 +02:00
Eugen Rochko
022a227b08 New translations strings.xml (Turkish) 2023-07-28 00:29:16 +02:00
Eugen Rochko
a2228259f1 New translations full_description.txt (Turkish) 2023-07-27 23:33:53 +02:00
Eugen Rochko
a61af7c56f New translations strings.xml (Turkish) 2023-07-27 23:33:52 +02:00
Eugen Rochko
5d6a646976 New translations strings.xml (French) 2023-07-27 23:33:51 +02:00
Eugen Rochko
628d0d7492 New translations strings.xml (French) 2023-07-27 22:05:11 +02:00
Eugen Rochko
b76c8745ec New translations strings.xml (Norwegian) 2023-07-25 22:57:26 +02:00
Eugen Rochko
51e67bc441 New translations strings.xml (Norwegian) 2023-07-25 21:52:29 +02:00
Gregory K
8887f75b70 Merge pull request #655 from nilathedragon/patch-1
Do not assume languages array will contain entries
2023-07-25 19:12:56 +03:00
Nila
9436a838c0 Do not assume languages array will contain entries 2023-07-25 17:28:41 +02:00
Eugen Rochko
ef120fa36f New translations strings.xml (Swedish) 2023-07-25 00:55:54 +02:00
Eugen Rochko
8c6385e2c5 New translations strings.xml (Swedish) 2023-07-24 23:57:42 +02:00
Eugen Rochko
0bd85d9905 New translations strings.xml (Persian) 2023-07-24 13:40:53 +02:00
Eugen Rochko
ce0dab7b28 New translations strings.xml (Czech) 2023-07-24 09:55:45 +02:00
sk
bdcebf1576 boop verisom 2023-07-23 22:03:25 +02:00
Eugen Rochko
ddcc5670ce New translations strings.xml (Bengali) 2023-07-22 17:48:06 +02:00
Eugen Rochko
86afa184e2 New translations strings.xml (Bengali) 2023-07-22 16:26:35 +02:00
Eugen Rochko
77f341f139 New translations strings.xml (German) 2023-07-22 12:27:40 +02:00
Eugen Rochko
918b5d99c2 New translations strings.xml (Swedish) 2023-07-21 22:09:32 +02:00
Eugen Rochko
7a098d6eff New translations strings.xml (Swedish) 2023-07-21 21:06:04 +02:00
sk
239b6f8202 fix pill-less navigation bar with labels 2023-07-21 11:41:34 +02:00
Jacoco
8404c79148 Re-implement some Akkoma specific things + hide filter settings (#729)
* Replace missing blurhash with accent color

* Correct Akkoma max account fields

* Skip discover on Akkoma

* Akkoma poll limits

* Hide filter settings on Akkoma

* clear search fragment on back

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-07-21 02:29:38 +02:00
sk
5b2d04e09d add navigation bar tab labels - with option to hide them 2023-07-21 01:45:08 +02:00
sk
6bd13f99d2 add a readme link to the sauna repo 2023-07-21 01:15:35 +02:00
sk
2e8e12c1c8 add font tracking to label_medium 2023-07-21 01:08:07 +02:00
sk
17929a6b2d upstream was right about the weird padding
see "Navigation bar target size and margins" in
https://m3.material.io/components/navigation-bar/specs
2023-07-21 00:37:05 +02:00
sk
9455eaf820 parse html in pronouns 2023-07-21 00:30:30 +02:00
sk
cc054487ba configurable pronoun display 2023-07-21 00:22:52 +02:00
sk
e2df320d00 fix edit history header lines 2023-07-21 00:03:11 +02:00
sk
d74313f996 remove code for below-header reply lines 2023-07-20 23:58:18 +02:00
sk
b3cab67049 fix null pointer when accessing draft content type
closes sk22#734
2023-07-20 23:43:46 +02:00
sk
996f0b22b9 use theme color for alt badge text 2023-07-20 23:39:11 +02:00
sk
67952ea98e fix bugged alt text badge state 2023-07-20 23:39:00 +02:00
sk
7a02ca435f make alt badge more transparent
closes sk22#735
2023-07-20 23:19:47 +02:00
Eugen Rochko
71f81283f5 New translations strings.xml (Polish) 2023-07-20 13:51:45 +02:00
Eugen Rochko
058c7c3c33 New translations strings.xml (Danish) 2023-07-20 10:31:41 +02:00
Eugen Rochko
870e33879b New translations strings.xml (Indonesian) 2023-07-19 07:48:32 +02:00
Eugen Rochko
3ca82bdfc5 New translations strings.xml (Japanese) 2023-07-19 05:08:28 +02:00
Eugen Rochko
4721bad286 New translations short_description.txt (Armenian) 2023-07-18 16:30:06 +02:00
Eugen Rochko
f040cf2f07 New translations full_description.txt (Armenian) 2023-07-18 16:30:05 +02:00
Eugen Rochko
8d50717c90 New translations strings.xml (Armenian) 2023-07-18 16:30:04 +02:00
Eugen Rochko
2512ad3c95 New translations strings.xml (Armenian) 2023-07-18 14:49:00 +02:00
sk
8d55f62da9 round inset notifications
closes sk22#665
2023-07-18 12:54:04 +02:00
Eugen Rochko
bc7e007634 New translations strings.xml (Swedish) 2023-07-18 12:03:42 +02:00
Eugen Rochko
1f3c87e0c7 New translations strings.xml (Swedish) 2023-07-18 10:31:42 +02:00
sk
ee0048a406 fix wrong margins for media posts with cw / without text 2023-07-18 09:59:47 +02:00
sk
14dcc769f2 fix monochrome icon
closes sk22#719
2023-07-18 09:01:13 +02:00
sk
f2e6255eb3 remove flagship instance reference 2023-07-18 08:52:40 +02:00
sk
7d392e20fb fix trending hashtags not loading
closes sk22#724
2023-07-18 08:40:54 +02:00
Eugen Rochko
73e08faee9 New translations strings.xml (Persian) 2023-07-13 20:45:58 +02:00
Eugen Rochko
02dc7711e4 New translations strings.xml (Persian) 2023-07-13 19:38:29 +02:00
Eugen Rochko
67b4d80e5b New translations strings.xml (Spanish) 2023-07-13 16:08:11 +02:00
Eugen Rochko
5168d2bb39 New translations strings.xml (Spanish) 2023-07-13 14:59:49 +02:00
Eugen Rochko
57190a75bf New translations strings.xml (Indonesian) 2023-07-13 11:54:32 +02:00
Eugen Rochko
f10e865895 New translations strings.xml (Indonesian) 2023-07-13 10:48:50 +02:00
299 changed files with 11840 additions and 1800 deletions

1
.github/FUNDING.yml vendored
View File

@@ -3,7 +3,6 @@
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # mastodon
open_collective: # Replace with a single Open Collective username e.g., user1
ko_fi: xsk22
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username e.g., user1

View File

@@ -54,9 +54,15 @@ You can create drafts, edit them, send them manually later or set a scheduled da
## Installation
### IzzyOnDroid
### Google Play Store
[apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
[https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
### F-Droid via IzzyOnDroid
[https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
@@ -64,11 +70,11 @@ Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first
[`https://apt.izzysoft.de/fdroid/repo`](https://apt.izzysoft.de/fdroid/repo)
### Google Play Store
### F-Droid via saunarepo
[play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
[https://repo.the-sauna.icu](https://repo.the-sauna.icu/)
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
<a href="https://repo.the-sauna.icu"><img height="28" alt="Get it on SaunaRepo" src="img/saunarepo-badge.svg"></a>
### F-Droid

View File

@@ -3,6 +3,12 @@ buildscript {
repositories {
google()
mavenCentral()
maven {
url "https://www.jitpack.io"
content {
includeModule 'com.github.UnifiedPush', 'android-connector'
}
}
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.0'

1
img/saunarepo-badge.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="124.25" height="28" role="img" aria-label="SAUNAREPO"><title>SAUNAREPO</title><g shape-rendering="crispEdges"><rect width="124.25" height="28" fill="#fb8441"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100"><image x="9" y="7" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyBmaWxsPSJ3aGl0ZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHRpdGxlPkFuZHJvaWQ8L3RpdGxlPjxwYXRoIGQ9Ik0xNy41MjMgMTUuMzQxNGMtLjU1MTEgMC0uOTk5My0uNDQ4Ni0uOTk5My0uOTk5N3MuNDQ4My0uOTk5My45OTkzLS45OTkzYy41NTExIDAgLjk5OTMuNDQ4My45OTkzLjk5OTMuMDAwMS41NTExLS40NDgyLjk5OTctLjk5OTMuOTk5N20tMTEuMDQ2IDBjLS41NTExIDAtLjk5OTMtLjQ0ODYtLjk5OTMtLjk5OTdzLjQ0ODItLjk5OTMuOTk5My0uOTk5M2MuNTUxMSAwIC45OTkzLjQ0ODMuOTk5My45OTkzIDAgLjU1MTEtLjQ0ODMuOTk5Ny0uOTk5My45OTk3bTExLjQwNDUtNi4wMmwxLjk5NzMtMy40NTkyYS40MTYuNDE2IDAgMDAtLjE1MjEtLjU2NzYuNDE2LjQxNiAwIDAwLS41Njc2LjE1MjFsLTIuMDIyMyAzLjUwM0MxNS41OTAyIDguMjQzOSAxMy44NTMzIDcuODUwOCAxMiA3Ljg1MDhzLTMuNTkwMi4zOTMxLTUuMTM2NyAxLjA5ODlMNC44NDEgNS40NDY3YS40MTYxLjQxNjEgMCAwMC0uNTY3Ny0uMTUyMS40MTU3LjQxNTcgMCAwMC0uMTUyMS41Njc2bDEuOTk3MyAzLjQ1OTJDMi42ODg5IDExLjE4NjcuMzQzMiAxNC42NTg5IDAgMTguNzYxaDI0Yy0uMzQzNS00LjEwMjEtMi42ODkyLTcuNTc0My02LjExODUtOS40Mzk2Ii8+PC9zdmc+"/><text transform="scale(.1)" x="721.25" y="175" textLength="802.5" fill="#fff" font-weight="bold">SAUNAREPO</text></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -15,8 +15,8 @@ android {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 33
versionCode 96
versionName "2.0.1+fork.96"
versionCode 100
versionName "2.1.4+fork.100"
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']
}
@@ -34,6 +34,7 @@ android {
}
githubRelease { initWith release }
playRelease { initWith release }
fdroidRelease { initWith release }
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
@@ -78,6 +79,7 @@ dependencies {
implementation 'com.github.bottom-software-foundation:bottom-java:2.1.0'
annotationProcessor 'org.parceler:parceler:1.1.12'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'com.github.UnifiedPush:android-connector:2.1.1'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
@@ -36,18 +37,6 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https" android:host="mastodon.social" android:pathPrefix="/@"/>
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https" android:host="mastodon.online" android:pathPrefix="/@"/>
</intent-filter>
</activity>
<activity
android:name=".PanicResponderActivity"
@@ -57,7 +46,6 @@
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
@@ -100,6 +88,15 @@
<category android:name="me.grishka.fcmtest"/>
</intent-filter>
</receiver>
<receiver android:exported="true" android:enabled="true" android:name=".UnifiedPushNotificationReceiver"
tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>
<action android:name="org.unifiedpush.android.connector.UNREGISTERED"/>
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT"/>
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED"/>
</intent-filter>
</receiver>
</application>

View File

@@ -169,7 +169,8 @@ public class AudioPlayerService extends Service{
}
updateNotification(false, false);
getSystemService(AudioManager.class).requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
int audiofocus = GlobalUserPreferences.overlayMedia ? AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK : AudioManager.AUDIOFOCUS_GAIN;
getSystemService(AudioManager.class).requestAudioFocus(audioFocusChangeListener, AudioManager.STREAM_MUSIC, audiofocus);
player=new MediaPlayer();
player.setOnPreparedListener(this::onPlayerPrepared);

View File

@@ -25,6 +25,8 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import androidx.annotation.StringRes;
public class GlobalUserPreferences{
private static final String TAG="GlobalUserPreferences";
@@ -57,6 +59,10 @@ public class GlobalUserPreferences{
public static AutoRevealMode autoRevealEqualSpoilers;
public static ColorPreference color;
public static boolean disableM3PillActiveIndicator;
public static boolean showNavigationLabels;
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
public static boolean overlayMedia;
public static boolean showSuicideHelp;
private static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
@@ -111,6 +117,12 @@ public class GlobalUserPreferences{
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
disableM3PillActiveIndicator=prefs.getBoolean("disableM3PillActiveIndicator", false);
showNavigationLabels=prefs.getBoolean("showNavigationLabels", true);
displayPronounsInTimelines=prefs.getBoolean("displayPronounsInTimelines", true);
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
overlayMedia=prefs.getBoolean("overlayMedia", false);
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
if (prefs.contains("prefixRepliesWithRe")) {
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
@@ -164,6 +176,12 @@ public class GlobalUserPreferences{
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
.putBoolean("forwardReportDefault", forwardReportDefault)
.putBoolean("disableM3PillActiveIndicator", disableM3PillActiveIndicator)
.putBoolean("showNavigationLabels", showNavigationLabels)
.putBoolean("displayPronounsInTimelines", displayPronounsInTimelines)
.putBoolean("displayPronounsInThreads", displayPronounsInThreads)
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
.putBoolean("overlayMedia", overlayMedia)
.putBoolean("showSuicideHelp", showSuicideHelp)
.apply();
}

View File

@@ -43,50 +43,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID());
} else if (intent.getBooleanExtra("compose", false)){
showCompose();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
} else {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
}
restartHomeFragment();
}
if(GithubSelfUpdater.needSelfUpdating()){
@@ -143,7 +100,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
}
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
new GetSearchResults(q, null, true)
new GetSearchResults(q, null, true, null, 0, 0)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
@@ -259,4 +216,51 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
Fragment fragment = getCurrentFragment();
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
}
public void restartHomeFragment(){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID());
} else if (intent.getBooleanExtra("compose", false)){
showCompose();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
} else {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
}
}
}

View File

@@ -14,6 +14,7 @@ import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
@@ -32,6 +33,7 @@ import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
@@ -58,7 +60,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
private static final int SUMMARY_ID = 791;
private static int notificationId = 0;
private static Map<String, Integer> notificationIdsForAccounts = new HashMap<>();
private static final Map<String, Integer> notificationIdsForAccounts = new HashMap<>();
@Override
public void onReceive(Context context, Intent intent){
@@ -148,6 +150,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
}
}
public void notifyUnifiedPush(Context context, String accountID, 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);
}
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
NotificationManager nm=context.getSystemService(NotificationManager.class);
AccountSession session=AccountSessionManager.get(accountID);
@@ -169,6 +176,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
.map(type->{
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
channel.setLightColor(context.getColor(R.color.primary_700));
channel.enableLights(true);
channel.setGroup(accountID);
return channel;
})
@@ -198,6 +207,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
.setShowWhen(true)
.setCategory(Notification.CATEGORY_SOCIAL)
.setAutoCancel(true)
.setLights(UiUtils.getThemeColor(context, android.R.attr.colorAccent), 500, 1000)
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
if (!GlobalUserPreferences.uniformNotificationIcon) {
@@ -314,11 +324,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
CreateStatus.Request req=new CreateStatus.Request();
req.status = initialText + input.toString();
req.language = preferences.postingDefaultLanguage;
req.visibility = preferences.postingDefaultVisibility;
req.language = notification.status.language;
req.visibility = notification.status.visibility;
req.inReplyToId = notification.status.id;
if (!notification.status.spoilerText.isEmpty() &&
if (notification.status.hasSpoiler() &&
(GlobalUserPreferences.prefixReplies == ALWAYS
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(notification.status.account.id)))
&& !notification.status.spoilerText.startsWith("re: ")) {

View File

@@ -0,0 +1,84 @@
package org.joinmastodon.android;
import android.content.Context;
import android.util.Log;
import org.jetbrains.annotations.NotNull;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.unifiedpush.android.connector.MessagingReceiver;
import java.util.List;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class UnifiedPushNotificationReceiver extends MessagingReceiver{
private static final String TAG="UnifiedPushNotificationReceiver";
public UnifiedPushNotificationReceiver() {
super();
}
@Override
public void onNewEndpoint(@NotNull Context context, @NotNull String endpoint, @NotNull String instance) {
// Called when a new endpoint be used for sending push messages
Log.d(TAG, "onNewEndpoint: New Endpoint " + endpoint + " for "+ instance);
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
if (account != null)
account.getPushSubscriptionManager().registerAccountForPush(null, endpoint);
}
@Override
public void onRegistrationFailed(@NotNull Context context, @NotNull String instance) {
// called when the registration is not possible, eg. no network
Log.d(TAG, "onRegistrationFailed: " + instance);
//re-register for gcm
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
if (account != null)
account.getPushSubscriptionManager().registerAccountForPush(null);
}
@Override
public void onUnregistered(@NotNull Context context, @NotNull String instance) {
// called when this application is unregistered from receiving push messages
Log.d(TAG, "onUnregistered: " + instance);
//re-register for gcm
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
if (account != null)
account.getPushSubscriptionManager().registerAccountForPush(null);
}
@Override
public void onMessage(@NotNull Context context, @NotNull byte[] message, @NotNull String instance) {
// Called when a new message is received. The message contains the full POST body of the push message
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
if (account == null)
return;
//this is stupid
// Mastodon stores the info to decrypt the message in the HTTP headers, which are not accessible in UnifiedPush,
// thus it is not possible to decrypt them. SO we need to re-request them from the server and transform them later on
// The official uses fcm and moves the headers to extra data, see
// https://github.com/mastodon/webpush-fcm-relay/blob/cac95b28d5364b0204f629283141ac3fb749e0c5/webpush-fcm-relay.go#L116
// https://github.com/tuskyapp/Tusky/pull/2303#issue-1112080540
account.getCacheController().getNotifications(null, 1, false, false, true, new Callback<>(){
@Override
public void onSuccess(PaginatedResponse<List<Notification>> result){
result.items
.stream()
.findFirst()
.ifPresent(value->MastodonAPIController.runInBackground(()->new PushNotificationReceiver().notifyUnifiedPush(context, instance, value)));
}
@Override
public void onError(ErrorResponse error){
//professional error handling
}
});
}
}

View File

@@ -121,13 +121,13 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
.orElseGet(() -> this.execNoAuth(domain));
}
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
return wrapProgress(activity, message, cancelable, null);
public MastodonAPIRequest<T> wrapProgress(Context context, @StringRes int message, boolean cancelable){
return wrapProgress(context, message, cancelable, null);
}
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable, Consumer<ProgressDialog> transform){
progressDialog=new ProgressDialog(activity);
progressDialog.setMessage(activity.getString(message));
public MastodonAPIRequest<T> wrapProgress(Context context, @StringRes int message, boolean cancelable, Consumer<ProgressDialog> transform){
progressDialog=new ProgressDialog(context);
progressDialog.setMessage(context.getString(message));
progressDialog.setCancelable(cancelable);
if (transform != null) transform.accept(progressDialog);
if(cancelable){

View File

@@ -97,7 +97,7 @@ public class PushSubscriptionManager{
deviceToken=getPrefs().getString("deviceToken", null);
int tokenVersion=getPrefs().getInt("version", 0);
if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){
registerAllAccountsForPush(false);
registerAllAccountsForPush(true); // TODO: revert this before release
return;
}
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");
@@ -120,9 +120,21 @@ public class PushSubscriptionManager{
return !TextUtils.isEmpty(deviceToken);
}
public void registerAccountForPush(PushSubscription subscription){
if(TextUtils.isEmpty(deviceToken))
throw new IllegalStateException("No device push token available");
// this function is used for registering push notifications using FCM
// to avoid NonFreeNet in F-Droid, this registration is disabled in it
// see https://github.com/LucasGGamerM/moshidon/issues/206 for more context
if(BuildConfig.BUILD_TYPE.equals("fdroidRelease") || TextUtils.isEmpty(deviceToken)){
Log.d(TAG, "Skipping registering for FCM push notifications");
return;
}
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/";
registerAccountForPush(subscription, endpoint);
}
public void registerAccountForPush(PushSubscription subscription, String endpoint){
MastodonAPIController.runInBackground(()->{
Log.d(TAG, "registerAccountForPush: started for "+accountID);
String encodedPublicKey, encodedAuthKey, pushAccountID;
@@ -151,12 +163,17 @@ public class PushSubscriptionManager{
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
return;
}
new RegisterForPushNotifications(deviceToken,
//work-around for adding the randomAccountId
String newEndpoint = endpoint;
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
newEndpoint += pushAccountID;
new RegisterForPushNotifications(newEndpoint,
encodedPublicKey,
encodedAuthKey,
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
subscription==null ? PushSubscription.Policy.ALL : subscription.policy,
pushAccountID)
subscription==null ? PushSubscription.Policy.ALL : subscription.policy)
.setCallback(new Callback<>(){
@Override
public void onSuccess(PushSubscription result){

View File

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

View File

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

View File

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

View File

@@ -6,18 +6,19 @@ import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.StatusPrivacy;
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable, Boolean indexable){
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
}
private static class Request{
public Boolean locked, discoverable;
public Boolean locked, discoverable, indexable;
public RequestSource source;
public Request(Boolean locked, Boolean discoverable, RequestSource source){
public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){
this.locked=locked;
this.discoverable=discoverable;
this.indexable=indexable;
this.source=source;
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.announcements;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class AddAnnouncementReaction extends MastodonAPIRequest<Object> {
public AddAnnouncementReaction(String id, String emoji) {
super(HttpMethod.PUT, "/announcements/" + id + "/reactions/" + emoji, Object.class);
setRequestBody(new Object());
}
}

View File

@@ -0,0 +1,9 @@
package org.joinmastodon.android.api.requests.announcements;
import org.joinmastodon.android.api.MastodonAPIRequest;
public class DeleteAnnouncementReaction extends MastodonAPIRequest<Object> {
public DeleteAnnouncementReaction(String id, String emoji) {
super(HttpMethod.DELETE, "/announcements/" + id + "/reactions/" + emoji, Object.class);
}
}

View File

@@ -6,6 +6,9 @@ import org.joinmastodon.android.model.FilterContext;
import java.util.EnumSet;
import java.util.List;
import androidx.annotation.Keep;
@Keep
class FilterRequest{
public String title;
public EnumSet<FilterContext> context;

View File

@@ -4,10 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.PushSubscription;
public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscription>{
public RegisterForPushNotifications(String deviceToken, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy, String accountID){
public RegisterForPushNotifications(String endpoint, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy){
super(HttpMethod.POST, "/push/subscription", PushSubscription.class);
Request r=new Request();
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
r.subscription.endpoint=endpoint;
r.data.alerts=alerts;
r.policy=policy;
r.subscription.keys.p256dh=encryptionKey;

View File

@@ -4,13 +4,19 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.SearchResults;
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
public GetSearchResults(String query, Type type, boolean resolve){
public GetSearchResults(String query, Type type, boolean resolve, String maxID, int offset, int count){
super(HttpMethod.GET, "/search", SearchResults.class);
addQueryParameter("q", query);
if(type!=null)
addQueryParameter("type", type.name().toLowerCase());
if(resolve)
addQueryParameter("resolve", "true");
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(offset>0)
addQueryParameter("offset", String.valueOf(offset));
if(count>0)
addQueryParameter("limit", String.valueOf(count));
}
public GetSearchResults limit(int limit){

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class AddStatusReaction extends MastodonAPIRequest<Status> {
public AddStatusReaction(String id, String emoji) {
super(HttpMethod.POST, "/statuses/" + id + "/react/" + emoji, Status.class);
setRequestBody(new Object());
}
}

View File

@@ -11,13 +11,11 @@ import java.util.ArrayList;
import java.util.List;
public class CreateStatus extends MastodonAPIRequest<Status>{
public static final Instant DRAFTS_AFTER_INSTANT = Instant.ofEpochMilli(253370764799999L) /* end of 9998 */;
private static final float draftFactor = 31536000000f /* one year */ / 253370764799999f /* end of 9998 */;
public static long EPOCH_OF_THE_YEAR_FIVE_THOUSAND=95617584000000L;
public static final Instant DRAFTS_AFTER_INSTANT=Instant.ofEpochMilli(EPOCH_OF_THE_YEAR_FIVE_THOUSAND - 1) /* end of 4999 */;
public static Instant getDraftInstant() {
// returns an instant between 9999-01-01 00:00:00 and 9999-12-31 23:59:59
// yes, this is a weird implementation for something that hardly matters
return DRAFTS_AFTER_INSTANT.plusMillis(1 + (long) (System.currentTimeMillis() * draftFactor));
return DRAFTS_AFTER_INSTANT.plusMillis(System.currentTimeMillis());
}
public CreateStatus(CreateStatus.Request req, String uuid){
@@ -36,6 +34,7 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public static class Request{
public String status;
public List<MediaAttribute> mediaAttributes;
public List<String> mediaIds;
public Poll poll;
public String inReplyToId;
@@ -55,5 +54,17 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public boolean multiple;
public boolean hideTotals;
}
public static class MediaAttribute{
public String id;
public String description;
public String focus;
public MediaAttribute(String id, String description, String focus){
this.id=id;
this.description=description;
this.focus=focus;
}
}
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class DeleteStatusReaction extends MastodonAPIRequest<Status> {
public DeleteStatusReaction(String id, String emoji) {
super(HttpMethod.POST, "/statuses/" + id + "/unreact/" + emoji, Status.class);
setRequestBody(new Object());
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class PleromaAddStatusReaction extends MastodonAPIRequest<Status> {
public PleromaAddStatusReaction(String id, String emoji) {
super(HttpMethod.PUT, "/pleroma/statuses/" + id + "/reactions/" + emoji, Status.class);
setRequestBody(new Object());
}
}

View File

@@ -0,0 +1,10 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class PleromaDeleteStatusReaction extends MastodonAPIRequest<Status> {
public PleromaDeleteStatusReaction(String id, String emoji) {
super(HttpMethod.DELETE, "/pleroma/statuses/" + id + "/reactions/" + emoji, Status.class);
}
}

View File

@@ -0,0 +1,14 @@
package org.joinmastodon.android.api.requests.statuses;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.EmojiReaction;
import java.util.List;
public class PleromaGetStatusReactions extends MastodonAPIRequest<List<EmojiReaction>> {
public PleromaGetStatusReactions(String id, String emoji) {
super(HttpMethod.GET, "/pleroma/statuses/" + id + "/reactions/" + (emoji != null ? emoji : ""), new TypeToken<>(){});
}
}

View File

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

View File

@@ -0,0 +1,10 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class GetTag extends MastodonAPIRequest<Hashtag>{
public GetTag(String tag){
super(HttpMethod.GET, "/tags/"+tag, Hashtag.class);
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class SetTagFollowed extends MastodonAPIRequest<Hashtag>{
public SetTagFollowed(String tag, boolean followed){
super(HttpMethod.POST, "/tags/"+tag+(followed ? "/follow" : "/unfollow"), Hashtag.class);
setRequestBody(new Object());
}
}

View File

@@ -13,7 +13,6 @@ import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class AccountLocalPreferences{
private final SharedPreferences prefs;
@@ -38,6 +37,9 @@ public class AccountLocalPreferences{
public String timelineReplyVisibility; // akkoma-only
public boolean keepOnlyLatestNotification;
public boolean emojiReactionsEnabled;
public ShowEmojiReactions showEmojiReactions;
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
@@ -62,6 +64,8 @@ public class AccountLocalPreferences{
publishButtonText=prefs.getString("publishButtonText", null);
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
}
public long getNotificationsPauseEndTime(){
@@ -93,6 +97,14 @@ public class AccountLocalPreferences{
.putString("publishButtonText", publishButtonText)
.putString("timelineReplyVisibility", timelineReplyVisibility)
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
.putString("showEmojiReactions", showEmojiReactions.name())
.apply();
}
public enum ShowEmojiReactions{
HIDE_EMPTY,
ONLY_OPENED,
ALWAYS
}
}

View File

@@ -146,6 +146,9 @@ public class AccountSession{
@Override
public void onError(ErrorResponse error){
Log.w(TAG, "Failed to load preferences for account "+getID()+": "+error);
if (preferences==null)
preferences=new Preferences();
preferencesFromAccountSource(self);
}
})
.exec(getID());
@@ -216,7 +219,7 @@ public class AccountSession{
public void savePreferencesIfPending(){
if(preferencesNeedSaving){
new UpdateAccountCredentialsPreferences(preferences, null, null)
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
@@ -300,6 +303,10 @@ public class AccountSession{
});
}
public void updateAccountInfo(){
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
}
public Optional<Instance> getInstance() {
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
}
@@ -310,4 +317,10 @@ public class AccountSession{
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
.build();
}
public String getDefaultAvatarUrl() {
return getInstance()
.map(instance->"https://"+domain+(instance.isAkkoma() ? "/images/avi.png" : "/avatars/original/missing.png"))
.orElse("");
}
}

View File

@@ -303,8 +303,7 @@ public class AccountSessionManager{
}
}
private void updateSessionLocalInfo(AccountSession session){
/*package*/ void updateSessionLocalInfo(AccountSession session){
new GetOwnAccount()
.setCallback(new Callback<>(){
@Override

View File

@@ -0,0 +1,19 @@
package org.joinmastodon.android.events;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.model.EmojiReaction;
import java.util.List;
public class EmojiReactionsUpdatedEvent{
public final String id;
public final List<EmojiReaction> reactions;
public final boolean updateTextPadding;
public RecyclerView.ViewHolder viewHolder;
public EmojiReactionsUpdatedEvent(String id, List<EmojiReaction> reactions, boolean updateTextPadding, RecyclerView.ViewHolder viewHolder){
this.id=id;
this.reactions=reactions;
this.updateTextPadding=updateTextPadding;
this.viewHolder=viewHolder;
}
}

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.model.Status;
public class StatusCountersUpdatedEvent{

View File

@@ -26,6 +26,8 @@ import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
@@ -69,10 +71,12 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
Status fakeStatus = a.toStatus();
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
textItem.textSelectable = true;
return List.of(
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
textItem
);
List<StatusDisplayItem> items=new ArrayList<>();
items.add(HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead));
items.add(textItem);
if(!isInstanceAkkoma()) items.add(new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true));
return items;
}
public void onMarkAsRead(String id) {

View File

@@ -22,6 +22,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account;
@@ -29,8 +30,11 @@ import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship;
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.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
@@ -54,6 +58,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -607,7 +612,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if (header != null) header.rebind();
}
public void onGapClick(GapStatusDisplayItem.Holder item){}
public void updateEmojiReactions(Status status, String itemID){
EmojiReactionsStatusDisplayItem.Holder reactions=findHolderOfType(itemID, EmojiReactionsStatusDisplayItem.Holder.class);
if(reactions != null){
reactions.getItem().status.reactions.clear();
reactions.getItem().status.reactions.addAll(status.reactions);
reactions.rebind();
}
}
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
int startPos = warning.getAbsoluteAdapterPosition();
@@ -762,6 +776,61 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
}
public void togglePostTranslation(Status status, String itemID){
switch(status.translationState){
case LOADING -> {
return;
}
case SHOWN -> {
status.translationState=Status.TranslationState.HIDDEN;
}
case HIDDEN -> {
if(status.translation!=null){
status.translationState=Status.TranslationState.SHOWN;
}else{
status.translationState=Status.TranslationState.LOADING;
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
.setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()==null)
return;
status.translation=result;
status.translationState=Status.TranslationState.SHOWN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
status.translationState=Status.TranslationState.HIDDEN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
}
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.translation_failed)
.setPositiveButton(R.string.ok, null)
.show();
}
})
.exec(accountID);
}
}
}
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}
}
public void rebuildAllDisplayItems(){
displayItems.clear();
for(T item:data){
@@ -782,6 +851,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
}
public void scrollBy(int x, int y) {
list.scrollBy(x, y);
}
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
public DisplayItemsAdapter(){

View File

@@ -29,6 +29,7 @@ import android.text.TextWatcher;
import android.text.format.DateFormat;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -300,6 +301,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
onCustomEmojiClick(emoji);
}
@Override
public void onEmojiSelected(String emoji){
if(getActivity().getCurrentFocus() instanceof EditText edit && edit == mainEditText){
edit.getText().replace(edit.getSelectionStart(), edit.getSelectionEnd(), emoji);
}
}
@Override
public void onBackspace(){
getActivity().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
@@ -412,7 +420,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
hasSpoiler=true;
spoilerWrap.setVisibility(View.VISIBLE);
spoilerBtn.setSelected(true);
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
}else if(editingStatus!=null && editingStatus.hasSpoiler()){
hasSpoiler=true;
spoilerWrap.setVisibility(View.VISIBLE);
spoilerEdit.setText(getArguments().getString("sourceSpoiler", editingStatus.spoilerText));
@@ -449,7 +457,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} else if (getArguments().containsKey("sourceContentType")) {
try {
String val = getArguments().getString("sourceContentType");
contentType = val == null ? null : ContentType.valueOf(val);
if (val != null) contentType = ContentType.valueOf(val);
} catch (IllegalArgumentException ignored) {}
}
@@ -666,11 +674,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
? UiUtils.formatRelativeTimestamp(getContext(), status.createdAt)
: getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), status.editedAt));
String sepp = getString(R.string.sk_separator);
String username = status.account.getDisplayUsername();
((TextView) view.findViewById(R.id.time_and_username)).setText(time == null ? username :
username + " " + sepp + " " + time);
if (status.spoilerText != null && !status.spoilerText.isBlank()) {
((TextView) view.findViewById(R.id.username)).setText(status.account.getDisplayUsername());
view.findViewById(R.id.separator).setVisibility(time==null ? View.GONE : View.VISIBLE);
view.findViewById(R.id.time).setVisibility(time==null ? View.GONE : View.VISIBLE);
if(time!=null) ((TextView) view.findViewById(R.id.time)).setText(time);
if (status.hasSpoiler()) {
TextView replyToSpoiler = view.findViewById(R.id.reply_to_spoiler);
replyToSpoiler.setVisibility(View.VISIBLE);
replyToSpoiler.setText(status.spoilerText);
@@ -818,6 +827,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
publishButton = wrap.findViewById(R.id.publish_btn);
languageButton = wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
languageButton.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if(!getLocalPrefs().bottomEncoding){
getLocalPrefs().bottomEncoding=true;
getLocalPrefs().save();
}
return false;
});
publishButton.setOnClickListener(v -> {
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
@@ -1058,6 +1075,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.scheduledAt=scheduledAt;
if(!mediaViewController.isEmpty()){
req.mediaIds=mediaViewController.getAttachmentIDs();
if(editingStatus != null){
req.mediaAttributes=mediaViewController.getAttachmentAttributes();
}
}
// ask whether to publish now when editing an existing draft
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
@@ -1297,7 +1317,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
if(usePhotoPicker){
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
}else{
intent=new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
@@ -1435,8 +1456,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void updateHeaders() {
UiUtils.setExtraTextInfo(getContext(), selfExtraText, null, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, pronouns, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
UiUtils.setExtraTextInfo(getContext(), selfExtraText, null, false, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, pronouns, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
}
private void buildVisibilityPopup(View v){

View File

@@ -239,16 +239,21 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
if (tags == null || tags.isEmpty()) return false;
editText.setText(String.join(",", tags));
editText.chipifyAllUnterminatedTokens();
editText.setText(tags);
editText.chipifyAllUnterminatedTokens();
return true;
}
private NachoTextView prepareChipTextView(NachoTextView nacho) {
nacho.addChipTerminator(',', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator('\n', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator(' ', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator(';', BEHAVIOR_CHIPIFY_ALL);
//Ill Be Back
nacho.setChipTerminators(
Map.of(
',', BEHAVIOR_CHIPIFY_ALL,
'\n', BEHAVIOR_CHIPIFY_ALL,
' ', BEHAVIOR_CHIPIFY_ALL,
';', BEHAVIOR_CHIPIFY_ALL
)
);
nacho.enableEditChipOnTouch(true, true);
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
return nacho;
@@ -344,7 +349,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
mainHashtag = name;
name = null;
}
if (TextUtils.isEmpty(mainHashtag)) {
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) {
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(null);
return;

View File

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

View File

@@ -17,8 +17,10 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
@@ -201,7 +203,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
}
// literally the same as AccountCardStatusDisplayItem and DiscoverAccountsFragment. code should be generalized
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.DisableableClickable{
private final ImageView cover, avatar;
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
private final ProgressBarButton actionButton, acceptButton, rejectButton;
@@ -233,15 +235,24 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
rejectProgress=findViewById(R.id.reject_progress);
rejectWrap=findViewById(R.id.reject_btn_wrap);
itemView.setOutlineProvider(OutlineProviders.roundedRect(6));
itemView.setClipToOutline(true);
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
avatar.setOutlineProvider(OutlineProviders.roundedRect(15));
avatar.setClipToOutline(true);
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
View border=findViewById(R.id.avatar_border);
border.setOutlineProvider(OutlineProviders.roundedRect(17));
border.setClipToOutline(true);
cover.setOutlineProvider(OutlineProviders.roundedRect(9));
cover.setClipToOutline(true);
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
itemView.setClipToOutline(true);
actionButton.setOnClickListener(this::onActionButtonClick);
acceptButton.setOnClickListener(this::onFollowRequestButtonClick);
rejectButton.setOnClickListener(this::onFollowRequestButtonClick);
itemView.setOnClickListener(v->this.onClick());
}
@Override
public boolean isEnabled(){
return false;
}
@Override
@@ -254,26 +265,23 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.x_posts, (int)(item.account.statusesCount%1000), item.account.statusesCount));
postsLabel.setText(getResources().getQuantityString(R.plurals.sk_posts_count_label, (int)(item.account.statusesCount%1000), item.account.statusesCount));
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id);
if(relationship == null || !relationship.followedBy){
UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(relationship==null || !relationship.followedBy){
actionWrap.setVisibility(View.GONE);
acceptWrap.setVisibility(View.VISIBLE);
rejectWrap.setVisibility(View.VISIBLE);
// i hate that i wasn't able to do this in xml
acceptButton.setCompoundDrawableTintList(acceptButton.getTextColors());
acceptProgress.setIndeterminateTintList(acceptButton.getTextColors());
rejectButton.setCompoundDrawableTintList(rejectButton.getTextColors());
rejectProgress.setIndeterminateTintList(rejectButton.getTextColors());
}else if(relationship==null){
actionWrap.setVisibility(View.GONE);
acceptWrap.setVisibility(View.GONE);
rejectWrap.setVisibility(View.GONE);
}else{
actionWrap.setVisibility(View.VISIBLE);
acceptWrap.setVisibility(View.GONE);
@@ -351,8 +359,9 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
public AccountWrapper(Account account){
this.account=account;
if(!TextUtils.isEmpty(account.avatar))
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
avaRequest=new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(getAccountID()).getDefaultAvatarUrl() : account.avatar,
V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.header))
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);

View File

@@ -121,7 +121,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
@Override
public void onClick() {
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name);
}
}
}

View File

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

View File

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.app.Fragment;
import android.app.NotificationManager;
import android.app.assist.AssistContent;
import android.graphics.drawable.RippleDrawable;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
@@ -126,12 +127,31 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
tabBarWrap=content.findViewById(R.id.tabbar_wrap);
// this one's for the pill haters (https://m3.material.io/components/navigation-bar/overview)
if (GlobalUserPreferences.disableM3PillActiveIndicator) {
for(int i=0; i<tabBar.getChildCount(); i++){
ViewGroup f=(ViewGroup) tabBar.getChildAt(i);
f.setBackgroundResource(R.drawable.bg_tabbar_tab_ripple);
if(GlobalUserPreferences.disableM3PillActiveIndicator){
tabBar.findViewById(R.id.tab_home_pill).setBackground(null);
tabBar.findViewById(R.id.tab_search_pill).setBackground(null);
tabBar.findViewById(R.id.tab_notifications_pill).setBackground(null);
tabBar.findViewById(R.id.tab_profile_pill).setBackgroundResource(R.drawable.bg_tab_profile);
View[] tabs={
tabBar.findViewById(R.id.tab_home),
tabBar.findViewById(R.id.tab_search),
tabBar.findViewById(R.id.tab_notifications),
tabBar.findViewById(R.id.tab_profile)
};
for(View tab : tabs){
tab.setBackgroundResource(R.drawable.bg_tabbar_tab_ripple);
((RippleDrawable) tab.getBackground())
.setRadius(V.dp(GlobalUserPreferences.showNavigationLabels ? 56 : 42));
}
tabBar.findViewById(R.id.tab_profile).setBackgroundResource(R.drawable.bg_tab_profile);
}
if(!GlobalUserPreferences.showNavigationLabels){
tabBar.findViewById(R.id.tab_home_label).setVisibility(View.GONE);
tabBar.findViewById(R.id.tab_search_label).setVisibility(View.GONE);
tabBar.findViewById(R.id.tab_notifications_label).setVisibility(View.GONE);
tabBar.findViewById(R.id.tab_profile_label).setVisibility(View.GONE);
}
tabBarAvatar=tabBar.findViewById(R.id.tab_profile_ava);
@@ -164,6 +184,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
});
}
}
tabBar.selectTab(currentTab);
return content;
}

View File

@@ -62,6 +62,7 @@ import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -336,11 +337,13 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
hashtagsMenu.clear();
hashtagsMenu.getItem().setVisible(hashtagsItems.size() > 0);
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(hashtagsMenu));
hashtagsItems.forEach((id, hashtag) -> {
MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name);
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
UiUtils.insetPopupMenuIcon(ctx, item);
});
hashtagsItems.entrySet().stream()
.sorted(Comparator.comparing(x -> x.getValue().name, String.CASE_INSENSITIVE_ORDER))
.forEach(entry -> {
MenuItem item = hashtagsMenu.add(Menu.NONE, entry.getKey(), Menu.NONE, entry.getValue().name);
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
UiUtils.insetPopupMenuIcon(ctx, item);
});
}
public void updateToolbarLogo(){
@@ -521,9 +524,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) {
args.putString("hashtag", hashtag.name);
args.putBoolean("following", hashtag.following);
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
UiUtils.openHashtagTimeline(getContext(), accountID, hashtag);
}
return true;
}

View File

@@ -13,25 +13,24 @@ import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class HomeTimelineFragment extends StatusListFragment {
private HomeTabFragment parent;
@@ -176,15 +175,23 @@ public class HomeTimelineFragment extends StatusListFragment {
}
@Override
public void onGapClick(GapStatusDisplayItem.Holder item){
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){
if(dataLoading)
return;
item.getItem().loading=true;
V.setVisibilityAnimated(item.progress, View.VISIBLE);
V.setVisibilityAnimated(item.text, View.GONE);
GapStatusDisplayItem gap=item.getItem();
gap.loading=true;
dataLoading=true;
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
String maxID = null;
String minID = null;
if (downwards) {
maxID = item.getItemID();
} else {
int gapPos=displayItems.indexOf(gap);
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
minID=nextItem.parentID;
}
currentRequest=new GetHomeTimeline(maxID, minID, 20, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
@@ -204,52 +211,96 @@ public class HomeTimelineFragment extends StatusListFragment {
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
}
}else{
Set<String> idsBelowGap=new HashSet<>();
boolean belowGap=false;
int gapPostIndex=0;
for(Status s:data){
if(belowGap){
idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){
belowGap=true;
s.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{
gapPostIndex++;
if(downwards) {
Set<String> idsBelowGap=new HashSet<>();
boolean belowGap=false;
int gapPostIndex=0;
for(Status s:data){
if(belowGap){
idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){
belowGap=true;
s.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{
gapPostIndex++;
}
}
}
int endIndex=0;
for(Status s:result){
endIndex++;
if(idsBelowGap.contains(s.id))
break;
}
if(endIndex==result.size()){
result.get(result.size()-1).hasGapAfter=true;
}else{
result=result.subList(0, endIndex);
}
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
targetList.clear();
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
for(Status s:result){
if(idsBelowGap.contains(s.id))
break;
if(typeFilterPredicate(s) && filterPredicate.test(s)){
int endIndex=0;
for(Status s:result){
endIndex++;
if(idsBelowGap.contains(s.id))
break;
}
if(endIndex==result.size()){
result.get(result.size()-1).hasGapAfter=true;
}else{
result=result.subList(0, endIndex);
}
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
targetList.clear();
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
for(Status s:result){
if(idsBelowGap.contains(s.id))
break;
targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s);
}
if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
}else{
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
}
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
} else {
String aboveGapID = gap.parentID;
int gapPostIndex = 0;
for (;gapPostIndex<data.size();gapPostIndex++){
if (Objects.equals(aboveGapID, data.get(gapPostIndex).id)) {
break;
}
}
// find if there's an overlap between the new data and the current data
int indexOfGapInResponse = 0;
for (;indexOfGapInResponse<result.size();indexOfGapInResponse++){
if (Objects.equals(aboveGapID, result.get(indexOfGapInResponse).id)) {
break;
}
}
// there is an overlap between new and current data
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
if(indexOfGapInResponse<result.size()){
result=result.subList(indexOfGapInResponse+1,result.size());
Optional<Status> gapStatus=data.stream()
.filter(s->Objects.equals(s.id, gap.parentID))
.findFirst();
if (gapStatus.isPresent()) {
gapStatus.get().hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
}
targetList.clear();
} else {
gap.loading=false;
}
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
for(Status s:result){
targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s);
}
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, FilterContext.HOME);
if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
}else{
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
}
list.scrollToPosition(getMainAdapterOffset()+gapPos+targetList.size());
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
}
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
}else{
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
}
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
}
}

View File

@@ -12,9 +12,10 @@ import android.view.View;
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.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
@@ -22,14 +23,15 @@ import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.NotificationHeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.joinmastodon.android.utils.ObjectIdComparator;
import org.parceler.Parcels;
@@ -43,7 +45,6 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.views.FragmentRootLinearLayout;
public class NotificationsListFragment extends BaseStatusListFragment<Notification> {
private boolean onlyMentions;
@@ -95,11 +96,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
if (n.type == Notification.Type.FOLLOW_REQUEST) {
ArrayList<StatusDisplayItem> items = new ArrayList<>();
items.add(titleItem);
items.add(new AccountCardStatusDisplayItem(n.id, this, n.account, n));
items.add(new AccountCardStatusDisplayItem(n.id, this, accountID, n.account, n));
return items;
}
if(n.status!=null){
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET); // | StatusDisplayItem.FLAG_NO_HEADER);
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS); // | StatusDisplayItem.FLAG_NO_HEADER);
if (GlobalUserPreferences.spectatorMode)
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, null, flags);
if(titleItem!=null)
items.add(0, titleItem);
@@ -244,8 +247,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Subscribe
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
for(Notification n:data){
if (n.status == null) continue;
if(n.status.getContentStatus().id.equals(ev.id)){
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
n.status.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
for(int i=0;i<list.getChildCount();i++){
@@ -259,8 +261,31 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
}
}
for(Notification n:preloadedData){
if (n.status == null) continue;
if(n.status.getContentStatus().id.equals(ev.id)){
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
n.status.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
}
}
}
@Subscribe
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
for(Notification n : data){
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
n.status.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
for(int i=0; i<list.getChildCount(); i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
reactions.rebind();
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
text.rebind();
}
}
}
}
for(Notification n : preloadedData){
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
n.status.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
}

View File

@@ -1,15 +1,15 @@
package org.joinmastodon.android.fragments;
import static org.joinmastodon.android.fragments.ProfileAboutFragment.MAX_FIELDS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Color;
@@ -23,7 +23,6 @@ import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.transition.ChangeBounds;
import android.transition.Fade;
import android.transition.TransitionManager;
@@ -60,8 +59,10 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
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.FollowerListFragment;
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
import org.joinmastodon.android.fragments.account_list.MutedAccountsListFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
import org.joinmastodon.android.model.Account;
@@ -133,6 +134,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private CoverImageView cover;
private View avatarBorder;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
private ImageView lockIcon, botIcon;
private ProgressBarButton actionButton, notifyButton;
private ViewPager2 pager;
private NestedRecyclerScrollView scrollView;
@@ -175,6 +177,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private MenuItem editSaveMenuItem;
private boolean savingEdits;
private int maxFields = ProfileAboutFragment.MAX_FIELDS;
// from ProfileAboutFragment
public UsableRecyclerView list;
private AboutAdapter adapter;
@@ -200,6 +204,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
loaded=true;
if(!isOwnProfile)
loadRelationship();
else if (isInstanceAkkoma()) {
maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
}
}else{
profileAccountID=getArguments().getString("profileAccountID");
if(!getArguments().getBoolean("noAutoLoad", false))
@@ -227,6 +234,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
avatarBorder=content.findViewById(R.id.avatar_border);
name=content.findViewById(R.id.name);
username=content.findViewById(R.id.username);
lockIcon=content.findViewById(R.id.lock_icon);
botIcon=content.findViewById(R.id.bot_icon);
bio=content.findViewById(R.id.bio);
followersCount=content.findViewById(R.id.followers_count);
followersLabel=content.findViewById(R.id.followers_label);
@@ -255,6 +264,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
list=content.findViewById(R.id.metadata);
rolesView=content.findViewById(R.id.roles);
avatarBorder.setOutlineProvider(OutlineProviders.roundedRect(26));
avatarBorder.setClipToOutline(true);
avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
avatar.setClipToOutline(true);
@@ -294,6 +305,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
tabbar.setTabTextSize(V.dp(14));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){
case 0 -> R.string.profile_featured;
case 1 -> R.string.profile_timeline;
case 2 -> R.string.profile_about;
default -> throw new IllegalStateException();
}));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
@@ -307,6 +324,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if (position == 4) tab.view.setVisibility(View.GONE);
}
});
tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@Override
public void onTabReselected(TabLayout.Tab tab){
if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt)
stt.scrollToTop();
}
});
cover.setOutlineProvider(new ViewOutlineProvider(){
@Override
@@ -342,7 +372,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
username.setOnClickListener(v->{
content.findViewById(R.id.username_wrap).setOnClickListener(v->{
try {
new GetInstance()
.setCallback(new Callback<>(){
@@ -363,11 +393,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.execRemote(Uri.parse(account.url).getHost());
} catch (NullPointerException ignored) {
// maybe the url was malformed?
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT);
Toast.makeText(getContext(), R.string.error, Toast.LENGTH_SHORT).show();
}
});
username.setOnLongClickListener(v->{
content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{
String usernameString=account.acct;
if(!usernameString.contains("@")){
usernameString+="@"+domain;
@@ -587,10 +617,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
}
@SuppressLint("SetTextI18n")
private void bindHeaderView(){
setTitle(account.displayName);
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() :
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
V.dp(100), V.dp(100)));
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
@@ -619,19 +653,15 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
String acct = ((isSelf || account.isRemote)
? account.getFullyQualifiedName()
: account.acct);
if(account.locked){
ssb=new SpannableStringBuilder("@");
ssb.append(acct);
ssb.append(" ");
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
lock.setTint(username.getCurrentTextColor());
ssb.append(getString(R.string.manually_approves_followers), new ImageSpan(lock, ImageSpan.ALIGN_BASELINE), 0);
username.setText(ssb);
}else{
// noinspection SetTextI18n
username.setText('@'+acct);
}
username.setText('@'+acct);
lockIcon.setVisibility(account.locked ? View.VISIBLE : View.GONE);
lockIcon.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor()));
botIcon.setVisibility(account.bot ? View.VISIBLE : View.GONE);
botIcon.setImageTintList(ColorStateList.valueOf(username.getCurrentTextColor()));
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(TextUtils.isEmpty(parsedBio)){
bio.setVisibility(View.GONE);
@@ -817,6 +847,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("profileDisplayUsername", account.getDisplayUsername());
}
Nav.go(getActivity(), ListsFragment.class, args);
}else if(id==R.id.muted_accounts){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), MutedAccountsListFragment.class, args);
}else if(id==R.id.blocked_accounts){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), BlockedAccountsListFragment.class, args);
}else if(id==R.id.followed_hashtags){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -1054,7 +1094,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
actionButton.setText(R.string.save_changes);
pager.setVisibility(View.GONE);
tabbar.setVisibility(View.GONE);
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate();
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay, getActivity().getTheme()).mutate();
avatar.setForeground(overlay);
updateMetadataHeight();
@@ -1073,6 +1113,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
);
name.setVisibility(View.GONE);
rolesView.setVisibility(View.GONE);
username.setVisibility(View.GONE);
bio.setVisibility(View.GONE);
countersLayout.setVisibility(View.GONE);
@@ -1121,6 +1162,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
nameEditWrap.setVisibility(View.GONE);
bioEditWrap.setVisibility(View.GONE);
name.setVisibility(View.VISIBLE);
rolesView.setVisibility(View.VISIBLE);
username.setVisibility(View.VISIBLE);
bio.setVisibility(View.VISIBLE);
countersLayout.setVisibility(View.VISIBLE);
@@ -1220,7 +1262,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(ava==null)
return;
int radius=V.dp(25);
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : account.avatar, ava), 0,
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
}
}
@@ -1292,9 +1334,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
FrameLayout view=new FrameLayout(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@@ -1302,8 +1342,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
Fragment fragment=getFragmentForPage(position);
FrameLayout fragmentView=tabViews[position];
fragmentView.setVisibility(View.VISIBLE);
if(fragmentView.getParent() instanceof ViewGroup parent)
parent.removeView(fragmentView);
((FrameLayout)holder.itemView).addView(fragmentView);
if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
@@ -1385,7 +1430,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
public int getItemCount(){
if(isInEditMode){
int size=fields.size();
if(size<MAX_FIELDS)
if(size<maxFields)
size++;
return size;
}
@@ -1421,16 +1466,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
}
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder {
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder{
private final TextView title;
private final LinkedTextView value;
// private final ImageView verifiedIcon;
public AboutViewHolder(){
super(R.layout.item_profile_about);
title=findViewById(R.id.title);
value=findViewById(R.id.value);
// verifiedIcon=findViewById(R.id.verified_icon);
}
@Override
@@ -1438,7 +1481,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
super.onBind(item);
title.setText(item.parsedName);
value.setText(item.parsedValue);
// verifiedIcon.setVisibility(item.verifiedAt!=null ? View.VISIBLE : View.GONE);
if(item.verifiedAt!=null){
int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63;
value.setTextColor(textColor);
value.setLinkTextColor(textColor);
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate();
check.setTint(textColor);
value.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, check, null);
}else{
value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent));
value.setCompoundDrawables(null, null, null, null);
}
}
@Override
@@ -1510,7 +1564,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public void onClick(){
fields.add(new AccountField());
if(fields.size()==MAX_FIELDS){ // replace this row with new row
if(fields.size()==maxFields){ // replace this row with new row
adapter.notifyItemChanged(fields.size()-1);
}else{
adapter.notifyItemInserted(fields.size()-1);

View File

@@ -2,8 +2,11 @@ package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.ImageButton;
import com.squareup.otto.Subscribe;
@@ -26,6 +29,7 @@ import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
private String nextMaxID;
@@ -80,7 +84,10 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
@Override
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, true, null);
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, null,
StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS |
StatusDisplayItem.FLAG_NO_FOOTER |
StatusDisplayItem.FLAG_NO_TRANSLATE);
}
@Override
@@ -183,6 +190,21 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
return null;
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(contentView!=null){
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
int insetBottom=insets.getSystemWindowInsetBottom();
((ViewGroup.MarginLayoutParams) list.getLayoutParams()).bottomMargin=insetBottom;
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+insetBottom;
insets=insets.inset(0, 0, 0, insetBottom);
}else{
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16);
}
}
super.onApplyWindowInsets(insets);
}
@Override
public Uri getWebUri(Uri.Builder base) {
// TODO: adapt when frontends finally implement a scheduled posts list

View File

@@ -9,6 +9,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
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.utils.InsetStatusItemDecoration;
@@ -57,7 +58,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
@Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET);
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, null, StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS);
int idx=data.indexOf(s);
if(idx>=0){
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
@@ -143,7 +144,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
}
}
String sep = getString(R.string.sk_separator);
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s));
items.add(1, new DummyStatusDisplayItem(s.id, this));
}
return items;
}

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android.fragments;
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ShowEmojiReactions.ONLY_OPENED;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -7,7 +9,9 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
@@ -16,9 +20,11 @@ import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.parceler.Parcels;
import java.util.ArrayList;
@@ -33,9 +39,14 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
protected EventListener eventListener=new EventListener();
protected List<StatusDisplayItem> buildDisplayItems(Status s){
boolean addFooter = !GlobalUserPreferences.spectatorMode ||
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), addFooter ? 0 : StatusDisplayItem.FLAG_NO_FOOTER);
boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id);
int flags = 0;
AccountLocalPreferences lp=getLocalPrefs();
if (GlobalUserPreferences.spectatorMode)
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
if (!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS;
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags);
}
protected abstract FilterContext getFilterContext();
@@ -230,6 +241,30 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
}
}
@Subscribe
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
for(Status s:data){
if(s.getContentStatus().id.equals(ev.id)){
s.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
reactions.rebind();
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
text.rebind();
}
}
}
}
for(Status s:preloadedData){
if(s.getContentStatus().id.equals(ev.id)){
s.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
}
}
}
@Subscribe
public void onStatusDeleted(StatusDeletedEvent ev){
if(!ev.accountID.equals(accountID))

View File

@@ -24,6 +24,7 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
@@ -105,6 +106,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
text.textSelectable=true;
else if(item instanceof FooterStatusDisplayItem footer)
footer.hideCounts=true;
else if(item instanceof SpoilerStatusDisplayItem spoiler){
for(StatusDisplayItem subItem:spoiler.contentItems){
if(subItem instanceof TextStatusDisplayItem text)
text.textSelectable=true;
}
}
}
}
@@ -417,6 +424,10 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
adapter.notifyDataSetChanged();
}
public Status getMainStatus(){
return mainStatus;
}
@Override
public boolean isItemEnabled(String id){
return !id.equals(mainStatus.id) || !mainStatus.filterRevealed;

View File

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

View File

@@ -48,7 +48,7 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
@Override
protected void doLoadData(int offset, int count){
refreshing=true;
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(SearchResults result){

View File

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

View File

@@ -14,6 +14,7 @@ import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
@@ -136,6 +137,10 @@ public abstract class PaginatedAccountListFragment<T> extends BaseAccountListFra
List<AccountViewModel> items = result.stream()
.filter(a -> d.size() > 1000 || d.stream()
.noneMatch(i -> i.account.url.equals(a.url)))
.peek(account ->{
if (account.getDomainFromURL().equals(getRemoteDomain()))
account.acct=account.getFullyQualifiedName();
})
.map(a->new AccountViewModel(a, accountID))
.collect(Collectors.toList());

View File

@@ -0,0 +1,97 @@
package org.joinmastodon.android.fragments.account_list;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.PleromaGetStatusReactions;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiReaction;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
public class StatusEmojiReactionsListFragment extends BaseAccountListFragment {
private String id;
private String emojiName;
private String url;
private int count;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
id = getArguments().getString("statusID");
emojiName = getArguments().getString("emoji");
url = getArguments().getString("url");
count = getArguments().getInt("count");
SpannableStringBuilder title = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.sk_users_reacted_with, count,
count, url == null ? emojiName : ":"+emojiName+":"));
if (url != null) {
Emoji emoji = new Emoji();
emoji.shortcode = emojiName;
emoji.url = url;
HtmlParser.parseCustomEmoji(title, Collections.singletonList(emoji));
}
setTitle(title);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (url != null) {
UiUtils.loadCustomEmojiInTextView(toolbarTitleView);
}
}
@Override
public void dataLoaded() {
super.dataLoaded();
footerProgress.setVisibility(View.GONE);
}
@Override
protected void doLoadData(int offset, int count){
currentRequest = new PleromaGetStatusReactions(id, emojiName)
.setCallback(new SimpleCallback<>(StatusEmojiReactionsListFragment.this){
@Override
public void onSuccess(List<EmojiReaction> result) {
if (getActivity() == null)
return;
List<AccountViewModel> items = result.get(0).accounts.stream()
.map(a -> new AccountViewModel(a, accountID))
.collect(Collectors.toList());
onDataLoaded(items);
}
@Override
public void onError(ErrorResponse error) {
super.onError(error);
}
})
.exec(accountID);
}
@Override
public void onResume(){
super.onResume();
if(!loaded && !dataLoading)
loadData();
}
@Override
public Uri getWebUri(Uri.Builder base) {
return null;
}
}

View File

@@ -16,6 +16,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
@@ -197,7 +198,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
}
}
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
private class AccountViewHolder extends BindableViewHolder<AccountWrapper> implements ImageLoaderViewHolder, UsableRecyclerView.DisableableClickable{
private final ImageView cover, avatar;
private final TextView name, username, bio, followersCount, followingCount, postsCount, followersLabel, followingLabel, postsLabel;
private final ProgressBarButton actionButton;
@@ -223,13 +224,22 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
actionProgress=findViewById(R.id.action_progress);
actionWrap=findViewById(R.id.action_btn_wrap);
itemView.setOutlineProvider(OutlineProviders.roundedRect(6));
itemView.setClipToOutline(true);
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
avatar.setOutlineProvider(OutlineProviders.roundedRect(15));
avatar.setClipToOutline(true);
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
View border=findViewById(R.id.avatar_border);
border.setOutlineProvider(OutlineProviders.roundedRect(17));
border.setClipToOutline(true);
cover.setOutlineProvider(OutlineProviders.roundedRect(9));
cover.setClipToOutline(true);
itemView.setOutlineProvider(OutlineProviders.roundedRect(12));
itemView.setClipToOutline(true);
actionButton.setOnClickListener(this::onActionButtonClick);
itemView.setOnClickListener(v->this.onClick());
}
@Override
public boolean isEnabled(){
return false;
}
@Override
@@ -242,12 +252,14 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.x_posts, (int)(item.account.statusesCount%1000), item.account.statusesCount));
postsLabel.setText(getResources().getQuantityString(R.plurals.sk_posts_count_label, (int)(item.account.statusesCount%1000), item.account.statusesCount));
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id);
UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(relationship==null){
actionWrap.setVisibility(View.GONE);
}else{
@@ -308,8 +320,9 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public AccountWrapper(Account account){
this.account=account;
if(!TextUtils.isEmpty(account.avatar))
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
avaRequest=new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.getInstance().getAccount(accountID).getDefaultAvatarUrl() : account.avatar,
V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.header))
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);

View File

@@ -54,6 +54,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private String accountID;
private String currentQuery;
private boolean disableDiscover;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -155,6 +157,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
});
disableDiscover=getArguments().getBoolean("disableDiscover");
searchView=view.findViewById(R.id.search_fragment);
if(searchFragment==null){
searchFragment=new SearchFragment();
@@ -170,8 +173,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
searchBack.setOnClickListener(v->{
if(searchActive) exitSearch(); else openSearch();
});
if(searchActive){
searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
if(searchActive) searchBack.setImageResource(R.drawable.ic_fluent_arrow_left_24_regular);
else searchBack.setEnabled(false);
if(searchActive || disableDiscover){
pager.setVisibility(View.GONE);
tabLayout.setVisibility(View.GONE);
searchView.setVisibility(View.VISIBLE);
@@ -211,8 +215,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
public void loadData(){
if(postsFragment!=null && !postsFragment.loaded && !postsFragment.dataLoading)
postsFragment.loadData();
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
hashtagsFragment.loadData();
}
private void enterSearch(){
@@ -232,15 +236,18 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
if(!searchActive)
return;
searchActive=false;
pager.setVisibility(View.VISIBLE);
tabLayout.setVisibility(View.VISIBLE);
searchView.setVisibility(View.GONE);
searchText.setText(R.string.sk_search_fediverse);
searchBack.setImageResource(R.drawable.ic_fluent_search_24_regular);
searchBack.setEnabled(false);
searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
tabsDivider.setVisibility(View.VISIBLE);
currentQuery=null;
searchFragment.clear();
if(disableDiscover) return;
pager.setVisibility(View.VISIBLE);
tabLayout.setVisibility(View.VISIBLE);
searchView.setVisibility(View.GONE);
tabsDivider.setVisibility(View.VISIBLE);
}
private Fragment getFragmentForPage(int page){
@@ -282,15 +289,19 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
FrameLayout view=new FrameLayout(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
@Override
public int getItemCount(){

View File

@@ -7,6 +7,7 @@ import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -22,7 +23,6 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
@@ -30,7 +30,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
@@ -54,7 +54,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
super.onCreate(savedInstanceState);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
setEmptyText(R.string.no_search_results);
setEmptyText(R.string.sk_recent_searches_placeholder);
loadData();
}
@@ -96,7 +96,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
args.putParcelable("profileAccount", Parcels.wrap(res.account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
case STATUS -> {
Status status=res.status.getContentStatus();
Bundle args=new Bundle();
@@ -112,7 +112,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}
@Override
protected void doLoadData(int offset, int count){
protected void doLoadData(int _offset, int count){
GetSearchResults.Type type;
if(currentFilter.size()==1){
type=switch(currentFilter.iterator().next()){
@@ -127,7 +127,21 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
dataLoaded();
return;
}
currentRequest=new GetSearchResults(currentQuery, type, true)
String maxID=null;
// TODO server-side bug
/*int offset=0;
if(_offset>0){
if(type==GetSearchResults.Type.STATUSES){
if(!preloadedData.isEmpty())
maxID=preloadedData.get(preloadedData.size()-1).status.id;
else if(!data.isEmpty())
maxID=data.get(data.size()-1).status.id;
}else{
offset=_offset;
}
}*/
int offset=_offset;
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
@@ -141,12 +155,15 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
results.add(new SearchResult(tag));
}
if(result.statuses!=null){
for(Status status:result.statuses)
results.add(new SearchResult(status));
Set<String> alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet());
for(Status status:result.statuses){
if(!alreadyLoadedStatuses.contains(status.id))
results.add(new SearchResult(status));
}
}
prevDisplayItems=new ArrayList<>(displayItems);
unfilteredResults=results;
onDataLoaded(filterSearchResults(results), false);
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
}
@Override
@@ -173,13 +190,16 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}
public void setQuery(String q, SearchResult.Type filter){
if(q.isBlank())
if(q.isBlank()) {
setEmptyText(R.string.sk_recent_searches_placeholder);
return;
}
if(currentRequest!=null){
currentRequest.cancel();
currentRequest=null;
}
currentQuery=q;
setEmptyText(R.string.no_search_results);
if(filter==null)
currentFilter=EnumSet.allOf(SearchResult.Type.class);
else
@@ -221,6 +241,13 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}
}
public void clear() {
data.clear();
preloadedData.clear();
adapter.notifyDataSetChanged();
V.setVisibilityAnimated(content, View.GONE);
}
@Override
public Uri getWebUri(Uri.Builder base) {
Uri.Builder searchUri = base.path("/search");

View File

@@ -10,7 +10,6 @@ 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,7 +25,7 @@ import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -38,19 +37,16 @@ import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.SearchViewHelper;
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -126,7 +122,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
recentsHeader.setVisible(!data.isEmpty());
});
}else{
currentRequest=new GetSearchResults(currentQuery, null, false)
currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0)
.limit(2)
.setCallback(new SimpleCallback<>(this){
@Override
@@ -381,16 +377,56 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}
private void openHashtag(SearchResult res){
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
wrapSuicideDialog(()->{
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
});
}
private boolean isInRecentMode(){
return TextUtils.isEmpty(currentQuery);
}
private void wrapSuicideDialog(Runnable r){
if(!GlobalUserPreferences.showSuicideHelp || currentQuery==null){
r.run();
return;
}
String[] terms=getContext().getString(R.string.sk_suicide_search_terms).toLowerCase().split(",");
String query=currentQuery.trim().toLowerCase();
boolean termMatches=false;
for(String term : terms){
if(query.contains(term)){
termMatches=true;
break;
}
}
if(!termMatches){
r.run();
return;
}
String url=getContext().getString(R.string.sk_suicide_helplines_url);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_search_suicide_title)
.setMessage(R.string.sk_search_suicide_message)
.setNegativeButton(R.string.sk_do_not_show_again, (dialog, which)->{
GlobalUserPreferences.showSuicideHelp = false;
GlobalUserPreferences.save();
r.run();
})
.setNeutralButton(R.string.sk_search_suicide_hotlines, (dialog, which)->UiUtils.launchWebBrowser(getContext(), url))
.setPositiveButton(R.string.ok, (dialog, which)->r.run())
.setOnDismissListener((dialog)->{})
.show();
}
private void onSearchViewEnter(){
deliverResult(currentQuery, null);
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty())
return;
wrapSuicideDialog(()->deliverResult(currentQuery, null));
}
private void onOpenURLClick(){
@@ -398,10 +434,12 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}
private void onGoToHashtagClick(){
String q=searchViewHelper.getQuery();
if(q.startsWith("#"))
q=q.substring(1);
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
wrapSuicideDialog(()->{
String q=searchViewHelper.getQuery();
if(q.startsWith("#"))
q=q.substring(1);
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
});
}
private void onGoToAccountClick(){
@@ -422,11 +460,11 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}
private void onGoToStatusSearchClick(){
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS));
}
private void onGoToAccountSearchClick(){
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT));
}
private void onClearRecentClick(){

View File

@@ -105,7 +105,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
@Override
public void onClick(){
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
UiUtils.openHashtagTimeline(getActivity(), accountID, item);
}
}
}

View File

@@ -165,9 +165,7 @@ public class AccountActivationFragment extends ToolbarFragment{
private void tryGetAccount(){
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
uiHandler.removeCallbacks(pollRunnable);
getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class);
startActivity(intent);
((MainActivity)getActivity()).restartHomeFragment();
return;
}
currentRequest=new GetOwnAccount()

View File

@@ -139,7 +139,9 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
headerView.findViewById(R.id.more).setVisibility(View.GONE);
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
((TextView) headerView.findViewById(R.id.time_and_username)).setText(R.string.sk_app_username);
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
headerView.findViewById(R.id.time).setVisibility(View.GONE);
((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);

View File

@@ -271,7 +271,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), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, 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

@@ -102,7 +102,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
selectedIDs.remove(id);
else
selectedIDs.add(id);
btn.setEnabled(!selectedIDs.isEmpty());
CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class);
if(holder!=null)
holder.rebind();
@@ -112,7 +111,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
btn=view.findViewById(R.id.btn_next);
btn.setEnabled(!selectedIDs.isEmpty());
btn.setOnClickListener(this::onButtonClick);
buttonBar=view.findViewById(R.id.button_bar);

View File

@@ -83,7 +83,7 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
reportStatus=Parcels.unwrap(getArguments().getParcelable("status"));
if(reportStatus!=null){
Status hiddenStatus=reportStatus.clone();
hiddenStatus.spoilerText=getString(R.string.post_hidden);
if(hiddenStatus.spoilerText==null) hiddenStatus.spoilerText=getString(R.string.post_hidden);
onDataLoaded(Collections.singletonList(hiddenStatus));
setTitle(R.string.report_title_post);
}else{
@@ -168,17 +168,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
((UsableRecyclerView)list).setIncludeMarginsInItemHitbox(false);
if(reportStatus!=null){
list.addItemDecoration(new RecyclerView.ItemDecoration(){
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
outRect.left=V.dp(16);
outRect.right=V.dp(16);
}
}
});
list.addItemDecoration(new RecyclerView.ItemDecoration(){
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
{
@@ -222,10 +211,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
if(holder instanceof StatusDisplayItem.Holder<?>){
outRect.left=outRect.right=V.dp(16);
}
int index=holder.getAbsoluteAdapterPosition()-mergeAdapter.getPositionForAdapter(adapter);
if(index==displayItems.size()){
outRect.top=V.dp(32);
}
}
});
}
@@ -251,18 +236,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
return null;
}
@Override
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){
if((Object)holder instanceof MediaGridStatusDisplayItem.Holder h){
View layout=h.getLayout();
layout.setOutlineProvider(OutlineProviders.roundedRect(8));
layout.setClipToOutline(true);
View overlay=h.getSensitiveOverlay();
overlay.setOutlineProvider(OutlineProviders.roundedRect(8));
overlay.setClipToOutline(true);
}
}
@Override
public void putRelationship(String id, Relationship rel){
super.putRelationship(id, rel);

View File

@@ -16,6 +16,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.List;
import java.util.Objects;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.imageloader.ImageCache;
@@ -32,7 +33,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
AccountSession s=AccountSessionManager.get(accountID);
onDataLoaded(List.of(
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.donate_url))),
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))),
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),

View File

@@ -20,6 +20,7 @@ import org.joinmastodon.android.utils.MastodonLanguage;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
private ListItem<Void> languageItem;
@@ -30,7 +31,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
// MEGALODON
private MastodonLanguage.LanguageResolver languageResolver;
private ListItem<Void> prefixRepliesItem, replyVisibilityItem;
private CheckableListItem<Void> forwardReportsItem, remoteLoadingItem, showBoostsItem, showRepliesItem, loadNewPostsItem, seeNewPostsBtnItem;
private CheckableListItem<Void> forwardReportsItem, remoteLoadingItem, showBoostsItem, showRepliesItem, loadNewPostsItem, seeNewPostsBtnItem, overlayMediaItem;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -47,6 +48,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
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),
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, ()->toggleCheckableItem(altTextItem)),
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, ()->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, ()->toggleCheckableItem(overlayMediaItem)),
customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_fluent_link_24_regular, ()->toggleCheckableItem(customTabsItem)),
confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_fluent_person_delete_24_regular, ()->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, ()->toggleCheckableItem(confirmBoostItem)),
@@ -162,6 +164,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
protected void onHidden(){
super.onHidden();
GlobalUserPreferences.playGifs=playGifsItem.checked;
GlobalUserPreferences.overlayMedia=overlayMediaItem.checked;
GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
GlobalUserPreferences.altTextReminders=altTextItem.checked;
GlobalUserPreferences.confirmUnfollow=customTabsItem.checked;

View File

@@ -37,8 +37,9 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> revealCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem;
// MEGALODON
private CheckableListItem<Void> trueBlackModeItem, marqueeItem, disableSwipeItem, reduceMotionItem, altIndicatorItem, noAltIndicatorItem, collapsePostsItem, spectatorModeItem, hideFabItem, translateOpenedItem, disablePillItem;
private CheckableListItem<Void> trueBlackModeItem, marqueeItem, disableSwipeItem, reduceMotionItem, altIndicatorItem, noAltIndicatorItem, collapsePostsItem, spectatorModeItem, hideFabItem, translateOpenedItem, disablePillItem, showNavigationLabelsItem;
private ListItem<Void> colorItem, publishTextItem, autoRevealCWsItem;
private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem;
private AccountLocalPreferences lp;
@@ -67,7 +68,11 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
spectatorModeItem=new CheckableListItem<>(R.string.sk_settings_hide_interaction, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.spectatorMode, R.drawable.ic_fluent_star_off_24_regular, ()->toggleCheckableItem(spectatorModeItem)),
hideFabItem=new CheckableListItem<>(R.string.sk_settings_hide_fab, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.autoHideFab, R.drawable.ic_fluent_edit_24_regular, ()->toggleCheckableItem(hideFabItem)),
translateOpenedItem=new CheckableListItem<>(R.string.sk_settings_translate_only_opened, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.translateButtonOpenedOnly, R.drawable.ic_fluent_translate_24_regular, ()->toggleCheckableItem(translateOpenedItem)),
disablePillItem=new CheckableListItem<>(R.string.sk_disable_pill_shaped_active_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableM3PillActiveIndicator, R.drawable.ic_fluent_pill_24_regular, ()->toggleCheckableItem(disablePillItem))
disablePillItem=new CheckableListItem<>(R.string.sk_disable_pill_shaped_active_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableM3PillActiveIndicator, R.drawable.ic_fluent_pill_24_regular, ()->toggleCheckableItem(disablePillItem)),
showNavigationLabelsItem=new CheckableListItem<>(R.string.sk_settings_show_labels_in_navigation_bar, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNavigationLabels, R.drawable.ic_fluent_tag_24_regular, ()->toggleCheckableItem(showNavigationLabelsItem), true),
pronounsInTimelinesItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_timelines, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInTimelines, 0, ()->toggleCheckableItem(pronounsInTimelinesItem)),
pronounsInThreadsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_threads, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInThreads, 0, ()->toggleCheckableItem(pronounsInThreadsItem)),
pronounsInUserListingsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_user_listings, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInUserListings, 0, ()->toggleCheckableItem(pronounsInUserListingsItem))
));
trueBlackModeItem.checkedChangeListener=checked->onTrueBlackModeClick();
}
@@ -90,7 +95,8 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
super.onHidden();
boolean restartPlease=
GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked;
GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked ||
GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked;
lp.revealCWs=revealCWsItem.checked;
lp.hideSensitiveMedia=hideSensitiveMediaItem.checked;
@@ -107,6 +113,10 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
GlobalUserPreferences.autoHideFab=hideFabItem.checked;
GlobalUserPreferences.translateButtonOpenedOnly=translateOpenedItem.checked;
GlobalUserPreferences.disableM3PillActiveIndicator=disablePillItem.checked;
GlobalUserPreferences.showNavigationLabels=showNavigationLabelsItem.checked;
GlobalUserPreferences.displayPronounsInTimelines=pronounsInTimelinesItem.checked;
GlobalUserPreferences.displayPronounsInThreads=pronounsInThreadsItem.checked;
GlobalUserPreferences.displayPronounsInUserListings=pronounsInUserListingsItem.checked;
GlobalUserPreferences.save();
if(restartPlease) restartActivityToApplyNewTheme();
else E.post(new StatusDisplaySettingsChangedEvent(accountID));

View File

@@ -2,10 +2,14 @@ package org.joinmastodon.android.fragments.settings;
import android.os.Bundle;
import androidx.annotation.StringRes;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
import org.joinmastodon.android.fragments.HasAccountID;
import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
@@ -15,12 +19,13 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import me.grishka.appkit.Nav;
public class SettingsInstanceFragment extends BaseSettingsFragment<Void> implements HasAccountID{
private CheckableListItem<Void> contentTypesItem, localOnlyItem, glitchModeItem;
private ListItem<Void> defaultContentTypeItem;
private CheckableListItem<Void> contentTypesItem, emojiReactionsItem, localOnlyItem, glitchModeItem;
private ListItem<Void> defaultContentTypeItem, showEmojiReactionsItem;
private AccountLocalPreferences lp;
@Override
@@ -35,12 +40,16 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")),
new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true),
contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, this::onContentTypeClick),
defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick),
defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true),
emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, this::onEmojiReactionsClick),
showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true),
localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, this::onLocalOnlyClick),
glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, ()->toggleCheckableItem(glitchModeItem))
));
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
emojiReactionsItem.checkedChangeListener=checked->onEmojiReactionsClick();
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
localOnlyItem.checkedChangeListener=checked->onLocalOnlyClick();
glitchModeItem.isEnabled=localOnlyItem.checked;
}
@@ -52,9 +61,11 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
protected void onHidden(){
super.onHidden();
lp.contentTypesEnabled=contentTypesItem.checked;
lp.emojiReactionsEnabled=emojiReactionsItem.checked;
lp.localOnlySupported=localOnlyItem.checked;
lp.glitchInstance=glitchModeItem.checked;
lp.save();
E.post(new StatusDisplaySettingsChangedEvent(accountID));
}
private void onServerClick(){
@@ -101,6 +112,36 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
.show();
}
private void onShowEmojiReactionsClick(){
int selected=lp.showEmojiReactions.ordinal();
int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_show_emoji_reactions)
.setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_show_emoji_reactions_hide_empty, R.string.sk_settings_show_emoji_reactions_only_opened, R.string.sk_settings_show_emoji_reactions_always).mapToObj(this::getString).toArray(String[]::new),
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{
lp.showEmojiReactions=AccountLocalPreferences.ShowEmojiReactions.values()[newSelected[0]];
showEmojiReactionsItem.subtitleRes=getShowEmojiReactionsString();
rebindItem(showEmojiReactionsItem);
})
.setNegativeButton(R.string.cancel, null)
.show();
}
private @StringRes int getShowEmojiReactionsString(){
return switch(lp.showEmojiReactions){
case HIDE_EMPTY -> R.string.sk_settings_show_emoji_reactions_hide_empty;
case ONLY_OPENED -> R.string.sk_settings_show_emoji_reactions_only_opened;
case ALWAYS -> R.string.sk_settings_show_emoji_reactions_always;
};
}
private void onEmojiReactionsClick(){
toggleCheckableItem(emojiReactionsItem);
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
rebindItem(showEmojiReactionsItem);
}
private void onLocalOnlyClick(){
toggleCheckableItem(localOnlyItem);
glitchModeItem.checked=localOnlyItem.checked && !isInstanceAkkoma();

View File

@@ -16,6 +16,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
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.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
@@ -29,6 +30,7 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
public class SettingsMainFragment extends BaseSettingsFragment<Void>{
private AccountSession account;
private boolean loggedOut;
private HideableSingleViewRecyclerAdapter bannerAdapter;
private Button updateButton1, updateButton2;
@@ -47,23 +49,31 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
account = AccountSessionManager.get(accountID);
setTitle(R.string.settings);
setSubtitle(AccountSessionManager.get(accountID).getFullUsername());
setSubtitle(account.getFullUsername());
onDataLoaded(List.of(
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick),
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick),
new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick),
new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_privacy_tip_24px, this::onPrivacyClick),
new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_filter_alt_24px, this::onFiltersClick),
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick),
new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick),
new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true),
new ListItem<>(R.string.log_out, 0, R.drawable.ic_fluent_sign_out_24_regular, this::onLogOutClick, R.attr.colorM3Error, false)
));
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if (!instance.isAkkoma())
data.add(2, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
}
AccountSessionManager.get(accountID).reloadPreferences(null);
AccountSession session=AccountSessionManager.get(accountID);
session.reloadPreferences(null);
session.updateAccountInfo();
E.register(this);
}
@@ -80,7 +90,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
protected void onHidden(){
super.onHidden();
if(!loggedOut)
AccountSessionManager.get(accountID).savePreferencesIfPending();
account.savePreferencesIfPending();
}
@Override
@@ -127,6 +137,10 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
}
private void onPrivacyClick(){
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
}
private void onFiltersClick(){
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
}
@@ -147,11 +161,9 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new M3AlertDialogBuilder(getActivity())
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
.setPositiveButton(R.string.log_out, (dialog, which)->AccountSessionManager.get(accountID).logOut(getActivity(), ()->{
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
loggedOut=true;
getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class);
startActivity(intent);
((MainActivity)getActivity()).restartHomeFragment();
}))
.setNegativeButton(R.string.cancel, null)
.show();

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android.fragments.settings;
import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.content.Intent;
@@ -25,8 +27,11 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.unifiedpush.android.connector.RegistrationDialogContent;
import org.unifiedpush.android.connector.UnifiedPush;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
@@ -52,7 +57,8 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
private boolean notificationsAllowed=true;
// MEGALODON
private CheckableListItem<Void> uniformIconItem, deleteItem, onlyLatestItem;
private boolean useUnifiedPush = false;
private CheckableListItem<Void> uniformIconItem, deleteItem, onlyLatestItem, unifiedPushItem;
private CheckableListItem<Void> postsItem, updateItem;
private AccountLocalPreferences lp;
@@ -64,6 +70,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
lp=AccountSessionManager.get(accountID).getLocalPreferences();
getPushSubscription();
useUnifiedPush=!getDistributor(getContext()).isEmpty();
onDataLoaded(List.of(
pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_fluent_alert_snooze_24_regular, ()->onPauseNotificationsClick(false)),
@@ -79,11 +86,19 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
uniformIconItem=new CheckableListItem<>(R.string.sk_settings_uniform_icon_for_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.uniformNotificationIcon, R.drawable.ic_ntf_logo, ()->toggleCheckableItem(uniformIconItem)),
deleteItem=new CheckableListItem<>(R.string.sk_settings_enable_delete_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enableDeleteNotifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, ()->toggleCheckableItem(deleteItem)),
onlyLatestItem=new CheckableListItem<>(R.string.sk_settings_single_notification, 0, CheckableListItem.Style.SWITCH, lp.keepOnlyLatestNotification, R.drawable.ic_fluent_convert_range_24_regular, ()->toggleCheckableItem(onlyLatestItem), true)
onlyLatestItem=new CheckableListItem<>(R.string.sk_settings_single_notification, 0, CheckableListItem.Style.SWITCH, lp.keepOnlyLatestNotification, R.drawable.ic_fluent_convert_range_24_regular, ()->toggleCheckableItem(onlyLatestItem), true),
unifiedPushItem=new CheckableListItem<>(R.string.sk_settings_unifiedpush, 0, CheckableListItem.Style.SWITCH, useUnifiedPush, R.drawable.ic_fluent_alert_arrow_up_24_regular, this::onUnifiedPush, true)
));
//only enable when distributors, who can receive notifications, are available
unifiedPushItem.isEnabled=!UnifiedPush.getDistributors(getContext(), new ArrayList<>()).isEmpty();
if (!unifiedPushItem.isEnabled) {
unifiedPushItem.subtitleRes=R.string.sk_settings_unifiedpush_no_distributor_body;
}
typeItems=List.of(mentionsItem, boostsItem, favoritesItem, followersItem, pollsItem, updateItem, postsItem);
pauseItem.checkedChangeListener=checked->onPauseNotificationsClick(true);
unifiedPushItem.checkedChangeListener=checked->onUnifiedPush();
updatePolicyItem(null);
updatePauseItem();
}
@@ -312,4 +327,38 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
bannerAdapter.setVisible(false);
}
}
}
private void onUnifiedPush(){
if(getDistributor(getContext()).isEmpty()){
List<String> distributors = UnifiedPush.getDistributors(getContext(), new ArrayList<>());
showUnifiedPushRegisterDialog(distributors);
return;
}
UnifiedPush.unregisterApp(
getContext(),
accountID
);
//re-register to fcm
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().registerAccountForPush(getPushSubscription());
unifiedPushItem.toggle();
rebindItem(unifiedPushItem);
}
private void showUnifiedPushRegisterDialog(List<String> distributors){
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_unifiedpush_choose).setItems(distributors.toArray(String[]::new),
(dialog, which)->{
String userDistrib = distributors.get(which);
UnifiedPush.saveDistributor(getContext(), userDistrib);
UnifiedPush.registerApp(
getContext(),
accountID,
new ArrayList<>(),
getContext().getPackageName()
);
unifiedPushItem.toggle();
rebindItem(unifiedPushItem);
}).setOnCancelListener(d->rebindItem(unifiedPushItem)).show();
}
}

View File

@@ -0,0 +1,41 @@
package org.joinmastodon.android.fragments.settings;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import java.util.List;
public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> discoverableItem, indexableItem;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.settings_privacy);
Account self=AccountSessionManager.get(accountID).self;
onDataLoaded(List.of(
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, ()->toggleCheckableItem(discoverableItem)),
indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_search_24px, ()->toggleCheckableItem(indexableItem))
));
if(self.source.indexable==null)
indexableItem.isEnabled=false;
}
@Override
protected void doLoadData(int offset, int count){}
@Override
public void onPause(){
super.onPause();
Account self=AccountSessionManager.get(accountID).self;
if(self.discoverable!=discoverableItem.checked || (self.source.indexable!=null && self.source.indexable!=indexableItem.checked)){
self.discoverable=discoverableItem.checked;
self.source.indexable=indexableItem.checked;
AccountSessionManager.get(accountID).savePreferencesLater();
}
}
}

View File

@@ -8,6 +8,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -24,6 +25,7 @@ import android.widget.Toast;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.requests.instance.GetInstanceExtendedDescription;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.model.viewmodel.ListItem;
@@ -126,6 +128,8 @@ public class SettingsServerAboutFragment extends LoaderFragment{
hlp.leftMargin=hlp.rightMargin=V.dp(16);
scrollingLayout.addView(heading, hlp);
// if a remote instance is shown, the account is remote and need to be loaded accordingly when shown
instance.contactAccount.isRemote=!AccountSessionManager.get(accountID).domain.equals(instance.normalizedUri);
AccountViewModel model=new AccountViewModel(instance.contactAccount, accountID);
AccountViewHolder holder=new AccountViewHolder(this, scrollingLayout, null);
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);

View File

@@ -153,18 +153,21 @@ public class SettingsServerFragment extends AppKitFragment{
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
FrameLayout view=new FrameLayout(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Fragment fragment=getFragmentForPage(position);
if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
getChildFragmentManager().beginTransaction().add(view.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){

View File

@@ -7,12 +7,17 @@ import androidx.annotation.Nullable;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.parceler.Parcel;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
/**
* Represents a user of Mastodon and their associated profile.
*/
@@ -23,22 +28,22 @@ public class Account extends BaseModel implements Searchable{
/**
* The account id
*/
@RequiredField
// @RequiredField
public String id;
/**
* The username of the account, not including domain.
*/
@RequiredField
// @RequiredField
public String username;
/**
* The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
*/
@RequiredField
// @RequiredField
public String acct;
/**
* The location of the user's profile page.
*/
@RequiredField
// @RequiredField
public String url;
// Display attributes
@@ -51,12 +56,12 @@ public class Account extends BaseModel implements Searchable{
/**
* The profile's bio / description.
*/
@RequiredField
// @RequiredField
public String note;
/**
* An image icon that is shown next to statuses and in the profile.
*/
@RequiredField
// @RequiredField
public String avatar;
/**
* A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.
@@ -134,6 +139,7 @@ public class Account extends BaseModel implements Searchable{
* When a timed mute will expire, if applicable.
*/
public Instant muteExpiresAt;
public boolean noindex;
public List<Role> roles;
@@ -157,16 +163,26 @@ public class Account extends BaseModel implements Searchable{
if(fields!=null){
for(AccountField f:fields)
f.postprocess();
} else {
fields = Collections.emptyList();
}
if(emojis!=null){
for(Emoji e:emojis)
e.postprocess();
} else {
emojis = Collections.emptyList();
}
if(moved!=null)
moved.postprocess();
if(TextUtils.isEmpty(displayName))
displayName=username;
if(fqn == null) fqn = getFullyQualifiedName();
if(id == null) id = "";
if(username == null) username = "";
if(TextUtils.isEmpty(displayName))
displayName = !TextUtils.isEmpty(username) ? username : "";
if(acct == null) acct = "";
if(url == null) url = "";
if(note == null) note = "";
if(avatar == null) avatar = "";
}
public boolean isLocal(){
@@ -191,6 +207,8 @@ public class Account extends BaseModel implements Searchable{
}
public String getFullyQualifiedName() {
if (TextUtils.isEmpty(acct))
return "";
return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL();
}
@@ -221,6 +239,7 @@ public class Account extends BaseModel implements Searchable{
", source="+source+
", suspended="+suspended+
", muteExpiresAt="+muteExpiresAt+
", noindex="+noindex+
'}';
}
}

View File

@@ -1,9 +1,11 @@
package org.joinmastodon.android.model;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@Parcel
@@ -20,6 +22,7 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
public Instant updatedAt;
public boolean read;
public List<Emoji> emojis;
public List<EmojiReaction> reactions;
public List<Mention> mentions;
public List<Hashtag> tags;
@@ -41,10 +44,17 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
'}';
}
public Status toStatus() {
Status s = Status.ofFake(id, content, publishedAt);
s.createdAt = startsAt != null ? startsAt : publishedAt;
if (updatedAt != null) s.editedAt = updatedAt;
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();
if(reactions==null) reactions=new ArrayList<>();
}
public Status toStatus() {
Status s=Status.ofFake(id, content, publishedAt);
s.createdAt=startsAt != null ? startsAt : publishedAt;
s.reactions=reactions;
if(updatedAt != null) s.editedAt=updatedAt;
return s;
}

View File

@@ -3,6 +3,7 @@ package org.joinmastodon.android.model;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadataRetriever;
import com.google.gson.annotations.SerializedName;
@@ -77,6 +78,13 @@ public class Attachment extends BaseModel{
return 0;
}
public boolean hasSound() {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(url);
String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO);
return "yes".equals(hasAudioStr);
}
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();

View File

@@ -41,6 +41,12 @@ public class Emoji extends BaseModel{
this.staticUrl = staticUrl;
}
public String getUrl(boolean playGifs){
String idealUrl=playGifs ? url : staticUrl;
if(idealUrl==null) return url==null ? staticUrl : url;
return idealUrl;
}
@Override
public String toString(){
return "Emoji{"+

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.model;
import java.util.ArrayList;
import java.util.List;
public class EmojiCategory{
@@ -10,4 +11,8 @@ public class EmojiCategory{
this.title=title;
this.emojis=emojis;
}
public EmojiCategory(EmojiCategory category){
this.title = category.title;
this.emojis = new ArrayList<>(category.emojis);
}
}

View File

@@ -0,0 +1,63 @@
package org.joinmastodon.android.model;
import org.joinmastodon.android.GlobalUserPreferences;
import org.parceler.Parcel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
@Parcel
public class EmojiReaction {
public List<Account> accounts;
public List<String> accountIds;
public int count;
public boolean me;
public String name;
public String url;
public String staticUrl;
public transient ImageLoaderRequest request;
public String getUrl(boolean playGifs){
String idealUrl=playGifs ? url : staticUrl;
if(idealUrl==null) return url==null ? staticUrl : url;
return idealUrl;
}
public static EmojiReaction of(Emoji info, Account me){
EmojiReaction reaction=new EmojiReaction();
reaction.me=true;
reaction.count=1;
reaction.name=info.shortcode;
reaction.url=info.url;
reaction.staticUrl=info.staticUrl;
reaction.accounts=new ArrayList<>(Collections.singleton(me));
reaction.accountIds=new ArrayList<>(Collections.singleton(me.id));
reaction.request=new UrlImageLoaderRequest(info.url, V.sp(24), V.sp(24));
return reaction;
}
public static EmojiReaction of(String emoji, Account me){
EmojiReaction reaction=new EmojiReaction();
reaction.me=true;
reaction.count=1;
reaction.name=emoji;
reaction.accounts=new ArrayList<>(Collections.singleton(me));
reaction.accountIds=new ArrayList<>(Collections.singleton(me.id));
return reaction;
}
public void add(Account self){
if(accounts==null) accounts=new ArrayList<>();
if(accountIds==null) accountIds=new ArrayList<>();
count++;
me=true;
accounts.add(self);
accountIds.add(self.id);
}
}

View File

@@ -14,6 +14,7 @@ public class FilterResult extends BaseModel {
@Override
public void postprocess() throws ObjectValidationException {
super.postprocess();
if (filter != null) filter.postprocess();
if(filter!=null) filter.postprocess();
if(keywordMatches==null) keywordMatches=List.of();
}
}

View File

@@ -23,6 +23,7 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
", following="+following+
", history="+history+
", statusesCount="+statusesCount+
", following="+following+
'}';
}
@@ -30,4 +31,19 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
public String getID(){
return name;
}
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Hashtag hashtag=(Hashtag) o;
return name.equals(hashtag.name);
}
@Override
public int hashCode(){
return name.hashCode();
}
}

View File

@@ -131,7 +131,7 @@ public class Instance extends BaseModel{
ci.domain=uri;
ci.normalizedDomain=IDN.toUnicode(uri);
ci.description=Html.fromHtml(shortDescription).toString().trim();
if(languages!=null && languages.size() > 0){
if(languages!=null&&languages.size()>0){
ci.language=languages.get(0);
ci.languages=languages;
}else{

View File

@@ -20,4 +20,22 @@ public class Mention extends BaseModel{
", url='"+url+'\''+
'}';
}
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Mention mention=(Mention) o;
if(!id.equals(mention.id)) return false;
return url.equals(mention.url);
}
@Override
public int hashCode(){
int result=id.hashCode();
result=31*result+url.hashCode();
return result;
}
}

View File

@@ -1,9 +1,12 @@
package org.joinmastodon.android.model;
import android.content.Context;
import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.annotation.StringRes;
@@ -20,6 +23,40 @@ public class PushNotification extends BaseModel{
@RequiredField
public String body;
public static PushNotification fromNotification(Context context, Notification notification){
PushNotification pushNotification = new PushNotification();
pushNotification.notificationType = switch(notification.type) {
case FOLLOW -> PushNotification.Type.FOLLOW;
case MENTION -> PushNotification.Type.MENTION;
case REBLOG -> PushNotification.Type.REBLOG;
case FAVORITE -> PushNotification.Type.FAVORITE;
case POLL -> PushNotification.Type.POLL;
case STATUS -> PushNotification.Type.STATUS;
case UPDATE -> PushNotification.Type.UPDATE;
case SIGN_UP -> PushNotification.Type.SIGN_UP;
case REPORT -> PushNotification.Type.REPORT;
//Follow request, and reactions are not supported by the API
default -> throw new IllegalStateException("Unexpected value: "+notification.type);
};
String notificationTitle = context.getString(switch(notification.type){
case FOLLOW -> R.string.user_followed_you;
case MENTION -> R.string.sk_notification_mention;
case REBLOG -> R.string.notification_boosted;
case FAVORITE -> R.string.user_favorited;
case POLL -> R.string.poll_ended;
case UPDATE -> R.string.sk_post_edited;
case SIGN_UP -> R.string.sk_signed_up;
case REPORT -> R.string.sk_reported;
default -> throw new IllegalStateException("Unexpected value: "+notification.type);
});
pushNotification.title = UiUtils.generateFormattedString(notificationTitle, notification.account.displayName).toString();
pushNotification.icon = notification.status.account.avatarStatic;
pushNotification.body = notification.status.getStrippedText();
return pushNotification;
}
@Override
public String toString(){
return "PushNotification{"+

View File

@@ -37,6 +37,8 @@ public class Source extends BaseModel{
* The number of pending follow requests.
*/
public int followRequestCount;
public Boolean indexable;
public boolean hideCollections;
@Override
public void postprocess() throws ObjectValidationException{
@@ -54,6 +56,8 @@ public class Source extends BaseModel{
", sensitive="+sensitive+
", language='"+language+'\''+
", followRequestCount="+followRequestCount+
", indexable="+indexable+
", hideCollections="+hideCollections+
'}';
}
}

View File

@@ -5,25 +5,44 @@ import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDese
import android.text.TextUtils;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import androidx.annotation.NonNull;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.utils.StatusTextEncoder;
import org.parceler.Parcel;
import java.lang.reflect.Type;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;
@Parcel
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
@@ -74,14 +93,17 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public Status quote; // can be boolean in calckey
public List<EmojiReaction> reactions;
protected List<EmojiReaction> emojiReactions; // akkoma
public transient boolean filterRevealed;
public transient boolean spoilerRevealed;
public transient boolean sensitiveRevealed;
public transient boolean textExpanded, textExpandable;
public transient boolean hasGapAfter;
public transient TranslatedStatus translation;
public transient boolean translationShown;
private transient String strippedText;
public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation;
public Status(){}
@@ -96,7 +118,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
t.postprocess();
for(Emoji e:emojis)
e.postprocess();
if (mediaAttachments == null) mediaAttachments = List.of();
if (mediaAttachments == null) mediaAttachments=List.of();
for(Attachment a:mediaAttachments)
a.postprocess();
account.postprocess();
@@ -110,10 +132,12 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
for(FilterResult fr:filtered)
fr.postprocess();
if (!TextUtils.isEmpty(spoilerText)) sensitive = true;
spoilerRevealed=TextUtils.isEmpty(spoilerText);
spoilerRevealed=!hasSpoiler();
if(!spoilerRevealed) sensitive=true;
sensitiveRevealed=!sensitive;
if (visibility.equals(StatusPrivacy.LOCAL)) localOnly = true;
if(visibility.equals(StatusPrivacy.LOCAL)) localOnly=true;
if(emojiReactions!=null) reactions=emojiReactions;
if(reactions==null) reactions=new ArrayList<>();
}
@Override
@@ -171,6 +195,10 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
pinned=ev.pinned;
}
public void update(EmojiReactionsUpdatedEvent ev){
reactions=ev.reactions;
}
public Status getContentStatus(){
return reblog!=null ? reblog : this;
}
@@ -181,12 +209,48 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
return strippedText;
}
public boolean hasSpoiler(){
return !TextUtils.isEmpty(spoilerText);
}
@NonNull
@Override
public Status clone(){
return (Status) super.clone();
}
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");
public boolean isEligibleForTranslation(AccountSession session){
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
boolean translateEnabled = instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
try {
String bottomText = BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
if(bottomText==null || bottomText.length()==0 || bottomText.equals("\u0005")) bottomText=null;
if(bottomText!=null){
translation=new Translation();
translation.content=bottomText;
translation.detectedSourceLanguage="\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48";
translation.provider="bottom-java";
return true;
}
} catch (TranslationError ignored) {}
return translateEnabled && !TextUtils.isEmpty(content) && !TextUtils.isEmpty(language)
&& !Objects.equals(Locale.getDefault().getLanguage(), language)
&& (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED);
}
public enum TranslationState{
HIDDEN,
SHOWN,
LOADING
}
public boolean isReblogPermitted(String accountID){
return visibility.isReblogPermitted(account.id.equals(
AccountSessionManager.getInstance().getAccount(accountID).self.id
@@ -194,17 +258,18 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
}
public static Status ofFake(String id, String text, Instant createdAt) {
Status s = new Status();
s.id = id;
s.mediaAttachments = List.of();
s.createdAt = createdAt;
s.content = s.text = text;
s.spoilerText = "";
s.visibility = StatusPrivacy.PUBLIC;
s.mentions = List.of();
s.tags = List.of();
s.emojis = List.of();
s.filtered = List.of();
Status s=new Status();
s.id=id;
s.mediaAttachments=List.of();
s.createdAt=createdAt;
s.content=s.text=text;
s.spoilerText="";
s.visibility=StatusPrivacy.PUBLIC;
s.reactions=List.of();
s.mentions=List.of();
s.tags =List.of();
s.emojis=List.of();
s.filtered=List.of();
return s;
}
@@ -216,21 +281,21 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public static class StatusDeserializer implements JsonDeserializer<Status> {
@Override
public Status deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
JsonObject obj=json.getAsJsonObject();
Status quote = null;
Status quote=null;
if (obj.has("quote") && obj.get("quote").isJsonObject())
quote = gson.fromJson(obj.get("quote"), Status.class);
quote=gson.fromJson(obj.get("quote"), Status.class);
obj.remove("quote");
Status reblog = null;
Status reblog=null;
if (obj.has("reblog"))
reblog = gson.fromJson(obj.get("reblog"), Status.class);
reblog=gson.fromJson(obj.get("reblog"), Status.class);
obj.remove("reblog");
Status status = gsonWithoutDeserializer.fromJson(json, Status.class);
status.quote = quote;
status.reblog = reblog;
Status status=gsonWithoutDeserializer.fromJson(json, Status.class);
status.quote=quote;
status.reblog=reblog;
return status;
}

View File

@@ -219,7 +219,7 @@ public class TimelineDefinition {
args.putString("listID", listId);
args.putBoolean("listIsExclusive", listIsExclusive);
} else if (type == TimelineType.HASHTAG) {
args.putString("hashtag", hashtagName);
args.putString("hashtagName", hashtagName);
args.putBoolean("localOnly", hashtagLocalOnly);
args.putStringArrayList("any", hashtagAny == null ? new ArrayList<>() : new ArrayList<>(hashtagAny));
args.putStringArrayList("all", hashtagAll == null ? new ArrayList<>() : new ArrayList<>(hashtagAll));

View File

@@ -1,7 +0,0 @@
package org.joinmastodon.android.model;
public class TranslatedStatus extends BaseModel {
public String content;
public String detectedSourceLanguage;
public String provider;
}

View File

@@ -0,0 +1,10 @@
package org.joinmastodon.android.model;
import org.joinmastodon.android.api.AllFieldsAreRequired;
@AllFieldsAreRequired
public class Translation extends BaseModel{
public String content;
public String detectedSourceLanguage;
public String provider;
}

View File

@@ -1,8 +1,10 @@
package org.joinmastodon.android.model.viewmodel;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
@@ -24,9 +26,13 @@ public class AccountViewModel{
public AccountViewModel(Account account, String accountID){
this.account=account;
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
AccountSession session = AccountSessionManager.get(accountID);
avaRequest=new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? session.getDefaultAvatarUrl() :
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
V.dp(50), V.dp(50));
emojiHelper=new CustomEmojiHelper();
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
if(session.getLocalPreferences().customEmojiInNames)
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
else
parsedName=account.displayName;

View File

@@ -8,6 +8,7 @@ import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -39,6 +40,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.LinearLayoutManager;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -144,21 +146,10 @@ public class AccountSwitcherSheet extends BottomSheet{
}
private void logOut(String accountID){
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
onLoggedOut(accountID);
}
@Override
public void onError(ErrorResponse error){
onLoggedOut(accountID);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
AccountSessionManager.get(accountID).logOut(activity, ()->{
dismiss();
((MainActivity)activity).restartHomeFragment();
});
}
private void logOutAll(){
@@ -326,15 +317,15 @@ public class AccountSwitcherSheet extends BottomSheet{
@Override
public void onClick(){
setOnDismissListener(null);
dismiss();
if (onClick != null) {
dismiss();
onClick.accept(item.getID(), false);
return;
}
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null)
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null){
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
activity.finish();
activity.startActivity(new Intent(activity, MainActivity.class));
((MainActivity)activity).restartHomeFragment();
}
}
@Override

File diff suppressed because one or more lines are too long

View File

@@ -37,6 +37,16 @@ public class OutlineProviders{
}
};
private final static int BUTTON_BG_HEIGHT=V.dp(40);
public static final ViewOutlineProvider M3_BUTTON=new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
int viewHeight=view.getHeight();
int top=Math.floorDiv(viewHeight - BUTTON_BG_HEIGHT, 2);
outline.setRoundRect(0, top, view.getWidth(), top + BUTTON_BG_HEIGHT, V.dp(20));
}
};
public static ViewOutlineProvider roundedRect(int dp){
ViewOutlineProvider provider=roundedRects.get(dp);
if(provider!=null)

View File

@@ -15,6 +15,7 @@ import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Notification;
@@ -41,12 +42,13 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
public CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
public CharSequence parsedName, parsedBio;
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account, Notification notification){
public AccountCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, String accountID, Account account, Notification notification){
super(parentID, parentFragment);
this.account=account;
this.notification=notification;
if(!TextUtils.isEmpty(account.avatar))
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
avaRequest=new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(accountID).getDefaultAvatarUrl() : account.avatar,
V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.header))
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), parentFragment.getAccountID());
@@ -111,15 +113,24 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
rejectWrap=findViewById(R.id.reject_btn_wrap);
View card=findViewById(R.id.card);
card.setOutlineProvider(OutlineProviders.roundedRect(6));
card.setOutlineProvider(OutlineProviders.roundedRect(12));
card.setClipToOutline(true);
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
avatar.setOutlineProvider(OutlineProviders.roundedRect(15));
avatar.setClipToOutline(true);
cover.setOutlineProvider(OutlineProviders.roundedRect(3));
View border=findViewById(R.id.avatar_border);
border.setOutlineProvider(OutlineProviders.roundedRect(17));
border.setClipToOutline(true);
cover.setOutlineProvider(OutlineProviders.roundedRect(9));
cover.setClipToOutline(true);
actionButton.setOnClickListener(this::onActionButtonClick);
acceptButton.setOnClickListener(this::onFollowRequestButtonClick);
rejectButton.setOnClickListener(this::onFollowRequestButtonClick);
card.setOnClickListener(v->onClick());
}
@Override
public boolean isEnabled(){
return false;
}
@Override
@@ -132,18 +143,19 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
postsCount.setText(UiUtils.abbreviateNumber(item.account.statusesCount));
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.x_posts, (int)(item.account.statusesCount%1000), item.account.statusesCount));
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.sk_posts_count_label, (int)(item.account.statusesCount%1000), item.account.statusesCount));
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=item.parentFragment.getRelationship(item.account.id);
if(item.notification.type == Notification.Type.FOLLOW_REQUEST && (relationship == null || !relationship.followedBy)){
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(item.notification.type==Notification.Type.FOLLOW_REQUEST && (relationship==null || !relationship.followedBy)){
actionWrap.setVisibility(View.GONE);
acceptWrap.setVisibility(View.VISIBLE);
rejectWrap.setVisibility(View.VISIBLE);
// i hate that i wasn't able to do this in xml
acceptButton.setCompoundDrawableTintList(acceptButton.getTextColors());
acceptProgress.setIndeterminateTintList(acceptButton.getTextColors());
rejectButton.setCompoundDrawableTintList(rejectButton.getTextColors());
@@ -163,7 +175,7 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
private void onFollowRequestButtonClick(View v) {
itemView.setHasTransientState(true);
UiUtils.handleFollowRequest((Activity) v.getContext(), item.account, item.parentFragment.getAccountID(), null, v == acceptButton, relationship, rel -> {
if(v.getContext()==null) return;
if(v.getContext()==null || rel==null) return;
itemView.setHasTransientState(false);
item.parentFragment.putRelationship(item.account.id, rel);
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();

View File

@@ -3,18 +3,17 @@ package org.joinmastodon.android.ui.displayitems;
import android.content.Context;
import android.view.ViewGroup;
import android.widget.Space;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import me.grishka.appkit.utils.V;
public class InsetDummyStatusDisplayItem extends StatusDisplayItem {
private final boolean addMediaGridMargin;
public class DummyStatusDisplayItem extends StatusDisplayItem {
public InsetDummyStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, boolean addMediaGridMargin) {
public DummyStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment) {
super(parentID, parentFragment);
this.addMediaGridMargin = addMediaGridMargin;
}
@Override
@@ -22,20 +21,22 @@ public class InsetDummyStatusDisplayItem extends StatusDisplayItem {
return Type.DUMMY;
}
public static class Holder extends StatusDisplayItem.Holder<InsetDummyStatusDisplayItem> {
public static class Holder extends StatusDisplayItem.Holder<DummyStatusDisplayItem> {
private final RecyclerView.LayoutParams params;
public Holder(Context context) {
super(new Space(context));
}
params=new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
@Override
public void onBind(InsetDummyStatusDisplayItem item) {
// BetterItemAnimator appears not to handle InsetStatusItemDecoration's getItemOffsets
// correctly, causing removed inset views to jump while animating. i don't quite
// understand it, but this workaround appears to work.
// see InsetStatusItemDecoration#getItemOffsets
ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
params.setMargins(0, item.addMediaGridMargin ? V.dp(4) : 0, 0, V.dp(16));
params.setMargins(0, 0, 0, V.dp(16));
itemView.setLayoutParams(params);
}
@Override
public void onBind(DummyStatusDisplayItem item) {}
}
}

View File

@@ -0,0 +1,417 @@
package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.announcements.AddAnnouncementReaction;
import org.joinmastodon.android.api.requests.announcements.DeleteAnnouncementReaction;
import org.joinmastodon.android.api.requests.statuses.AddStatusReaction;
import org.joinmastodon.android.api.requests.statuses.DeleteStatusReaction;
import org.joinmastodon.android.api.requests.statuses.PleromaAddStatusReaction;
import org.joinmastodon.android.api.requests.statuses.PleromaDeleteStatusReaction;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiReaction;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.CustomEmojiPopupKeyboard;
import org.joinmastodon.android.ui.utils.TextDrawable;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
public final Status status;
private final Drawable placeholder;
private final boolean hideEmpty, forAnnouncement, playGifs;
private final String accountID;
private static final float ALPHA_DISABLED=0.55f;
public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideEmpty, boolean forAnnouncement) {
super(parentID, parentFragment);
this.status=status;
this.hideEmpty=hideEmpty;
this.forAnnouncement=forAnnouncement;
this.accountID=accountID;
placeholder=parentFragment.getContext().getDrawable(R.drawable.image_placeholder).mutate();
placeholder.setBounds(0, 0, V.sp(24), V.sp(24));
playGifs=GlobalUserPreferences.playGifs;
}
@Override
public int getImageCount(){
return (int) status.reactions.stream().filter(r->r.getUrl(playGifs)!=null).count();
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return status.reactions.get(index).request;
}
@Override
public Type getType(){
return Type.EMOJI_REACTIONS;
}
public boolean isHidden(){
return status.reactions.isEmpty() && hideEmpty;
}
// borrowed from ProfileFragment
private void setActionProgressVisible(Holder.EmojiReactionViewHolder vh, boolean visible){
if(vh==null) return;
vh.progress.setVisibility(visible ? View.VISIBLE : View.GONE);
vh.btn.setClickable(!visible);
vh.btn.setAlpha(visible ? ALPHA_DISABLED : 1);
}
private MastodonAPIRequest<?> createRequest(String name, int count, boolean delete, Holder.EmojiReactionViewHolder vh, Runnable cb, Runnable err){
setActionProgressVisible(vh, true);
boolean ak=parentFragment.isInstanceAkkoma();
boolean keepSpinning=delete && count == 1;
if(forAnnouncement){
MastodonAPIRequest<Object> req=delete
? new DeleteAnnouncementReaction(status.id, name)
: new AddAnnouncementReaction(status.id, name);
return req.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
if(!keepSpinning) setActionProgressVisible(vh, false);
cb.run();
}
@Override
public void onError(ErrorResponse error){
setActionProgressVisible(vh, false);
error.showToast(parentFragment.getContext());
if(err!=null) err.run();
}
});
}else{
MastodonAPIRequest<Status> req=delete
? (ak ? new PleromaDeleteStatusReaction(status.id, name) : new DeleteStatusReaction(status.id, name))
: (ak ? new PleromaAddStatusReaction(status.id, name) : new AddStatusReaction(status.id, name));
return req.setCallback(new Callback<>(){
@Override
public void onSuccess(Status result){
if(!keepSpinning) setActionProgressVisible(vh, false);
cb.run();
}
@Override
public void onError(ErrorResponse error){
setActionProgressVisible(vh, false);
error.showToast(parentFragment.getContext());
if(err!=null) err.run();
}
});
}
}
public static class Holder extends StatusDisplayItem.Holder<EmojiReactionsStatusDisplayItem> implements ImageLoaderViewHolder, CustomEmojiPopupKeyboard.Listener {
private final UsableRecyclerView list;
private final LinearLayout root, line;
private CustomEmojiPopupKeyboard emojiKeyboard;
private final View space;
private final ImageButton addButton;
private final ProgressBar progress;
private final EmojiReactionsAdapter adapter;
private final ListImageLoaderWrapper imgLoader;
public Holder(Activity activity, ViewGroup parent) {
super(activity, R.layout.display_item_emoji_reactions, parent);
root=(LinearLayout) itemView;
line=findViewById(R.id.line);
list=findViewById(R.id.list);
imgLoader=new ListImageLoaderWrapper(activity, list, new RecyclerViewDelegate(list), null);
list.setAdapter(adapter=new EmojiReactionsAdapter(this, imgLoader));
addButton=findViewById(R.id.add_btn);
progress=findViewById(R.id.progress);
addButton.setOnClickListener(this::onReactClick);
space=findViewById(R.id.space);
list.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));
}
@Override
public void onBind(EmojiReactionsStatusDisplayItem item) {
if(emojiKeyboard != null) root.removeView(emojiKeyboard.getView());
AccountSession session=item.parentFragment.getSession();
item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), V.sp(24), V.sp(24))
: null);
emojiKeyboard=new CustomEmojiPopupKeyboard(
(Activity) item.parentFragment.getContext(),
AccountSessionManager.getInstance().getCustomEmojis(session.domain),
session.domain, true);
emojiKeyboard.setListener(this);
space.setVisibility(View.GONE);
root.addView(emojiKeyboard.getView());
boolean hidden=item.isHidden();
root.setVisibility(hidden ? View.GONE : View.VISIBLE);
line.setVisibility(hidden ? View.GONE : View.VISIBLE);
line.setPadding(
list.getPaddingLeft(),
hidden ? 0 : V.dp(8),
list.getPaddingRight(),
item.forAnnouncement ? V.dp(8) : 0
);
imgLoader.updateImages();
adapter.notifyDataSetChanged();
}
private void hideEmojiKeyboard(){
space.setVisibility(View.GONE);
addButton.setSelected(false);
if(emojiKeyboard.isVisible()) emojiKeyboard.toggleKeyboardPopup(null);
}
@Override
public void onEmojiSelected(Emoji emoji) {
addEmojiReaction(emoji.shortcode, emoji);
hideEmojiKeyboard();
}
@Override
public void onEmojiSelected(String emoji){
addEmojiReaction(emoji, null);
hideEmojiKeyboard();
}
private void addEmojiReaction(String emoji, Emoji info) {
int countBefore=item.status.reactions.size();
for(int i=0; i<item.status.reactions.size(); i++){
EmojiReaction r=item.status.reactions.get(i);
if(r.name.equals(emoji) && r.me){
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
scroller.setTargetPosition(i);
list.getLayoutManager().startSmoothScroll(scroller);
return; // nothing to do, already added
}
}
progress.setVisibility(View.VISIBLE);
addButton.setClickable(false);
addButton.setAlpha(ALPHA_DISABLED);
Runnable resetBtn=()->{
progress.setVisibility(View.GONE);
addButton.setClickable(true);
addButton.setAlpha(1f);
};
Account me=AccountSessionManager.get(item.accountID).self;
EmojiReaction existing=null;
for(int i=0; i<item.status.reactions.size(); i++){
EmojiReaction r=item.status.reactions.get(i);
if(r.name.equals(emoji)){
existing=r;
break;
}
}
EmojiReaction finalExisting=existing;
item.createRequest(emoji, existing==null ? 1 : existing.count, false, null, ()->{
resetBtn.run();
if(finalExisting==null){
int pos=item.status.reactions.size();
item.status.reactions.add(pos, info!=null ? EmojiReaction.of(info, me) : EmojiReaction.of(emoji, me));
adapter.notifyItemRangeInserted(pos, 1);
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
scroller.setTargetPosition(pos);
list.getLayoutManager().startSmoothScroll(scroller);
}else{
finalExisting.add(me);
adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting));
}
E.post(new EmojiReactionsUpdatedEvent(item.status.id, item.status.reactions, countBefore==0, adapter.parentHolder));
}, resetBtn).exec(item.accountID);
}
@Override
public void onBackspace() {}
private void onReactClick(View v){
emojiKeyboard.toggleKeyboardPopup(null);
v.setSelected(emojiKeyboard.isVisible());
space.setVisibility(emojiKeyboard.isVisible() ? View.VISIBLE : View.GONE);
DisplayMetrics displayMetrics = new DisplayMetrics();
int[] locationOnScreen = new int[2];
((Activity) v.getContext()).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
v.getLocationOnScreen(locationOnScreen);
double fromScreenTop = (double) locationOnScreen[1] / displayMetrics.heightPixels;
if (fromScreenTop > 0.75) {
item.parentFragment.scrollBy(0, (int) (displayMetrics.heightPixels * 0.3));
}
}
@Override
public void setImage(int index, Drawable image){
View child=list.getChildAt(index);
if(child==null) return;
((EmojiReactionViewHolder) list.getChildViewHolder(child)).setImage(index, image);
}
@Override
public void clearImage(int index){
if(item.status.reactions.get(index).getUrl(item.playGifs)==null) return;
setImage(index, item.placeholder);
}
private class EmojiReactionsAdapter extends UsableRecyclerView.Adapter<EmojiReactionViewHolder> implements ImageLoaderRecyclerAdapter{
ListImageLoaderWrapper imgLoader;
Holder parentHolder;
public EmojiReactionsAdapter(Holder parentHolder, ListImageLoaderWrapper imgLoader){
super(imgLoader);
this.parentHolder=parentHolder;
this.imgLoader=imgLoader;
}
@NonNull
@Override
public EmojiReactionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new EmojiReactionViewHolder(parent.getContext(), list);
}
@Override
public void onBindViewHolder(EmojiReactionViewHolder holder, int position){
holder.bind(Pair.create(item, item.status.reactions.get(position)));
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
return item.status.reactions.size();
}
@Override
public int getImageCountForItem(int position){
return item.status.reactions.get(position).getUrl(item.playGifs)==null ? 0 : 1;
}
@Override
public ImageLoaderRequest getImageRequest(int position, int image){
return item.status.reactions.get(position).request;
}
}
private static class EmojiReactionViewHolder extends BindableViewHolder<Pair<EmojiReactionsStatusDisplayItem, EmojiReaction>> implements ImageLoaderViewHolder{
private final ProgressBarButton btn;
private final ProgressBar progress;
public EmojiReactionViewHolder(Context context, RecyclerView list){
super(context, R.layout.item_emoji_reaction, list);
btn=findViewById(R.id.btn);
progress=findViewById(R.id.progress);
itemView.setClickable(true);
}
@Override
public void setImage(int index, Drawable drawable){
drawable.setBounds(0, 0, V.sp(24), V.sp(24));
btn.setCompoundDrawablesRelative(drawable, null, null, null);
if(drawable instanceof Animatable) ((Animatable) drawable).start();
}
@Override
public void clearImage(int index){
setImage(index, item.first.placeholder);
}
@Override
public void onBind(Pair<EmojiReactionsStatusDisplayItem, EmojiReaction> item){
item.first.setActionProgressVisible(this, false);
EmojiReactionsStatusDisplayItem parent=item.first;
EmojiReaction reaction=item.second;
btn.setText(UiUtils.abbreviateNumber(reaction.count));
btn.setContentDescription(reaction.name);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) btn.setTooltipText(reaction.name);
if(reaction.getUrl(parent.playGifs)==null){
Paint p=new Paint();
p.setTextSize(V.sp(18));
TextDrawable drawable=new TextDrawable(p, reaction.name);
btn.setCompoundDrawablesRelative(drawable, null, null, null);
}else{
btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null);
}
btn.setSelected(reaction.me);
btn.setOnClickListener(e->{
boolean deleting=reaction.me;
parent.createRequest(reaction.name, reaction.count, deleting, this, ()->{
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
for(int i=0; i<parent.status.reactions.size(); i++){
EmojiReaction r=parent.status.reactions.get(i);
if(!r.name.equals(reaction.name)) continue;
if(deleting && r.count==1) {
parent.status.reactions.remove(i);
adapter.notifyItemRemoved(i);
break;
}
r.me=!deleting;
if(deleting) r.count--;
else r.count++;
adapter.notifyItemChanged(i);
break;
}
if(parent.isHidden()){
adapter.parentHolder.root.setVisibility(View.GONE);
adapter.parentHolder.line.setVisibility(View.GONE);
}
E.post(new EmojiReactionsUpdatedEvent(parent.status.id, parent.status.reactions, parent.status.reactions.isEmpty(), adapter.parentHolder));
adapter.parentHolder.imgLoader.updateImages();
}, null).exec(parent.parentFragment.getAccountID());
});
if (parent.parentFragment.isInstanceAkkoma()) {
// glitch-soc doesn't have this, afaik
btn.setOnLongClickListener(e->{
EmojiReaction emojiReaction=parent.status.reactions.get(getAbsoluteAdapterPosition());
Bundle args=new Bundle();
args.putString("account", parent.parentFragment.getAccountID());
args.putString("statusID", parent.status.id);
int atSymbolIndex = emojiReaction.name.indexOf("@");
args.putString("emoji", atSymbolIndex != -1 ? emojiReaction.name.substring(0, atSymbolIndex) : emojiReaction.name);
args.putString("url", emojiReaction.getUrl(parent.playGifs));
args.putInt("count", emojiReaction.count);
Nav.go(parent.parentFragment.getActivity(), StatusEmojiReactionsListFragment.class, args);
return true;
});
}
}
}
}
}

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