Compare commits

...

1175 Commits

Author SHA1 Message Date
sk
83786171a7 ellipsize tab bar labels 2023-10-13 13:49:54 +02:00
sk
f638a538c1 Merge remote-tracking branch 'upstream/l10n_master' 2023-10-13 13:45:36 +02:00
sk22
11e2316cbc Translated using Weblate (German)
Currently translated at 100.0% (393 of 393 strings)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-13 11:43:24 +00:00
sk
f2754cc5c9 bump version 2023-10-13 13:43:08 +02:00
sk
6a65edd089 fix draft/schedule view for large display sizes
cc @LucasGGamerM
see 77e19b4d6f
2023-10-13 13:39:51 +02:00
LucasGGamerM
4d49b10585 fix(compose): make 'This post will be saved as a draft' view work better on smaller screens
cc: @sk22
2023-10-13 13:27:13 +02:00
sk
86bfab81bd lucas is right
closes sk22#857
2023-10-13 02:27:34 +02:00
sk
50e313cff0 fix gap max_id when post before gap is filtered
hopefully fixes sk22#856
2023-10-13 02:22:03 +02:00
sk
5d07cde6dd fix gap not filtering posts 2023-10-13 01:48:45 +02:00
sk
f84a923102 implement global default color
closes sk22#853
2023-10-13 01:28:59 +02:00
sk
c9eac34ae6 fix gap item alpha not being reset
closes sk22#851
2023-10-13 00:27:35 +02:00
sk
181a0577c8 fix missing gap item when status removed 2023-10-13 00:24:32 +02:00
sk
0426084194 fix changes breaking off-screen bound holders
i hope i didn't break everything lol
2023-10-13 00:14:29 +02:00
Eugen Rochko
197d0caf44 New translations strings.xml (Japanese) 2023-10-11 18:34:49 +02:00
Eugen Rochko
a4a082f76a New translations strings.xml (Japanese) 2023-10-11 17:14:57 +02:00
Eugen Rochko
2528d48010 New translations strings.xml (Icelandic) 2023-10-10 18:10:41 +02:00
Eugen Rochko
5456d71979 New translations strings.xml (Chinese Traditional) 2023-10-10 10:25:33 +02:00
Eugen Rochko
e36aae3cf3 New translations strings.xml (Thai) 2023-10-09 20:31:09 +02:00
Grishka
d6040c0895 Hide the FAB while editing profile 2023-10-09 19:42:54 +02:00
Eugen Rochko
6d12e2dd72 New translations strings.xml (Thai) 2023-10-09 19:20:32 +02:00
sk
ff47e6edba remove unused method 2023-10-09 18:46:52 +02:00
sk
327ceb04d4 fix deleting notifications
closes sk22#677
closes sk22#633
2023-10-09 18:35:27 +02:00
sk
40a34b07de add option to disable underlined links
closes sk22#847
2023-10-09 18:07:19 +02:00
Eugen Rochko
f117249bb5 New translations strings.xml (Thai) 2023-10-09 18:02:31 +02:00
sk
1c90164ece fix broken hashtag links
closes sk22#848
2023-10-09 17:59:22 +02:00
sk
0d5fb250bc migrate theme colors
closes sk22#849
2023-10-09 17:54:54 +02:00
sk
e9bd5a373a remove unused icons and replace some wrong ones 2023-10-09 17:39:18 +02:00
sk
c494d283ba remove duplicate section, replace icons
closes sk22#850
2023-10-09 17:38:55 +02:00
Eugen Rochko
cf1d537367 New translations strings.xml (Vietnamese) 2023-10-09 06:03:28 +02:00
Eugen Rochko
517d13b400 New translations strings.xml (Vietnamese) 2023-10-09 05:07:06 +02:00
Eugen Rochko
fae870c93a New translations strings.xml (Urdu (India)) 2023-10-08 21:13:23 +02:00
Eugen Rochko
f8e00dcc80 New translations strings.xml (Kabyle) 2023-10-08 21:13:22 +02:00
Eugen Rochko
5fdbb597bb New translations strings.xml (Igbo) 2023-10-08 21:13:21 +02:00
Eugen Rochko
d74b286a9d New translations strings.xml (Occitan) 2023-10-08 21:13:20 +02:00
Eugen Rochko
ecb3c521ff New translations strings.xml (Scottish Gaelic) 2023-10-08 21:13:19 +02:00
Eugen Rochko
1d093ce928 New translations strings.xml (Sinhala) 2023-10-08 21:13:18 +02:00
Eugen Rochko
46b711af2e New translations strings.xml (Bosnian) 2023-10-08 21:13:17 +02:00
Eugen Rochko
772e6ddb5d New translations strings.xml (Filipino) 2023-10-08 21:13:16 +02:00
Eugen Rochko
f84e8443d2 New translations strings.xml (Burmese) 2023-10-08 21:13:15 +02:00
Eugen Rochko
250c18ebf1 New translations strings.xml (Hindi) 2023-10-08 21:13:14 +02:00
Eugen Rochko
e3d0f38b79 New translations strings.xml (Croatian) 2023-10-08 21:13:14 +02:00
Eugen Rochko
c512f97783 New translations strings.xml (Bengali) 2023-10-08 21:13:13 +02:00
Eugen Rochko
0594680775 New translations strings.xml (Persian) 2023-10-08 21:13:12 +02:00
Eugen Rochko
f999881f59 New translations strings.xml (Indonesian) 2023-10-08 21:13:11 +02:00
Eugen Rochko
4fe9192ac6 New translations strings.xml (Portuguese, Brazilian) 2023-10-08 21:13:10 +02:00
Eugen Rochko
d936702fa9 New translations strings.xml (Galician) 2023-10-08 21:13:08 +02:00
Eugen Rochko
74e284b0de New translations strings.xml (Vietnamese) 2023-10-08 21:13:07 +02:00
Eugen Rochko
4c42b72ed8 New translations strings.xml (Chinese Simplified) 2023-10-08 21:13:06 +02:00
Eugen Rochko
0e0046df65 New translations strings.xml (Turkish) 2023-10-08 21:13:05 +02:00
Eugen Rochko
c80d1d10c2 New translations strings.xml (Slovenian) 2023-10-08 21:13:05 +02:00
Eugen Rochko
da97971011 New translations strings.xml (Portuguese) 2023-10-08 21:13:04 +02:00
Eugen Rochko
700447dbe7 New translations strings.xml (Polish) 2023-10-08 21:13:03 +02:00
Eugen Rochko
37e7b5ee93 New translations strings.xml (Norwegian) 2023-10-08 21:13:02 +02:00
Eugen Rochko
1265afa93f New translations strings.xml (Dutch) 2023-10-08 21:13:01 +02:00
Eugen Rochko
1e09481b02 New translations strings.xml (Korean) 2023-10-08 21:13:00 +02:00
Eugen Rochko
9996a5a05e New translations strings.xml (Armenian) 2023-10-08 21:12:59 +02:00
Eugen Rochko
f20aac7c81 New translations strings.xml (Hungarian) 2023-10-08 21:12:58 +02:00
Eugen Rochko
98f7b0bacd New translations strings.xml (Hebrew) 2023-10-08 21:12:57 +02:00
Eugen Rochko
3f6d3fb3a2 New translations strings.xml (Irish) 2023-10-08 21:12:56 +02:00
Eugen Rochko
663b49c76b New translations strings.xml (Basque) 2023-10-08 21:12:55 +02:00
Eugen Rochko
16e38f2541 New translations strings.xml (Greek) 2023-10-08 21:12:54 +02:00
Eugen Rochko
842cc55e47 New translations strings.xml (German) 2023-10-08 21:12:53 +02:00
Eugen Rochko
72db099e6f New translations strings.xml (Danish) 2023-10-08 21:12:52 +02:00
Eugen Rochko
be130bc3a7 New translations strings.xml (Czech) 2023-10-08 21:12:51 +02:00
Eugen Rochko
42253336e1 New translations strings.xml (Catalan) 2023-10-08 21:12:50 +02:00
Eugen Rochko
572631e1d7 New translations strings.xml (Belarusian) 2023-10-08 21:12:49 +02:00
Eugen Rochko
723777a800 New translations strings.xml (Arabic) 2023-10-08 21:12:48 +02:00
Eugen Rochko
b825d534c1 New translations strings.xml (Spanish) 2023-10-08 21:12:47 +02:00
Eugen Rochko
b9749620a8 New translations strings.xml (French) 2023-10-08 21:12:46 +02:00
Eugen Rochko
70ea9989aa New translations strings.xml (Romanian) 2023-10-08 21:12:45 +02:00
Eugen Rochko
b3ec9c981c New translations strings.xml (Chinese Traditional) 2023-10-08 21:12:44 +02:00
Eugen Rochko
bf72085abb New translations strings.xml (Finnish) 2023-10-08 21:12:43 +02:00
Eugen Rochko
64dd416b59 New translations strings.xml (Russian) 2023-10-08 21:12:42 +02:00
Eugen Rochko
ab2a920455 New translations strings.xml (Swedish) 2023-10-08 21:12:41 +02:00
Eugen Rochko
7580446d60 New translations strings.xml (Italian) 2023-10-08 21:12:40 +02:00
Eugen Rochko
ade18ac6fc New translations strings.xml (Ukrainian) 2023-10-08 21:12:39 +02:00
Eugen Rochko
005c851d72 New translations strings.xml (Japanese) 2023-10-08 21:12:38 +02:00
Eugen Rochko
0f1d46c765 New translations strings.xml (Icelandic) 2023-10-08 21:12:36 +02:00
Eugen Rochko
21fbb07b1d New translations strings.xml (Thai) 2023-10-08 21:12:36 +02:00
sk
58e0ce3970 add tooltips 2023-10-08 16:36:56 +02:00
sk
139a7d7c98 change settings order 2023-10-08 16:36:48 +02:00
sk
a1c81e89e8 don't show labels per default 2023-10-08 16:36:41 +02:00
sk
a5c197b496 hearts everywhere
closes sk22#846
2023-10-08 13:04:26 +02:00
sk
df49ef9d58 redder red 2023-10-08 12:54:59 +02:00
sk
f747d4c979 toggle label font weight according to m3 2023-10-08 12:49:29 +02:00
sk
98677cd307 on page change listener may also load first page 2023-10-08 11:48:59 +02:00
sk
cd3de97d55 Merge remote-tracking branch 'upstream/l10n_master' 2023-10-07 23:47:36 +02:00
sk
4853a25710 bump version 2023-10-07 23:47:01 +02:00
SomeTr
eba9a1da7b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (18 of 18 strings)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-10-05 07:53:12 +00:00
Eugen Rochko
c17745368d New translations strings.xml (Italian) 2023-10-04 22:24:20 +02:00
Eugen Rochko
e78b518654 New translations strings.xml (Ukrainian) 2023-10-04 18:44:26 +02:00
Eugen Rochko
55a8634be2 New translations strings.xml (Japanese) 2023-10-04 16:52:54 +02:00
Eugen Rochko
ac891eea53 New translations strings.xml (Icelandic) 2023-10-04 15:28:52 +02:00
Eugen Rochko
74fa2a3081 New translations strings.xml (Thai) 2023-10-03 21:27:27 +02:00
Grishka
6c1c5b7759 Merge branch 'l10n_master' 2023-10-03 03:53:50 +03:00
Grishka
1f4152b588 Fix #705 and improve handling of unknown attachment dimensions 2023-10-03 02:52:07 +03:00
Grishka
70386ea1b2 Update appkit to finally fix that ViewPager2 crash 2023-10-03 02:11:04 +03:00
Eugen Rochko
cbce90c461 New translations strings.xml (Sinhala) 2023-10-02 21:16:49 +02:00
Eugen Rochko
74ae3bf706 New translations strings.xml (Armenian) 2023-10-02 07:26:27 +02:00
Grishka
1feccdc26d Fixes 2023-10-01 23:11:33 +03:00
Eugen Rochko
c38c2a425b New translations strings.xml (Indonesian) 2023-10-01 17:30:46 +02:00
Eugen Rochko
f43352b790 New translations strings.xml (Indonesian) 2023-10-01 16:30:51 +02:00
Grishka
c5b52b2781 Fix default server not loading sometimes 2023-10-01 12:17:21 +03:00
Grishka
b91840fb95 Another attempt to fix ZoomPanView crash 2023-10-01 07:16:21 +03:00
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
Eugen Rochko
e40841c128 New translations strings.xml (Portuguese, Brazilian) 2023-09-30 20:26:54 +02:00
sk
a21a74a8e7 bonk version 2023-09-30 19:26:15 +02:00
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
sk
3f2e6d2be6 remove log.d call 2023-07-17 22:53:16 +02:00
sk
a74c285f77 fix alt text editor theme
closes sk22#700
2023-07-17 22:51:45 +02:00
sk
8aec4a2717 fix inconsistent spoiler state, apparently?
closes sk22#711
2023-07-17 22:23:45 +02:00
sk
a106193039 use fediverse string 2023-07-17 21:59:39 +02:00
sk
5736605771 Merge remote-tracking branch 'weblate/main' 2023-07-17 21:56:19 +02:00
sk
9f0a710e40 update string 2023-07-17 21:55:33 +02:00
sk
f6f8cfb8e8 fix m3 filled disabled button opacity 2023-07-17 21:49:05 +02:00
sk
4e28f011dd Merge remote-tracking branch 'upstream/master' 2023-07-17 21:35:24 +02:00
Grishka
4bb255e0bb Fix #619 2023-07-17 20:25:08 +03:00
sk22
a48d545989 Translated using Weblate (French)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-07-17 04:03:22 +00:00
EndermanCo
e58cf54a5a Translated using Weblate (Persian)
Currently translated at 98.2% (329 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-07-17 04:03:22 +00:00
gicorada
2a45776f01 Translated using Weblate (Italian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/it/
2023-07-17 04:03:21 +00:00
michaelmathy
332aa906b5 Translated using Weblate (French)
Currently translated at 88.8% (16 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fr/
2023-07-17 04:03:21 +00:00
ihor_ck
ef624dfe30 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-07-17 04:03:21 +00:00
Hudobni Volk
1b0b9bedea Translated using Weblate (Slovenian)
Currently translated at 91.9% (308 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/sl/
2023-07-17 04:03:21 +00:00
alextecplayz
e2672bea6e Translated using Weblate (Romanian)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-07-17 04:03:21 +00:00
Oliebol
5916001462 Translated using Weblate (Dutch)
Currently translated at 80.5% (270 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-07-17 04:03:21 +00:00
gicorada
cacd403b89 Translated using Weblate (Italian)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/it/
2023-07-17 04:03:21 +00:00
Espasant3
2732f710da Translated using Weblate (Galician)
Currently translated at 96.1% (322 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-07-17 04:03:21 +00:00
michaelmathy
070268eafd Translated using Weblate (French)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-07-17 04:03:21 +00:00
sk22
7149971351 Translated using Weblate (German)
Currently translated at 100.0% (335 of 335 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-07-17 04:03:21 +00:00
sk
f46668c023 Merge remote-tracking branch 'upstream/master' into fix-divergent-branches 2023-07-16 18:24:59 +02:00
sk
74d0937078 Merge remote-tracking branch 'weblate/main' 2023-07-16 18:05:55 +02:00
EndermanCo
b236e88ef2 Translated using Weblate (Persian)
Currently translated at 95.1% (312 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-07-16 16:02:44 +00:00
Gaemy
12acf6761b Translated using Weblate (Spanish)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/es/
2023-07-16 16:02:44 +00:00
Gaemy
8522f1fd29 Translated using Weblate (Spanish)
Currently translated at 100.0% (328 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-07-16 16:02:44 +00:00
sk22
7677ad39ca Merge upstream redesign (#714)
* merge toolbar fragment

* Fix store screenshot generator

* Fix alert color

* Fix #609

* Fix crash

* bigger hitbox for chips

* support mastodon languages

* merge ui utils

* merge stuff

* fix icon

* ensure 48dp touch target

* init local prefs, add helper function for enum values

* update compose action layout

* merge compose-adj files

* update extended footer

* fix poll wrong option checked

closes sk22#641

* no border when disabled

closes sk22#640

* Fix #610

* Minor fixes

* Fix alert color

* Fix #609

* Fix crash

* Fix #610

* Minor fixes

* add resources

* more compatible mastodon language

* fix html parser

* mark as read on refresh

* update tab bar

* tweak m3 buttons

* update compose-adj files

* tweak and update styles

* m3 expand button

* flag icon should be 18dp, actually

* More minor fixes

closes #612

* More minor fixes

closes #612

* Bump version

* fix no create status event when redrafting

* add material 3 assets

* New translations strings.xml (Greek)

* New translations strings.xml (Greek)

* New translations strings.xml (Italian)

* New translations strings.xml (Greek)

* New translations strings.xml (Italian)

* New translations strings.xml (Thai)

* New translations strings.xml (Thai)

* New translations strings.xml (Italian)

* New translations strings.xml (Thai)

* use new buttons for profile fragment

* merge compose fragment

* merge all the styles! oh dear

* New translations full_description.txt (Indonesian)

* New translations full_description.txt (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations full_description.txt (Chinese Simplified)

* Fix #615

* Minor fixes

* Fix #611

* A bunch of crash fixes

* New translations strings.xml (Greek)

* Make the default server configurable

* Pass the system timezone to server when signing up

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Japanese)

* Fix #615

* Minor fixes

* Fix #611

* A bunch of crash fixes

* Make the default server configurable

* Pass the system timezone to server when signing up

* oops. accidentally pasted the commit message in the code

* Remove unused code that caused a crash for some users ¯\_(ツ)_/¯

* New translations strings.xml (Japanese)

* New translations strings.xml (Japanese)

* Remove unused code that caused a crash for some users ¯\_(ツ)_/¯

* New translations strings.xml (Polish)

* New translations strings.xml (Polish)

* New translations strings.xml (Turkish)

* New translations strings.xml (Belarusian)

* prepare merging profile fragment

* merge profile fragment

* New translations strings.xml (Belarusian)

* New translations strings.xml (Greek)

* fix icon padding

* apply post header changes

* minor margin tweaks

* fix footer buttons

* fix header announcement buttons

* New translations strings.xml (Japanese)

* New translations strings.xml (Japanese)

* New translations strings.xml (Japanese)

* New translations strings.xml (Japanese)

* New translations strings.xml (Japanese)

* New translations strings.xml (Japanese)

* New translations full_description.txt (Japanese)

* New translations strings.xml (Icelandic)

* New translations strings.xml (Icelandic)

* New translations strings.xml (Icelandic)

* fix replying

* New translations strings.xml (Icelandic)

* fix translate button

* fix more button visibility

* fix counts label styling

* fix disabled boost button opacity

* fix tab layouts

* fix notification icon color crash

* New translations strings.xml (Greek)

* implement elevation listener in home tab

* fix elevation and listener in home tab

* add elevation scroll listener to notifications

* New translations strings.xml (Scottish Gaelic)

* Add editorconfig

So that PRs like #625 don't happen again

* Crash fix

* 🤔

* New translations strings.xml (Greek)

* New translations strings.xml (Japanese)

* New translations strings.xml (French)

* New translations strings.xml (French)

* New translations strings.xml (French)

* fix notification elevation and integrate divider

* 🤔

* Crash fix

* Add editorconfig

So that PRs like #625 don't happen again

* New translations strings.xml (Turkish)

* save interactions in cache

* New translations strings.xml (Turkish)

* merge new discover/search

* New translations strings.xml (Bengali)

* New translations strings.xml (Scottish Gaelic)

* New translations strings.xml (Bengali)

* merge new settings fragments

* fix no auth callback always being executed

* allow opening server info from profile

closes sk22#593

* fix hide boosts icon color

closes sk22#676

* New translations strings.xml (Turkish)

* New translations strings.xml (Turkish)

* New translations strings.xml (Turkish)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Turkish)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (German)

* New translations strings.xml (German)

* New translations strings.xml (Turkish)

* update fedinuke list

from source; doesn't contain any modifications regarding a recent issue

* New translations strings.xml (Turkish)

* remove unused class

* fix crash

* darken m3 outline color a bit

* use m3 outline again

* fix misalignment

closes sk22#682

* New translations strings.xml (Turkish)

* New translations full_description.txt (Turkish)

* New translations short_description.txt (Turkish)

* fix crash

* fix metadata sorting

* show pronouns in header/account lists

* fix broken divider line

closes sk22#679

* trim pronouns

* improve pronoun display

* New translations strings.xml (French)

* New translations strings.xml (Japanese)

* fix broken federated timeline

closes sk22#685

* fix broken -1 fallback behavior

closes sk22#681

* don't display nothing if server about request fails

closes sk22#678

* New translations strings.xml (Ukrainian)

* migrate global prefs to local prefs

* do confirm unfollow by default

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations full_description.txt (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Russian)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Vietnamese)

* New translations full_description.txt (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* make sure list in prefs are always mutable and nut null

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Russian)

* fix pronouns edge case

* add back fix for stretched images

closes sk22#636

* fix null pointer on missing default posting language

* fix default posting language not being applied

* bigger username hitbox

closes sk22#688

* fix rtl header username alignment

closes sk22#689

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* hopefully fix crashes

closes sk22#692

* New translations strings.xml (Ukrainian)

* New translations full_description.txt (Ukrainian)

* fix pronoun crash

* New translations strings.xml (Persian)

* New translations strings.xml (Ukrainian)

* re-add true black mode

* asterisk can be a pronoun

* New translations strings.xml (Persian)

* true black mode fixes and clean-ups

* material 3 button background for switcher

* darker tab bar selected background

* better align follow/following button widths

* restore rainbow refresh colors

* fix search transition

* fix min width issue with switcher button

* fix no elevation when true black is enabled in light theme

* use statusForContent to determine spoilerRevealed

closes sk22#694

* New translations strings.xml (Persian)

* New translations strings.xml (Persian)

* New translations strings.xml (Persian)

* New translations strings.xml (Persian)

* New translations strings.xml (Persian)

* New translations strings.xml (Persian)

* fix profile tab bar in true black theme

* fix m3 default button style

closes sk22#697

* prettier role badges

closes sk22#663

* fix translate button spacing

closes sk22#655

* use m3 switches in dialogs

closes sk22#653

* implement color palette switcher

* fix color palettes being overwritten

* add display and notification settings

* clean up code

* per-account single notification setting

* add missing items to notification types

* add prefix replies setting

* add show replies/boosts and reply visibility

* add load/see new posts settings

* fix spectator mode missing spoiler padding

* add a bunch of display settings

* update fedinuke

* add content type settings

* add settings for local-onlu

* add missing settings items

* fix visibility button icon tint

* hopefully fix some crashes

* normalize padding above edit text

* apparently, some people don't like pills

closes sk22#706

* fix play button color

closes sk22#705
2023-07-16 18:01:42 +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
Grishka
91b4dc412b Merge branch 'l10n_master' 2023-07-13 05:59:52 +03:00
nemobis
3cfea0e660 Fix typo in README.md (#701) 2023-07-12 00:22:17 +02:00
Eugen Rochko
c896fd8df8 New translations strings.xml (Persian) 2023-07-10 20:31:28 +02:00
Eugen Rochko
72cd987284 New translations strings.xml (Persian) 2023-07-10 19:33:53 +02:00
Eugen Rochko
d868d05080 New translations strings.xml (Persian) 2023-07-10 18:35:13 +02:00
Eugen Rochko
9dfb039a69 New translations strings.xml (Persian) 2023-07-10 17:14:05 +02:00
Eugen Rochko
f2920e877b New translations strings.xml (Persian) 2023-07-10 14:23:55 +02:00
Eugen Rochko
ecb0b3f9d7 New translations strings.xml (Persian) 2023-07-10 13:27:29 +02:00
EndermanCo
13ff54663c Translated using Weblate (Persian)
Currently translated at 94.5% (310 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-07-10 08:38:05 +00:00
AiOO
604d9b008a Translated using Weblate (Korean)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ko/
2023-07-10 08:38:04 +00:00
AiOO
7044a69a71 Translated using Weblate (Korean)
Currently translated at 100.0% (328 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ko/
2023-07-10 08:38:04 +00:00
Eugen Rochko
67c128be69 New translations strings.xml (Persian) 2023-07-09 20:48:43 +02:00
Eugen Rochko
1ecbbc2d4b New translations strings.xml (Ukrainian) 2023-07-09 19:53:27 +02:00
Eugen Rochko
82c481b014 New translations strings.xml (Persian) 2023-07-09 19:53:26 +02:00
Eugen Rochko
e7da6d7897 New translations full_description.txt (Ukrainian) 2023-07-09 18:55:26 +02:00
Eugen Rochko
e9f1e3038b New translations strings.xml (Ukrainian) 2023-07-09 18:55:25 +02:00
Eugen Rochko
a591096819 New translations strings.xml (Ukrainian) 2023-07-09 17:59:09 +02:00
Eugen Rochko
f1ef60475f New translations strings.xml (Ukrainian) 2023-07-09 14:35:38 +02:00
Eugen Rochko
ce75bb3984 New translations strings.xml (Russian) 2023-07-08 15:46:20 +02:00
Eugen Rochko
d59235e04c New translations strings.xml (Ukrainian) 2023-07-08 13:47:53 +02:00
Eugen Rochko
42f8f7e58f New translations strings.xml (Ukrainian) 2023-07-08 12:50:49 +02:00
Eugen Rochko
f9cbc9ae27 New translations strings.xml (Ukrainian) 2023-07-08 11:54:51 +02:00
Eugen Rochko
a50d6599bf New translations strings.xml (Ukrainian) 2023-07-08 10:56:13 +02:00
Eugen Rochko
cc578b496e New translations strings.xml (Vietnamese) 2023-07-08 09:39:17 +02:00
Eugen Rochko
662944d246 New translations strings.xml (Ukrainian) 2023-07-08 09:39:16 +02:00
Eugen Rochko
42810df4a5 New translations full_description.txt (Ukrainian) 2023-07-08 08:40:55 +02:00
Eugen Rochko
c3a058d2e1 New translations strings.xml (Vietnamese) 2023-07-08 08:40:54 +02:00
Eugen Rochko
ce5d835ae5 New translations strings.xml (Ukrainian) 2023-07-08 08:40:53 +02:00
Eugen Rochko
60dd561729 New translations strings.xml (Vietnamese) 2023-07-08 07:25:45 +02:00
Eugen Rochko
08c9f9ad7d New translations strings.xml (Russian) 2023-07-08 03:34:48 +02:00
Eugen Rochko
d47907906d New translations strings.xml (Ukrainian) 2023-07-07 23:24:33 +02:00
Eugen Rochko
935f0f6e05 New translations strings.xml (Ukrainian) 2023-07-07 22:27:12 +02:00
Eugen Rochko
ac0b21d574 New translations strings.xml (Ukrainian) 2023-07-07 21:27:12 +02:00
Eugen Rochko
9b5d05369f New translations strings.xml (Ukrainian) 2023-07-07 20:30:29 +02:00
Eugen Rochko
489f9f5e59 New translations strings.xml (Ukrainian) 2023-07-07 19:30:49 +02:00
Espasant3
e401dc8d6e Translated using Weblate (Galician)
Currently translated at 98.7% (324 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-07-07 16:38:05 +00:00
FineFindus
3e89087aba Translated using Weblate (German)
Currently translated at 100.0% (328 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-07-07 16:38:04 +00:00
Eugen Rochko
e62f7c23c9 New translations full_description.txt (Ukrainian) 2023-07-07 18:29:20 +02:00
Eugen Rochko
91ed7d49b5 New translations strings.xml (Ukrainian) 2023-07-07 18:29:19 +02:00
Eugen Rochko
c9e9abd811 New translations strings.xml (Ukrainian) 2023-07-07 17:11:15 +02:00
Eugen Rochko
b1e5023f62 New translations strings.xml (Ukrainian) 2023-07-07 16:10:53 +02:00
Eugen Rochko
604690b3f5 New translations strings.xml (Japanese) 2023-07-07 11:50:09 +02:00
Eugen Rochko
13ada6ecc6 New translations strings.xml (French) 2023-07-07 10:47:28 +02:00
Eugen Rochko
b897eb913e New translations short_description.txt (Turkish) 2023-07-06 22:08:52 +02:00
Eugen Rochko
c25602a650 New translations full_description.txt (Turkish) 2023-07-06 22:08:51 +02:00
Eugen Rochko
2defc9af3f New translations strings.xml (Turkish) 2023-07-06 22:08:50 +02:00
Eugen Rochko
446525389b New translations strings.xml (Turkish) 2023-07-06 21:11:01 +02:00
Eugen Rochko
756b30d04f New translations strings.xml (Turkish) 2023-07-06 20:15:49 +02:00
Eugen Rochko
51ec842815 New translations strings.xml (German) 2023-07-06 20:15:48 +02:00
Eugen Rochko
c38822849e New translations strings.xml (German) 2023-07-06 19:01:48 +02:00
Eugen Rochko
3c69201f67 New translations strings.xml (Chinese Simplified) 2023-07-06 19:01:46 +02:00
Eugen Rochko
ed9d701406 New translations strings.xml (Turkish) 2023-07-06 17:29:14 +02:00
Eugen Rochko
e70c5aa2e9 New translations strings.xml (Chinese Simplified) 2023-07-06 17:29:13 +02:00
Eugen Rochko
0c4589b257 New translations strings.xml (Turkish) 2023-07-06 15:04:45 +02:00
Eugen Rochko
84d08392fb New translations strings.xml (Turkish) 2023-07-06 14:03:25 +02:00
Eugen Rochko
8ff117308d New translations strings.xml (Turkish) 2023-07-06 13:06:03 +02:00
Eugen Rochko
b6c703adbc New translations strings.xml (Bengali) 2023-07-05 18:26:29 +02:00
Eugen Rochko
22e6934de5 New translations strings.xml (Scottish Gaelic) 2023-07-05 17:21:45 +02:00
Eugen Rochko
1b8a1d69ac New translations strings.xml (Bengali) 2023-07-05 17:21:43 +02:00
Eugen Rochko
b6ae83937b New translations strings.xml (Turkish) 2023-07-05 16:09:20 +02:00
Eugen Rochko
7115556663 New translations strings.xml (Turkish) 2023-07-05 15:08:07 +02:00
Eugen Rochko
cb3296661e New translations strings.xml (French) 2023-07-05 09:09:16 +02:00
Eugen Rochko
6dd20a6df9 New translations strings.xml (French) 2023-07-05 07:58:16 +02:00
Eugen Rochko
71c1d0e59a New translations strings.xml (French) 2023-07-04 21:49:47 +02:00
Eugen Rochko
2b275e1ff7 New translations strings.xml (Japanese) 2023-07-04 15:23:25 +02:00
EndermanCo
8520ee42cb Translated using Weblate (Persian)
Currently translated at 85.9% (282 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-07-04 09:38:04 +00:00
Eugen Rochko
a7fcae1033 New translations strings.xml (Greek) 2023-07-04 11:27:42 +02:00
Grishka
19bd189b33 🤔 2023-07-04 02:53:48 +03:00
Grishka
2d5089c047 Crash fix 2023-07-04 02:51:50 +03:00
Grishka
be7469bd54 Merge branch 'l10n_master' 2023-07-04 01:36:27 +03:00
Grishka
146d8daa6e Add editorconfig
So that PRs like #625 don't happen again
2023-07-04 01:05:55 +03:00
Eugen Rochko
f3928d9e09 New translations strings.xml (Scottish Gaelic) 2023-07-03 19:38:09 +02:00
Eugen Rochko
d4090d459d New translations strings.xml (Greek) 2023-07-03 17:49:55 +02:00
Eugen Rochko
7dd7554c08 New translations strings.xml (Icelandic) 2023-07-03 15:01:45 +02:00
Eugen Rochko
9de9a1d97d New translations strings.xml (Icelandic) 2023-07-03 13:53:20 +02:00
Eugen Rochko
04ee366fbe New translations strings.xml (Icelandic) 2023-07-03 12:41:02 +02:00
Eugen Rochko
c8784150fc New translations strings.xml (Icelandic) 2023-07-03 11:41:40 +02:00
Eugen Rochko
7b7bccb37a New translations full_description.txt (Japanese) 2023-07-03 10:26:59 +02:00
Eugen Rochko
84e2636bca New translations strings.xml (Japanese) 2023-07-03 10:26:58 +02:00
Eugen Rochko
dc73613b56 New translations strings.xml (Japanese) 2023-07-03 09:22:45 +02:00
Eugen Rochko
fd8868ef4d New translations strings.xml (Japanese) 2023-07-03 08:20:20 +02:00
alextecplayz
f443d659a3 Translated using Weblate (Romanian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ro/
2023-07-03 05:59:10 +00:00
EndermanCo
24eb82a79d Translated using Weblate (Persian)
Currently translated at 80.4% (264 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-07-03 05:59:10 +00:00
alextecplayz
43d806fb01 Translated using Weblate (Romanian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ro/
2023-07-03 05:59:10 +00:00
Eugen Rochko
127df0b8e0 New translations strings.xml (Japanese) 2023-07-03 07:24:53 +02:00
Eugen Rochko
c2989df902 New translations strings.xml (Japanese) 2023-07-03 06:27:42 +02:00
Eugen Rochko
3f9ee99b69 New translations strings.xml (Japanese) 2023-07-03 05:11:26 +02:00
FineFindus
d47c4e63d7 fix(hashtag-timeline): add additional tags as separated parameter (#647) 2023-07-03 01:32:56 +02:00
Eugen Rochko
7204c4e804 New translations strings.xml (Greek) 2023-07-02 23:45:59 +02:00
Eugen Rochko
f1131cf8e7 New translations strings.xml (Belarusian) 2023-07-02 23:45:58 +02:00
Eugen Rochko
5b9a8beb07 New translations strings.xml (Belarusian) 2023-07-02 22:43:38 +02:00
Eugen Rochko
9e18e35c66 New translations strings.xml (Turkish) 2023-07-02 21:45:11 +02:00
Eugen Rochko
9db3dfa955 New translations strings.xml (Polish) 2023-07-02 21:45:11 +02:00
Eugen Rochko
4a3e56d300 New translations strings.xml (Polish) 2023-07-02 20:27:01 +02:00
Eugen Rochko
3bb4125c50 New translations strings.xml (Japanese) 2023-07-02 19:31:37 +02:00
Eugen Rochko
ce58883618 New translations strings.xml (Japanese) 2023-07-02 18:03:47 +02:00
Grishka
e7d3c60bac Remove unused code that caused a crash for some users ¯\_(ツ)_/¯ 2023-07-02 18:52:46 +03:00
Eugen Rochko
08e90139ad New translations strings.xml (Japanese) 2023-07-02 16:35:17 +02:00
Eugen Rochko
0d4dc34453 New translations strings.xml (Chinese Simplified) 2023-07-02 16:35:16 +02:00
Grishka
fe142c4626 Pass the system timezone to server when signing up 2023-07-02 16:35:19 +03:00
Grishka
d8dfa6017d Make the default server configurable 2023-07-02 16:22:32 +03:00
Eugen Rochko
b7a5d4296b New translations strings.xml (Greek) 2023-07-02 11:19:25 +02:00
Grishka
85d4c1fc24 A bunch of crash fixes 2023-07-02 12:03:21 +03:00
Grishka
66489d79be Fix #611 2023-07-02 11:04:20 +03:00
Grishka
30a66a26c6 Minor fixes 2023-07-02 10:50:01 +03:00
Grishka
fbc3081e68 Fix #615 2023-07-02 09:54:53 +03:00
EndermanCo
4ac7615cfb Translated using Weblate (Persian)
Currently translated at 77.7% (255 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-07-02 05:38:04 +00:00
Andrewblasco
a96b0d06a4 Translated using Weblate (Spanish)
Currently translated at 100.0% (328 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-07-02 05:38:04 +00:00
Eugen Rochko
7a66c94907 New translations full_description.txt (Chinese Simplified) 2023-07-02 06:12:35 +02:00
Eugen Rochko
4e38bc5769 New translations strings.xml (Chinese Simplified) 2023-07-02 06:12:34 +02:00
Eugen Rochko
0dc428dbd6 New translations full_description.txt (Chinese Simplified) 2023-07-02 04:57:11 +02:00
Eugen Rochko
6ac7fc94ea New translations full_description.txt (Indonesian) 2023-07-02 01:46:05 +02:00
Eugen Rochko
af28ed1783 New translations strings.xml (Thai) 2023-07-01 15:02:48 +02:00
Eugen Rochko
00daf084f2 New translations strings.xml (Italian) 2023-07-01 15:02:47 +02:00
Eugen Rochko
5518848e28 New translations strings.xml (Thai) 2023-07-01 13:40:15 +02:00
Eugen Rochko
6ded856b2f New translations strings.xml (Thai) 2023-07-01 12:30:46 +02:00
Eugen Rochko
d302f5132e New translations strings.xml (Italian) 2023-07-01 12:30:44 +02:00
Eugen Rochko
012e29ee3a New translations strings.xml (Greek) 2023-07-01 12:30:43 +02:00
Eugen Rochko
dbe9579d7f New translations strings.xml (Italian) 2023-07-01 11:28:20 +02:00
Eugen Rochko
c8d0221d9b New translations strings.xml (Greek) 2023-07-01 11:28:19 +02:00
Eugen Rochko
885c663d93 New translations strings.xml (Greek) 2023-07-01 10:06:44 +02:00
Grishka
3b399d5815 Merge branch 'l10n_master'
# Conflicts:
#	mastodon/src/main/res/values-ar-rSA/strings.xml
#	mastodon/src/main/res/values-be-rBY/strings.xml
#	mastodon/src/main/res/values-bn-rBD/strings.xml
#	mastodon/src/main/res/values-bs-rBA/strings.xml
#	mastodon/src/main/res/values-ca-rES/strings.xml
#	mastodon/src/main/res/values-cs-rCZ/strings.xml
#	mastodon/src/main/res/values-da-rDK/strings.xml
#	mastodon/src/main/res/values-de-rDE/strings.xml
#	mastodon/src/main/res/values-el-rGR/strings.xml
#	mastodon/src/main/res/values-es-rES/strings.xml
#	mastodon/src/main/res/values-eu-rES/strings.xml
#	mastodon/src/main/res/values-fa-rIR/strings.xml
#	mastodon/src/main/res/values-fil-rPH/strings.xml
#	mastodon/src/main/res/values-fr-rFR/strings.xml
#	mastodon/src/main/res/values-gd-rGB/strings.xml
#	mastodon/src/main/res/values-gl-rES/strings.xml
#	mastodon/src/main/res/values-hr-rHR/strings.xml
#	mastodon/src/main/res/values-hu-rHU/strings.xml
#	mastodon/src/main/res/values-hy-rAM/strings.xml
#	mastodon/src/main/res/values-in-rID/strings.xml
#	mastodon/src/main/res/values-is-rIS/strings.xml
#	mastodon/src/main/res/values-it-rIT/strings.xml
#	mastodon/src/main/res/values-iw-rIL/strings.xml
#	mastodon/src/main/res/values-ja-rJP/strings.xml
#	mastodon/src/main/res/values-kab/strings.xml
#	mastodon/src/main/res/values-ko-rKR/strings.xml
#	mastodon/src/main/res/values-my-rMM/strings.xml
#	mastodon/src/main/res/values-nl-rNL/strings.xml
#	mastodon/src/main/res/values-no-rNO/strings.xml
#	mastodon/src/main/res/values-oc-rFR/strings.xml
#	mastodon/src/main/res/values-pl-rPL/strings.xml
#	mastodon/src/main/res/values-pt-rBR/strings.xml
#	mastodon/src/main/res/values-pt-rPT/strings.xml
#	mastodon/src/main/res/values-ro-rRO/strings.xml
#	mastodon/src/main/res/values-ru-rRU/strings.xml
#	mastodon/src/main/res/values-si-rLK/strings.xml
#	mastodon/src/main/res/values-sl-rSI/strings.xml
#	mastodon/src/main/res/values-sv-rSE/strings.xml
#	mastodon/src/main/res/values-th-rTH/strings.xml
#	mastodon/src/main/res/values-tr-rTR/strings.xml
#	mastodon/src/main/res/values-uk-rUA/strings.xml
#	mastodon/src/main/res/values-vi-rVN/strings.xml
#	mastodon/src/main/res/values-zh-rCN/strings.xml
#	mastodon/src/main/res/values-zh-rTW/strings.xml
2023-06-30 20:37:37 +03:00
Grishka
9f18d1bc8b Bump version 2023-06-30 20:35:42 +03:00
Grishka
9a8cf61e38 More minor fixes
closes #612
2023-06-30 20:28:52 +03:00
Grishka
eb822282c0 Minor fixes 2023-06-30 09:18:48 +03:00
Grishka
273823a65f Fix #610 2023-06-30 08:25:05 +03:00
Grishka
f2aa1400c5 Fix crash 2023-06-29 22:31:15 +03:00
Grishka
cf8a9e1823 Fix #609 2023-06-29 22:29:51 +03:00
Grishka
b3320d534b Fix alert color 2023-06-29 22:24:35 +03:00
Grishka
f58b4c2989 Fix store screenshot generator 2023-06-29 22:17:59 +03:00
Eugen Rochko
47b3b1e307 New translations strings.xml (Hungarian) 2023-06-29 14:26:58 +02:00
Eugen Rochko
e5649c4a42 New translations strings.xml (Czech) 2023-06-29 14:26:57 +02:00
Eugen Rochko
cea77eca02 New translations strings.xml (Czech) 2023-06-29 13:11:45 +02:00
Eugen Rochko
4fbbcfba59 New translations strings.xml (Czech) 2023-06-29 12:06:31 +02:00
Eugen Rochko
6a24f70537 New translations strings.xml (Hungarian) 2023-06-29 11:09:23 +02:00
Eugen Rochko
4681160924 New translations strings.xml (Czech) 2023-06-29 11:09:22 +02:00
Eugen Rochko
6f8c2f4a44 New translations strings.xml (Hungarian) 2023-06-29 10:05:05 +02:00
Eugen Rochko
69de9dce38 New translations strings.xml (Italian) 2023-06-28 23:16:22 +02:00
Eugen Rochko
721ae9c68d New translations strings.xml (Thai) 2023-06-28 21:11:38 +02:00
Eugen Rochko
6a6ed89d29 New translations strings.xml (Thai) 2023-06-28 20:08:17 +02:00
Eugen Rochko
3e4377a366 New translations strings.xml (Italian) 2023-06-28 20:08:16 +02:00
Gregory K
287de66e0c Merge pull request #608 from sk22/fix/polls-update-content-status
Fix poll not updating content status when boosted
2023-06-28 18:29:12 +03:00
sk
df2ae9d964 fix poll not updating status when boosted
closes sk22#403
2023-06-28 17:24:46 +02:00
sk
277282d7f5 fix first notifications loaded multiple times
closes sk22#616
2023-06-28 01:37:21 +02:00
gallegonovato
4f983829b7 Translated using Weblate (Spanish)
Currently translated at 99.3% (326 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-06-27 20:02:28 +00:00
Eugen Rochko
ef55f1f49b New translations strings.xml (Thai) 2023-06-27 21:03:47 +02:00
Eugen Rochko
aebf7e9f1f New translations strings.xml (Thai) 2023-06-27 19:57:32 +02:00
Eugen Rochko
a014ce6eb5 New translations strings.xml (Thai) 2023-06-27 19:00:48 +02:00
Eugen Rochko
aed1efceb9 New translations strings.xml (Thai) 2023-06-27 17:58:06 +02:00
Eugen Rochko
124c375b14 New translations strings.xml (Thai) 2023-06-27 16:58:01 +02:00
Eugen Rochko
1d46e22a7f New translations strings.xml (Dutch) 2023-06-27 16:58:00 +02:00
Eugen Rochko
28c334429d New translations strings.xml (Czech) 2023-06-27 16:57:59 +02:00
Eugen Rochko
4726f98d4f New translations strings.xml (Thai) 2023-06-27 15:47:31 +02:00
Eugen Rochko
985f382436 New translations strings.xml (Thai) 2023-06-27 14:36:42 +02:00
Eugen Rochko
f822c788b0 New translations strings.xml (Armenian) 2023-06-27 13:38:29 +02:00
sk
664851cac5 update build.gradle 2023-06-27 09:40:59 +02:00
Eugen Rochko
46f8982aa6 New translations strings.xml (Vietnamese) 2023-06-27 05:57:29 +02:00
Eugen Rochko
7b3cec9289 New translations strings.xml (Vietnamese) 2023-06-27 04:48:02 +02:00
Eugen Rochko
a3f227cb8d New translations strings.xml (Thai) 2023-06-26 22:51:56 +02:00
Eugen Rochko
d71feb2cfc New translations strings.xml (Thai) 2023-06-26 21:47:41 +02:00
Grishka
b69565e9e6 Fix polls 2023-06-26 16:09:02 +03:00
Grishka
bf996feccf Add fading edge for alt text
closes #595
2023-06-26 16:04:24 +03:00
Eugen Rochko
b1143b0eec New translations strings.xml (Slovenian) 2023-06-26 08:30:13 +02:00
Codeberg Translate
4c6914c271 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/
2023-06-26 06:02:28 +00:00
tuongdai252
e03a62064e Translated using Weblate (Vietnamese)
Currently translated at 73.4% (241 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/vi/
2023-06-26 06:02:28 +00:00
Eugen Rochko
ce977163c2 New translations strings.xml (Slovenian) 2023-06-26 07:28:02 +02:00
Eugen Rochko
ded74bda83 New translations strings.xml (Chinese Traditional) 2023-06-26 04:01:11 +02:00
Eugen Rochko
7180113397 New translations strings.xml (Chinese Traditional) 2023-06-26 02:37:02 +02:00
Grishka
81ac8a3bc9 More minor fixes 2023-06-25 14:24:13 +03:00
Grishka
4b74da5d38 Fix another color 2023-06-25 09:50:44 +03:00
Eugen Rochko
0375cfa260 New translations short_description.txt (Vietnamese) 2023-06-25 05:27:54 +02:00
Eugen Rochko
52108a675a New translations full_description.txt (Arabic) 2023-06-25 05:26:57 +02:00
Grishka
406e95d3f7 Fix #606 2023-06-25 06:19:36 +03:00
Grishka
a9f355dea9 Fix text colors 2023-06-25 03:12:17 +03:00
Eugen Rochko
1a63c12327 New translations strings.xml (Urdu (India)) 2023-06-25 02:07:40 +02:00
Eugen Rochko
7bc2f8b352 New translations strings.xml (Kabyle) 2023-06-25 02:07:39 +02:00
Eugen Rochko
d56ef227a7 New translations strings.xml (Igbo) 2023-06-25 02:07:38 +02:00
Eugen Rochko
fcc371ab5a New translations strings.xml (Occitan) 2023-06-25 02:07:37 +02:00
Eugen Rochko
9f43a99772 New translations strings.xml (Scottish Gaelic) 2023-06-25 02:07:36 +02:00
Eugen Rochko
9fc5a4e390 New translations strings.xml (Sinhala) 2023-06-25 02:07:35 +02:00
Eugen Rochko
a2b3f873f6 New translations strings.xml (Bosnian) 2023-06-25 02:07:34 +02:00
Eugen Rochko
f6c1509e48 New translations strings.xml (Filipino) 2023-06-25 02:07:33 +02:00
Eugen Rochko
d9bbb32a28 New translations strings.xml (Burmese) 2023-06-25 02:07:32 +02:00
Eugen Rochko
d2ef6e77af New translations strings.xml (Hindi) 2023-06-25 02:07:31 +02:00
Eugen Rochko
6133e9bac3 New translations strings.xml (Croatian) 2023-06-25 02:07:30 +02:00
Eugen Rochko
6ca48f35f1 New translations strings.xml (Thai) 2023-06-25 02:07:29 +02:00
Eugen Rochko
cb016b4383 New translations strings.xml (Bengali) 2023-06-25 02:07:28 +02:00
Eugen Rochko
d0b21df28b New translations strings.xml (Portuguese, Brazilian) 2023-06-25 02:07:28 +02:00
Eugen Rochko
ce48ee888a New translations strings.xml (Icelandic) 2023-06-25 02:07:27 +02:00
Eugen Rochko
5cd8bc5a46 New translations strings.xml (Galician) 2023-06-25 02:07:26 +02:00
Eugen Rochko
4109cd75d3 New translations strings.xml (Vietnamese) 2023-06-25 02:07:25 +02:00
Eugen Rochko
97a889e019 New translations strings.xml (Chinese Traditional) 2023-06-25 02:07:24 +02:00
Eugen Rochko
b9a1b3591d New translations strings.xml (Ukrainian) 2023-06-25 02:07:22 +02:00
Eugen Rochko
fde84e3cfb New translations strings.xml (Turkish) 2023-06-25 02:07:21 +02:00
Eugen Rochko
0985eb4fac New translations strings.xml (Swedish) 2023-06-25 02:07:21 +02:00
Eugen Rochko
5957c1a221 New translations strings.xml (Slovenian) 2023-06-25 02:07:20 +02:00
Eugen Rochko
e7e34aa2c8 New translations strings.xml (Russian) 2023-06-25 02:07:19 +02:00
Eugen Rochko
b8742591b8 New translations strings.xml (Portuguese) 2023-06-25 02:07:17 +02:00
Eugen Rochko
50e73ac12e New translations strings.xml (Polish) 2023-06-25 02:07:16 +02:00
Eugen Rochko
9ed277a9b2 New translations strings.xml (Norwegian) 2023-06-25 02:07:15 +02:00
Eugen Rochko
c9e2984d68 New translations strings.xml (Dutch) 2023-06-25 02:07:14 +02:00
Eugen Rochko
00aaff10a7 New translations strings.xml (Korean) 2023-06-25 02:07:13 +02:00
Eugen Rochko
6da4256adf New translations strings.xml (Japanese) 2023-06-25 02:07:12 +02:00
Eugen Rochko
da9c826791 New translations strings.xml (Italian) 2023-06-25 02:07:11 +02:00
Eugen Rochko
bed02e248e New translations strings.xml (Armenian) 2023-06-25 02:07:11 +02:00
Eugen Rochko
f53531889f New translations strings.xml (Hungarian) 2023-06-25 02:07:10 +02:00
Eugen Rochko
ef3246ae2a New translations strings.xml (Hebrew) 2023-06-25 02:07:09 +02:00
Eugen Rochko
5504b534f7 New translations strings.xml (Irish) 2023-06-25 02:07:08 +02:00
Eugen Rochko
0e553d7868 New translations strings.xml (Finnish) 2023-06-25 02:07:07 +02:00
Eugen Rochko
38be51367e New translations strings.xml (Basque) 2023-06-25 02:07:06 +02:00
Eugen Rochko
0d4af0970c New translations strings.xml (Greek) 2023-06-25 02:07:05 +02:00
Eugen Rochko
b51335ffd6 New translations strings.xml (German) 2023-06-25 02:07:04 +02:00
Eugen Rochko
444fa07984 New translations strings.xml (Danish) 2023-06-25 02:07:03 +02:00
Eugen Rochko
feb0f304fb New translations strings.xml (Catalan) 2023-06-25 02:07:02 +02:00
Eugen Rochko
ccd4a1aa9f New translations strings.xml (Belarusian) 2023-06-25 02:07:01 +02:00
Eugen Rochko
b3723c2977 New translations strings.xml (Arabic) 2023-06-25 02:07:00 +02:00
Eugen Rochko
c5b70a9ada New translations strings.xml (Spanish) 2023-06-25 02:06:59 +02:00
Eugen Rochko
82789179e7 New translations strings.xml (French) 2023-06-25 02:06:58 +02:00
Eugen Rochko
b0a309a817 New translations strings.xml (Romanian) 2023-06-25 02:06:57 +02:00
Eugen Rochko
32a27b6e59 New translations strings.xml (Chinese Simplified) 2023-06-25 02:06:56 +02:00
Eugen Rochko
f73a318dad New translations strings.xml (Czech) 2023-06-25 02:06:55 +02:00
Eugen Rochko
4fff2c5f5c New translations strings.xml (Persian) 2023-06-25 02:06:54 +02:00
Eugen Rochko
81abac657f New translations strings.xml (Indonesian) 2023-06-25 02:06:53 +02:00
Grishka
74decd3ec7 Merge branch 'm3_redesign' 2023-06-25 02:55:26 +03:00
Grishka
11d17d1f3f Fix ripple color again 2023-06-25 02:52:54 +03:00
Grishka
ca2384ba8c Remove unused resources and fix ripple colors 2023-06-25 02:41:45 +03:00
Grishka
ded23342db Fix layout in post edit history 2023-06-25 01:46:17 +03:00
Grishka
a35c14865f Refresh the notifications list when it's opened 2023-06-25 01:28:08 +03:00
Grishka
0952d97557 Unified account row 2023-06-25 01:18:38 +03:00
Grishka
e1db5f15ca M3 redesign: search/discover 2023-06-24 22:56:55 +03:00
Espasant3
a99741c732 Translated using Weblate (Galician)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/gl/
2023-06-24 17:02:27 +00:00
ihor_ck
d6c8e8afc1 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-06-24 17:02:26 +00:00
Linerly
879981e335 Translated using Weblate (Indonesian)
Currently translated at 100.0% (328 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-06-24 17:02:26 +00:00
Choukajohn
87840dd731 Translated using Weblate (French)
Currently translated at 100.0% (328 of 328 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-06-24 17:02:26 +00:00
Grishka
c9e467ac2f And again 2023-06-24 10:43:28 +03:00
Grishka
74a5e970d9 Fix #604 again 2023-06-23 23:07:28 +03:00
Eugen Rochko
935de7d02e New translations strings.xml (Chinese Simplified) 2023-06-23 13:54:49 +02:00
Codeberg Translate
cb8fddd156 Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/
2023-06-22 23:12:23 +00:00
Andrewblasco
40ca834880 Translated using Weblate (Spanish)
Currently translated at 100.0% (309 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-06-22 23:12:23 +00:00
ling0412
c10206ef6f Translated using Weblate (Chinese (Simplified))
Currently translated at 95.4% (295 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-06-22 23:12:23 +00:00
sk22
e752d10e31 Translated using Weblate (German)
Currently translated at 100.0% (309 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-06-22 23:12:23 +00:00
EndermanCo
119bbc2b5c Translated using Weblate (Persian)
Currently translated at 82.2% (254 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-06-22 23:12:23 +00:00
Oliebol
6136260adc Translated using Weblate (Dutch)
Currently translated at 87.3% (270 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-06-22 23:12:23 +00:00
Andrewblasco
979ee9fdff Translated using Weblate (Spanish)
Currently translated at 100.0% (309 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-06-22 23:12:23 +00:00
EndermanCo
ab71e06ef1 Translated using Weblate (Persian)
Currently translated at 80.9% (250 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-06-22 23:12:23 +00:00
Linerly
6d487f011f Translated using Weblate (Indonesian)
Currently translated at 100.0% (309 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-06-22 23:12:23 +00:00
gallegonovato
50403cf674 Translated using Weblate (Spanish)
Currently translated at 100.0% (309 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-06-22 23:12:23 +00:00
EndermanCo
276df264cf Translated using Weblate (Persian)
Currently translated at 57.6% (178 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-06-22 23:12:23 +00:00
ihor_ck
55e18cf9af Translated using Weblate (Ukrainian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-06-22 23:12:23 +00:00
ihor_ck
e5392d3265 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (309 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-06-22 23:12:23 +00:00
LucasGGamerM
2de7c1d3b9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.6% (308 of 309 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-06-22 23:12:23 +00:00
LucasGGamerM
c21885139c Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (305 of 305 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-06-22 23:12:23 +00:00
EndermanCo
275cbaa924 Translated using Weblate (Persian)
Currently translated at 54.7% (167 of 305 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fa/
2023-06-22 23:12:23 +00:00
abidin24
4b6c35c9c0 Translated using Weblate (Arabic)
Currently translated at 10.4% (32 of 305 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ar/
2023-06-22 23:12:23 +00:00
ihor_ck
d339fa1e12 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (18 of 18 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-06-22 23:12:23 +00:00
Alfika07
31a5fc9153 Translated using Weblate (Hungarian)
Currently translated at 40.3% (123 of 305 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/hu/
2023-06-22 23:12:23 +00:00
ihor_ck
966f758d9f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (305 of 305 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-06-22 23:12:23 +00:00
LucasGGamerM
6979c5097d Translated using Weblate (Portuguese (Brazil))
Currently translated at 85.2% (260 of 305 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pt_BR/
2023-06-22 23:12:23 +00:00
a_mento
68005c762f Translated using Weblate (Basque)
Currently translated at 94.0% (287 of 305 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/eu/
2023-06-22 23:12:23 +00:00
FineFindus
06543d5fc2 fix(edit-timelines/hashtags): set tags as list (#596)
* fix(edit-timelines/hashtags): set tags as list

Fixes https://github.com/sk22/megalodon/issues/595, by setting the tags a tags and not as a list.

* refactor(edit-timelines/hashtags): set all tag terminator at once

* feat(edit-timelines/hashtags): add comment
2023-06-21 22:34:22 +02:00
sk
a70d39065c add some new icons 2023-06-21 10:44:54 +02:00
sk
dc75882cee slightly adjust margin
closes sk22#585
2023-06-21 10:32:41 +02:00
sk
e47f253c0e unset name if hashtag is applied from name
closes sk22#588
2023-06-21 10:27:57 +02:00
sk
d05f3932b2 fix empty hashtag when editing 2023-06-21 10:24:50 +02:00
FineFindus
be425282a6 Hashtag timelines with multiple tags (#584)
* feat(api/hashtag): add any, all, and none parameter

* feat(timeline/hashtag): load with any, all and none parameter

* feat(timeline/hashtag): save any, all and none in timeline definition

* feat: set hastag parameter in UI

* feat: move strings to string res

* feat: show hint for tags

* refactor: use method for setting up tags text

* improve edit dialog, allow creating hashtag timelines

* add chips for hashtags

* add option for displaying only local posts in hashtag

* improve layout and wording

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-06-21 01:38:51 +02:00
Grishka
a04522ff72 Fix #605 2023-06-20 22:56:37 +03:00
Grishka
90a93ffba6 Fix #604 2023-06-20 22:54:09 +03:00
Eugen Rochko
069c55d4b9 New translations strings.xml (Persian) 2023-06-17 20:19:46 +01:00
Eugen Rochko
1efaf4b605 New translations strings.xml (Persian) 2023-06-17 19:21:04 +01:00
Eugen Rochko
ff74516ce2 New translations strings.xml (Persian) 2023-06-17 17:57:39 +01:00
Eugen Rochko
aee4b7aaab New translations full_description.txt (Persian) 2023-06-17 16:23:41 +01:00
Eugen Rochko
1d94479bde New translations strings.xml (Persian) 2023-06-17 16:23:40 +01:00
Eugen Rochko
85c95d899e New translations full_description.txt (Persian) 2023-06-17 15:24:06 +01:00
Eugen Rochko
18d4210e7d New translations strings.xml (Persian) 2023-06-17 15:24:05 +01:00
Eugen Rochko
ac25bc6d42 New translations strings.xml (Persian) 2023-06-17 14:17:46 +01:00
Eugen Rochko
ec05ef3b4c New translations full_description.txt (Persian) 2023-06-16 21:57:49 +01:00
Eugen Rochko
9827d97374 New translations strings.xml (Persian) 2023-06-16 21:57:48 +01:00
Eugen Rochko
4fab92d516 New translations strings.xml (Persian) 2023-06-16 20:43:12 +01:00
Eugen Rochko
44bc9c4e40 New translations strings.xml (Persian) 2023-06-16 19:47:01 +01:00
Eugen Rochko
1030773ef6 New translations strings.xml (Persian) 2023-06-16 18:50:21 +01:00
Eugen Rochko
1a0cb4b8c8 New translations strings.xml (Persian) 2023-06-16 17:47:36 +01:00
Eugen Rochko
4295a3672c New translations strings.xml (Persian) 2023-06-16 16:32:57 +01:00
Eugen Rochko
fd2a8fe230 New translations strings.xml (Persian) 2023-06-16 15:14:44 +01:00
Eugen Rochko
e2d1eccfb9 New translations strings.xml (Persian) 2023-06-16 14:17:09 +01:00
sk
bb4a52f03a fix threads opened from notification 2023-06-15 22:44:46 +02:00
sk
50360059ce fix margins 2023-06-15 20:38:47 +02:00
LucasGGamerM
63bcef990b feat: use correct coloring for new exclusive list explanation
cc: @sk22
2023-06-15 20:34:51 +02:00
sk
94eb6b5775 one less thing for parceler to complain about 2023-06-15 19:21:44 +02:00
sk
6595a088fb support exclusive lists
closes sk22#576
2023-06-15 19:21:26 +02:00
sk
b463ef65ce fix parceler complaining about private class member 2023-06-15 18:13:59 +02:00
sk
b22a25e7af workaround for proguard errors
re: sk22#572
2023-06-15 18:12:29 +02:00
Eugen Rochko
05d1d3e725 New translations full_description.txt (Persian) 2023-06-15 10:57:44 +01:00
Eugen Rochko
37261928c2 New translations full_description.txt (Persian) 2023-06-15 09:56:31 +01:00
Eugen Rochko
63132110d9 New translations full_description.txt (Persian) 2023-06-15 08:46:22 +01:00
Eugen Rochko
ffd7e415a2 New translations full_description.txt (Persian) 2023-06-15 07:42:12 +01:00
Eugen Rochko
d43664d018 New translations full_description.txt (Persian) 2023-06-15 06:36:15 +01:00
Eugen Rochko
8e06362ff8 New translations full_description.txt (Persian) 2023-06-14 20:11:35 +01:00
Eugen Rochko
625ccfd31f New translations strings.xml (Persian) 2023-06-14 20:11:34 +01:00
Eugen Rochko
d7b5d242ff New translations strings.xml (Persian) 2023-06-14 19:12:25 +01:00
Eugen Rochko
7973c87b9a New translations strings.xml (Persian) 2023-06-14 18:16:52 +01:00
Eugen Rochko
3085b1507b New translations strings.xml (Persian) 2023-06-14 16:46:46 +01:00
Eugen Rochko
f17ef17492 New translations strings.xml (Persian) 2023-06-14 15:49:02 +01:00
Eugen Rochko
8d0a31d0f9 New translations strings.xml (Persian) 2023-06-14 15:47:38 +02:00
Eugen Rochko
6c6d3fed05 New translations strings.xml (Persian) 2023-06-14 14:49:35 +02:00
Eugen Rochko
b616e2e11b New translations strings.xml (Persian) 2023-06-14 13:42:47 +02:00
Eugen Rochko
b18771bb79 New translations full_description.txt (Persian) 2023-06-14 12:38:20 +02:00
Eugen Rochko
40ef4c179a New translations strings.xml (Persian) 2023-06-13 18:31:58 +02:00
Eugen Rochko
62212dc6c9 New translations strings.xml (Persian) 2023-06-13 17:23:01 +02:00
Eugen Rochko
3ed2b67037 New translations strings.xml (Czech) 2023-06-13 15:55:50 +02:00
Eugen Rochko
d240750606 New translations strings.xml (Persian) 2023-06-13 14:44:08 +02:00
Eugen Rochko
ed009d3e2e New translations strings.xml (Persian) 2023-06-13 13:15:59 +02:00
sk
3af7518cf4 disable proguard for now :/ 2023-06-13 12:20:03 +02:00
sk
91b9fdf5ce add gson proguard rules 2023-06-13 12:13:55 +02:00
Eugen Rochko
be2fa0f217 New translations strings.xml (Persian) 2023-06-13 12:09:09 +02:00
sk
22f5667549 fix javadoc 2023-06-13 11:50:57 +02:00
sk
8115578e94 boop version 2023-06-13 10:19:13 +02:00
sk
08dc122b6b tweak min/max heights 2023-06-13 10:07:30 +02:00
sk
e3199c009e hopefully fix typetoken crashes 2023-06-13 09:47:48 +02:00
Eugen Rochko
a8d9b4538b New translations strings.xml (Persian) 2023-06-12 22:41:37 +02:00
Eugen Rochko
2b7b3de043 New translations strings.xml (Persian) 2023-06-12 21:44:04 +02:00
sk
2e09010151 boop version 2023-06-12 20:44:25 +02:00
Grishka
aa9caefed1 Fix unread notifications 2023-06-12 18:54:46 +03:00
Grishka
14bbe1ffef Update the update icon 2023-06-12 18:50:06 +03:00
Grishka
17957b69d1 Filtered posts in timelines (AND-8) 2023-06-07 04:47:54 +03:00
Grishka
a24b4363d7 Bring toolbar appearance more in line with design 2023-06-05 04:46:21 +03:00
Grishka
02ddad22e7 Update profile menu (AND-34) 2023-06-05 04:32:18 +03:00
Grishka
a705512dc5 Update post options menu (AND-33) 2023-06-05 04:25:11 +03:00
Grishka
cfd6954755 Update launcher icon (AND-84) 2023-06-05 02:34:58 +03:00
Grishka
702ac43f86 Fix shortcut icon (AND-85) 2023-06-05 01:45:23 +03:00
Grishka
6ede2d22bb Fix #591 2023-06-04 23:02:48 +03:00
Grishka
315d26ad52 Settings tweaks 2023-06-04 05:32:27 +03:00
Grishka
a78b0687f7 Fix it again 2023-06-04 04:50:40 +03:00
Grishka
148b8e9369 Fix #583 2023-06-04 02:40:59 +03:00
Grishka
ae6ce0f9b0 Fix #548 2023-06-04 02:11:40 +03:00
Grishka
31c8665653 Settings M3 redesign wip 2023-06-04 02:04:55 +03:00
Grishka
7c6ec2e3d7 Fix #587 2023-05-30 21:32:08 +03:00
Grishka
31c7116a15 Merge branch 'master' into m3_redesign
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java
2023-05-28 20:58:27 +03:00
Grishka
5c480b37b3 Notifications M3 redesign (+ read marker support) 2023-05-27 13:31:01 +03:00
Grishka
92335d8678 Merge branch 'master' into m3_redesign 2023-05-25 21:02:17 +03:00
Grishka
1dcb5717ea Fix #553 2023-05-23 12:56:49 +03:00
Grishka
36f1a557d7 Fix default tab in profile 2023-05-23 12:17:26 +03:00
Grishka
bd7157c172 Reporting M3 redesign 2023-05-22 17:08:04 +03:00
Grishka
34a2af8429 Compose M3 redesign: custom emoji keyboard 2023-05-13 04:27:12 +03:00
Grishka
15883f2138 Compose: language selection 2023-05-12 22:21:21 +03:00
Grishka
89501271ce Compose autocomplete improvements 2023-05-12 03:39:46 +03:00
Grishka
968a6ea9b3 Merge branch 'master' into m3_redesign
# Conflicts:
#	mastodon/build.gradle
2023-05-11 05:49:34 +03:00
Grishka
e253d8f4f3 Pull user row into a separate view holder & update its design 2023-05-11 03:45:23 +03:00
Grishka
cfabe47e10 Select "timeline" tab in profiles by default 2023-05-10 02:10:19 +03:00
Grishka
d3fe7857b7 Move media & poll stuff out of ComposeFragment to separate classes 2023-05-09 23:59:56 +03:00
Grishka
642e96a439 Compose M3 redesign wip 2023-05-09 21:34:42 +03:00
Grishka
2b8451e045 Merge branch 'master' into m3_redesign 2023-05-01 21:38:31 +03:00
Grishka
62074e554a Account switcher M3 redesign 2023-05-01 13:43:00 +03:00
Grishka
0434cda2da Thread view M3 redesign 2023-04-30 23:04:45 +03:00
Grishka
4b4c88d44d Support opening mastodon.online and mastodon.social links 2023-04-17 06:47:49 +03:00
Grishka
9e116bec97 Merge branch 'master' into m3_redesign
# Conflicts:
#	mastodon/build.gradle
2023-04-12 19:41:39 +03:00
Grishka
dc25e16c00 fix 2023-04-09 01:48:54 +03:00
Grishka
42a7c324fa Merge branch 'master' into m3_redesign 2023-04-07 23:16:53 +03:00
Grishka
849888d128 Welcome fragment redesign again 2023-04-07 22:40:00 +03:00
Grishka
8a7e910e7c Reblog -> boost 2023-03-30 19:14:12 +03:00
Grishka
72d72d443e Limit aspect ratio to 9:20 2023-03-30 18:53:59 +03:00
Grishka
7b0a3f0f96 More media layout optimizations 2023-03-27 04:04:16 +03:00
Grishka
cf4604e0d8 More media layout optimizations 2023-03-24 01:33:33 +03:00
Grishka
e873dd7d0a New sensitive media design 2023-03-24 00:33:02 +03:00
Grishka
4492e940e5 Media attachments minor fixes 2023-03-23 22:36:52 +03:00
Grishka
c833c03dc3 New play button & alt text support for gifs & videos 2023-03-23 03:15:15 +03:00
Grishka
30b0d226b5 Tab bar M3 design 2023-03-23 01:49:39 +03:00
Grishka
8afad21113 Add "error" M3 button style 2023-03-23 01:09:03 +03:00
Grishka
6b5e5b0f25 More minor tweaks 2023-03-23 00:58:38 +03:00
Grishka
3c0ab6822f Add elevation effect stuff to a base class for all fragments that need it 2023-03-23 00:31:17 +03:00
Grishka
f7215d00ca Profile about tab redesign 2023-03-22 17:22:22 +03:00
Grishka
43bbe9be0f Fix LinkedTextView breaking XML layout editor 2023-03-22 17:01:13 +03:00
Grishka
477a691c9e Fix z-order and remove V.dp from more custom views 2023-03-22 16:55:09 +03:00
Grishka
e5d60050a2 Add filters to AccontTimelineFragment 2023-03-22 03:22:04 +03:00
Grishka
9a698fda18 Merge branch 'master' into m3_redesign 2023-03-22 02:47:13 +03:00
Grishka
d5701c1073 Merge branch 'master' into m3_redesign 2023-03-22 02:41:26 +03:00
Grishka
955b9a4b2b Featured tab in profiles 2023-03-22 02:30:42 +03:00
Grishka
09ffda2605 Merge branch 'master' into m3_redesign 2023-03-17 22:07:51 +03:00
Grishka
039fb0c505 Profile redesign: header 2023-03-17 22:07:28 +03:00
Grishka
20799ef1a8 Posts redesign wip 2023-03-14 19:17:37 +03:00
864 changed files with 40756 additions and 14909 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

@@ -10,7 +10,7 @@
&nbsp;
<a href="#installation"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
> A fork of the [Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app, focusing on [Glitch](https://glitch-soc.github.io/docs) compatibility, a pretty UI and adding new features that I feel make using the Fediverse a more pleasent experience.
> A fork of the [Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app, focusing on [Glitch](https://glitch-soc.github.io/docs) compatibility, a pretty UI and adding new features that I feel make using the Fediverse a more pleasant experience.
## Key features
@@ -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

279
mastodon/.editorconfig Normal file
View File

@@ -0,0 +1,279 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
insert_final_newline = false
max_line_length = 300
tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_visual_guides = none
ij_wrap_on_typing = false
[*.java]
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = false
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = true
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_align_types_in_multi_catch = true
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = off
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = off
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 0
ij_java_block_brace_style = end_of_line
ij_java_block_comment_add_space = false
ij_java_block_comment_at_first_column = true
ij_java_builder_methods = none
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = off
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = end_of_line
ij_java_class_count_to_use_import_on_demand = 99
ij_java_class_names_in_javadoc = 1
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_not_wrap_after_single_annotation_in_parameter = false
ij_java_do_while_brace_force = never
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = false
ij_java_doc_add_blank_line_after_return = false
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = false
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = false
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_enum_constants_wrap = off
ij_java_extends_keyword_wrap = off
ij_java_extends_list_wrap = off
ij_java_field_annotation_wrap = split_into_lines
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = never
ij_java_imports_layout = android.**,|,com.**,|,junit.**,|,net.**,|,org.**,|,java.**,|,javax.**,|,*,|,$*,|
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 2
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
ij_java_keep_blank_lines_in_code = 2
ij_java_keep_blank_lines_in_declarations = 2
ij_java_keep_builder_methods_indents = false
ij_java_keep_control_statement_in_one_line = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = false
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_add_space_on_reformat = false
ij_java_line_comment_at_first_column = true
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = end_of_line
ij_java_method_call_chain_wrap = off
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = off
ij_java_modifier_list_wrap = false
ij_java_multi_catch_types_wrap = normal
ij_java_names_count_to_use_import_on_demand = 99
ij_java_new_line_after_lparen_in_annotation = false
ij_java_new_line_after_lparen_in_record_header = false
ij_java_parameter_annotation_wrap = off
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = false
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_record_components_wrap = normal
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = false
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = off
ij_java_rparen_on_new_line_in_annotation = false
ij_java_rparen_on_new_line_in_record_header = false
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = false
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = false
ij_java_space_before_catch_keyword = false
ij_java_space_before_catch_left_brace = false
ij_java_space_before_catch_parentheses = false
ij_java_space_before_class_left_brace = false
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_do_left_brace = false
ij_java_space_before_else_keyword = false
ij_java_space_before_else_left_brace = false
ij_java_space_before_finally_keyword = false
ij_java_space_before_finally_left_brace = false
ij_java_space_before_for_left_brace = false
ij_java_space_before_for_parentheses = false
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = false
ij_java_space_before_if_parentheses = false
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = false
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = false
ij_java_space_before_switch_parentheses = false
ij_java_space_before_synchronized_left_brace = false
ij_java_space_before_synchronized_parentheses = false
ij_java_space_before_try_left_brace = false
ij_java_space_before_try_parentheses = false
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = false
ij_java_space_before_while_left_brace = false
ij_java_space_before_while_parentheses = false
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = false
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = false
ij_java_spaces_around_annotation_eq = true
ij_java_spaces_around_assignment_operators = false
ij_java_spaces_around_bitwise_operators = false
ij_java_spaces_around_equality_operators = false
ij_java_spaces_around_lambda_arrow = false
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = false
ij_java_spaces_around_relational_operators = false
ij_java_spaces_around_shift_operators = false
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = false
ij_java_spaces_within_array_initializer_braces = false
ij_java_spaces_within_braces = false
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = false
ij_java_spaces_within_for_parentheses = false
ij_java_spaces_within_if_parentheses = false
ij_java_spaces_within_method_call_parentheses = false
ij_java_spaces_within_method_parentheses = false
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_record_header = false
ij_java_spaces_within_switch_parentheses = false
ij_java_spaces_within_synchronized_parentheses = false
ij_java_spaces_within_try_parentheses = false
ij_java_spaces_within_while_parentheses = false
ij_java_special_else_if_treatment = true
ij_java_subclass_name_suffix = Impl
ij_java_ternary_operation_signs_on_next_line = false
ij_java_ternary_operation_wrap = off
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = off
ij_java_throws_list_wrap = off
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
ij_continuation_indent_size = 4
ij_xml_align_attributes = false
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
ij_xml_block_comment_add_space = false
ij_xml_block_comment_at_first_column = true
ij_xml_keep_blank_lines = 2
ij_xml_keep_indents_on_empty_lines = false
ij_xml_keep_line_breaks = false
ij_xml_keep_line_breaks_in_text = true
ij_xml_keep_whitespaces = false
ij_xml_keep_whitespaces_around_cdata = preserve
ij_xml_keep_whitespaces_inside_cdata = false
ij_xml_line_comment_at_first_column = true
ij_xml_space_after_tag_name = false
ij_xml_space_around_equals_in_attribute = false
ij_xml_space_inside_empty_tag = true
ij_xml_text_wrap = normal
ij_xml_use_custom_settings = true

View File

@@ -15,16 +15,16 @@ android {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 33
versionCode 92
versionName "1.2.3+fork.92"
versionCode 103
versionName "2.1.6+fork.103"
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']
}
buildTypes {
release {
// minifyEnabled true
// shrinkResources true
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug{
@@ -32,15 +32,9 @@ android {
versionNameSuffix '-debug'
applicationIdSuffix '.debug'
}
githubRelease{
initWith release
}
playRelease{
initWith release
minifyEnabled true
shrinkResources true
versionNameSuffix '-play'
}
githubRelease { initWith release }
playRelease { initWith release }
fdroidRelease { initWith release }
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
@@ -75,7 +69,8 @@ dependencies {
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
implementation 'me.grishka.litex:viewpager:1.0.0'
implementation 'me.grishka.litex:viewpager2:1.0.0'
implementation 'me.grishka.appkit:appkit:1.2.8'
implementation 'me.grishka.litex:palette:1.0.0'
implementation 'me.grishka.appkit:appkit:1.2.14'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'com.squareup:otto:1.3.8'
@@ -83,10 +78,11 @@ dependencies {
implementation 'org.parceler:parceler-api:1.1.12'
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:1.1.5'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'com.github.UnifiedPush:android-connector:2.1.1'
androidTestImplementation 'androidx.test:core:1.4.1-alpha05'
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

View File

@@ -30,6 +30,9 @@
*;
}
# i don't know how proguard works
-keep class org.joinmastodon.android.** { *; }
# Keep all enums for debugging purposes
-keepnames public enum * {
*;
@@ -46,3 +49,39 @@
-keep interface org.parceler.Parcel
-keep @org.parceler.Parcel class * { *; }
-keep class **$$Parcelable { *; }
##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
##---------------End: proguard configuration for Gson ----------
-dontobfuscate

View File

@@ -65,6 +65,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.*;
@LargeTest
public class StoreScreenshotsGenerator{
private static final String PHOTO_FILE="IMG_1010.jpg";
private static final long LOAD_WAIT_TIMEOUT=20_000;
@Rule
public ActivityScenarioRule<MainActivity> activityScenarioRule=new ActivityScenarioRule<>(MainActivity.class);
@@ -84,14 +85,14 @@ public class StoreScreenshotsGenerator{
AccountSession session=AccountSessionManager.getInstance().getAccount(AccountSessionManager.getInstance().getLastActiveAccountID());
MastodonApp.context.deleteDatabase(session.getID()+".db");
onView(isRoot()).perform(waitId(R.id.more, 5000));
onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT));
Thread.sleep(500);
takeScreenshot("HomeTimeline");
GlobalUserPreferences.theme=GlobalUserPreferences.ThemePreference.DARK;
activityScenarioRule.getScenario().recreate();
onView(isRoot()).perform(waitId(R.id.more, 5000));
onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT));
Thread.sleep(500);
takeScreenshot("HomeTimeline_Dark");
@@ -100,8 +101,8 @@ public class StoreScreenshotsGenerator{
activityScenarioRule.getScenario().onActivity(activity->UiUtils.openProfileByID(activity, session.getID(), args.getString("profileAccountID")));
Thread.sleep(500);
onView(isRoot()).perform(waitId(R.id.avatar_border, 5000)); // wait for profile to load
onView(isRoot()).perform(waitId(R.id.more, 5000)); // wait for timeline to load
onView(isRoot()).perform(waitId(R.id.avatar_border, LOAD_WAIT_TIMEOUT)); // wait for profile to load
onView(isRoot()).perform(waitId(R.id.more, LOAD_WAIT_TIMEOUT)); // wait for timeline to load
Thread.sleep(500);
takeScreenshot("Profile");

View File

@@ -2,15 +2,22 @@ package org.joinmastodon.android.ui.utils;
import static org.junit.Assert.*;
import android.content.Context;
import android.content.res.Resources;
import android.util.Pair;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.model.Instance;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
public class UiUtilsTest {
@@ -103,4 +110,152 @@ public class UiUtilsTest {
"somewhere.else"
));
}
private final String[] args = new String[] { "Megalodon", "" };
private String gen(String format, CharSequence... args) {
return UiUtils.generateFormattedString(format, args).toString();
}
@Test
public void generateFormattedString() {
assertEquals(
"ordered substitution",
"Megalodon reacted with ♡",
gen("%s reacted with %s", args)
);
assertEquals(
"1 2 3 4 5",
gen("%s %s %s %s %s", "1", "2", "3", "4", "5")
);
assertEquals(
"indexed substitution",
"with ♡ was reacted by Megalodon",
gen("with %2$s was reacted by %1$s", args)
);
assertEquals(
"indexed substitution, in order",
"Megalodon reacted with ♡",
gen("%1$s reacted with %2$s", args)
);
assertEquals(
"indexed substitution, 0-based",
"Megalodon reacted with ♡",
gen("%0$s reacted with %1$s", args)
);
assertEquals(
"indexed substitution, 5 items",
"5 4 3 2 1",
gen("%5$s %4$s %3$s %2$s %1$s", "1", "2", "3", "4", "5")
);
assertEquals(
"one argument missing",
"Megalodon reacted with ♡",
gen("reacted with %s", args)
);
assertEquals(
"multiple arguments missing",
"Megalodon reacted with ♡",
gen("reacted with", args)
);
assertEquals(
"multiple arguments missing, numbers in expeced positions",
"1 2 x 3 4 5",
gen("%s x %s", "1", "2", "3", "4", "5")
);
assertEquals(
"one leading and trailing space",
"Megalodon reacted with ♡",
gen(" reacted with ", args)
);
assertEquals(
"multiple leading and trailing spaces",
"Megalodon reacted with ♡",
gen(" reacted with ", args)
);
assertEquals(
"invalid format produces expected invalid result",
"Megalodon reacted with % s ♡",
gen("reacted with % s", args)
);
assertEquals(
"plain string as format, all arguments get added",
"a x b c",
gen("x", new String[] { "a", "b", "c" })
);
assertEquals("empty input produces empty output", "", gen(""));
// not supported:
// assertEquals("a b a", gen("%1$s %2$s %2$s %1$s", new String[] { "a", "b", "c" }));
// assertEquals("x", gen("%s %1$s %2$s %1$s %s", new String[] { "a", "b", "c" }));
}
private AccountField makeField(String name, String value) {
AccountField f = new AccountField();
f.name = name;
f.value = value;
return f;
}
private Account fakeAccount(AccountField... fields) {
Account a = new Account();
a.fields = Arrays.asList(fields);
return a;
}
@Test
public void extractPronouns() {
assertEquals("they", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("name and pronouns", "https://pronouns.site"),
makeField("pronouns", "they"),
makeField("pronouns something", "bla bla")
)).orElseThrow());
assertTrue(UiUtils.extractPronouns(MastodonApp.context, fakeAccount()).isEmpty());
assertEquals("it/its", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns pronouns pronouns", "hi hi hi"),
makeField("pronouns", "it/its"),
makeField("the pro's nouns", "professional")
)).orElseThrow());
assertEquals("she/he", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("my name is", "jeanette shork, apparently"),
makeField("my pronouns are", "she/he")
)).orElseThrow());
assertEquals("they/them", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "https://pronouns.cc/pronouns/they/them")
)).orElseThrow());
Context german = UiUtils.getLocalizedContext(MastodonApp.context, Locale.GERMAN);
assertEquals("sie/ihr", UiUtils.extractPronouns(german, fakeAccount(
makeField("pronomen lauten", "sie/ihr"),
makeField("pronouns are", "she/her"),
makeField("die pronomen", "stehen oben")
)).orElseThrow());
assertEquals("er/ihm", UiUtils.extractPronouns(german, fakeAccount(
makeField("die pronomen", "stehen unten"),
makeField("pronomen sind", "er/ihm"),
makeField("pronouns are", "he/him")
)).orElseThrow());
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "-- * (asterisk) --")
)).orElseThrow());
}
}

View File

@@ -1,10 +1,10 @@
package org.joinmastodon.android.utils;
import static org.joinmastodon.android.model.Filter.FilterAction.*;
import static org.joinmastodon.android.model.Filter.FilterContext.*;
import static org.joinmastodon.android.model.FilterAction.*;
import static org.joinmastodon.android.model.FilterContext.*;
import static org.junit.Assert.*;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Status;
import org.junit.Test;
@@ -14,8 +14,8 @@ import java.util.List;
public class StatusFilterPredicateTest {
private static final Filter hideMeFilter = new Filter(), warnMeFilter = new Filter();
private static final List<Filter> allFilters = List.of(hideMeFilter, warnMeFilter);
private static final LegacyFilter hideMeFilter = new LegacyFilter(), warnMeFilter = new LegacyFilter();
private static final List<LegacyFilter> allFilters = List.of(hideMeFilter, warnMeFilter);
private static final Status
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),

View File

@@ -100,8 +100,8 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
public void maybeCheckForUpdates(){
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
return;
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD);
if(timeSinceLastCheck>=CHECK_PERIOD){
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", 0);
if(timeSinceLastCheck>CHECK_PERIOD || forceUpdate){
setState(UpdateState.CHECKING);
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
}
@@ -148,7 +148,8 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
curForkNumber=Integer.parseInt(matcher.group(4));
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
if(newVersion>curVersion || newForkNumber>curForkNumber){
if(newVersion>curVersion || newForkNumber>curForkNumber || forceUpdate){
forceUpdate=false;
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
for(JsonElement el:obj.getAsJsonArray("assets")){
@@ -323,6 +324,15 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
}
}
@Override
public void reset(){
getPrefs().edit().clear().apply();
File apk=getUpdateApkFile();
if(apk.exists())
apk.delete();
state=UpdateState.NO_UPDATE;
}
/*public static class InstallerStatusReceiver extends BroadcastReceiver{
@Override

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"/>
@@ -28,7 +29,6 @@
android:supportsRtl="true"
android:localeConfig="@xml/locales_config"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Mastodon.AutoLightDark"
android:largeHeap="true">
@@ -46,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>
@@ -89,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

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

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<style>
*{
box-sizing: border-box;
overflow-wrap: break-word;
}
body{
background: {{colorSurface}};
padding: 16px 16px 0 16px;
margin: 0;
color: {{colorOnSurface}};
font-family: Roboto, sans-serif;
font-size: 14px;
line-height: 20px;
-webkit-tap-highlight-color: {{colorPrimaryTransparent}};
}
a{
text-decoration: none;
color: {{colorPrimary}};
}
p, h1, h2, h3, h4, h5, h6, ul, ol{
margin-bottom: 8px;
margin-top: 0;
}
h1, h2{
font-size: 16px;
line-height: 24px;
font-weight: 500;
}
h3, h4, h5, h6{
font-size: 14px;
line-height: 20px;
font-weight: 500;
}
b, strong{
font-weight: 500;
}
ul, ol{
padding-inline-start: 16px;
}
ul>li, ol>li{
padding-inline-start: 4px;
}
</style>
</head>
<body>
{{content}}
</body>
</html>

View File

@@ -0,0 +1,78 @@
package com.hootsuite.nachos;
import android.content.res.ColorStateList;
public class ChipConfiguration {
private final int mChipHorizontalSpacing;
private final ColorStateList mChipBackground;
private final int mChipCornerRadius;
private final int mChipTextColor;
private final int mChipTextSize;
private final int mChipHeight;
private final int mChipVerticalSpacing;
private final int mMaxAvailableWidth;
/**
* Creates a new ChipConfiguration. You can pass in {@code -1} or {@code null} for any of the parameters to indicate that parameter should be
* ignored.
*
* @param chipHorizontalSpacing the amount of horizontal space (in pixels) to put between consecutive chips
* @param chipBackground the {@link ColorStateList} to set as the background of the chips
* @param chipCornerRadius the corner radius of the chip background, in pixels
* @param chipTextColor the color to set as the text color of the chips
* @param chipTextSize the font size (in pixels) to use for the text of the chips
* @param chipHeight the height (in pixels) of each chip
* @param chipVerticalSpacing the amount of vertical space (in pixels) to put between chips on consecutive lines
* @param maxAvailableWidth the maximum available with for a chip (the width of a full line of text in the text view)
*/
ChipConfiguration(int chipHorizontalSpacing,
ColorStateList chipBackground,
int chipCornerRadius,
int chipTextColor,
int chipTextSize,
int chipHeight,
int chipVerticalSpacing,
int maxAvailableWidth) {
mChipHorizontalSpacing = chipHorizontalSpacing;
mChipBackground = chipBackground;
mChipCornerRadius = chipCornerRadius;
mChipTextColor = chipTextColor;
mChipTextSize = chipTextSize;
mChipHeight = chipHeight;
mChipVerticalSpacing = chipVerticalSpacing;
mMaxAvailableWidth = maxAvailableWidth;
}
public int getChipHorizontalSpacing() {
return mChipHorizontalSpacing;
}
public ColorStateList getChipBackground() {
return mChipBackground;
}
public int getChipCornerRadius() {
return mChipCornerRadius;
}
public int getChipTextColor() {
return mChipTextColor;
}
public int getChipTextSize() {
return mChipTextSize;
}
public int getChipHeight() {
return mChipHeight;
}
public int getChipVerticalSpacing() {
return mChipVerticalSpacing;
}
public int getMaxAvailableWidth() {
return mMaxAvailableWidth;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
package com.hootsuite.nachos.chip;
import androidx.annotation.Nullable;
public interface Chip {
/**
* @return the text represented by this Chip
*/
CharSequence getText();
/**
* @return the data associated with this Chip or null if no data is associated with it
*/
@Nullable
Object getData();
/**
* @return the width of the Chip or -1 if the Chip hasn't been given the chance to calculate its width
*/
int getWidth();
/**
* Sets the UI state.
*
* @param stateSet one of the state constants in {@link android.view.View}
*/
void setState(int[] stateSet);
}

View File

@@ -0,0 +1,44 @@
package com.hootsuite.nachos.chip;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hootsuite.nachos.ChipConfiguration;
/**
* Interface to allow the creation and configuration of chips
*
* @param <C> The type of {@link Chip} that the implementation will create/configure
*/
public interface ChipCreator<C extends Chip> {
/**
* Creates a chip from the given context and text. Use this method when creating a brand new chip from a piece of text.
*
* @param context the {@link Context} to use to initialize the chip
* @param text the text the Chip should represent
* @param data the data to associate with the Chip, or null to associate no data
* @return the created chip
*/
C createChip(@NonNull Context context, @NonNull CharSequence text, @Nullable Object data);
/**
* Creates a chip from the given context and existing chip. Use this method when recreating a chip from an existing one.
*
* @param context the {@link Context} to use to initialize the chip
* @param existingChip the chip that the created chip should be based on
* @return the created chip
*/
C createChip(@NonNull Context context, @NonNull C existingChip);
/**
* Applies the given {@link ChipConfiguration} to the given {@link Chip}. Use this method to customize the appearance/behavior of a chip before
* adding it to the text.
*
* @param chip the chip to configure
* @param chipConfiguration the configuration to apply to the chip
*/
void configureChip(@NonNull C chip, @NonNull ChipConfiguration chipConfiguration);
}

View File

@@ -0,0 +1,20 @@
package com.hootsuite.nachos.chip;
public class ChipInfo {
private final CharSequence mText;
private final Object mData;
public ChipInfo(CharSequence text, Object data) {
this.mText = text;
this.mData = data;
}
public CharSequence getText() {
return mText;
}
public Object getData() {
return mData;
}
}

View File

@@ -0,0 +1,510 @@
package com.hootsuite.nachos.chip;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.text.style.ImageSpan;
import androidx.annotation.Dimension;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.joinmastodon.android.R;
/**
* A Span that displays text and an optional icon inside of a material design chip. The chip's dimensions, colors etc. can be extensively customized
* through the various setter methods available in this class.
* The basic structure of the chip is the following:
* For chips with the icon on right:
* <pre>
*
* (chip vertical spacing / 2)
* ----------------------------------------------------------
* | |
* (left margin) | (padding edge) text (padding between image) icon | (right margin)
* | |
* ----------------------------------------------------------
* (chip vertical spacing / 2)
*
* </pre>
* For chips with the icon on the left (see {@link #setShowIconOnLeft(boolean)}):
* <pre>
*
* (chip vertical spacing / 2)
* ----------------------------------------------------------
* | |
* (left margin) | icon (padding between image) text (padding edge) | (right margin)
* | |
* ----------------------------------------------------------
* (chip vertical spacing / 2)
* </pre>
*/
public class ChipSpan extends ImageSpan implements Chip {
private static final float SCALE_PERCENT_OF_CHIP_HEIGHT = 0.70f;
private static final boolean ICON_ON_LEFT_DEFAULT = true;
private int[] mStateSet = new int[]{};
private String mEllipsis;
private ColorStateList mDefaultBackgroundColor;
private ColorStateList mBackgroundColor;
private int mTextColor;
private int mCornerRadius = -1;
private int mIconBackgroundColor;
private int mTextSize = -1;
private int mPaddingEdgePx;
private int mPaddingBetweenImagePx;
private int mLeftMarginPx;
private int mRightMarginPx;
private int mMaxAvailableWidth = -1;
private CharSequence mText;
private String mTextToDraw;
private Drawable mIcon;
private boolean mShowIconOnLeft = ICON_ON_LEFT_DEFAULT;
private int mChipVerticalSpacing = 0;
private int mChipHeight = -1;
private int mChipWidth = -1;
private int mIconWidth;
private int mCachedSize = -1;
private Object mData;
/**
* Constructs a new ChipSpan.
*
* @param context a {@link Context} that will be used to retrieve default configurations from resource files
* @param text the text for the ChipSpan to display
* @param icon an optional icon (can be {@code null}) for the ChipSpan to display
*/
public ChipSpan(@NonNull Context context, @NonNull CharSequence text, @Nullable Drawable icon, Object data) {
super(icon);
mIcon = icon;
mText = text;
mTextToDraw = mText.toString();
mEllipsis = context.getString(R.string.chip_ellipsis);
mDefaultBackgroundColor = context.getColorStateList(R.color.chip_material_background);
mBackgroundColor = mDefaultBackgroundColor;
mTextColor = context.getColor(R.color.chip_default_text_color);
mIconBackgroundColor = context.getColor(R.color.chip_default_icon_background_color);
Resources resources = context.getResources();
mPaddingEdgePx = resources.getDimensionPixelSize(R.dimen.chip_default_padding_edge);
mPaddingBetweenImagePx = resources.getDimensionPixelSize(R.dimen.chip_default_padding_between_image);
mLeftMarginPx = resources.getDimensionPixelSize(R.dimen.chip_default_left_margin);
mRightMarginPx = resources.getDimensionPixelSize(R.dimen.chip_default_right_margin);
mData = data;
}
/**
* Copy constructor to recreate a ChipSpan from an existing one
*
* @param context a {@link Context} that will be used to retrieve default configurations from resource files
* @param chipSpan the ChipSpan to copy
*/
public ChipSpan(@NonNull Context context, @NonNull ChipSpan chipSpan) {
this(context, chipSpan.getText(), chipSpan.getDrawable(), chipSpan.getData());
mDefaultBackgroundColor = chipSpan.mDefaultBackgroundColor;
mTextColor = chipSpan.mTextColor;
mIconBackgroundColor = chipSpan.mIconBackgroundColor;
mCornerRadius = chipSpan.mCornerRadius;
mTextSize = chipSpan.mTextSize;
mPaddingEdgePx = chipSpan.mPaddingEdgePx;
mPaddingBetweenImagePx = chipSpan.mPaddingBetweenImagePx;
mLeftMarginPx = chipSpan.mLeftMarginPx;
mRightMarginPx = chipSpan.mRightMarginPx;
mMaxAvailableWidth = chipSpan.mMaxAvailableWidth;
mShowIconOnLeft = chipSpan.mShowIconOnLeft;
mChipVerticalSpacing = chipSpan.mChipVerticalSpacing;
mChipHeight = chipSpan.mChipHeight;
mStateSet = chipSpan.mStateSet;
}
@Override
public Object getData() {
return mData;
}
/**
* Sets the height of the chip. This height should not include any extra spacing (for extra vertical spacing call {@link #setChipVerticalSpacing(int)}).
* The background of the chip will fill the full height provided here. If this method is never called, the chip will have the height of one full line
* of text by default. If {@code -1} is passed here, the chip will revert to this default behavior.
*
* @param chipHeight the height to set in pixels
*/
public void setChipHeight(int chipHeight) {
mChipHeight = chipHeight;
}
/**
* Sets the vertical spacing to include in between chips. Half of the value set here will be placed as empty space above the chip and half the value
* will be placed as empty space below the chip. Therefore chips on consecutive lines will have the full value as vertical space in between them.
* This spacing is achieved by adjusting the font metrics used by the text view containing these chips; however it does not come into effect until
* at least one chip is created. Note that vertical spacing is dependent on having a fixed chip height (set in {@link #setChipHeight(int)}). If a
* height is not specified in that method, the value set here will be ignored.
*
* @param chipVerticalSpacing the vertical spacing to set in pixels
*/
public void setChipVerticalSpacing(int chipVerticalSpacing) {
mChipVerticalSpacing = chipVerticalSpacing;
}
/**
* Sets the font size for the chip's text. If this method is never called, the chip text will have the same font size as the text in the TextView
* containing this chip by default. If {@code -1} is passed here, the chip will revert to this default behavior.
*
* @param size the font size to set in pixels
*/
public void setTextSize(int size) {
mTextSize = size;
invalidateCachedSize();
}
/**
* Sets the color for the chip's text.
*
* @param color the color to set (as a hexadecimal number in the form 0xAARRGGBB)
*/
public void setTextColor(int color) {
mTextColor = color;
}
/**
* Sets where the icon (if an icon was provided in the constructor) will appear.
*
* @param showIconOnLeft if true, the icon will appear on the left, otherwise the icon will appear on the right
*/
public void setShowIconOnLeft(boolean showIconOnLeft) {
this.mShowIconOnLeft = showIconOnLeft;
invalidateCachedSize();
}
/**
* Sets the left margin. This margin will appear as empty space (it will not share the chip's background color) to the left of the chip.
*
* @param leftMarginPx the left margin to set in pixels
*/
public void setLeftMargin(int leftMarginPx) {
mLeftMarginPx = leftMarginPx;
invalidateCachedSize();
}
/**
* Sets the right margin. This margin will appear as empty space (it will not share the chip's background color) to the right of the chip.
*
* @param rightMarginPx the right margin to set in pixels
*/
public void setRightMargin(int rightMarginPx) {
this.mRightMarginPx = rightMarginPx;
invalidateCachedSize();
}
/**
* Sets the background color. To configure which color in the {@link ColorStateList} is shown you can call {@link #setState(int[])}.
* Passing {@code null} here will cause the chip to revert to it's default background.
*
* @param backgroundColor a {@link ColorStateList} containing backgrounds for different states.
* @see #setState(int[])
*/
public void setBackgroundColor(@Nullable ColorStateList backgroundColor) {
mBackgroundColor = backgroundColor != null ? backgroundColor : mDefaultBackgroundColor;
}
/**
* Sets the chip background corner radius.
*
* @param cornerRadius The corner radius value, in pixels.
*/
public void setCornerRadius(@Dimension int cornerRadius) {
mCornerRadius = cornerRadius;
}
/**
* Sets the icon background color. This is the color of the circle that gets drawn behind the icon passed to the
* {@link #ChipSpan(Context, CharSequence, Drawable, Object)} constructor}
*
* @param iconBackgroundColor the icon background color to set (as a hexadecimal number in the form 0xAARRGGBB)
*/
public void setIconBackgroundColor(int iconBackgroundColor) {
mIconBackgroundColor = iconBackgroundColor;
}
public void setMaxAvailableWidth(int maxAvailableWidth) {
mMaxAvailableWidth = maxAvailableWidth;
invalidateCachedSize();
}
/**
* Sets the UI state. This state will be reflected in the background color drawn for the chip.
*
* @param stateSet one of the state constants in {@link android.view.View}
* @see #setBackgroundColor(ColorStateList)
*/
@Override
public void setState(int[] stateSet) {
this.mStateSet = stateSet != null ? stateSet : new int[]{};
}
@Override
public CharSequence getText() {
return mText;
}
@Override
public int getWidth() {
// If we haven't actually calculated a chip width yet just return -1, otherwise return the chip width + margins
return mChipWidth != -1 ? (mLeftMarginPx + mChipWidth + mRightMarginPx) : -1;
}
@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
boolean usingFontMetrics = (fm != null);
// Adjust the font metrics regardless of whether or not there is a cached size so that the text view can maintain its height
if (usingFontMetrics) {
adjustFontMetrics(paint, fm);
}
if (mCachedSize == -1 && usingFontMetrics) {
mIconWidth = (mIcon != null) ? calculateChipHeight(fm.top, fm.bottom) : 0;
int actualWidth = calculateActualWidth(paint);
mCachedSize = actualWidth;
if (mMaxAvailableWidth != -1) {
int maxAvailableWidthMinusMargins = mMaxAvailableWidth - mLeftMarginPx - mRightMarginPx;
if (actualWidth > maxAvailableWidthMinusMargins) {
mTextToDraw = mText + mEllipsis;
while ((calculateActualWidth(paint) > maxAvailableWidthMinusMargins) && mTextToDraw.length() > 0) {
int lastCharacterIndex = mTextToDraw.length() - mEllipsis.length() - 1;
if (lastCharacterIndex < 0) {
break;
}
mTextToDraw = mTextToDraw.substring(0, lastCharacterIndex) + mEllipsis;
}
// Avoid a negative width
mChipWidth = Math.max(0, maxAvailableWidthMinusMargins);
mCachedSize = mMaxAvailableWidth;
}
}
}
return mCachedSize;
}
private int calculateActualWidth(Paint paint) {
// Only change the text size if a text size was set
if (mTextSize != -1) {
paint.setTextSize(mTextSize);
}
int totalPadding = mPaddingEdgePx;
// Find text width
Rect bounds = new Rect();
paint.getTextBounds(mTextToDraw, 0, mTextToDraw.length(), bounds);
int textWidth = bounds.width();
if (mIcon != null) {
totalPadding += mPaddingBetweenImagePx;
} else {
totalPadding += mPaddingEdgePx;
}
mChipWidth = totalPadding + textWidth + mIconWidth;
return getWidth();
}
public void invalidateCachedSize() {
mCachedSize = -1;
}
/**
* Adjusts the provided font metrics to make it seem like the font takes up {@code mChipHeight + mChipVerticalSpacing} pixels in height.
* This effectively ensures that the TextView will have a height equal to {@code mChipHeight + mChipVerticalSpacing} + whatever padding it has set.
* In {@link #draw(Canvas, CharSequence, int, int, float, int, int, int, Paint)} the chip itself is drawn to that it is vertically centered with
* {@code mChipVerticalSpacing / 2} pixels of space above and below it
*
* @param paint the paint whose font metrics should be adjusted
* @param fm the font metrics object to populate through {@link Paint#getFontMetricsInt(Paint.FontMetricsInt)}
*/
private void adjustFontMetrics(Paint paint, Paint.FontMetricsInt fm) {
// Only actually adjust font metrics if we have a chip height set
if (mChipHeight != -1) {
paint.getFontMetricsInt(fm);
int textHeight = fm.descent - fm.ascent;
// Break up the vertical spacing in half because half will go above the chip, half will go below the chip
int halfSpacing = mChipVerticalSpacing / 2;
// Given that the text is centered vertically within the chip, the amount of space above or below the text (inbetween the text and chip)
// is half their difference in height:
int spaceBetweenChipAndText = (mChipHeight - textHeight) / 2;
int textTop = fm.top;
int chipTop = fm.top - spaceBetweenChipAndText;
int textBottom = fm.bottom;
int chipBottom = fm.bottom + spaceBetweenChipAndText;
// The text may have been taller to begin with so we take the most negative coordinate (highest up) to be the top of the content
int topOfContent = Math.min(textTop, chipTop);
// Same as above but we want the largest positive coordinate (lowest down) to be the bottom of the content
int bottomOfContent = Math.max(textBottom, chipBottom);
// Shift the top up by halfSpacing and the bottom down by halfSpacing
int topOfContentWithSpacing = topOfContent - halfSpacing;
int bottomOfContentWithSpacing = bottomOfContent + halfSpacing;
// Change the font metrics so that the TextView thinks the font takes up the vertical space of a chip + spacing
fm.ascent = topOfContentWithSpacing;
fm.descent = bottomOfContentWithSpacing;
fm.top = topOfContentWithSpacing;
fm.bottom = bottomOfContentWithSpacing;
}
}
private int calculateChipHeight(int top, int bottom) {
// If a chip height was set we can return that, otherwise calculate it from top and bottom
return mChipHeight != -1 ? mChipHeight : bottom - top;
}
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
// Shift everything mLeftMarginPx to the left to create an empty space on the left (creating the margin)
x += mLeftMarginPx;
if (mChipHeight != -1) {
// If we set a chip height, adjust to vertically center chip in the line
// Adding (bottom - top) / 2 shifts the chip down so the top of it will be centered vertically
// Subtracting (mChipHeight / 2) shifts the chip back up so that the center of it will be centered vertically (as desired)
top += ((bottom - top) / 2) - (mChipHeight / 2);
bottom = top + mChipHeight;
}
// Perform actual drawing
drawBackground(canvas, x, top, bottom, paint);
drawText(canvas, x, top, bottom, paint, mTextToDraw);
if (mIcon != null) {
drawIcon(canvas, x, top, bottom, paint);
}
}
private void drawBackground(Canvas canvas, float x, int top, int bottom, Paint paint) {
int backgroundColor = mBackgroundColor.getColorForState(mStateSet, mBackgroundColor.getDefaultColor());
paint.setColor(backgroundColor);
int height = calculateChipHeight(top, bottom);
RectF rect = new RectF(x, top, x + mChipWidth, bottom);
int cornerRadius = (mCornerRadius != -1) ? mCornerRadius : height / 2;
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint);
paint.setColor(mTextColor);
}
private void drawText(Canvas canvas, float x, int top, int bottom, Paint paint, CharSequence text) {
if (mTextSize != -1) {
paint.setTextSize(mTextSize);
}
int height = calculateChipHeight(top, bottom);
Paint.FontMetrics fm = paint.getFontMetrics();
// The top value provided here is the y coordinate for the very top of the chip
// The y coordinate we are calculating is where the baseline of the text will be drawn
// Our objective is to have the midpoint between the top and baseline of the text be in line with the vertical center of the chip
// First we add height / 2 which will put the baseline at the vertical center of the chip
// Then we add half the height of the text which will lower baseline so that the midpoint is at the vertical center of the chip as desired
float adjustedY = top + ((height / 2) + ((-fm.top - fm.bottom) / 2));
// The x coordinate provided here is the left-most edge of the chip
// If there is no icon or the icon is on the right, then the text will start at the left-most edge, but indented with the edge padding, so we
// add mPaddingEdgePx
// If there is an icon and it's on the left, the text will start at the left-most edge, but indented by the combined width of the icon and
// the padding between the icon and text, so we add (mIconWidth + mPaddingBetweenImagePx)
float adjustedX = x + ((mIcon == null || !mShowIconOnLeft) ? mPaddingEdgePx : (mIconWidth + mPaddingBetweenImagePx));
canvas.drawText(text, 0, text.length(), adjustedX, adjustedY, paint);
}
private void drawIcon(Canvas canvas, float x, int top, int bottom, Paint paint) {
drawIconBackground(canvas, x, top, bottom, paint);
drawIconBitmap(canvas, x, top, bottom, paint);
}
private void drawIconBackground(Canvas canvas, float x, int top, int bottom, Paint paint) {
int height = calculateChipHeight(top, bottom);
paint.setColor(mIconBackgroundColor);
// Since it's a circle the diameter is equal to the height, so the radius == diameter / 2 == height / 2
int radius = height / 2;
// The coordinates that get passed to drawCircle are for the center of the circle
// x is the left edge of the chip, (x + mChipWidth) is the right edge of the chip
// So the center of the circle is one radius distance from either the left or right edge (depending on which side the icon is being drawn on)
float circleX = mShowIconOnLeft ? (x + radius) : (x + mChipWidth - radius);
// The y coordinate is always just one radius distance from the top
canvas.drawCircle(circleX, top + radius, radius, paint);
paint.setColor(mTextColor);
}
private void drawIconBitmap(Canvas canvas, float x, int top, int bottom, Paint paint) {
int height = calculateChipHeight(top, bottom);
// Create a scaled down version of the bitmap to fit within the circle (whose diameter == height)
Bitmap iconBitmap = Bitmap.createBitmap(mIcon.getIntrinsicWidth(), mIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Bitmap scaledIconBitMap = scaleDown(iconBitmap, (float) height * SCALE_PERCENT_OF_CHIP_HEIGHT, true);
iconBitmap.recycle();
Canvas bitmapCanvas = new Canvas(scaledIconBitMap);
mIcon.setBounds(0, 0, bitmapCanvas.getWidth(), bitmapCanvas.getHeight());
mIcon.draw(bitmapCanvas);
// We are drawing a square icon inside of a circle
// The coordinates we pass to canvas.drawBitmap have to be for the top-left corner of the bitmap
// The bitmap should be inset by half of (circle width - bitmap width)
// Since it's a circle, the circle's width is equal to it's height which is equal to the chip height
float xInsetWithinCircle = (height - bitmapCanvas.getWidth()) / 2;
// The icon x coordinate is going to be insetWithinCircle pixels away from the left edge of the circle
// If the icon is on the left, the left edge of the circle is just x
// If the icon is on the right, the left edge of the circle is x + mChipWidth - height
float iconX = mShowIconOnLeft ? (x + xInsetWithinCircle) : (x + mChipWidth - height + xInsetWithinCircle);
// The y coordinate works the same way (only it's always from the top edge)
float yInsetWithinCircle = (height - bitmapCanvas.getHeight()) / 2;
float iconY = top + yInsetWithinCircle;
canvas.drawBitmap(scaledIconBitMap, iconX, iconY, paint);
}
private Bitmap scaleDown(Bitmap realImage, float maxImageSize, boolean filter) {
float ratio = Math.min(maxImageSize / realImage.getWidth(), maxImageSize / realImage.getHeight());
int width = Math.round(ratio * realImage.getWidth());
int height = Math.round(ratio * realImage.getHeight());
return Bitmap.createScaledBitmap(realImage, width, height, filter);
}
@Override
public String toString() {
return mText.toString();
}
}

View File

@@ -0,0 +1,60 @@
package com.hootsuite.nachos.chip;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import androidx.annotation.NonNull;
import com.hootsuite.nachos.ChipConfiguration;
public class ChipSpanChipCreator implements ChipCreator<ChipSpan> {
@Override
public ChipSpan createChip(@NonNull Context context, @NonNull CharSequence text, Object data) {
return new ChipSpan(context, text, null, data);
}
@Override
public ChipSpan createChip(@NonNull Context context, @NonNull ChipSpan existingChip) {
return new ChipSpan(context, existingChip);
}
@Override
public void configureChip(@NonNull ChipSpan chip, @NonNull ChipConfiguration chipConfiguration) {
int chipHorizontalSpacing = chipConfiguration.getChipHorizontalSpacing();
ColorStateList chipBackground = chipConfiguration.getChipBackground();
int chipCornerRadius = chipConfiguration.getChipCornerRadius();
int chipTextColor = chipConfiguration.getChipTextColor();
int chipTextSize = chipConfiguration.getChipTextSize();
int chipHeight = chipConfiguration.getChipHeight();
int chipVerticalSpacing = chipConfiguration.getChipVerticalSpacing();
int maxAvailableWidth = chipConfiguration.getMaxAvailableWidth();
if (chipHorizontalSpacing != -1) {
chip.setLeftMargin(chipHorizontalSpacing / 2);
chip.setRightMargin(chipHorizontalSpacing / 2);
}
if (chipBackground != null) {
chip.setBackgroundColor(chipBackground);
}
if (chipCornerRadius != -1) {
chip.setCornerRadius(chipCornerRadius);
}
if (chipTextColor != Color.TRANSPARENT) {
chip.setTextColor(chipTextColor);
}
if (chipTextSize != -1) {
chip.setTextSize(chipTextSize);
}
if (chipHeight != -1) {
chip.setChipHeight(chipHeight);
}
if (chipVerticalSpacing != -1) {
chip.setChipVerticalSpacing(chipVerticalSpacing);
}
if (maxAvailableWidth != -1) {
chip.setMaxAvailableWidth(maxAvailableWidth);
}
}
}

View File

@@ -0,0 +1,95 @@
package com.hootsuite.nachos.terminator;
import android.text.Editable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hootsuite.nachos.tokenizer.ChipTokenizer;
import java.util.Map;
/**
* This interface is used to handle the management of characters that should trigger the creation of chips in a text view.
*
* @see ChipTokenizer
*/
public interface ChipTerminatorHandler {
/**
* When a chip terminator character is encountered in newly inserted text, all tokens in the whole text view will be chipified
*/
int BEHAVIOR_CHIPIFY_ALL = 0;
/**
* When a chip terminator character is encountered in newly inserted text, only the current token (that in which the chip terminator character
* was found) will be chipified. This token may extend beyond where the chip terminator character was located.
*/
int BEHAVIOR_CHIPIFY_CURRENT_TOKEN = 1;
/**
* When a chip terminator character is encountered in newly inserted text, only the text from the previous chip up until the chip terminator
* character will be chipified. This may not be an entire token.
*/
int BEHAVIOR_CHIPIFY_TO_TERMINATOR = 2;
/**
* Constant for use with {@link #setPasteBehavior(int)}. Use this if a paste should behave the same as a standard text input (the chip temrinators
* will all behave according to their pre-determined behavior set through {@link #addChipTerminator(char, int)} or {@link #setChipTerminators(Map)}).
*/
int PASTE_BEHAVIOR_USE_DEFAULT = -1;
/**
* Sets all the characters that will be marked as chip terminators. This will replace any previously set chip terminators.
*
* @param chipTerminators a map of characters to be marked as chip terminators to behaviors that describe how to respond to the characters, or null
* to remove all chip terminators
*/
void setChipTerminators(@Nullable Map<Character, Integer> chipTerminators);
/**
* Adds a character as a chip terminator. When the provided character is encountered in entered text, the nearby text will be chipified according
* to the behavior provided here.
* {@code behavior} Must be one of:
* <ul>
* <li>{@link #BEHAVIOR_CHIPIFY_ALL}</li>
* <li>{@link #BEHAVIOR_CHIPIFY_CURRENT_TOKEN}</li>
* <li>{@link #BEHAVIOR_CHIPIFY_TO_TERMINATOR}</li>
* </ul>
*
* @param character the character to mark as a chip terminator
* @param behavior the behavior describing how to respond to the chip terminator
*/
void addChipTerminator(char character, int behavior);
/**
* Customizes the way paste events are handled.
* If one of:
* <ul>
* <li>{@link #BEHAVIOR_CHIPIFY_ALL}</li>
* <li>{@link #BEHAVIOR_CHIPIFY_CURRENT_TOKEN}</li>
* <li>{@link #BEHAVIOR_CHIPIFY_TO_TERMINATOR}</li>
* </ul>
* is passed, all chip terminators will be handled with that behavior when a paste event occurs.
* If {@link #PASTE_BEHAVIOR_USE_DEFAULT} is passed, whatever behavior is configured for a particular chip terminator
* (through {@link #setChipTerminators(Map)} or {@link #addChipTerminator(char, int)} will be used for that chip terminator
*
* @param pasteBehavior the behavior to use on a paste event
*/
void setPasteBehavior(int pasteBehavior);
/**
* Parses the provided text looking for characters marked as chip terminators through {@link #addChipTerminator(char, int)} and {@link #setChipTerminators(Map)}.
* The provided {@link Editable} will be modified if chip terminators are encountered.
*
* @param tokenizer the {@link ChipTokenizer} to use to identify and chipify tokens in the text
* @param text the text in which to search for chip terminators tokens to be chipped
* @param start the index at which to begin looking for chip terminators (inclusive)
* @param end the index at which to end looking for chip terminators (exclusive)
* @param isPasteEvent true if this handling is for a paste event in which case the behavior set in {@link #setPasteBehavior(int)} will be used,
* otherwise false
* @return an non-negative integer indicating the index where the cursor (selection) should be placed once the handling is complete,
* or a negative integer indicating that the cursor should not be moved.
*/
int findAndHandleChipTerminators(@NonNull ChipTokenizer tokenizer, @NonNull Editable text, int start, int end, boolean isPasteEvent);
}

View File

@@ -0,0 +1,115 @@
package com.hootsuite.nachos.terminator;
import android.text.Editable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hootsuite.nachos.tokenizer.ChipTokenizer;
import java.util.HashMap;
import java.util.Map;
public class DefaultChipTerminatorHandler implements ChipTerminatorHandler {
@Nullable
private Map<Character, Integer> mChipTerminators;
private int mPasteBehavior = BEHAVIOR_CHIPIFY_TO_TERMINATOR;
@Override
public void setChipTerminators(@Nullable Map<Character, Integer> chipTerminators) {
mChipTerminators = chipTerminators;
}
@Override
public void addChipTerminator(char character, int behavior) {
if (mChipTerminators == null) {
mChipTerminators = new HashMap<>();
}
mChipTerminators.put(character, behavior);
}
@Override
public void setPasteBehavior(int pasteBehavior) {
mPasteBehavior = pasteBehavior;
}
@Override
public int findAndHandleChipTerminators(@NonNull ChipTokenizer tokenizer, @NonNull Editable text, int start, int end, boolean isPasteEvent) {
// If we don't have a tokenizer or any chip terminators, there's nothing to look for
if (mChipTerminators == null) {
return -1;
}
TextIterator textIterator = new TextIterator(text, start, end);
int selectionIndex = -1;
characterLoop:
while (textIterator.hasNextCharacter()) {
char theChar = textIterator.nextCharacter();
if (isChipTerminator(theChar)) {
int behavior = (isPasteEvent && mPasteBehavior != PASTE_BEHAVIOR_USE_DEFAULT) ? mPasteBehavior : mChipTerminators.get(theChar);
int newSelection = -1;
switch (behavior) {
case BEHAVIOR_CHIPIFY_ALL:
selectionIndex = handleChipifyAll(textIterator, tokenizer);
break characterLoop;
case BEHAVIOR_CHIPIFY_CURRENT_TOKEN:
newSelection = handleChipifyCurrentToken(textIterator, tokenizer);
break;
case BEHAVIOR_CHIPIFY_TO_TERMINATOR:
newSelection = handleChipifyToTerminator(textIterator, tokenizer);
break;
}
if (newSelection != -1) {
selectionIndex = newSelection;
}
}
}
return selectionIndex;
}
private int handleChipifyAll(TextIterator textIterator, ChipTokenizer tokenizer) {
textIterator.deleteCharacter(true);
tokenizer.terminateAllTokens(textIterator.getText());
return textIterator.totalLength();
}
private int handleChipifyCurrentToken(TextIterator textIterator, ChipTokenizer tokenizer) {
textIterator.deleteCharacter(true);
Editable text = textIterator.getText();
int index = textIterator.getIndex();
int tokenStart = tokenizer.findTokenStart(text, index);
int tokenEnd = tokenizer.findTokenEnd(text, index);
if (tokenStart < tokenEnd) {
CharSequence chippedText = tokenizer.terminateToken(text.subSequence(tokenStart, tokenEnd), null);
textIterator.replace(tokenStart, tokenEnd, chippedText);
return tokenStart + chippedText.length();
}
return -1;
}
private int handleChipifyToTerminator(TextIterator textIterator, ChipTokenizer tokenizer) {
Editable text = textIterator.getText();
int index = textIterator.getIndex();
if (index > 0) {
int tokenStart = tokenizer.findTokenStart(text, index);
if (tokenStart < index) {
CharSequence chippedText = tokenizer.terminateToken(text.subSequence(tokenStart, index), null);
textIterator.replace(tokenStart, index + 1, chippedText);
} else {
textIterator.deleteCharacter(false);
}
} else {
textIterator.deleteCharacter(false);
}
return -1;
}
private boolean isChipTerminator(char character) {
return mChipTerminators != null && mChipTerminators.keySet().contains(character);
}
}

View File

@@ -0,0 +1,63 @@
package com.hootsuite.nachos.terminator;
import android.text.Editable;
public class TextIterator {
private Editable mText;
private int mStart;
private int mEnd;
private int mIndex;
public TextIterator(Editable text, int start, int end) {
mText = text;
mStart = start;
mEnd = end;
mIndex = mStart - 1; // Subtract 1 so that the first call to nextCharacter() will return the first character
}
public int totalLength() {
return mText.length();
}
public int windowLength() {
return mEnd - mStart;
}
public Editable getText() {
return mText;
}
public int getIndex() {
return mIndex;
}
public boolean hasNextCharacter() {
return (mIndex + 1) < mEnd;
}
public char nextCharacter() {
mIndex++;
return mText.charAt(mIndex);
}
public void deleteCharacter(boolean maintainIndex) {
mText.replace(mIndex, mIndex + 1, "");
if (!maintainIndex) {
mIndex--;
}
mEnd--;
}
public void replace(int replaceStart, int replaceEnd, CharSequence chippedText) {
mText.replace(replaceStart, replaceEnd, chippedText);
// Update indexes
int newLength = chippedText.length();
int oldLength = replaceEnd - replaceStart;
mIndex = replaceStart + newLength - 1;
mEnd += newLength - oldLength;
}
}

View File

@@ -0,0 +1,89 @@
package com.hootsuite.nachos.tokenizer;
import android.text.Editable;
import android.text.Spanned;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hootsuite.nachos.ChipConfiguration;
import com.hootsuite.nachos.chip.Chip;
import java.util.ArrayList;
import java.util.List;
/**
* Base implementation of the {@link ChipTokenizer} interface that performs no actions and returns default values.
* This class allows for the easy creation of a ChipTokenizer that only implements some of the methods of the interface.
*/
public abstract class BaseChipTokenizer implements ChipTokenizer {
@Override
public void applyConfiguration(Editable text, ChipConfiguration chipConfiguration) {
// Do nothing
}
@Override
public int findTokenStart(CharSequence charSequence, int i) {
// Do nothing
return 0;
}
@Override
public int findTokenEnd(CharSequence charSequence, int i) {
// Do nothing
return 0;
}
@NonNull
@Override
public List<Pair<Integer, Integer>> findAllTokens(CharSequence text) {
// Do nothing
return new ArrayList<>();
}
@Override
public CharSequence terminateToken(CharSequence charSequence, @Nullable Object data) {
// Do nothing
return charSequence;
}
@Override
public void terminateAllTokens(Editable text) {
// Do nothing
}
@Override
public int findChipStart(Chip chip, Spanned text) {
// Do nothing
return 0;
}
@Override
public int findChipEnd(Chip chip, Spanned text) {
// Do nothing
return 0;
}
@NonNull
@Override
public Chip[] findAllChips(int start, int end, Spanned text) {
return new Chip[]{};
}
@Override
public void revertChipToToken(Chip chip, Editable text) {
// Do nothing
}
@Override
public void deleteChip(Chip chip, Editable text) {
// Do nothing
}
@Override
public void deleteChipAndPadding(Chip chip, Editable text) {
// Do nothing
}
}

View File

@@ -0,0 +1,134 @@
package com.hootsuite.nachos.tokenizer;
import android.text.Editable;
import android.text.Spanned;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hootsuite.nachos.ChipConfiguration;
import com.hootsuite.nachos.chip.Chip;
import java.util.List;
/**
* An extension of {@link android.widget.MultiAutoCompleteTextView.Tokenizer Tokenizer} that provides extra support
* for chipification.
* <p>
* In the context of this interface, a token is considered to be plain (non-chipped) text. Once a token is terminated it becomes or contains a chip.
* </p>
* <p>
* The CharSequences passed to the ChipTokenizer methods may contain both chipped text
* and plain text so the tokenizer must have some method of distinguishing between the two (e.g. using a delimeter character.
* The {@link #terminateToken(CharSequence, Object)} method is where a chip can be formed and returned to replace the plain text.
* Whatever class the implementation deems to represent a chip, must implement the {@link Chip} interface.
* </p>
*
* @see SpanChipTokenizer
*/
public interface ChipTokenizer {
/**
* Configures this ChipTokenizer to produce chips with the provided attributes. For each of these attributes, {@code -1} or {@code null} may be
* passed to indicate that the attribute may be ignored.
* <p>
* This will also apply the provided {@link ChipConfiguration} to any existing chips in the provided text.
* </p>
*
* @param text the text in which to search for existing chips to apply the configuration to
* @param chipConfiguration a {@link ChipConfiguration} containing customizations for the chips produced by this class
*/
void applyConfiguration(Editable text, ChipConfiguration chipConfiguration);
/**
* Returns the start of the token that ends at offset
* <code>cursor</code> within <code>text</code>.
*/
int findTokenStart(CharSequence text, int cursor);
/**
* Returns the end of the token (minus trailing punctuation)
* that begins at offset <code>cursor</code> within <code>text</code>.
*/
int findTokenEnd(CharSequence text, int cursor);
/**
* Searches through {@code text} for any tokens.
*
* @param text the text in which to search for un-terminated tokens
* @return a list of {@link Pair}s of the form (startIndex, endIndex) containing the locations of all
* unterminated tokens
*/
@NonNull
List<Pair<Integer, Integer>> findAllTokens(CharSequence text);
/**
* Returns <code>text</code>, modified, if necessary, to ensure that
* it ends with a token terminator (for example a space or comma).
*/
CharSequence terminateToken(CharSequence text, @Nullable Object data);
/**
* Terminates (converts from token into chip) all unterminated tokens in the provided text.
* This method CAN alter the provided text.
*
* @param text the text in which to terminate all tokens
*/
void terminateAllTokens(Editable text);
/**
* Finds the index of the first character in {@code text} that is a part of {@code chip}
*
* @param chip the chip whose start should be found
* @param text the text in which to search for the start of {@code chip}
* @return the start index of the chip
*/
int findChipStart(Chip chip, Spanned text);
/**
* Finds the index of the character after the last character in {@code text} that is a part of {@code chip}
*
* @param chip the chip whose end should be found
* @param text the text in which to search for the end of {@code chip}
* @return the end index of the chip
*/
int findChipEnd(Chip chip, Spanned text);
/**
* Searches through {@code text} for any chips
*
* @param start index to start looking for terminated tokens (inclusive)
* @param end index to end looking for terminated tokens (exclusive)
* @param text the text in which to search for terminated tokens
* @return a list of objects implementing the {@link Chip} interface to represent the terminated tokens
*/
@NonNull
Chip[] findAllChips(int start, int end, Spanned text);
/**
* Effectively does the opposite of {@link #terminateToken(CharSequence, Object)} by reverting the provided chip back into a token.
* This method CAN alter the provided text.
*
* @param chip the chip to revert into a token
* @param text the text in which the chip resides
*/
void revertChipToToken(Chip chip, Editable text);
/**
* Removes a chip and any text it encompasses from {@code text}. This method CAN alter the provided text.
*
* @param chip the chip to remove
* @param text the text to remove the chip from
*/
void deleteChip(Chip chip, Editable text);
/**
* Removes a chip, any text it encompasses AND any padding text (such as spaces) that may have been inserted when the chip was created in
* {@link #terminateToken(CharSequence, Object)} or after. This method CAN alter the provided text.
*
* @param chip the chip to remove
* @param text the text to remove the chip and padding from
*/
void deleteChipAndPadding(Chip chip, Editable text);
}

View File

@@ -0,0 +1,246 @@
package com.hootsuite.nachos.tokenizer;
import android.content.Context;
import android.text.Editable;
import android.text.SpannableString;
import android.text.Spanned;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hootsuite.nachos.ChipConfiguration;
import com.hootsuite.nachos.chip.Chip;
import com.hootsuite.nachos.chip.ChipCreator;
import com.hootsuite.nachos.chip.ChipSpan;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* A default implementation of {@link ChipTokenizer}.
* This implementation does the following:
* <ul>
* <li>Surrounds each token with a space and the Unit Separator ASCII control character (31) - See the diagram below
* <ul>
* <li>The spaces are included so that android keyboards can distinguish the chips as different words and provide accurate
* autocorrect suggestions</li>
* </ul>
* </li>
* <li>Replaces each token with a {@link ChipSpan} containing the same text, once the token terminates</li>
* <li>Uses the values passed to {@link #applyConfiguration(Editable, ChipConfiguration)} to configure any ChipSpans that get created</li>
* </ul>
* Each terminated token will therefore look like the following (this is what will be returned from {@link #terminateToken(CharSequence, Object)}):
* <pre>
* -----------------------------------------------------------
* | SpannableString |
* | ---------------------------------------------------- |
* | | ChipSpan | |
* | | | |
* | | space separator text separator space | |
* | | | |
* | ---------------------------------------------------- |
* -----------------------------------------------------------
* </pre>
*
* @see ChipSpan
*/
public class SpanChipTokenizer<C extends Chip> implements ChipTokenizer {
/**
* The character used to separate chips internally is the US (Unit Separator) ASCII control character.
* This character is used because it's untypable so we have complete control over when chips are created.
*/
public static final char CHIP_SPAN_SEPARATOR = 31;
public static final char AUTOCORRECT_SEPARATOR = ' ';
private Context mContext;
@Nullable
private ChipConfiguration mChipConfiguration;
@NonNull
private ChipCreator<C> mChipCreator;
@NonNull
private Class<C> mChipClass;
private Comparator<Pair<Integer, Integer>> mReverseTokenIndexesSorter = new Comparator<Pair<Integer, Integer>>() {
@Override
public int compare(Pair<Integer, Integer> lhs, Pair<Integer, Integer> rhs) {
return rhs.first - lhs.first;
}
};
public SpanChipTokenizer(Context context, @NonNull ChipCreator<C> chipCreator, @NonNull Class<C> chipClass) {
mContext = context;
mChipCreator = chipCreator;
mChipClass = chipClass;
}
@Override
public void applyConfiguration(Editable text, ChipConfiguration chipConfiguration) {
mChipConfiguration = chipConfiguration;
for (C chip : findAllChips(0, text.length(), text)) {
// Recreate the chips with the new configuration
int chipStart = findChipStart(chip, text);
deleteChip(chip, text);
text.insert(chipStart, terminateToken(mChipCreator.createChip(mContext, chip)));
}
}
@Override
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
// Work backwards until we find a CHIP_SPAN_SEPARATOR
while (i > 0 && text.charAt(i - 1) != CHIP_SPAN_SEPARATOR) {
i--;
}
// Work forwards to skip over any extra whitespace at the beginning of the token
while (i > 0 && i < text.length() && Character.isWhitespace(text.charAt(i))) {
i++;
}
return i;
}
@Override
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
// Work forwards till we find a CHIP_SPAN_SEPARATOR
while (i < len) {
if (text.charAt(i) == CHIP_SPAN_SEPARATOR) {
return (i - 1); // subtract one because the CHIP_SPAN_SEPARATOR will be preceded by a space
} else {
i++;
}
}
return len;
}
@NonNull
@Override
public List<Pair<Integer, Integer>> findAllTokens(CharSequence text) {
List<Pair<Integer, Integer>> unterminatedTokens = new ArrayList<>();
boolean insideChip = false;
// Iterate backwards through the text (to avoid messing up indexes)
for (int index = text.length() - 1; index >= 0; index--) {
char theCharacter = text.charAt(index);
// Every time we hit a CHIP_SPAN_SEPARATOR character we switch from being inside to outside
// or outside to inside a chip
// This check must happen before the whitespace check because CHIP_SPAN_SEPARATOR is considered a whitespace character
if (theCharacter == CHIP_SPAN_SEPARATOR) {
insideChip = !insideChip;
continue;
}
// Completely skip over whitespace
if (Character.isWhitespace(theCharacter)) {
continue;
}
// If we're ever outside a chip, see if the text we're in is a viable token for chipification
if (!insideChip) {
int tokenStart = findTokenStart(text, index);
int tokenEnd = findTokenEnd(text, index);
// Can only actually be chipified if there's at least one character between them
if (tokenEnd - tokenStart >= 1) {
unterminatedTokens.add(new Pair<>(tokenStart, tokenEnd));
index = tokenStart;
}
}
}
return unterminatedTokens;
}
@Override
public CharSequence terminateToken(CharSequence text, @Nullable Object data) {
// Remove leading/trailing whitespace
CharSequence trimmedText = text.toString().trim();
return terminateToken(mChipCreator.createChip(mContext, trimmedText, data));
}
private CharSequence terminateToken(C chip) {
// Surround the text with CHIP_SPAN_SEPARATOR and spaces
// The spaces allow autocorrect to correctly identify words
String chipSeparator = Character.toString(CHIP_SPAN_SEPARATOR);
String autoCorrectSeparator = Character.toString(AUTOCORRECT_SEPARATOR);
CharSequence textWithSeparator = autoCorrectSeparator + chipSeparator + chip.getText() + chipSeparator + autoCorrectSeparator;
// Build the container object to house the ChipSpan and space
SpannableString spannableString = new SpannableString(textWithSeparator);
// Attach the ChipSpan
if (mChipConfiguration != null) {
mChipCreator.configureChip(chip, mChipConfiguration);
}
spannableString.setSpan(chip, 0, textWithSeparator.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableString;
}
@Override
public void terminateAllTokens(Editable text) {
List<Pair<Integer, Integer>> unterminatedTokens = findAllTokens(text);
// Sort in reverse order (so index changes don't affect anything)
Collections.sort(unterminatedTokens, mReverseTokenIndexesSorter);
for (Pair<Integer, Integer> indexes : unterminatedTokens) {
int start = indexes.first;
int end = indexes.second;
CharSequence textToChip = text.subSequence(start, end);
CharSequence chippedText = terminateToken(textToChip, null);
text.replace(start, end, chippedText);
}
}
@Override
public int findChipStart(Chip chip, Spanned text) {
return text.getSpanStart(chip);
}
@Override
public int findChipEnd(Chip chip, Spanned text) {
return text.getSpanEnd(chip);
}
@SuppressWarnings("unchecked")
@NonNull
@Override
public C[] findAllChips(int start, int end, Spanned text) {
C[] spansArray = text.getSpans(start, end, mChipClass);
return (spansArray != null) ? spansArray : (C[]) Array.newInstance(mChipClass, 0);
}
@Override
public void revertChipToToken(Chip chip, Editable text) {
int chipStart = findChipStart(chip, text);
int chipEnd = findChipEnd(chip, text);
text.removeSpan(chip);
text.replace(chipStart, chipEnd, chip.getText());
}
@Override
public void deleteChip(Chip chip, Editable text) {
int chipStart = findChipStart(chip, text);
int chipEnd = findChipEnd(chip, text);
text.removeSpan(chip);
// On the emulator for some reason the text automatically gets deleted and chipStart and chipEnd end up both being -1, so in that case we
// don't need to call text.delete(...)
if (chipStart != chipEnd) {
text.delete(chipStart, chipEnd);
}
}
@Override
public void deleteChipAndPadding(Chip chip, Editable text) {
// This implementation does not add any extra padding outside of the span so we can just delete the chip normally
deleteChip(chip, text);
}
}

View File

@@ -0,0 +1,32 @@
package com.hootsuite.nachos.validator;
import android.text.SpannableStringBuilder;
import android.util.Pair;
import androidx.annotation.NonNull;
import com.hootsuite.nachos.tokenizer.ChipTokenizer;
import java.util.List;
/**
* A {@link NachoValidator} that deems text to be invalid if it contains
* unterminated tokens and fixes the text by chipifying all the unterminated tokens.
*/
public class ChipifyingNachoValidator implements NachoValidator {
@Override
public boolean isValid(@NonNull ChipTokenizer chipTokenizer, CharSequence text) {
// The text is considered valid if there are no unterminated tokens (everything is a chip)
List<Pair<Integer, Integer>> unterminatedTokens = chipTokenizer.findAllTokens(text);
return unterminatedTokens.isEmpty();
}
@Override
public CharSequence fixText(@NonNull ChipTokenizer chipTokenizer, CharSequence invalidText) {
SpannableStringBuilder newText = new SpannableStringBuilder(invalidText);
chipTokenizer.terminateAllTokens(newText);
return newText;
}
}

View File

@@ -0,0 +1,5 @@
package com.hootsuite.nachos.validator;
public interface IllegalCharacterIdentifier {
boolean isCharacterIllegal(Character c);
}

View File

@@ -0,0 +1,29 @@
package com.hootsuite.nachos.validator;
import androidx.annotation.NonNull;
import com.hootsuite.nachos.tokenizer.ChipTokenizer;
/**
* Interface used to ensure that a given CharSequence complies to a particular format.
*/
public interface NachoValidator {
/**
* Validates the specified text.
*
* @return true If the text currently in the text editor is valid.
* @see #fixText(ChipTokenizer, CharSequence)
*/
boolean isValid(@NonNull ChipTokenizer chipTokenizer, CharSequence text);
/**
* Corrects the specified text to make it valid.
*
* @param invalidText A string that doesn't pass validation: isValid(invalidText)
* returns false
* @return A string based on invalidText such as invoking isValid() on it returns true.
* @see #isValid(ChipTokenizer, CharSequence)
*/
CharSequence fixText(@NonNull ChipTokenizer chipTokenizer, CharSequence invalidText);
}

View File

@@ -31,7 +31,6 @@ import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcels;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import androidx.annotation.Nullable;
@@ -57,6 +56,7 @@ public class AudioPlayerService extends Service{
private static HashSet<Callback> callbacks=new HashSet<>();
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener=this::onAudioFocusChanged;
private boolean resumeAfterAudioFocusGain;
private boolean isBuffering=true;
private BroadcastReceiver receiver=new BroadcastReceiver(){
@Override
@@ -169,13 +169,15 @@ 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);
player.setOnErrorListener(this::onPlayerError);
player.setOnCompletionListener(this::onPlayerCompletion);
player.setOnSeekCompleteListener(this::onPlayerSeekCompleted);
player.setOnInfoListener(this::onPlayerInfo);
try{
player.setDataSource(this, Uri.parse(attachment.url));
player.prepareAsync();
@@ -187,7 +189,9 @@ public class AudioPlayerService extends Service{
}
private void onPlayerPrepared(MediaPlayer mp){
Log.i(TAG, "onPlayerPrepared");
playerReady=true;
isBuffering=false;
player.start();
updateSessionState(false);
}
@@ -205,6 +209,21 @@ public class AudioPlayerService extends Service{
stopSelf();
}
private boolean onPlayerInfo(MediaPlayer mp, int what, int extra){
switch(what){
case MediaPlayer.MEDIA_INFO_BUFFERING_START -> {
isBuffering=true;
updateSessionState(false);
}
case MediaPlayer.MEDIA_INFO_BUFFERING_END -> {
isBuffering=false;
updateSessionState(false);
}
default -> Log.i(TAG, "onPlayerInfo() called with: mp = ["+mp+"], what = ["+what+"], extra = ["+extra+"]");
}
return true;
}
private void onAudioFocusChanged(int change){
switch(change){
case AudioManager.AUDIOFOCUS_LOSS -> {
@@ -212,7 +231,7 @@ public class AudioPlayerService extends Service{
pause(false);
}
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
resumeAfterAudioFocusGain=true;
resumeAfterAudioFocusGain=isPlaying();
pause(false);
}
case AudioManager.AUDIOFOCUS_GAIN -> {
@@ -232,12 +251,16 @@ public class AudioPlayerService extends Service{
private void updateSessionState(boolean removeNotification){
session.setPlaybackState(new PlaybackState.Builder()
.setState(player.isPlaying() ? PlaybackState.STATE_PLAYING : PlaybackState.STATE_PAUSED, player.getCurrentPosition(), 1f)
.setState(switch(getPlayState()){
case PLAYING -> PlaybackState.STATE_PLAYING;
case PAUSED -> PlaybackState.STATE_PAUSED;
case BUFFERING -> PlaybackState.STATE_BUFFERING;
}, player.getCurrentPosition(), 1f)
.setActions(PlaybackState.ACTION_STOP | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_SEEK_TO)
.build());
updateNotification(!player.isPlaying(), removeNotification);
for(Callback cb:callbacks)
cb.onPlayStateChanged(attachment.id, player.isPlaying(), player.getCurrentPosition());
cb.onPlayStateChanged(attachment.id, getPlayState(), player.getCurrentPosition());
}
private void updateNotification(boolean dismissable, boolean removeNotification){
@@ -258,7 +281,7 @@ public class AudioPlayerService extends Service{
if(playerReady){
boolean isPlaying=player.isPlaying();
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_pause_24 : R.drawable.ic_play_24),
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_fluent_pause_24_filled : R.drawable.ic_fluent_play_24_filled),
getString(isPlaying ? R.string.pause : R.string.play),
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
.build());
@@ -310,6 +333,12 @@ public class AudioPlayerService extends Service{
return attachment.id;
}
public PlayState getPlayState(){
if(isBuffering)
return PlayState.BUFFERING;
return player.isPlaying() ? PlayState.PLAYING : PlayState.PAUSED;
}
public static void registerCallback(Callback cb){
callbacks.add(cb);
}
@@ -333,7 +362,13 @@ public class AudioPlayerService extends Service{
}
public interface Callback{
void onPlayStateChanged(String attachmentID, boolean playing, int position);
void onPlayStateChanged(String attachmentID, PlayState state, int position);
void onPlaybackStopped(String attachmentID);
}
public enum PlayState{
PLAYING,
PAUSED,
BUFFERING
}
}

View File

@@ -32,7 +32,6 @@ public class ExternalShareActivity extends FragmentStackActivity{
UiUtils.setUserPreferredTheme(this);
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);

View File

@@ -1,139 +1,131 @@
package org.joinmastodon.android;
import static org.joinmastodon.android.api.MastodonAPIController.gson;
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.MATERIAL3;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class GlobalUserPreferences{
private static final String TAG="GlobalUserPreferences";
public static boolean playGifs;
public static boolean useCustomTabs;
public static boolean altTextReminders, confirmUnfollow, confirmBoost, confirmDeletePost;
public static ThemePreference theme;
// MEGALODON
public static boolean trueBlackTheme;
public static boolean showReplies;
public static boolean showBoosts;
public static boolean loadNewPosts;
public static boolean showNewPostsButton;
public static boolean showInteractionCounts;
public static boolean alwaysExpandContentWarnings;
public static boolean disableMarquee;
public static boolean toolbarMarquee;
public static boolean disableSwipe;
public static boolean voteButtonForSingleChoice;
public static boolean enableDeleteNotifications;
public static boolean translateButtonOpenedOnly;
public static boolean uniformNotificationIcon;
public static boolean reduceMotion;
public static boolean keepOnlyLatestNotification;
public static boolean disableAltTextReminder;
public static boolean showAltIndicator;
public static boolean showNoAltIndicator;
public static boolean enablePreReleases;
public static PrefixRepliesMode prefixReplies;
public static boolean bottomEncoding;
public static boolean collapseLongPosts;
public static boolean spectatorMode;
public static boolean autoHideFab;
public static boolean replyLineAboveHeader;
public static boolean compactReblogReplyLine;
public static boolean confirmBeforeReblog;
public static boolean allowRemoteLoading;
public static boolean forwardReportDefault;
public static AutoRevealMode autoRevealEqualSpoilers;
public static String publishButtonText;
public static ThemePreference theme;
public static boolean disableM3PillActiveIndicator;
public static boolean showNavigationLabels;
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
public static boolean overlayMedia;
public static boolean showSuicideHelp;
public static boolean underlinedLinks;
public static ColorPreference color;
public static Map<String, List<String>> recentLanguages;
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
public static Set<String> accountsWithLocalOnlySupport;
public static Set<String> accountsInGlitchMode;
public static Set<String> accountsWithContentTypesEnabled;
public static Map<String, ContentType> accountsDefaultContentTypes;
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
/**
* Pleroma
*/
public static String replyVisibility;
public static boolean likeIcon;
private static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
}
private static <T> T fromJson(String json, Type type, T orElse) {
if (json == null) return orElse;
try { return gson.fromJson(json, type); }
catch (JsonSyntaxException ignored) { return orElse; }
public static <T> T fromJson(String json, Type type, T orElse){
if(json==null) return orElse;
try{
T value=gson.fromJson(json, type);
return value==null ? orElse : value;
}catch(JsonSyntaxException ignored){
return orElse;
}
}
public static void removeAccount(String accountId) {
recentLanguages.remove(accountId);
pinnedTimelines.remove(accountId);
accountsInGlitchMode.remove(accountId);
accountsWithLocalOnlySupport.remove(accountId);
accountsWithContentTypesEnabled.remove(accountId);
accountsDefaultContentTypes.remove(accountId);
save();
public static <T extends Enum<T>> T enumValue(Class<T> enumType, String name) {
try { return Enum.valueOf(enumType, name); }
catch (NullPointerException npe) { return null; }
}
public static void load(){
SharedPreferences prefs=getPrefs();
playGifs=prefs.getBoolean("playGifs", true);
useCustomTabs=prefs.getBoolean("useCustomTabs", true);
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
altTextReminders=prefs.getBoolean("altTextReminders", true);
confirmUnfollow=prefs.getBoolean("confirmUnfollow", true);
confirmBoost=prefs.getBoolean("confirmBoost", false);
confirmDeletePost=prefs.getBoolean("confirmDeletePost", true);
// MEGALODON
trueBlackTheme=prefs.getBoolean("trueBlackTheme", false);
showReplies=prefs.getBoolean("showReplies", true);
showBoosts=prefs.getBoolean("showBoosts", true);
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
disableMarquee=prefs.getBoolean("disableMarquee", false);
toolbarMarquee=prefs.getBoolean("toolbarMarquee", true);
disableSwipe=prefs.getBoolean("disableSwipe", false);
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
reduceMotion=prefs.getBoolean("reduceMotion", false);
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name()));
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
spectatorMode=prefs.getBoolean("spectatorMode", false);
autoHideFab=prefs.getBoolean("autoHideFab", true);
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
publishButtonText=prefs.getString("publishButtonText", "");
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
replyVisibility=prefs.getString("replyVisibility", null);
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
disableM3PillActiveIndicator=prefs.getBoolean("disableM3PillActiveIndicator", false);
showNavigationLabels=prefs.getBoolean("showNavigationLabels", false);
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);
underlinedLinks=prefs.getBoolean("underlinedLinks", true);
color=ColorPreference.valueOf(prefs.getString("color", MATERIAL3.name()));
likeIcon=prefs.getBoolean("likeIcon", false);
if (prefs.contains("prefixRepliesWithRe")) {
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
@@ -144,33 +136,33 @@ public class GlobalUserPreferences{
.apply();
}
try {
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
} catch (IllegalArgumentException|ClassCastException ignored) {
// invalid color name or color was previously saved as integer
color=ColorPreference.PINK;
}
int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE);
if(migrationLevel < 61)
migrateToUpstreamVersion61();
if(migrationLevel < BuildConfig.VERSION_CODE)
prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply();
}
public static void save(){
getPrefs().edit()
.putBoolean("playGifs", playGifs)
.putBoolean("useCustomTabs", useCustomTabs)
.putBoolean("showReplies", showReplies)
.putBoolean("showBoosts", showBoosts)
.putInt("theme", theme.ordinal())
.putBoolean("altTextReminders", altTextReminders)
.putBoolean("confirmUnfollow", confirmUnfollow)
.putBoolean("confirmBoost", confirmBoost)
.putBoolean("confirmDeletePost", confirmDeletePost)
// MEGALODON
.putBoolean("loadNewPosts", loadNewPosts)
.putBoolean("showNewPostsButton", showNewPostsButton)
.putBoolean("trueBlackTheme", trueBlackTheme)
.putBoolean("showInteractionCounts", showInteractionCounts)
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
.putBoolean("disableMarquee", disableMarquee)
.putBoolean("toolbarMarquee", toolbarMarquee)
.putBoolean("disableSwipe", disableSwipe)
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
.putBoolean("reduceMotion", reduceMotion)
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("disableAltTextReminder", disableAltTextReminder)
.putBoolean("showAltIndicator", showAltIndicator)
.putBoolean("showNoAltIndicator", showNoAltIndicator)
.putBoolean("enablePreReleases", enablePreReleases)
@@ -179,36 +171,22 @@ public class GlobalUserPreferences{
.putBoolean("spectatorMode", spectatorMode)
.putBoolean("autoHideFab", autoHideFab)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putString("publishButtonText", publishButtonText)
.putBoolean("bottomEncoding", bottomEncoding)
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
.putInt("theme", theme.ordinal())
.putString("color", color.name())
.putString("recentLanguages", gson.toJson(recentLanguages))
.putString("pinnedTimelines", gson.toJson(pinnedTimelines))
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
.putString("replyVisibility", replyVisibility)
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
.putBoolean("allowRemoteLoading", allowRemoteLoading)
.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)
.putBoolean("underlinedLinks", underlinedLinks)
.putString("color", color.name())
.putBoolean("likeIcon", likeIcon)
.apply();
}
public enum ColorPreference{
MATERIAL3,
PINK,
PURPLE,
GREEN,
BLUE,
BROWN,
RED,
YELLOW
}
public enum ThemePreference{
AUTO,
LIGHT,
@@ -226,4 +204,58 @@ public class GlobalUserPreferences{
ALWAYS,
TO_OTHERS
}
//region preferences migrations
private static void migrateToUpstreamVersion61(){
Log.d(TAG, "Migrating preferences to upstream version 61!!");
Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
Type pinnedTimelinesType = new TypeToken<Map<String, ArrayList<TimelineDefinition>>>() {}.getType();
Type recentLanguagesType = new TypeToken<Map<String, ArrayList<String>>>() {}.getType();
// migrate global preferences
SharedPreferences prefs=getPrefs();
altTextReminders=!prefs.getBoolean("disableAltTextReminder", false);
confirmBoost=prefs.getBoolean("confirmBeforeReblog", false);
toolbarMarquee=!prefs.getBoolean("disableMarquee", false);
save();
// migrate local preferences
AccountSessionManager asm=AccountSessionManager.getInstance();
// reset: Set<String> accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
Map<String, ContentType> accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
Map<String, ArrayList<TimelineDefinition>> pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
Set<String> accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
Set<String> accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
Map<String, ArrayList<String>> recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
for(AccountSession session : asm.getLoggedInAccounts()){
String accountID=session.getID();
AccountLocalPreferences localPrefs=session.getLocalPreferences();
localPrefs.revealCWs=prefs.getBoolean("alwaysExpandContentWarnings", false);
localPrefs.recentLanguages=recentLanguages.get(accountID);
// reset: localPrefs.contentTypesEnabled=accountsWithContentTypesEnabled.contains(accountID);
localPrefs.defaultContentType=accountsDefaultContentTypes.getOrDefault(accountID, ContentType.PLAIN);
localPrefs.showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
localPrefs.timelines=pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID));
localPrefs.localOnlySupported=accountsWithLocalOnlySupport.contains(accountID);
localPrefs.glitchInstance=accountsInGlitchMode.contains(accountID);
localPrefs.publishButtonText=prefs.getString("publishButtonText", null);
localPrefs.keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
localPrefs.showReplies=prefs.getBoolean("showReplies", true);
localPrefs.showBoosts=prefs.getBoolean("showBoosts", true);
if(session.getInstance().map(Instance::isAkkoma).orElse(false)){
localPrefs.timelineReplyVisibility=prefs.getString("replyVisibility", null);
}
localPrefs.save();
}
}
//endregion
}

View File

@@ -5,13 +5,16 @@ import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.Toast;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
@@ -21,6 +24,7 @@ import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.joinmastodon.android.utils.ProvidesAssistContent;
@@ -28,56 +32,19 @@ import org.parceler.Parcels;
import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState){
UiUtils.setUserPreferredTheme(this);
AccountSession session=getCurrentSession();
UiUtils.setUserPreferredTheme(this, session);
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 {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
}
restartHomeFragment();
}
if(GithubSelfUpdater.needSelfUpdating()){
@@ -111,11 +78,55 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
}
}else if(intent.getBooleanExtra("compose", false)){
showCompose();
}else if(Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
}/*else if(intent.hasExtra(PackageInstaller.EXTRA_STATUS) && GithubSelfUpdater.needSelfUpdating()){
GithubSelfUpdater.getInstance().handleIntentFromInstaller(intent, this);
}*/
}
public void handleURL(Uri uri, String accountID){
if(uri==null)
return;
if(!"https".equals(uri.getScheme()) && !"http".equals(uri.getScheme()))
return;
AccountSession session;
if(accountID==null)
session=AccountSessionManager.getInstance().getLastActiveAccount();
else
session=AccountSessionManager.get(accountID);
if(session==null || !session.activated)
return;
openSearchQuery(uri.toString(), session.getID(), R.string.opening_link, false);
}
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
new GetSearchResults(q, null, true, null, 0, 0)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
Bundle args=new Bundle();
args.putString("account", accountID);
if(result.statuses!=null && !result.statuses.isEmpty()){
args.putParcelable("status", Parcels.wrap(result.statuses.get(0)));
Nav.go(MainActivity.this, ThreadFragment.class, args);
}else if(result.accounts!=null && !result.accounts.isEmpty()){
args.putParcelable("profileAccount", Parcels.wrap(result.accounts.get(0)));
Nav.go(MainActivity.this, ProfileFragment.class, args);
}else{
Toast.makeText(MainActivity.this, fromSearch ? R.string.no_search_results : R.string.link_not_supported, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onError(ErrorResponse error){
error.showToast(MainActivity.this);
}
})
.wrapProgress(this, progressText, true)
.exec(accountID);
}
private void showFragmentForNotification(Notification notification, String accountID){
try{
notification.postprocess();
@@ -123,7 +134,9 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
Log.w("MainActivity", x);
return;
}
UiUtils.showFragmentForNotification(this, notification, accountID, null);
Bundle args = new Bundle();
args.putBoolean("noTransition", true);
UiUtils.showFragmentForNotification(this, notification, accountID, args);
}
private void showFragmentForExternalShare(Bundle args) {
@@ -204,4 +217,81 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
Fragment fragment = getCurrentFragment();
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
}
public AccountSession getCurrentSession(){
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
return AccountSessionManager.getInstance()
.getAccount(intent.getStringExtra("account"));
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
return session;
}
public void restartActivity(){
finish();
startActivity(new Intent(this, MainActivity.class));
}
public void restartHomeFragment(){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
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

@@ -3,6 +3,7 @@ package org.joinmastodon.android;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.webkit.WebView;
import org.joinmastodon.android.api.PushSubscriptionManager;
@@ -28,5 +29,8 @@ public class MastodonApp extends Application{
PushSubscriptionManager.tryRegisterFCM();
GlobalUserPreferences.load();
if(BuildConfig.DEBUG){
WebView.setWebContentsDebuggingEnabled(true);
}
}
}

View File

@@ -25,7 +25,6 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.NotificationReceivedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Mention;
import org.joinmastodon.android.model.NotificationAction;
@@ -38,7 +37,9 @@ import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -57,6 +58,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
private static final int SUMMARY_ID = 791;
private static int notificationId = 0;
private static final Map<String, Integer> notificationIdsForAccounts = new HashMap<>();
@Override
public void onReceive(Context context, Intent intent){
@@ -88,9 +90,12 @@ public class PushNotificationReceiver extends BroadcastReceiver{
Log.w(TAG, "onReceive: account for id '"+pushAccountID+"' not found");
return;
}
if(account.getLocalPreferences().getNotificationsPauseEndTime()>System.currentTimeMillis()){
Log.i(TAG, "onReceive: dropping notification because user has paused notifications for this account");
return;
}
String accountID=account.getID();
PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s);
E.post(new NotificationReceivedEvent(accountID, pn.notificationId+""));
new GetNotificationByID(pn.notificationId+"")
.setCallback(new Callback<>(){
@Override
@@ -143,9 +148,15 @@ 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);
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
AccountSession session=AccountSessionManager.get(accountID);
Account self=session.self;
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
Notification.Builder builder;
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
@@ -163,6 +174,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;
})
@@ -192,11 +205,12 @@ 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) {
builder.setSmallIcon(switch (pn.notificationType) {
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
case FAVORITE -> GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled;
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
case MENTION -> R.drawable.ic_fluent_mention_24_filled;
@@ -215,7 +229,21 @@ public class PushNotificationReceiver extends BroadcastReceiver{
builder.setSubText(accountName);
}
int id = GlobalUserPreferences.keepOnlyLatestNotification ? NOTIFICATION_ID : notificationId++;
int id;
if(session.getLocalPreferences().keepOnlyLatestNotification){
if(notificationIdsForAccounts.containsKey(accountID)){
// we overwrite the existing notification
id=notificationIdsForAccounts.get(accountID);
}else{
// there's no existing notification, so we increment
id=notificationId++;
// and store the notification id for this account
notificationIdsForAccounts.put(accountID, id);
}
}else{
// we don't want to overwrite anything, therefore incrementing
id=notificationId++;
}
if (notification != null){
switch (pn.notificationType){
@@ -294,11 +322,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

@@ -13,22 +13,21 @@ import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.SearchResult;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -43,6 +42,8 @@ public class CacheController{
private final String accountID;
private DatabaseHelper db;
private final Runnable databaseCloseRunnable=this::closeDatabase;
private boolean loadingNotifications;
private final ArrayList<Callback<PaginatedResponse<List<Notification>>>> pendingNotificationsCallbacks=new ArrayList<>();
private static final int POST_FLAG_GAP_AFTER=1;
@@ -58,7 +59,6 @@ public class CacheController{
cancelDelayedClose();
databaseThread.postRunnable(()->{
try{
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase();
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){
@@ -66,15 +66,12 @@ public class CacheController{
ArrayList<Status> result=new ArrayList<>();
cursor.moveToFirst();
String newMaxID;
outer:
do{
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
status.postprocess();
int flags=cursor.getInt(1);
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0) ? status.id : null;
newMaxID=status.id;
if (!new StatusFilterPredicate(filters, Filter.FilterContext.HOME).test(status))
continue outer;
result.add(status);
}while(cursor.moveToNext());
String _newMaxID=newMaxID;
@@ -85,11 +82,11 @@ public class CacheController{
Log.w(TAG, "getHomeTimeline: corrupted status object in database", x);
}
}
new GetHomeTimeline(maxID, null, count, null)
new GetHomeTimeline(maxID, null, count, null, AccountSessionManager.get(accountID).getLocalPreferences().timelineReplyVisibility)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters, Filter.FilterContext.HOME)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
callback.onSuccess(new CacheablePaginatedResponse<>(result, result.isEmpty() ? null : result.get(result.size()-1).id, false));
putHomeTimeline(result, maxID==null);
}
@@ -117,7 +114,7 @@ public class CacheController{
values.put("id", s.id);
values.put("json", MastodonAPIController.gson.toJson(s));
int flags=0;
if(s.hasGapAfter)
if(Objects.equals(s.hasGapAfter, s.id))
flags|=POST_FLAG_GAP_AFTER;
values.put("flags", flags);
values.put("time", s.createdAt.getEpochSecond());
@@ -126,12 +123,39 @@ public class CacheController{
});
}
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<CacheablePaginatedResponse<List<Notification>>> callback){
public void updateStatus(Status status) {
runOnDbThread((db)->{
ContentValues statusUpdate=new ContentValues(1);
statusUpdate.put("json", MastodonAPIController.gson.toJson(status));
db.update("home_timeline", statusUpdate, "id = ?", new String[] { status.id });
});
}
public void updateNotification(Notification notification) {
runOnDbThread((db)->{
ContentValues notificationUpdate=new ContentValues(1);
notificationUpdate.put("json", MastodonAPIController.gson.toJson(notification));
String[] notificationArgs = new String[] { notification.id };
db.update("notifications_all", notificationUpdate, "id = ?", notificationArgs);
db.update("notifications_mentions", notificationUpdate, "id = ?", notificationArgs);
db.update("notifications_posts", notificationUpdate, "id = ?", notificationArgs);
ContentValues statusUpdate=new ContentValues(1);
statusUpdate.put("json", MastodonAPIController.gson.toJson(notification.status));
db.update("home_timeline", statusUpdate, "id = ?", new String[] { notification.status.id });
});
}
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<PaginatedResponse<List<Notification>>> callback){
cancelDelayedClose();
databaseThread.postRunnable(()->{
try{
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(accountID);
List<Filter> filters=accountSession.wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.NOTIFICATIONS)).collect(Collectors.toList());
if(!onlyMentions && !onlyPosts && loadingNotifications){
synchronized(pendingNotificationsCallbacks){
pendingNotificationsCallbacks.add(callback);
}
return;
}
if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase();
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
@@ -140,42 +164,56 @@ public class CacheController{
ArrayList<Notification> result=new ArrayList<>();
cursor.moveToFirst();
String newMaxID;
outer:
do{
Notification ntf=MastodonAPIController.gson.fromJson(cursor.getString(0), Notification.class);
ntf.postprocess();
newMaxID=ntf.id;
if(ntf.status!=null){
if (!new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status))
continue outer;
}
result.add(ntf);
}while(cursor.moveToNext());
String _newMaxID=newMaxID;
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
AccountSessionManager.get(accountID).filterStatusContainingObjects(result, n->n.status, FilterContext.NOTIFICATIONS);
uiHandler.post(()->callback.onSuccess(new PaginatedResponse<>(result, _newMaxID)));
return;
}
}catch(IOException x){
Log.w(TAG, "getNotifications: corrupted notification object in database", x);
}
}
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isAkkoma())
if(!onlyMentions && !onlyPosts)
loadingNotifications=true;
boolean isAkkoma = AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false);
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), isAkkoma)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Notification> result){
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
if(ntf.status!=null){
return new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status);
}
return true;
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
ArrayList<Notification> filtered=new ArrayList<>(result);
AccountSessionManager.get(accountID).filterStatusContainingObjects(filtered, n->n.status, FilterContext.NOTIFICATIONS);
PaginatedResponse<List<Notification>> res=new PaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id);
callback.onSuccess(res);
putNotifications(result, onlyMentions, onlyPosts, maxID==null);
if(!onlyMentions){
loadingNotifications=false;
synchronized(pendingNotificationsCallbacks){
for(Callback<PaginatedResponse<List<Notification>>> cb:pendingNotificationsCallbacks){
cb.onSuccess(res);
}
pendingNotificationsCallbacks.clear();
}
}
}
@Override
public void onError(ErrorResponse error){
callback.onError(error);
if(!onlyMentions){
loadingNotifications=false;
synchronized(pendingNotificationsCallbacks){
for(Callback<PaginatedResponse<List<Notification>>> cb:pendingNotificationsCallbacks){
cb.onError(error);
}
pendingNotificationsCallbacks.clear();
}
}
}
})
.exec(accountID);
@@ -327,7 +365,7 @@ public class CacheController{
createRecentSearchesTable(db);
}
if(oldVersion<3){
// MEGALODON-SPECIFIC
// MEGALODON
createPostsNotificationsTable(db);
}
if(oldVersion<4){

View File

@@ -117,6 +117,9 @@ public class MastodonAPIController{
synchronized(req){
req.okhttpCall=call;
}
if(req.timeout>0){
call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS);
}
if(BuildConfig.DEBUG)
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);
@@ -153,13 +156,17 @@ public class MastodonAPIController{
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] response body: "+respJson);
if(req.respTypeToken!=null)
respObj=gson.fromJson(respJson, req.respTypeToken.getType());
else
else if(req.respClass!=null)
respObj=gson.fromJson(respJson, req.respClass);
else
respObj=null;
}else{
if(req.respTypeToken!=null)
respObj=gson.fromJson(reader, req.respTypeToken.getType());
else
else if(req.respClass!=null)
respObj=gson.fromJson(reader, req.respClass);
else
respObj=null;
}
}catch(JsonIOException|JsonSyntaxException x){
if (req.context != null && response.body().contentType().subtype().equals("html")) {

View File

@@ -49,6 +49,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
Token token;
boolean canceled, isRemote;
Map<String, String> headers;
long timeout;
private ProgressDialog progressDialog;
protected boolean removeUnsupportedItems;
@Nullable Context context;
@@ -117,16 +118,16 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
.findAny())
.map(AccountSession::getID)
.map(this::exec)
.orElse(this.execNoAuth(domain));
.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){
@@ -152,6 +153,10 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
headers.put(key, value);
}
protected void setTimeout(long timeout){
this.timeout=timeout;
}
protected String getPathPrefix(){
return "/api/v1";
}

View File

@@ -87,7 +87,6 @@ public class PushSubscriptionManager{
private String accountID;
private PrivateKey privateKey;
private PublicKey publicKey;
private PublicKey serverKey;
private byte[] authKey;
public PushSubscriptionManager(String accountID){
@@ -121,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;
@@ -152,20 +163,21 @@ 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){
MastodonAPIController.runInBackground(()->{
result.serverKey=result.serverKey.replace('/','_');
result.serverKey=result.serverKey.replace('+','-');
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
if(session==null)
return;

View File

@@ -0,0 +1,9 @@
package org.joinmastodon.android.api;
import com.google.gson.reflect.TypeToken;
public abstract class ResultlessMastodonAPIRequest extends MastodonAPIRequest<Void>{
public ResultlessMastodonAPIRequest(HttpMethod method, String path){
super(method, path, (Class<Void>)null);
}
}

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,14 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
import java.util.List;
public class GetAccountFeaturedHashtags extends MastodonAPIRequest<List<Hashtag>>{
public GetAccountFeaturedHashtags(String id){
super(HttpMethod.GET, "/accounts/"+id+"/featured_tags", new TypeToken<>(){});
}
}

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

@@ -21,22 +21,22 @@ public class GetAccountStatuses extends MastodonAPIRequest<List<Status>>{
switch(filter){
case DEFAULT -> addQueryParameter("exclude_replies", "true");
case INCLUDE_REPLIES -> {}
case PINNED -> addQueryParameter("pinned", "true");
case MEDIA -> addQueryParameter("only_media", "true");
case NO_REBLOGS -> {
addQueryParameter("exclude_replies", "true");
addQueryParameter("exclude_reblogs", "true");
}
case OWN_POSTS_AND_REPLIES -> addQueryParameter("exclude_reblogs", "true");
case PINNED -> addQueryParameter("pinned", "true");
}
}
public enum Filter{
DEFAULT,
INCLUDE_REPLIES,
PINNED,
MEDIA,
NO_REBLOGS,
OWN_POSTS_AND_REPLIES
OWN_POSTS_AND_REPLIES,
PINNED
}
}

View File

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

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

@@ -0,0 +1,35 @@
package org.joinmastodon.android.api.requests.accounts;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Account;
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, Boolean indexable){
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
}
private static class Request{
public Boolean locked, discoverable, indexable;
public RequestSource source;
public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){
this.locked=locked;
this.discoverable=discoverable;
this.indexable=indexable;
this.source=source;
}
}
private static class RequestSource{
public StatusPrivacy privacy;
public String language;
public RequestSource(StatusPrivacy privacy, String language){
this.privacy=privacy;
this.language=language;
}
}
}

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

@@ -0,0 +1,22 @@
package org.joinmastodon.android.api.requests.catalog;
import android.net.Uri;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.catalog.CatalogDefaultInstance;
import java.util.List;
public class GetCatalogDefaultInstances extends MastodonAPIRequest<List<CatalogDefaultInstance>>{
public GetCatalogDefaultInstances(){
super(HttpMethod.GET, null, new TypeToken<>(){});
setTimeout(500);
}
@Override
public Uri getURL(){
return Uri.parse("https://api.joinmastodon.org/default-servers");
}
}

View File

@@ -0,0 +1,23 @@
package org.joinmastodon.android.api.requests.filters;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterKeyword;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
public class CreateFilter extends MastodonAPIRequest<Filter>{
public CreateFilter(String title, EnumSet<FilterContext> context, FilterAction action, int expiresIn, List<FilterKeyword> words){
super(HttpMethod.POST, "/filters", Filter.class);
setRequestBody(new FilterRequest(title, context, action, expiresIn==0 ? null : expiresIn, words.stream().map(w->new KeywordAttribute(null, null, w.keyword, w.wholeWord)).collect(Collectors.toList())));
}
@Override
protected String getPathPrefix(){
return "/api/v2";
}
}

View File

@@ -0,0 +1,14 @@
package org.joinmastodon.android.api.requests.filters;
import org.joinmastodon.android.api.ResultlessMastodonAPIRequest;
public class DeleteFilter extends ResultlessMastodonAPIRequest{
public DeleteFilter(String id){
super(HttpMethod.DELETE, "/filters/"+id);
}
@Override
protected String getPathPrefix(){
return "/api/v2";
}
}

View File

@@ -0,0 +1,26 @@
package org.joinmastodon.android.api.requests.filters;
import org.joinmastodon.android.model.FilterAction;
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;
public FilterAction filterAction;
public Integer expiresIn;
public List<KeywordAttribute> keywordsAttributes;
public FilterRequest(String title, EnumSet<FilterContext> context, FilterAction filterAction, Integer expiresIn, List<KeywordAttribute> keywordsAttributes){
this.title=title;
this.context=context;
this.filterAction=filterAction;
this.expiresIn=expiresIn;
this.keywordsAttributes=keywordsAttributes;
}
}

View File

@@ -1,4 +1,4 @@
package org.joinmastodon.android.api.requests.accounts;
package org.joinmastodon.android.api.requests.filters;
import com.google.gson.reflect.TypeToken;
@@ -7,8 +7,13 @@ import org.joinmastodon.android.model.Filter;
import java.util.List;
public class GetWordFilters extends MastodonAPIRequest<List<Filter>>{
public GetWordFilters(){
public class GetFilters extends MastodonAPIRequest<List<Filter>>{
public GetFilters(){
super(HttpMethod.GET, "/filters", new TypeToken<>(){});
}
@Override
protected String getPathPrefix(){
return "/api/v2";
}
}

View File

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

View File

@@ -0,0 +1,18 @@
package org.joinmastodon.android.api.requests.filters;
import com.google.gson.annotations.SerializedName;
class KeywordAttribute{
public String id;
@SerializedName("_destroy")
public Boolean delete;
public String keyword;
public Boolean wholeWord;
public KeywordAttribute(String id, Boolean delete, String keyword, Boolean wholeWord){
this.id=id;
this.delete=delete;
this.keyword=keyword;
this.wholeWord=wholeWord;
}
}

View File

@@ -0,0 +1,30 @@
package org.joinmastodon.android.api.requests.filters;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterKeyword;
import java.util.EnumSet;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class UpdateFilter extends MastodonAPIRequest<Filter>{
public UpdateFilter(String id, String title, EnumSet<FilterContext> context, FilterAction action, int expiresIn, List<FilterKeyword> words, List<String> deletedWords){
super(HttpMethod.PUT, "/filters/"+id, Filter.class);
List<KeywordAttribute> attrs=Stream.of(
words.stream().map(w->new KeywordAttribute(w.id, null, w.keyword, w.wholeWord)),
deletedWords.stream().map(wid->new KeywordAttribute(wid, true, null, null))
).flatMap(Function.identity()).collect(Collectors.toList());
setRequestBody(new FilterRequest(title, context, action, expiresIn==0 ? null : expiresIn, attrs));
}
@Override
protected String getPathPrefix(){
return "/api/v2";
}
}

View File

@@ -0,0 +1,16 @@
package org.joinmastodon.android.api.requests.instance;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.time.Instant;
public class GetInstanceExtendedDescription extends MastodonAPIRequest<GetInstanceExtendedDescription.Response>{
public GetInstanceExtendedDescription(){
super(HttpMethod.GET, "/instance/extended_description", Response.class);
}
public static class Response{
public Instant updatedAt;
public String content;
}
}

View File

@@ -4,16 +4,18 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
public class CreateList extends MastodonAPIRequest<ListTimeline> {
public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) {
public CreateList(String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.POST, "/lists", ListTimeline.class);
Request req = new Request();
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
}
public static class Request {
public String title;
public boolean exclusive;
public ListTimeline.RepliesPolicy repliesPolicy;
}
}

View File

@@ -4,10 +4,11 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
public class UpdateList extends MastodonAPIRequest<ListTimeline> {
public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
public UpdateList(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
CreateList.Request req = new CreateList.Request();
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
}

View File

@@ -1,17 +1,12 @@
package org.joinmastodon.android.api.requests.markers;
import org.joinmastodon.android.api.ApiUtils;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Marker;
import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.TimelineMarkers;
import java.util.EnumSet;
public class GetMarkers extends MastodonAPIRequest<Markers> {
public GetMarkers(EnumSet<Marker.Type> timelines) {
super(HttpMethod.GET, "/markers", Markers.class);
for (String type : ApiUtils.enumSetToStrings(timelines, Marker.Type.class)){
addQueryParameter("timeline[]", type);
}
public class GetMarkers extends MastodonAPIRequest<TimelineMarkers>{
public GetMarkers(){
super(HttpMethod.GET, "/markers", TimelineMarkers.class);
addQueryParameter("timeline[]", "home");
addQueryParameter("timeline[]", "notifications");
}
}

View File

@@ -2,11 +2,11 @@ package org.joinmastodon.android.api.requests.markers;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.gson.JsonObjectBuilder;
import org.joinmastodon.android.model.Marker;
import org.joinmastodon.android.model.TimelineMarkers;
public class SaveMarkers extends MastodonAPIRequest<SaveMarkers.Response>{
public class SaveMarkers extends MastodonAPIRequest<TimelineMarkers>{
public SaveMarkers(String lastSeenHomePostID, String lastSeenNotificationID){
super(HttpMethod.POST, "/markers", Response.class);
super(HttpMethod.POST, "/markers", TimelineMarkers.class);
JsonObjectBuilder builder=new JsonObjectBuilder();
if(lastSeenHomePostID!=null)
builder.add("home", new JsonObjectBuilder().add("last_read_id", lastSeenHomePostID));
@@ -14,8 +14,4 @@ public class SaveMarkers extends MastodonAPIRequest<SaveMarkers.Response>{
builder.add("notifications", new JsonObjectBuilder().add("last_read_id", lastSeenNotificationID));
setRequestBody(builder.build());
}
public static class Response{
public Marker home, notifications;
}
}

View File

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

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,24 @@ 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){
addQueryParameter("limit", String.valueOf(limit));
return this;
}
@Override

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

@@ -4,20 +4,19 @@ import android.text.TextUtils;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class GetBubbleTimeline extends MastodonAPIRequest<List<Status>> {
public GetBubbleTimeline(String maxID, int limit) {
public GetBubbleTimeline(String maxID, int limit, String replyVisibility) {
super(HttpMethod.GET, "/timelines/bubble", new TypeToken<>(){});
if(!TextUtils.isEmpty(maxID))
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
if(GlobalUserPreferences.replyVisibility != null)
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
if(replyVisibility != null)
addQueryParameter("reply_visibility", replyVisibility);
}
}

View File

@@ -2,22 +2,32 @@ package org.joinmastodon.android.api.requests.timelines;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class GetHashtagTimeline extends MastodonAPIRequest<List<Status>>{
public GetHashtagTimeline(String hashtag, String maxID, String minID, int limit){
public GetHashtagTimeline(String hashtag, String maxID, String minID, int limit, List<String> containsAny, List<String> containsAll, List<String> containsNone, boolean localOnly, String replyVisibility){
super(HttpMethod.GET, "/timelines/tag/"+hashtag, new TypeToken<>(){});
if (localOnly)
addQueryParameter("local", "true");
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(minID!=null)
addQueryParameter("min_id", minID);
if(limit>0)
addQueryParameter("limit", ""+limit);
if(GlobalUserPreferences.replyVisibility != null)
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
if(containsAny!=null)
for (String tag : containsAny)
addQueryParameter("any[]", tag);
if(containsAll!=null)
for (String tag : containsAll)
addQueryParameter("all[]", tag);
if(containsNone!=null)
for (String tag : containsNone)
addQueryParameter("none[]", tag);
if(replyVisibility != null)
addQueryParameter("reply_visibility", replyVisibility);
}
}

View File

@@ -2,14 +2,13 @@ package org.joinmastodon.android.api.requests.timelines;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
public GetHomeTimeline(String maxID, String minID, int limit, String sinceID){
public GetHomeTimeline(String maxID, String minID, int limit, String sinceID, String replyVisibility){
super(HttpMethod.GET, "/timelines/home", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
@@ -19,7 +18,7 @@ public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
addQueryParameter("since_id", sinceID);
if(limit>0)
addQueryParameter("limit", ""+limit);
if(GlobalUserPreferences.replyVisibility != null)
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
if(replyVisibility != null)
addQueryParameter("reply_visibility", replyVisibility);
}
}

View File

@@ -2,14 +2,13 @@ package org.joinmastodon.android.api.requests.timelines;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class GetListTimeline extends MastodonAPIRequest<List<Status>> {
public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID) {
public GetListTimeline(String listID, String maxID, String minID, int limit, String sinceID, String replyVisibility) {
super(HttpMethod.GET, "/timelines/list/"+listID, new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
@@ -19,7 +18,7 @@ public class GetListTimeline extends MastodonAPIRequest<List<Status>> {
addQueryParameter("limit", ""+limit);
if(sinceID!=null)
addQueryParameter("since_id", sinceID);
if(GlobalUserPreferences.replyVisibility != null)
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
if(replyVisibility != null)
addQueryParameter("reply_visibility", replyVisibility);
}
}

View File

@@ -4,14 +4,13 @@ import android.text.TextUtils;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
public GetPublicTimeline(boolean local, boolean remote, String maxID, int limit){
public GetPublicTimeline(boolean local, boolean remote, String maxID, int limit, String replyVisibility){
super(HttpMethod.GET, "/timelines/public", new TypeToken<>(){});
if(local)
addQueryParameter("local", "true");
@@ -21,7 +20,7 @@ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
if(GlobalUserPreferences.replyVisibility != null)
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
if(replyVisibility != null)
addQueryParameter("reply_visibility", replyVisibility);
}
}

View File

@@ -0,0 +1,149 @@
package org.joinmastodon.android.api.session;
import static org.joinmastodon.android.GlobalUserPreferences.fromJson;
import static org.joinmastodon.android.GlobalUserPreferences.enumValue;
import static org.joinmastodon.android.api.MastodonAPIController.gson;
import android.content.SharedPreferences;
import androidx.annotation.StringRes;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type;
import java.util.ArrayList;
public class AccountLocalPreferences{
private final SharedPreferences prefs;
public boolean showInteractionCounts;
public boolean customEmojiInNames;
public boolean revealCWs;
public boolean hideSensitiveMedia;
public boolean serverSideFiltersSupported;
// MEGALODON
public boolean showReplies;
public boolean showBoosts;
public ArrayList<String> recentLanguages;
public boolean bottomEncoding;
public ContentType defaultContentType;
public boolean contentTypesEnabled;
public ArrayList<TimelineDefinition> timelines;
public boolean localOnlySupported;
public boolean glitchInstance;
public String publishButtonText;
public String timelineReplyVisibility; // akkoma-only
public boolean keepOnlyLatestNotification;
public boolean emojiReactionsEnabled;
public ShowEmojiReactions showEmojiReactions;
public ColorPreference color;
public ArrayList<Emoji> recentCustomEmoji;
private final static Type recentLanguagesType=new TypeToken<ArrayList<String>>() {}.getType();
private final static Type timelinesType=new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
private final static Type recentCustomEmojiType=new TypeToken<ArrayList<Emoji>>() {}.getType();
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
this.prefs=prefs;
showInteractionCounts=prefs.getBoolean("interactionCounts", false);
customEmojiInNames=prefs.getBoolean("emojiInNames", true);
revealCWs=prefs.getBoolean("revealCWs", false);
hideSensitiveMedia=prefs.getBoolean("hideSensitive", true);
serverSideFiltersSupported=prefs.getBoolean("serverSideFilters", false);
// MEGALODON
showReplies=prefs.getBoolean("showReplies", true);
showBoosts=prefs.getBoolean("showBoosts", true);
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new ArrayList<>());
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", ContentType.PLAIN.name()));
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", true);
timelines=fromJson(prefs.getString("timelines", null), timelinesType, TimelineDefinition.getDefaultTimelines(session.getID()));
localOnlySupported=prefs.getBoolean("localOnlySupported", false);
glitchInstance=prefs.getBoolean("glitchInstance", false);
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()));
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
}
public long getNotificationsPauseEndTime(){
return prefs.getLong("notificationsPauseTime", 0L);
}
public void setNotificationsPauseEndTime(long time){
prefs.edit().putLong("notificationsPauseTime", time).apply();
}
public ColorPreference getCurrentColor(){
return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3;
}
public void save(){
prefs.edit()
.putBoolean("interactionCounts", showInteractionCounts)
.putBoolean("emojiInNames", customEmojiInNames)
.putBoolean("revealCWs", revealCWs)
.putBoolean("hideSensitive", hideSensitiveMedia)
.putBoolean("serverSideFilters", serverSideFiltersSupported)
// MEGALODON
.putBoolean("showReplies", showReplies)
.putBoolean("showBoosts", showBoosts)
.putString("recentLanguages", gson.toJson(recentLanguages))
.putBoolean("bottomEncoding", bottomEncoding)
.putString("defaultContentType", defaultContentType==null ? null : defaultContentType.name())
.putBoolean("contentTypesEnabled", contentTypesEnabled)
.putString("timelines", gson.toJson(timelines))
.putBoolean("localOnlySupported", localOnlySupported)
.putBoolean("glitchInstance", glitchInstance)
.putString("publishButtonText", publishButtonText)
.putString("timelineReplyVisibility", timelineReplyVisibility)
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
.putString("showEmojiReactions", showEmojiReactions.name())
.putString("color", color!=null ? color.name() : null)
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))
.apply();
}
public enum ColorPreference{
MATERIAL3,
PINK,
PURPLE,
GREEN,
BLUE,
BROWN,
RED,
YELLOW;
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
};
}
}
public enum ShowEmojiReactions{
HIDE_EMPTY,
ONLY_OPENED,
ALWAYS
}
}

View File

@@ -1,25 +1,52 @@
package org.joinmastodon.android.api.session;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import org.joinmastodon.android.E;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.StatusInteractionController;
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentialsPreferences;
import org.joinmastodon.android.api.requests.markers.GetMarkers;
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.events.NotificationsMarkerUpdatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterResult;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.model.Token;
import org.joinmastodon.android.utils.ObjectIdComparator;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class AccountSession{
private static final String TAG="AccountSession";
public Token token;
public Account self;
public String domain;
@@ -32,15 +59,17 @@ public class AccountSession{
public PushSubscription pushSubscription;
public boolean needUpdatePushSettings;
public long filtersLastUpdated;
public List<Filter> wordFilters=new ArrayList<>();
public List<LegacyFilter> wordFilters=new ArrayList<>();
public String pushAccountID;
public Preferences preferences;
public AccountActivationInfo activationInfo;
public Markers markers;
public Preferences preferences;
private transient MastodonAPIController apiController;
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
private transient CacheController cacheController;
private transient PushSubscriptionManager pushSubscriptionManager;
private transient SharedPreferences prefs;
private transient boolean preferencesNeedSaving;
private transient AccountLocalPreferences localPreferences;
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
this.token=token;
@@ -58,10 +87,6 @@ public class AccountSession{
return domain+"_"+self.id;
}
public String getFullUsername() {
return "@"+self.username+"@"+domain;
}
public MastodonAPIController getApiController(){
if(apiController==null)
apiController=new MastodonAPIController(this);
@@ -92,6 +117,214 @@ public class AccountSession{
return pushSubscriptionManager;
}
public String getFullUsername(){
return '@'+self.username+'@'+domain;
}
public void preferencesFromAccountSource(Account account) {
if (account != null && account.source != null && preferences != null) {
if (account.source.privacy != null)
preferences.postingDefaultVisibility = account.source.privacy;
if (account.source.language != null)
preferences.postingDefaultLanguage = account.source.language;
}
}
public void reloadPreferences(Consumer<Preferences> callback){
new GetPreferences()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Preferences result){
preferences=result;
preferencesFromAccountSource(self);
if(callback!=null)
callback.accept(result);
AccountSessionManager.getInstance().writeAccountsFile();
}
@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());
}
public SharedPreferences getRawLocalPreferences(){
if(prefs==null)
prefs=MastodonApp.context.getSharedPreferences(getID(), Context.MODE_PRIVATE);
return prefs;
}
public void reloadNotificationsMarker(Consumer<String> callback){
new GetMarkers()
.setCallback(new Callback<>(){
@Override
public void onSuccess(TimelineMarkers result){
if(result.notifications!=null && !TextUtils.isEmpty(result.notifications.lastReadId)){
String id=result.notifications.lastReadId;
String lastKnown=getLastKnownNotificationsMarker();
if(ObjectIdComparator.INSTANCE.compare(id, lastKnown)<0){
// Marker moved back -- previous marker update must have failed.
// Pretend it didn't happen and repeat the request.
id=lastKnown;
new SaveMarkers(null, id).exec(getID());
}
callback.accept(id);
setNotificationsMarker(id, false);
}
}
@Override
public void onError(ErrorResponse error){}
})
.exec(getID());
}
public String getLastKnownNotificationsMarker(){
return getRawLocalPreferences().getString("notificationsMarker", null);
}
public void setNotificationsMarker(String id, boolean clearUnread){
getRawLocalPreferences().edit().putString("notificationsMarker", id).apply();
E.post(new NotificationsMarkerUpdatedEvent(getID(), id, clearUnread));
}
public void logOut(Activity activity, Runnable onDone){
new RevokeOauthToken(app.clientId, app.clientSecret, token.accessToken)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
AccountSessionManager.getInstance().removeAccount(getID());
onDone.run();
}
@Override
public void onError(ErrorResponse error){
AccountSessionManager.getInstance().removeAccount(getID());
onDone.run();
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(getID());
}
public void savePreferencesLater(){
preferencesNeedSaving=true;
}
public void savePreferencesIfPending(){
if(preferencesNeedSaving){
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
preferencesNeedSaving=false;
self=result;
AccountSessionManager.getInstance().writeAccountsFile();
}
@Override
public void onError(ErrorResponse error){
Log.e(TAG, "failed to save preferences: "+error);
}
})
.exec(getID());
}
}
public AccountLocalPreferences getLocalPreferences(){
if(localPreferences==null)
localPreferences=new AccountLocalPreferences(getRawLocalPreferences(), this);
return localPreferences;
}
public void filterStatuses(List<Status> statuses, FilterContext context){
filterStatuses(statuses, context, null);
}
public void filterStatuses(List<Status> statuses, FilterContext context, Account profile){
filterStatusContainingObjects(statuses, Function.identity(), context, profile);
}
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context){
filterStatusContainingObjects(objects, extractor, context, null);
}
private boolean statusIsOnOwnProfile(Status s, Account profile){
return self != null && profile != null && s.account != null
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
}
private boolean isFilteredType(Status s){
return (!localPreferences.showReplies && s.inReplyToId != null)
|| (!localPreferences.showBoosts && s.reblog != null);
}
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){
localPreferences.serverSideFiltersSupported=true;
localPreferences.save();
break;
}
}
List<T> removeUs=new ArrayList<>();
for(int i=0; i<objects.size(); i++){
T o=objects.get(i);
if(filterStatusContainingObject(o, extractor, context, profile)){
Status s=extractor.apply(o);
removeUs.add(o);
if(s!=null && s.hasGapAfter!=null && i>0){
// oops, we're about to remove an item that has a gap after...
// gotta find the previous status that's not also about to be removed
for(int j=i-1; j>=0; j--){
T p=objects.get(j);
Status prev=extractor.apply(objects.get(j));
if(prev!=null && !removeUs.contains(p)){
prev.hasGapAfter=s.hasGapAfter;
break;
}
}
}
}
}
objects.removeAll(removeUs);
}
public <T> boolean filterStatusContainingObject(T object, Function<T, Status> extractor, FilterContext context, Account profile){
Status s=extractor.apply(object);
if(s==null)
return false;
// don't hide own posts in own profile
if(statusIsOnOwnProfile(s, profile))
return false;
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
return true;
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
if(localPreferences.serverSideFiltersSupported){
for(FilterResult filter : s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;
}
}else if(wordFilters!=null){
for(LegacyFilter filter : wordFilters){
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
return true;
}
}
return false;
}
public void updateAccountInfo(){
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
}
public Optional<Instance> getInstance() {
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
}
@@ -102,4 +335,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

@@ -21,23 +21,18 @@ import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
import org.joinmastodon.android.api.requests.filters.GetLegacyFilters;
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.requests.markers.GetMarkers;
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
import org.joinmastodon.android.events.EmojiUpdatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Marker;
import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.Token;
import java.io.File;
@@ -50,10 +45,10 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -155,11 +150,19 @@ public class AccountSessionManager{
return session;
}
public static AccountSession get(String id){
return getInstance().getAccount(id);
}
@Nullable
public AccountSession tryGetAccount(String id){
return sessions.get(id);
}
public static Optional<AccountSession> getOptional(String id) {
return Optional.ofNullable(getInstance().tryGetAccount(id));
}
@Nullable
public AccountSession tryGetAccount(Account account) {
return sessions.get(account.getDomainFromURL() + "_" + account.id);
@@ -192,13 +195,19 @@ public class AccountSessionManager{
AccountSession session=getAccount(id);
session.getCacheController().closeDatabase();
MastodonApp.context.deleteDatabase(id+".db");
GlobalUserPreferences.removeAccount(id);
MastodonApp.context.getSharedPreferences(id, 0).edit().clear().commit();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
MastodonApp.context.deleteSharedPreferences(id);
}else{
new File(MastodonApp.context.getDir("shared_prefs", Context.MODE_PRIVATE), id+".xml").delete();
}
sessions.remove(id);
if(lastActiveAccountID.equals(id)){
if(sessions.isEmpty())
lastActiveAccountID=null;
else
lastActiveAccountID=getLoggedInAccounts().get(0).getID();
prefs.edit().putString("lastActiveAccount", lastActiveAccountID).apply();
}
writeAccountsFile();
String domain=session.domain.toLowerCase();
@@ -271,14 +280,13 @@ public class AccountSessionManager{
HashSet<String> domains=new HashSet<>();
for(AccountSession session:sessions.values()){
domains.add(session.domain.toLowerCase());
if(now-session.infoLastUpdated>24L*3600_000L || session == activeSession){
updateSessionPreferences(session);
if(session == activeSession || now-session.infoLastUpdated>24L*3600_000L){
session.reloadPreferences(null);
updateSessionLocalInfo(session);
}
if(now-session.filtersLastUpdated>3600_000L || session == activeSession){
if(session == activeSession || (session.getLocalPreferences().serverSideFiltersSupported && now-session.filtersLastUpdated>3600_000L)){
updateSessionWordFilters(session);
}
updateSessionMarkers(session);
}
if(loadedInstances){
maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null);
@@ -289,61 +297,20 @@ public class AccountSessionManager{
long now=System.currentTimeMillis();
for(String domain:domains){
Long lastUpdated=instancesLastUpdated.get(domain);
if(lastUpdated==null || now-lastUpdated>24L*3600_000L || domain.equals(activeDomain)){
if(domain.equals(activeDomain) || lastUpdated==null || now-lastUpdated>24L*3600_000L){
updateInstanceInfo(domain);
}
}
}
private void preferencesFromSource(AccountSession session, Account account) {
if (account != null && account.source != null && session.preferences != null) {
if (account.source.privacy != null)
session.preferences.postingDefaultVisibility = account.source.privacy;
if (account.source.language != null)
session.preferences.postingDefaultLanguage = account.source.language;
}
}
private void updateSessionLocalInfo(AccountSession session){
/*package*/ void updateSessionLocalInfo(AccountSession session){
new GetOwnAccount()
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
session.self=result;
session.infoLastUpdated=System.currentTimeMillis();
preferencesFromSource(session, result);
writeAccountsFile();
}
@Override
public void onError(ErrorResponse error){}
})
.exec(session.getID());
}
private void updateSessionPreferences(AccountSession session){
new GetPreferences().setCallback(new Callback<>() {
@Override
public void onSuccess(Preferences preferences) {
session.preferences=preferences;
preferencesFromSource(session, session.self);
}
@Override
public void onError(ErrorResponse error) {
session.preferences = new Preferences();
preferencesFromSource(session, session.self);
}
}).exec(session.getID());
}
private void updateSessionWordFilters(AccountSession session){
new GetWordFilters()
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Filter> result){
session.wordFilters=result;
session.filtersLastUpdated=System.currentTimeMillis();
session.preferencesFromAccountSource(result);
writeAccountsFile();
}
@@ -355,19 +322,22 @@ public class AccountSessionManager{
.exec(session.getID());
}
private void updateSessionMarkers(AccountSession session) {
new GetMarkers(EnumSet.allOf(Marker.Type.class)).setCallback(new Callback<>() {
@Override
public void onSuccess(Markers markers) {
session.markers = markers;
writeAccountsFile();
}
private void updateSessionWordFilters(AccountSession session){
new GetLegacyFilters()
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<LegacyFilter> result){
session.wordFilters=result;
session.filtersLastUpdated=System.currentTimeMillis();
writeAccountsFile();
}
@Override
public void onError(ErrorResponse error) {
@Override
public void onError(ErrorResponse error){
}
}).exec(session.getID());
}
})
.exec(session.getID());
}
public void updateInstanceInfo(String domain){

View File

@@ -1,4 +0,0 @@
package org.joinmastodon.android.events;
public class AllNotificationsSeenEvent {
}

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

@@ -6,10 +6,12 @@ public class ListUpdatedCreatedEvent {
public final String id;
public final String title;
public final ListTimeline.RepliesPolicy repliesPolicy;
public final boolean exclusive;
public ListUpdatedCreatedEvent(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
public ListUpdatedCreatedEvent(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
this.id = id;
this.title = title;
this.exclusive = exclusive;
this.repliesPolicy = repliesPolicy;
}
}

View File

@@ -1,9 +0,0 @@
package org.joinmastodon.android.events;
public class NotificationReceivedEvent {
public String account, id;
public NotificationReceivedEvent(String account, String id) {
this.account = account;
this.id = id;
}
}

View File

@@ -0,0 +1,13 @@
package org.joinmastodon.android.events;
public class NotificationsMarkerUpdatedEvent{
public final String accountID;
public final String marker;
public final boolean clearUnread;
public NotificationsMarkerUpdatedEvent(String accountID, String marker, boolean clearUnread){
this.accountID=accountID;
this.marker=marker;
this.clearUnread=clearUnread;
}
}

View File

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

View File

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

View File

@@ -6,9 +6,11 @@ public class StatusCountersUpdatedEvent{
public String id;
public long favorites, reblogs, replies;
public boolean favorited, reblogged, bookmarked, pinned;
public Status status;
public StatusCountersUpdatedEvent(Status s){
id=s.id;
status=s;
favorites=s.favouritesCount;
reblogs=s.reblogsCount;
replies=s.repliesCount;

View File

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

View File

@@ -9,19 +9,16 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback;
@@ -48,27 +45,21 @@ public class AccountTimelineFragment extends StatusListFragment{
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
user=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
filter=GetAccountStatuses.Filter.valueOf(getArguments().getString("filter"));
super.onAttach(activity);
}
@Override
protected void doLoadData(int offset, int count){
if(user==null) // TODO figure out why this happens
return;
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
currentRequest=new GetAccountStatuses(user.id, getMaxID(), null, count, filter)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
AccountSessionManager asm = AccountSessionManager.getInstance();
result=result.stream().filter(status -> {
// don't hide own posts in own profile
if (asm.isSelf(accountID, user) && asm.isSelf(accountID, status.account)) return true;
else return new StatusFilterPredicate(accountID, getFilterContext()).test(status);
}).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty());
boolean more=applyMaxID(result);
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
onDataLoaded(result, more);
}
})
.exec(accountID);
@@ -77,6 +68,7 @@ public class AccountTimelineFragment extends StatusListFragment{
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
view.setBackground(null); // prevents unnecessary overdraw
}
@Override
@@ -86,20 +78,20 @@ public class AccountTimelineFragment extends StatusListFragment{
loadData();
}
protected void onStatusCreated(StatusCreatedEvent ev){
protected void onStatusCreated(Status status){
AccountSessionManager asm = AccountSessionManager.getInstance();
if(!asm.isSelf(accountID, ev.status.account) || !asm.isSelf(accountID, user))
if(!asm.isSelf(accountID, status.account) || !asm.isSelf(accountID, user))
return;
if(filter==GetAccountStatuses.Filter.PINNED) return;
if(filter==GetAccountStatuses.Filter.DEFAULT){
// Keep replies to self, discard all other replies
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
if(status.inReplyToAccountId!=null && !status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
return;
}else if(filter==GetAccountStatuses.Filter.MEDIA){
if(Optional.ofNullable(ev.status.mediaAttachments).map(List::isEmpty).orElse(true))
if(Optional.ofNullable(status.mediaAttachments).map(List::isEmpty).orElse(true))
return;
}
prependItems(Collections.singletonList(ev.status), true);
prependItems(Collections.singletonList(status), true);
if (isOnTop()) scrollToTop();
}
@@ -130,8 +122,8 @@ public class AccountTimelineFragment extends StatusListFragment{
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
protected FilterContext getFilterContext() {
return FilterContext.ACCOUNT;
}
@Override

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

View File

@@ -6,14 +6,9 @@ import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -26,20 +21,27 @@ 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;
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.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
@@ -54,6 +56,8 @@ 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;
@@ -68,10 +72,11 @@ import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, IsOnTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends MastodonRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, IsOnTop, HasFab, ProvidesAssistContent.ProvidesWebUri {
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
protected DisplayItemsAdapter adapter;
protected String accountID;
@@ -83,6 +88,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected Rect tmpRect=new Rect();
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
protected boolean currentlyScrolling;
protected String maxID;
public BaseStatusListFragment(){
super(20);
@@ -96,7 +102,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if(GlobalUserPreferences.disableMarquee){
if(GlobalUserPreferences.toolbarMarquee){
setTitleMarqueeEnabled(false);
setSubtitleMarqueeEnabled(false);
}
@@ -149,6 +155,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
protected String getMaxID(){
if(refreshing) return null;
if(maxID!=null) return maxID;
if(!preloadedData.isEmpty())
return preloadedData.get(preloadedData.size()-1).getID();
else if(!data.isEmpty())
@@ -157,6 +165,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null;
}
protected boolean applyMaxID(List<Status> result){
boolean empty=result.isEmpty();
if(!empty) maxID=result.get(result.size()-1).id;
return !empty;
}
protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
protected abstract void addAccountToKnown(T s);
@@ -350,7 +364,11 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void getSelectorBounds(View view, Rect outRect){
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
int lastIndex = -1, firstIndex = -1;
list.getDecoratedBoundsWithMargins(view, outRect);
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
list.getDecoratedBoundsWithMargins(view, outRect);
}else{
outRect.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
}
RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
if(holder instanceof StatusDisplayItem.Holder){
if(((StatusDisplayItem.Holder<?>) holder).getItem().getType()==StatusDisplayItem.Type.GAP){
@@ -427,6 +445,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
protected int getMainAdapterOffset(){
if(list.getAdapter() instanceof MergeRecyclerAdapter mergeAdapter){
return mergeAdapter.getPositionForAdapter(adapter);
}
return 0;
}
@@ -438,6 +459,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
c.drawLine(0, y, parent.getWidth(), y, paint);
}
protected boolean needDividerForExtraItem(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
return false;
}
public abstract void onItemClick(String id);
protected void updatePoll(String itemID, Status status, Poll poll){
@@ -525,81 +550,75 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
.exec(accountID);
}
public void onRevealSpoilerClick(TextStatusDisplayItem.Holder holder){
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
Status status=holder.getItem().status;
revealSpoiler(status, holder.getItemID());
toggleSpoiler(status, holder.getItemID());
}
public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){
Status status=holder.getItem().status;
revealSpoiler(status, holder.getItemID());
}
protected void revealSpoiler(Status status, String itemID){
status.spoilerRevealed=true;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null)
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null)
header.rebind();
updateImagesSpoilerState(status, itemID);
}
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){
Status status=holder.getItem().status;
status.spoilerRevealed=!status.spoilerRevealed;
if(!TextUtils.isEmpty(status.spoilerText)){
TextStatusDisplayItem.Holder text = findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class);
if(text!=null){
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
}
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
Status status = holder.getItem().status;
if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false);
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
if(mediaGrid!=null){
if(!status.sensitiveRevealed) mediaGrid.revealSensitive();
else mediaGrid.hideSensitive();
}else{
status.sensitiveRevealed=false;
notifyItemChangedAfter(holder.getItem(), MediaGridStatusDisplayItem.class);
}
holder.rebind();
updateImagesSpoilerState(status, holder.getItemID());
}
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true);
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
}
protected void toggleSpoiler(Status status, String itemID){
status.spoilerRevealed=!status.spoilerRevealed;
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
status.sensitiveRevealed = false;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null) spoiler.rebind();
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
int index=displayItems.indexOf(spoilerItem);
if(status.spoilerRevealed){
displayItems.addAll(index+1, spoilerItem.contentItems);
adapter.notifyItemRangeInserted(index+1, spoilerItem.contentItems.size());
}else{
displayItems.subList(index+1, index+1+spoilerItem.contentItems.size()).clear();
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
}
notifyItemChanged(itemID, TextStatusDisplayItem.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.rebind();
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
list.invalidateItemDecorations();
}
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
if (holder.getItem().status.textExpandable != expandable && list != null) {
holder.getItem().status.textExpandable = expandable;
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if (header != null) header.rebind();
Status s=holder.getItem().status;
if(s.textExpandable!=expandable && list!=null) {
s.textExpandable=expandable;
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.bindCollapseButton();
}
}
public void onToggleExpanded(Status status, String itemID) {
status.textExpanded = !status.textExpanded;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
notifyItemChanged(itemID, TextStatusDisplayItem.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if (text != null) text.rebind();
if (header != null) header.rebind();
if(header!=null) header.animateExpandToggle();
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
}
protected void updateImagesSpoilerState(Status status, String itemID){
ArrayList<Integer> updatedPositions=new ArrayList<>();
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
if(mediaGrid!=null){
mediaGrid.setRevealed(status.spoilerRevealed);
updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset());
}
int i=0;
for(StatusDisplayItem item:displayItems){
if(itemID.equals(item.parentID) && item instanceof MediaGridStatusDisplayItem && !updatedPositions.contains(i)){
adapter.notifyItemChanged(i);
}
i++;
}
}
public void onImageUpdated(MediaGridStatusDisplayItem.Holder holder, int index) {
holder.rebind();
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
if(mediaGrid!=null){
adapter.notifyItemChanged(mediaGrid.getAbsoluteAdapterPosition());
}
}
public void onGapClick(GapStatusDisplayItem.Holder item){}
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
int startPos = warning.getAbsoluteAdapterPosition();
@@ -655,9 +674,61 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null;
}
/**
* Use this as a fallback if findHolderOfType fails to find the ViewHolder.
* It might still be bound but off-screen and therefore not a child of the RecyclerView -
* resulting in the ViewHolder displaying an outdated state once scrolled back into view.
*/
protected <I extends StatusDisplayItem> int notifyItemChanged(String id, Class<I> type){
boolean encounteredParent=false;
for(int i=0; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
boolean idEquals=id.equals(item.parentID);
if(!encounteredParent && idEquals) encounteredParent=true; // reached top of the parent
else if(encounteredParent && !idEquals) break; // passed by bottom of the parent. man muss ja wissen wann schluss is
if(idEquals && type.isInstance(item)){
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedAfter(StatusDisplayItem afterThis, Class<I> type){
int startIndex=displayItems.indexOf(afterThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem");
String parentID=afterThis.parentID;
for(int i=startIndex; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class<I> type){
int startIndex=displayItems.indexOf(beforeThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem");
String parentID=beforeThis.parentID;
for(int i=startIndex; i>=0; i--){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
@Nullable
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
for(int i=0;i<list.getChildCount();i++){
for(int i=0; i<list.getChildCount(); i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
return type.cast(holder);
@@ -754,11 +825,89 @@ 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());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
}
@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);
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
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());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
}
public void rebuildAllDisplayItems(){
displayItems.clear();
for(T item:data){
displayItems.addAll(buildDisplayItems(item));
}
adapter.notifyDataSetChanged();
}
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){}
@Override
protected void onDataLoaded(List<T> d, boolean more) {
if(getContext()==null) return;
super.onDataLoaded(d, more);
// more available, but the page isn't even full yet? seems wrong, let's load some more
if (more && d.size() < itemsPerPage) preloader.onScrolledToLastItem();
if(more && data.size() < itemsPerPage){
preloader.onScrolledToLastItem();
}
}
public void scrollBy(int x, int y) {
list.scrollBy(x, y);
}
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
@@ -770,7 +919,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@NonNull
@Override
public BindableViewHolder<StatusDisplayItem> onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return (BindableViewHolder<StatusDisplayItem>) StatusDisplayItem.createViewHolder(StatusDisplayItem.Type.values()[viewType & (~0x80000000)], getActivity(), parent);
BindableViewHolder<StatusDisplayItem> holder=(BindableViewHolder<StatusDisplayItem>) StatusDisplayItem.createViewHolder(StatusDisplayItem.Type.values()[viewType & (~0x80000000)], getActivity(), parent, BaseStatusListFragment.this);
onModifyItemViewHolder(holder);
return holder;
}
@Override
@@ -801,15 +952,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
private Paint dividerPaint=new Paint(), hiddenMediaPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
private Typeface mediumTypeface=Typeface.create("sans-serif-medium", Typeface.NORMAL);
private Layout mediaHiddenTitleLayout, mediaHiddenTextLayout, tapToRevealTextLayout;
private int currentMediaHiddenLayoutsWidth=0;
private Paint dividerPaint=new Paint();
{
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorPollVoted));
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OutlineVariant));
dividerPaint.setStyle(Paint.Style.STROKE);
dividerPaint.setStrokeWidth(V.dp(1));
dividerPaint.setStrokeWidth(V.dp(0.5f));
}
@Override
@@ -819,80 +967,23 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
View bottomSibling=parent.getChildAt(i+1);
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) continue;
if(needDrawDivider(holder, siblingHolder)){
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
}
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
for(int i=0;i<parent.getChildCount();i++){
View child=parent.getChildAt(i);
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
hiddenMediaPaint.setColor(0x80000000);
c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
}
}
private boolean needDrawDivider(RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder){
if(needDividerForExtraItem(holder.itemView, siblingHolder.itemView, holder, siblingHolder))
return true;
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh){
// Do not draw dividers between hashtag and/or account rows
if((ih instanceof HashtagStatusDisplayItem.Holder || ih instanceof AccountStatusDisplayItem.Holder) && (sh instanceof HashtagStatusDisplayItem.Holder || sh instanceof AccountStatusDisplayItem.Holder))
return false;
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) return false;
return (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP;
}
for(int i=0;i<parent.getChildCount();i++){
View child=parent.getChildAt(i);
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
if(!imgHolder.getItem().status.spoilerRevealed){
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
int listWidth=getListWidthForMediaLayout();
int width=Math.min(listWidth, UiUtils.MAX_WIDTH);
if(currentMediaHiddenLayoutsWidth!=width)
rebuildMediaHiddenLayouts(width-V.dp(32));
c.save();
float totalHeight;
boolean hiddenByAuthor=imgHolder.getItem().status.sensitive;
if(hiddenByAuthor)
totalHeight=mediaHiddenTitleLayout.getHeight()+mediaHiddenTextLayout.getHeight()+V.dp(8);
else
totalHeight=tapToRevealTextLayout.getHeight();
c.translate(child.getX()+V.dp(16), child.getY()+child.getHeight()/2f-totalHeight/2f);
if(hiddenByAuthor){
mediaHiddenTitleLayout.draw(c);
c.translate(0, mediaHiddenTitleLayout.getHeight()+V.dp(8));
mediaHiddenTextLayout.draw(c);
}else{
tapToRevealTextLayout.draw(c);
}
c.restore();
}
}
}
}
}
private void rebuildMediaHiddenLayouts(int width){
currentMediaHiddenLayoutsWidth=width;
String title=getString(R.string.sensitive_content);
TextPaint titlePaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
titlePaint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorGray50));
titlePaint.setTextSize(V.dp(22));
titlePaint.setTypeface(mediumTypeface);
mediaHiddenTitleLayout=StaticLayout.Builder.obtain(title, 0, title.length(), titlePaint, width)
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.build();
String tapToReveal=getString(R.string.tap_to_reveal);
tapToRevealTextLayout=StaticLayout.Builder.obtain(tapToReveal, 0, tapToReveal.length(), titlePaint, width)
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.build();
TextPaint textPaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorGray200));
textPaint.setTextSize(V.dp(16));
String text=getString(R.string.sensitive_content_explain);
mediaHiddenTextLayout=StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, width)
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.setLineSpacing(V.dp(5), 1f)
.build();
return false;
}
}
}

View File

@@ -5,7 +5,8 @@ import android.net.Uri;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Status;
@@ -27,7 +28,7 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(HeaderPaginationList<Status> result){
if (getActivity() == null) return;
if(getActivity()==null) return;
if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else
@@ -39,8 +40,13 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
}
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
// no-op
}
@Override
protected FilterContext getFilterContext() {
return FilterContext.ACCOUNT;
}
@Override

View File

@@ -1,10 +1,18 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.content.res.TypedArray;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.text.SpannableStringBuilder;
import android.text.style.BulletSpan;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -12,41 +20,57 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.UpdateAttachment;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Attachment;
import org.parceler.Parcels;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.utils.ColorPalette;
import org.joinmastodon.android.ui.utils.UiUtils;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.ToolbarFragment;
import java.util.Collections;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
public class ComposeImageDescriptionFragment extends MastodonToolbarFragment implements OnBackPressedListener{
private static final String TAG="ComposeImageDescription";
private String accountID, attachmentID;
private EditText edit;
private Button saveButton;
private ImageView image;
private ContextThemeWrapper themeWrapper;
private PhotoViewer photoViewer;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment");
setHasOptionsMenu(true);
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
setTitle(R.string.edit_image);
accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment");
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().getCurrentColor())
.apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
setTitle(R.string.add_alt_text);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
return super.onCreateView(themeWrapper.getSystemService(LayoutInflater.class), container, savedInstanceState);
}
@Override
@@ -54,14 +78,48 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
View view=inflater.inflate(R.layout.fragment_image_description, container, false);
edit=view.findViewById(R.id.edit);
ImageView image=view.findViewById(R.id.photo);
image=view.findViewById(R.id.photo);
int width=getArguments().getInt("width", 0);
int height=getArguments().getInt("height", 0);
if(width>0 && height>0){
// image.setAspectRatio(Math.max(1f, (float)width/height));
}
image.setOnClickListener(v->openPhotoViewer());
Uri uri=getArguments().getParcelable("uri");
ViewImageLoader.load(image, null, new UrlImageLoaderRequest(uri, 1000, 1000));
Attachment.Type type=Attachment.Type.valueOf(getArguments().getString("attachmentType"));
if(type==Attachment.Type.IMAGE)
ViewImageLoader.load(image, null, new UrlImageLoaderRequest(uri, 1000, 1000));
else
loadVideoThumbIntoView(image, uri);
edit.setText(getArguments().getString("existingDescription"));
return view;
}
private void loadVideoThumbIntoView(ImageView target, Uri uri){
MastodonAPIController.runInBackground(()->{
Context context=getActivity();
if(context==null)
return;
try{
MediaMetadataRetriever mmr=new MediaMetadataRetriever();
mmr.setDataSource(context, uri);
Bitmap frame=mmr.getFrameAtTime(3_000_000);
mmr.release();
int size=Math.max(frame.getWidth(), frame.getHeight());
int maxSize=V.dp(250);
if(size>maxSize){
float factor=maxSize/(float)size;
frame=Bitmap.createScaledBitmap(frame, Math.round(frame.getWidth()*factor), Math.round(frame.getHeight()*factor), true);
}
Bitmap finalFrame=frame;
target.post(()->target.setImageBitmap(finalFrame));
}catch(Exception x){
Log.w(TAG, "loadVideoThumbIntoView: error getting video frame", x);
}
});
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
@@ -71,43 +129,114 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment{
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
TypedArray ta=getActivity().obtainStyledAttributes(new int[]{R.attr.secondaryButtonStyle});
int buttonStyle=ta.getResourceId(0, 0);
ta.recycle();
saveButton=new Button(getActivity(), null, 0, buttonStyle);
saveButton.setText(R.string.save);
saveButton.setOnClickListener(this::onSaveClick);
FrameLayout wrap=new FrameLayout(getActivity());
wrap.addView(saveButton, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT));
wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
wrap.setClipToPadding(false);
MenuItem item=menu.add(R.string.publish);
item.setActionView(wrap);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
inflater.inflate(R.menu.compose_image_description, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
if(item.getItemId()==R.id.help){
SpannableStringBuilder msg=new SpannableStringBuilder(getText(R.string.alt_text_help));
BulletSpan[] spans=msg.getSpans(0, msg.length(), BulletSpan.class);
for(BulletSpan span:spans){
BulletSpan betterSpan;
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.Q)
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface));
else
betterSpan=new BulletSpan(V.dp(10), UiUtils.getThemeColor(themeWrapper, R.attr.colorM3OnSurface), V.dp(1.5f));
msg.setSpan(betterSpan, msg.getSpanStart(span), msg.getSpanEnd(span), msg.getSpanFlags(span));
msg.removeSpan(span);
}
new M3AlertDialogBuilder(themeWrapper)
.setTitle(R.string.what_is_alt_text)
.setMessage(msg)
.setPositiveButton(R.string.ok, null)
.show();
}
return true;
}
private void onSaveClick(View v){
new UpdateAttachment(attachmentID, edit.getText().toString().trim())
.setCallback(new Callback<>(){
@Override
public void onSuccess(Attachment result){
Bundle r=new Bundle();
r.putParcelable("attachment", Parcels.wrap(result));
setResult(true, r);
Nav.finish(ComposeImageDescriptionFragment.this);
}
@Override
public boolean onBackPressed(){
deliverResult();
return false;
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.saving, false)
.exec(accountID);
@Override
protected LayoutInflater getToolbarLayoutInflater(){
return LayoutInflater.from(themeWrapper);
}
private void deliverResult(){
Bundle r=new Bundle();
r.putString("text", edit.getText().toString().trim());
r.putString("attachment", attachmentID);
setResult(true, r);
}
private void openPhotoViewer(){
Attachment fakeAttachment=new Attachment();
fakeAttachment.id="local";
fakeAttachment.type=Attachment.Type.valueOf(getArguments().getString("attachmentType"));
int width=getArguments().getInt("width", 0);
int height=getArguments().getInt("height", 0);
Uri uri=getArguments().getParcelable("uri");
fakeAttachment.url=uri.toString();
fakeAttachment.meta=new Attachment.Metadata();
fakeAttachment.meta.width=width;
fakeAttachment.meta.height=height;
photoViewer=new PhotoViewer(getActivity(), Collections.singletonList(fakeAttachment), 0, new PhotoViewer.Listener(){
@Override
public void setPhotoViewVisibility(int index, boolean visible){
image.setAlpha(visible ? 1f : 0f);
}
@Override
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
int[] pos={0, 0};
image.getLocationOnScreen(pos);
outRect.set(pos[0], pos[1], pos[0]+image.getWidth(), pos[1]+image.getHeight());
image.setElevation(1f);
return true;
}
@Override
public void setTransitioningViewTransform(float translateX, float translateY, float scale){
image.setTranslationX(translateX);
image.setTranslationY(translateY);
image.setScaleX(scale);
image.setScaleY(scale);
}
@Override
public void endPhotoViewTransition(){
Drawable d=image.getDrawable();
image.setImageDrawable(null);
image.setImageDrawable(d);
image.setTranslationX(0f);
image.setTranslationY(0f);
image.setScaleX(1f);
image.setScaleY(1f);
image.setElevation(0f);
}
@Nullable
@Override
public Drawable getPhotoViewCurrentDrawable(int index){
return image.getDrawable();
}
@Override
public void photoViewerDismissed(){
photoViewer=null;
}
@Override
public void onRequestPermissions(String[] permissions){
}
});
photoViewer.removeMenu();
}
}

View File

@@ -1,12 +1,14 @@
package org.joinmastodon.android.fragments;
import static android.view.Menu.NONE;
import static com.hootsuite.nachos.terminator.ChipTerminatorHandler.BEHAVIOR_CHIPIFY_ALL;
import static org.joinmastodon.android.ui.utils.UiUtils.makeBackItem;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -14,46 +16,51 @@ import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences;
import com.hootsuite.nachos.NachoTextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition> implements ScrollableToTop {
public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
private String accountID;
private TimelinesAdapter adapter;
private final ItemTouchHelper itemTouchHelper;
@@ -62,6 +69,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>();
private final List<ListTimeline> listTimelines = new ArrayList<>();
private final List<Hashtag> hashtags = new ArrayList<>();
private MenuItem addHashtagItem;
public EditTimelinesFragment() {
super(10);
@@ -114,7 +122,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
super.onViewCreated(view, savedInstanceState);
itemTouchHelper.attachToRecyclerView(list);
refreshLayout.setEnabled(false);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3OutlineVariant, 0.5f, 56, 16));
}
@Override
@@ -132,23 +140,36 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
}
TimelineDefinition tl = timelineByMenuItem.get(item);
if (tl != null) {
data.add(tl.copy());
adapter.notifyItemInserted(data.size());
saveTimelines();
updateOptionsMenu();
};
addTimeline(tl);
} else if (item == addHashtagItem) {
makeTimelineEditor(null, (hashtag) -> {
if (hashtag != null) addTimeline(hashtag);
}, null);
}
return true;
}
private void addTimeline(TimelineDefinition tl) {
data.add(tl.copy());
adapter.notifyItemInserted(data.size());
saveTimelines();
updateOptionsMenu();
}
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
if (data.contains(tl)) return;
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, tl.getTitle(getContext()));
item.setIcon(tl.getIcon().iconRes);
MenuItem item = addOptionsItem(menu, tl.getTitle(getContext()), tl.getIcon().iconRes);
timelineByMenuItem.put(item, tl);
}
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon) {
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, name);
item.setIcon(icon);
return item;
}
private void updateOptionsMenu() {
if (getActivity() == null) return;
if(getActivity()==null) return;
optionsMenu.clear();
timelineByMenuItem.clear();
@@ -167,8 +188,9 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
makeBackItem(listsMenu);
makeBackItem(hashtagsMenu);
TimelineDefinition.getAllTimelines(accountID).forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
addHashtagItem = addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0);
@@ -179,10 +201,12 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
}
private void saveTimelines() {
updated = true;
GlobalUserPreferences.pinnedTimelines.put(accountID, data.size() > 0 ? data : List.of(TimelineDefinition.HOME_TIMELINE));
GlobalUserPreferences.save();
}
updated=true;
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
prefs.timelines=data;
prefs.save();
}
private void removeTimeline(int position) {
data.remove(position);
@@ -193,7 +217,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
@Override
protected void doLoadData(int offset, int count){
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)), false);
onDataLoaded(AccountSessionManager.get(accountID).getLocalPreferences().timelines);
updateOptionsMenu();
}
@@ -213,6 +237,145 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
if (updated) UiUtils.restartApp();
}
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
if (tags == null || tags.isEmpty()) return false;
editText.setText(tags);
editText.chipifyAllUnterminatedTokens();
return true;
}
private NachoTextView prepareChipTextView(NachoTextView nacho) {
//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;
}
@SuppressLint("ClickableViewAccessibility")
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove) {
Context ctx = getContext();
View view = getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
View divider = view.findViewById(R.id.divider);
Button advancedBtn = view.findViewById(R.id.advanced);
EditText editText = view.findViewById(R.id.input);
if (item != null) editText.setText(item.getCustomTitle());
editText.setHint(item != null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap);
boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG;
advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE);
advancedBtn.setOnClickListener(l -> {
advancedBtn.setSelected(!advancedBtn.isSelected());
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
tagWrap.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
UiUtils.beginLayoutTransition((ViewGroup) view);
});
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch);
view.findViewById(R.id.local_only)
.setOnClickListener(l -> localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
EditText tagMain = view.findViewById(R.id.tag_main);
NachoTextView tagsAny = prepareChipTextView(view.findViewById(R.id.tags_any));
NachoTextView tagsAll = prepareChipTextView(view.findViewById(R.id.tags_all));
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none));
if (item != null) {
tagMain.setText(item.getHashtagName());
boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
hasAdvanced = setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
hasAdvanced = setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
if (item.isHashtagLocalOnly()) {
localOnlySwitch.setChecked(true);
hasAdvanced = true;
}
if (hasAdvanced) {
advancedBtn.setSelected(true);
advancedBtn.setText(R.string.sk_advanced_options_hide);
tagWrap.setVisibility(View.VISIBLE);
divider.setVisibility(View.VISIBLE);
}
}
ImageButton btn = view.findViewById(R.id.button);
PopupMenu popup = new PopupMenu(ctx, btn);
TimelineDefinition.Icon currentIcon = item != null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
btn.setImageResource(currentIcon.iconRes);
btn.setTag(currentIcon.ordinal());
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
btn.setOnTouchListener(popup.getDragToOpenListener());
btn.setOnClickListener(l -> popup.show());
Menu menu = popup.getMenu();
TimelineDefinition.Icon defaultIcon = item != null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
if (!currentIcon.equals(defaultIcon)) {
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
}
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
if (icon.hidden || icon.ordinal() == (int) btn.getTag()) continue;
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
}
UiUtils.enablePopupMenuIcons(ctx, popup);
popup.setOnMenuItemClickListener(menuItem -> {
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
btn.setImageResource(icon.iconRes);
btn.setTag(menuItem.getItemId());
btn.setContentDescription(ctx.getString(icon.nameRes));
return true;
});
AlertDialog.Builder builder = new M3AlertDialogBuilder(ctx)
.setTitle(item == null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
.setView(view)
.setPositiveButton(R.string.save, (d, which) -> {
tagsAny.chipifyAllUnterminatedTokens();
tagsAll.chipifyAllUnterminatedTokens();
tagsNone.chipifyAllUnterminatedTokens();
String name = editText.getText().toString().trim();
String mainHashtag = tagMain.getText().toString().trim();
if (TextUtils.isEmpty(mainHashtag)) {
mainHashtag = name;
name = null;
}
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) {
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(null);
return;
}
TimelineDefinition tl = item != null ? item : TimelineDefinition.ofHashtag(name);
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[(int) btn.getTag()];
tl.setIcon(icon);
tl.setTitle(name);
tl.setTagOptions(
mainHashtag,
tagsAny.getChipValues(),
tagsAll.getChipValues(),
tagsNone.getChipValues(),
localOnlySwitch.isChecked()
);
onSave.accept(tl);
})
.setNegativeButton(R.string.cancel, (d, which) -> {});
if (onRemove != null) builder.setNeutralButton(R.string.sk_remove, (d, which) -> onRemove.run());
builder.show();
btn.requestFocus();
}
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
@NonNull
@Override
@@ -256,60 +419,19 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
});
}
private void onSave(TimelineDefinition tl) {
saveTimelines();
rebind();
}
private void onRemove() {
removeTimeline(getAbsoluteAdapterPosition());
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onClick() {
Context ctx = getContext();
LinearLayout view = (LinearLayout) getActivity().getLayoutInflater()
.inflate(R.layout.edit_timeline, (ViewGroup) itemView, false);
TextInputFrameLayout inputLayout = view.findViewById(R.id.input);
EditText editText = inputLayout.getEditText();
editText.setText(item.getCustomTitle());
editText.setHint(item.getDefaultTitle(ctx));
ImageButton btn = view.findViewById(R.id.button);
PopupMenu popup = new PopupMenu(ctx, btn);
TimelineDefinition.Icon currentIcon = item.getIcon();
btn.setImageResource(currentIcon.iconRes);
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
btn.setOnTouchListener(popup.getDragToOpenListener());
btn.setOnClickListener(l -> popup.show());
Menu menu = popup.getMenu();
TimelineDefinition.Icon defaultIcon = item.getDefaultIcon();
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
if (!currentIcon.equals(defaultIcon)) {
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
}
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
if (icon.hidden || icon.equals(item.getIcon())) continue;
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
}
UiUtils.enablePopupMenuIcons(ctx, popup);
popup.setOnMenuItemClickListener(menuItem -> {
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
btn.setImageResource(icon.iconRes);
btn.setContentDescription(ctx.getString(icon.nameRes));
item.setIcon(icon);
return true;
});
new M3AlertDialogBuilder(ctx)
.setTitle(R.string.sk_edit_timeline)
.setView(view)
.setPositiveButton(R.string.save, (d, which) -> {
item.setTitle(editText.getText().toString().trim());
rebind();
saveTimelines();
})
.setNeutralButton(R.string.sk_remove, (d, which) ->
removeTimeline(getAbsoluteAdapterPosition()))
.setNegativeButton(R.string.cancel, (d, which) -> {})
.show();
btn.requestFocus();
makeTimelineEditor(item, this::onSave, this::onRemove);
}
}

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