Compare commits

..

183 Commits

Author SHA1 Message Date
sk
fdff407514 refactor code 2023-10-06 15:01:17 +02:00
LucasGGamerM
422c3c3809 fix(translations): fix crash when status language is null 2023-10-02 17:00: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
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
150 changed files with 3171 additions and 571 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 33
versionCode 99
versionName "2.0.3+fork.99"
versionCode 100
versionName "2.1.4+fork.100"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
}

View File

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

View File

@@ -176,6 +176,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
.map(type->{
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
channel.setLightColor(context.getColor(R.color.primary_700));
channel.enableLights(true);
channel.setGroup(accountID);
return channel;
})
@@ -205,6 +207,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
.setShowWhen(true)
.setCategory(Notification.CATEGORY_SOCIAL)
.setAutoCancel(true)
.setLights(UiUtils.getThemeColor(context, android.R.attr.colorAccent), 500, 1000)
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
if (!GlobalUserPreferences.uniformNotificationIcon) {
@@ -321,8 +324,8 @@ 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.hasSpoiler() &&

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -219,7 +219,7 @@ public class AccountSession{
public void savePreferencesIfPending(){
if(preferencesNeedSaving){
new UpdateAccountCredentialsPreferences(preferences, null, null)
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
@@ -303,6 +303,10 @@ public class AccountSession{
});
}
public void updateAccountInfo(){
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
}
public Optional<Instance> getInstance() {
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
}

View File

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

View File

@@ -22,6 +22,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account;
@@ -29,7 +30,9 @@ import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
@@ -55,6 +58,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -772,6 +776,61 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
}
public void togglePostTranslation(Status status, String itemID){
switch(status.translationState){
case LOADING -> {
return;
}
case SHOWN -> {
status.translationState=Status.TranslationState.HIDDEN;
}
case HIDDEN -> {
if(status.translation!=null){
status.translationState=Status.TranslationState.SHOWN;
}else{
status.translationState=Status.TranslationState.LOADING;
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
.setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()==null)
return;
status.translation=result;
status.translationState=Status.TranslationState.SHOWN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
status.translationState=Status.TranslationState.HIDDEN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
}
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.translation_failed)
.setPositiveButton(R.string.ok, null)
.show();
}
})
.exec(accountID);
}
}
}
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}
}
public void rebuildAllDisplayItems(){
displayItems.clear();
for(T item:data){

View File

@@ -29,6 +29,7 @@ import android.text.TextWatcher;
import android.text.format.DateFormat;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -826,6 +827,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
publishButton = wrap.findViewById(R.id.publish_btn);
languageButton = wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
languageButton.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if(!getLocalPrefs().bottomEncoding){
getLocalPrefs().bottomEncoding=true;
getLocalPrefs().save();
}
return false;
});
publishButton.setOnClickListener(v -> {
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
@@ -1308,7 +1317,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
if(usePhotoPicker){
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
}else{
intent=new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);

View File

@@ -239,16 +239,21 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
if (tags == null || tags.isEmpty()) return false;
editText.setText(String.join(",", tags));
editText.chipifyAllUnterminatedTokens();
editText.setText(tags);
editText.chipifyAllUnterminatedTokens();
return true;
}
private NachoTextView prepareChipTextView(NachoTextView nacho) {
nacho.addChipTerminator(',', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator('\n', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator(' ', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator(';', BEHAVIOR_CHIPIFY_ALL);
//Ill Be Back
nacho.setChipTerminators(
Map.of(
',', BEHAVIOR_CHIPIFY_ALL,
'\n', BEHAVIOR_CHIPIFY_ALL,
' ', BEHAVIOR_CHIPIFY_ALL,
';', BEHAVIOR_CHIPIFY_ALL
)
);
nacho.enableEditChipOnTouch(true, true);
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
return nacho;

View File

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

View File

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

View File

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

View File

@@ -184,6 +184,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
});
}
}
tabBar.selectTab(currentTab);
return content;
}

View File

@@ -524,9 +524,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) {
args.putString("hashtag", hashtag.name);
args.putBoolean("following", hashtag.following);
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
UiUtils.openHashtagTimeline(getContext(), accountID, hashtag);
}
return true;
}

View File

@@ -23,7 +23,6 @@ import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.transition.ChangeBounds;
import android.transition.Fade;
import android.transition.TransitionManager;
@@ -60,8 +59,10 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.account_list.BlockedAccountsListFragment;
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
import org.joinmastodon.android.fragments.account_list.MutedAccountsListFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
import org.joinmastodon.android.model.Account;
@@ -304,6 +305,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
tabbar.setTabTextSize(V.dp(14));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){
case 0 -> R.string.profile_featured;
case 1 -> R.string.profile_timeline;
case 2 -> R.string.profile_about;
default -> throw new IllegalStateException();
}));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
@@ -317,6 +324,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if (position == 4) tab.view.setVisibility(View.GONE);
}
});
tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@Override
public void onTabReselected(TabLayout.Tab tab){
if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt)
stt.scrollToTop();
}
});
cover.setOutlineProvider(new ViewOutlineProvider(){
@Override
@@ -827,6 +847,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("profileDisplayUsername", account.getDisplayUsername());
}
Nav.go(getActivity(), ListsFragment.class, args);
}else if(id==R.id.muted_accounts){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), MutedAccountsListFragment.class, args);
}else if(id==R.id.blocked_accounts){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), BlockedAccountsListFragment.class, args);
}else if(id==R.id.followed_hashtags){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -1304,9 +1334,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
FrameLayout view=new FrameLayout(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@@ -1314,8 +1342,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
Fragment fragment=getFragmentForPage(position);
FrameLayout fragmentView=tabViews[position];
fragmentView.setVisibility(View.VISIBLE);
if(fragmentView.getParent() instanceof ViewGroup parent)
parent.removeView(fragmentView);
((FrameLayout)holder.itemView).addView(fragmentView);
if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){

View File

@@ -24,6 +24,7 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
@@ -105,6 +106,12 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
text.textSelectable=true;
else if(item instanceof FooterStatusDisplayItem footer)
footer.hideCounts=true;
else if(item instanceof SpoilerStatusDisplayItem spoiler){
for(StatusDisplayItem subItem:spoiler.contentItems){
if(subItem instanceof TextStatusDisplayItem text)
text.textSelectable=true;
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -289,15 +289,19 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
FrameLayout view=new FrameLayout(parent.getContext());
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
@Override
public int getItemCount(){

View File

@@ -23,7 +23,6 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
@@ -31,7 +30,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
@@ -97,7 +96,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
args.putParcelable("profileAccount", Parcels.wrap(res.account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
case STATUS -> {
Status status=res.status.getContentStatus();
Bundle args=new Bundle();
@@ -113,7 +112,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}
@Override
protected void doLoadData(int offset, int count){
protected void doLoadData(int _offset, int count){
GetSearchResults.Type type;
if(currentFilter.size()==1){
type=switch(currentFilter.iterator().next()){
@@ -128,7 +127,21 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
dataLoaded();
return;
}
currentRequest=new GetSearchResults(currentQuery, type, true)
String maxID=null;
// TODO server-side bug
/*int offset=0;
if(_offset>0){
if(type==GetSearchResults.Type.STATUSES){
if(!preloadedData.isEmpty())
maxID=preloadedData.get(preloadedData.size()-1).status.id;
else if(!data.isEmpty())
maxID=data.get(data.size()-1).status.id;
}else{
offset=_offset;
}
}*/
int offset=_offset;
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
@@ -142,12 +155,15 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
results.add(new SearchResult(tag));
}
if(result.statuses!=null){
for(Status status:result.statuses)
results.add(new SearchResult(status));
Set<String> alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet());
for(Status status:result.statuses){
if(!alreadyLoadedStatuses.contains(status.id))
results.add(new SearchResult(status));
}
}
prevDisplayItems=new ArrayList<>(displayItems);
unfilteredResults=results;
onDataLoaded(filterSearchResults(results), false);
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
}
@Override

View File

@@ -122,7 +122,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
recentsHeader.setVisible(!data.isEmpty());
});
}else{
currentRequest=new GetSearchResults(currentQuery, null, false)
currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0)
.limit(2)
.setCallback(new SimpleCallback<>(this){
@Override
@@ -378,7 +378,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
private void openHashtag(SearchResult res){
wrapSuicideDialog(()->{
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
});
}
@@ -424,6 +424,8 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}
private void onSearchViewEnter(){
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty())
return;
wrapSuicideDialog(()->deliverResult(currentQuery, null));
}
@@ -436,7 +438,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
String q=searchViewHelper.getQuery();
if(q.startsWith("#"))
q=q.substring(1);
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
});
}

View File

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

View File

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

View File

@@ -271,7 +271,7 @@ public class SignupFragment extends ToolbarFragment{
@Override
public void tail(Node node, int depth){
if(node instanceof Element){
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

View File

@@ -33,7 +33,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
AccountSession s=AccountSessionManager.get(accountID);
onDataLoaded(List.of(
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag), null)),
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))),
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),

View File

@@ -55,6 +55,8 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
onDataLoaded(List.of(
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick),
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick),
new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_privacy_tip_24px, this::onPrivacyClick),
new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_filter_alt_24px, this::onFiltersClick),
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick),
new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick),
new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true),
@@ -69,7 +71,9 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
}
account.reloadPreferences(null);
AccountSession session=AccountSessionManager.get(accountID);
session.reloadPreferences(null);
session.updateAccountInfo();
E.register(this);
}
@@ -133,6 +137,10 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
}
private void onPrivacyClick(){
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
}
private void onFiltersClick(){
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
}
@@ -155,9 +163,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
loggedOut=true;
getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class);
startActivity(intent);
((MainActivity)getActivity()).restartHomeFragment();
}))
.setNegativeButton(R.string.cancel, null)
.show();

View File

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

View File

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

View File

@@ -139,6 +139,7 @@ public class Account extends BaseModel implements Searchable{
* When a timed mute will expire, if applicable.
*/
public Instant muteExpiresAt;
public boolean noindex;
public List<Role> roles;
@@ -238,6 +239,7 @@ public class Account extends BaseModel implements Searchable{
", source="+source+
", suspended="+suspended+
", muteExpiresAt="+muteExpiresAt+
", noindex="+noindex+
'}';
}
}

View File

@@ -46,26 +46,26 @@ public class Attachment extends BaseModel{
public int getWidth(){
if(meta==null)
return 0;
return 1920;
if(meta.width>0)
return meta.width;
if(meta.original!=null && meta.original.width>0)
return meta.original.width;
if(meta.small!=null && meta.small.width>0)
return meta.small.width;
return 0;
return 1920;
}
public int getHeight(){
if(meta==null)
return 0;
return 1080;
if(meta.height>0)
return meta.height;
if(meta.original!=null && meta.original.height>0)
return meta.original.height;
if(meta.small!=null && meta.small.height>0)
return meta.small.height;
return 0;
return 1080;
}
public double getDuration(){

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,8 +5,21 @@ import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDese
import android.text.TextUtils;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import androidx.annotation.NonNull;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
@@ -15,16 +28,21 @@ import com.google.gson.JsonParseException;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.utils.StatusTextEncoder;
import org.parceler.Parcel;
import java.lang.reflect.Type;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;
@Parcel
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
@@ -83,9 +101,9 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient boolean sensitiveRevealed;
public transient boolean textExpanded, textExpandable;
public transient boolean hasGapAfter;
public transient TranslatedStatus translation;
public transient boolean translationShown;
private transient String strippedText;
public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation;
public Status(){}
@@ -201,6 +219,38 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
return (Status) super.clone();
}
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
public boolean isEligibleForTranslation(AccountSession session){
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
boolean translateEnabled = instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
try {
String bottomText = BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
if(bottomText==null || bottomText.length()==0 || bottomText.equals("\u0005")) bottomText=null;
if(bottomText!=null){
translation=new Translation();
translation.content=bottomText;
translation.detectedSourceLanguage="\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48";
translation.provider="bottom-java";
return true;
}
} catch (TranslationError ignored) {}
return translateEnabled && !TextUtils.isEmpty(content) && !TextUtils.isEmpty(language)
&& !Objects.equals(Locale.getDefault().getLanguage(), language)
&& (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED);
}
public enum TranslationState{
HIDDEN,
SHOWN,
LOADING
}
public boolean isReblogPermitted(String accountID){
return visibility.isReblogPermitted(account.id.equals(
AccountSessionManager.getInstance().getAccount(accountID).self.id

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -34,6 +36,7 @@ import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -125,13 +128,13 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
new StickyHeadersOverlay(activity, 0).install(list);
LinearLayout ll=new LinearLayout(activity) {
@Override
public boolean onInterceptTouchEvent(MotionEvent e){
if (e.getAction() == MotionEvent.ACTION_MOVE) {
getParent().requestDisallowInterceptTouchEvent(true);
}
return false;
@Override
public boolean onInterceptTouchEvent(MotionEvent e){
if (e.getAction() == MotionEvent.ACTION_MOVE) {
getParent().requestDisallowInterceptTouchEvent(true);
}
return false;
}
};
ll.setOrientation(LinearLayout.VERTICAL);
ll.setElevation(V.dp(3));
@@ -139,43 +142,52 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
ll.addView(list, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
FrameLayout bottomPanel=new FrameLayout(activity);
bottomPanel.setPadding(V.dp(16), V.dp(8), V.dp(16), V.dp(8));
bottomPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
ll.addView(bottomPanel, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
if(forReaction){
FrameLayout topPanel=new FrameLayout(activity);
topPanel.setPadding(V.dp(16), V.dp(12), V.dp(16), V.dp(12));
topPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
ll.addView(topPanel, 0, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
InputMethodManager imm=(InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
// TODO: support filtering custom emoji
EditText input=new EditText(activity);
input.setHint(R.string.sk_enter_emoji_hint);
input.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!s.toString().isEmpty()) {
if (emojiRegex.matcher(s.toString()).find()) {
public void onTextChanged(CharSequence s, int start, int before, int count){
// Only check the emoji regex if the text field was empty before
if(start == 0){
if(emojiRegex.matcher(s.toString()).find()){
imm.hideSoftInputFromWindow(input.getWindowToken(), 0);
listener.onEmojiSelected(s.toString().substring(before));
input.getText().clear();
} else {
Toast.makeText(activity, R.string.sk_enter_emoji_toast, Toast.LENGTH_SHORT).show();
input.getText().clear();
}
}
for(int i=0; i<adapter.getAdapterCount(); i++){
SingleCategoryAdapter currentAdapter=(SingleCategoryAdapter) adapter.getAdapterAt(i);
currentAdapter.getFilter().filter(s.toString());
}
}
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void afterTextChanged(Editable s) {}
});
input.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener(){
@Override
public void onViewAttachedToWindow(@NonNull View view){}
FrameLayout.LayoutParams params=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START);
int pad=forReaction ? 0 : V.dp(36 + 16);
params.setMargins(pad, V.dp(8), pad, V.dp(8));
bottomPanel.addView(input, params);
}
@Override
public void onViewDetachedFromWindow(@NonNull View view){
input.getText().clear();
}
});
topPanel.addView(input);
}else{ // in compose fragment
FrameLayout bottomPanel=new FrameLayout(activity);
bottomPanel.setPadding(V.dp(16), V.dp(8), V.dp(16), V.dp(8));
bottomPanel.setBackgroundResource(R.drawable.bg_m3_surface2);
ll.addView(bottomPanel, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
if(!forReaction){
ImageButton hideKeyboard=new ImageButton(activity);
hideKeyboard.setImageResource(R.drawable.ic_fluent_keyboard_dock_24_regular);
hideKeyboard.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, R.attr.colorM3OnSurfaceVariant)));
@@ -207,13 +219,16 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
}
}
private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter{
private final EmojiCategory category;
private final List<ImageLoaderRequest> requests;
private class SingleCategoryAdapter extends UsableRecyclerView.Adapter<RecyclerView.ViewHolder> implements ImageLoaderRecyclerAdapter, Filterable{
private EmojiCategory category;
private final EmojiCategory originalCategory;
private List<ImageLoaderRequest> requests;
public SingleCategoryAdapter(EmojiCategory category){
super(imgLoader);
this.category=category;
this.originalCategory=new EmojiCategory(category);
requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.getUrl(playGifs), V.dp(24), V.dp(24))).collect(Collectors.toList());
}
@@ -225,17 +240,22 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position){
if(category.emojis.size() == 0) {
holder.itemView.setVisibility(View.GONE);
}
if(holder instanceof EmojiViewHolder evh){
evh.bind(category.emojis.get(position-1));
evh.positionWithinCategory=position-1;
}else if(holder instanceof SectionHeaderViewHolder shvh){
shvh.bind(TextUtils.isEmpty(category.title) ? domain : category.title);
}
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
if(category.emojis.size() == 0) return 0;
return category.emojis.size()+1;
}
@@ -253,6 +273,39 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
public ImageLoaderRequest getImageRequest(int position, int image){
return requests.get(position-1);
}
@Override
public Filter getFilter(){
return emojiFilter;
}
private final Filter emojiFilter = new Filter(){
@Override
protected FilterResults performFiltering(CharSequence charSequence){
List<Emoji> filteredEmoji=new ArrayList<>();
String search=charSequence.toString().toLowerCase().trim();
if(charSequence==null || charSequence.length()==0){
filteredEmoji.addAll(originalCategory.emojis);
}else{
for(Emoji emoji : originalCategory.emojis){
if(emoji.shortcode.toLowerCase().contains(search)){
filteredEmoji.add(emoji);
}
}
}
FilterResults results=new FilterResults();
results.values=filteredEmoji;
return results;
}
@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults){
category.emojis.clear();
category.emojis.addAll((List) filterResults.values);
requests=category.emojis.stream().map(e->new UrlImageLoaderRequest(e.getUrl(playGifs), V.dp(24), V.dp(24))).collect(Collectors.toList());
notifyDataSetChanged();
}
};
}
private class SectionHeaderViewHolder extends BindableViewHolder<String> implements StickyHeadersOverlay.HeaderViewHolder{

View File

@@ -79,7 +79,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
progressBgInset=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted_inset, activity.getTheme()).mutate();
itemView.setOnClickListener(this::onButtonClick);
button.setOutlineProvider(OutlineProviders.M3_BUTTON);
button.setOutlineProvider(OutlineProviders.roundedRect(20));
button.setClipToOutline(true);
}

View File

@@ -15,6 +15,7 @@ import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Status;
@@ -53,7 +54,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText, Status status) {
super(parentID, parentFragment);
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
HtmlParser.parseCustomEmoji(ssb, emojis);
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
HtmlParser.parseCustomEmoji(ssb, emojis);
this.text=ssb;
emojiHelper.setText(ssb);
this.icon=icon;

View File

@@ -54,7 +54,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
public abstract class StatusDisplayItem{
public final String parentID;
public final BaseStatusListFragment<?> parentFragment;
public boolean inset, insetPadding=true;
public boolean inset;
public int index;
public boolean
hasDescendantNeighbor = false,
@@ -180,10 +180,8 @@ public abstract class StatusDisplayItem{
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, hashtag.name, List.of(),
R.drawable.ic_fluent_number_symbol_20sp_filled, null,
i -> {
args.putString("hashtag", hashtag.name);
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
}, status
i->UiUtils.openHashtagTimeline(fragment.getActivity(), accountID, hashtag),
status
)));
}
@@ -397,10 +395,11 @@ public abstract class StatusDisplayItem{
}
public Optional<StatusDisplayItem> getDisplayItemOffset(int offset){
int nextPos=getAbsoluteAdapterPosition() + offset;
List<StatusDisplayItem> displayItems=item.parentFragment.getDisplayItems();
return displayItems.size() > nextPos
? Optional.of(displayItems.get(nextPos))
int thisPos=displayItems.indexOf(item);
int offsetPos=thisPos + offset;
return displayItems.size() > offsetPos && thisPos >= 0 && offsetPos >= 0
? Optional.of(displayItems.get(offsetPos))
: Optional.empty();
}

View File

@@ -1,71 +1,53 @@
package org.joinmastodon.android.ui.displayitems;
import static org.joinmastodon.android.ui.utils.UiUtils.opacityIn;
import android.app.Activity;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.TranslatedStatus;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.utils.StatusTextEncoder;
import java.util.Locale;
import java.util.regex.Pattern;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.MovieDrawable;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private CharSequence translatedText;
private CustomEmojiHelper translationEmojiHelper=new CustomEmojiHelper();
public boolean textSelectable;
public boolean reduceTopPadding;
public boolean disableTranslate;
public final Status status;
public boolean disableTranslate, translationShown;
private AccountSession session;
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
super(parentID, parentFragment);
this.text=text;
this.status=status;
this.disableTranslate=disableTranslate;
this.translationShown=status.translationShown;
emojiHelper.setText(text);
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
}
public void setTranslationShown(boolean translationShown) {
this.translationShown = translationShown;
status.translationShown = translationShown;
}
@Override
@@ -75,38 +57,47 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public int getImageCount(){
return emojiHelper.getImageCount();
return getCurrentEmojiHelper().getImageCount();
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return emojiHelper.getImageRequest(index);
return getCurrentEmojiHelper().getImageRequest(index);
}
public void setTranslatedText(String text){
Status statusForContent=status.getContentStatus();
translatedText=HtmlParser.parse(text, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, parentFragment.getAccountID());
translationEmojiHelper.setText(translatedText);
}
private CustomEmojiHelper getCurrentEmojiHelper(){
return status.translationState==Status.TranslationState.SHOWN ? translationEmojiHelper : emojiHelper;
}
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text;
private final TextView translateInfo, readMore;
private final View textWrap, translateWrap, translateProgress;
private final Button translateButton;
private final ScrollView textScrollView;
private final ViewStub translationFooterStub;
private View translationFooter, translationButtonWrap;
private TextView translationInfo;
private Button translationButton;
private ProgressBar translationProgress;
private final float textMaxHeight, textCollapsedHeight;
private final float textMaxHeight;
private final LinearLayout.LayoutParams collapseParams, wrapParams;
private final ViewGroup parent;
private final TextView readMore;
private final ScrollView textScrollView;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent);
this.parent=parent;
text=findViewById(R.id.text);
textWrap = (LinearLayout) itemView;
translateWrap=findViewById(R.id.translate_wrap);
translateButton=findViewById(R.id.translate_btn);
translateInfo=findViewById(R.id.translate_info);
translateProgress=findViewById(R.id.translate_progress);
translationFooterStub=findViewById(R.id.translation_info);
textScrollView=findViewById(R.id.text_scroll_view);
readMore=findViewById(R.id.read_more);
textMaxHeight=activity.getResources().getDimension(R.dimen.text_max_height);
textCollapsedHeight=activity.getResources().getDimension(R.dimen.text_collapsed_height);
float textCollapsedHeight=activity.getResources().getDimension(R.dimen.text_collapsed_height);
collapseParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) textCollapsedHeight);
wrapParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
readMore.setOnClickListener(v -> item.parentFragment.onToggleExpanded(item.status, getItemID()));
@@ -114,83 +105,20 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(TextStatusDisplayItem item){
boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText);
text.setText(item.translationShown
? HtmlParser.parse(item.status.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
: item.text);
text.setTextIsSelectable(item.textSelectable);
if (item.textSelectable) {
textScrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
if(item.status.translationState==Status.TranslationState.SHOWN){
if(item.translatedText==null){
item.setTranslatedText(item.status.translation.content);
}
text.setText(item.translatedText);
}else{
text.setText(item.text);
}
text.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false);
itemView.setClickable(false);
itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom());
text.setTextColor(UiUtils.getThemeColor(text.getContext(), item.inset ? R.attr.colorM3OnSurfaceVariant : R.attr.colorM3OnSurface));
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(item.session.domain);
boolean translateEnabled = !item.disableTranslate && instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
String bottomText = null;
try {
bottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
} catch (TranslationError ignored) {}
boolean translateVisible = (bottomText != null || (
translateEnabled &&
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
item.status.language != null &&
// todo: compare to mastodon locale instead (how do i query that?!)
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())))
&& (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
translateButton.setText(item.translationShown ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translationShown ? itemView.getResources().getString(R.string.sk_translated_using, bottomText != null ? "bottom-java" : item.status.translation.provider) : "");
String finalBottomText = bottomText;
translateButton.setOnClickListener(v->{
if (item.status.translation == null) {
if (finalBottomText != null) {
try {
item.status.translation = new TranslatedStatus();
item.status.translation.content = finalBottomText;
item.setTranslationShown(true);
} catch (TranslationError err) {
item.status.translation = null;
Toast.makeText(itemView.getContext(), err.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
rebind();
return;
}
translateProgress.setVisibility(View.VISIBLE);
translateButton.setClickable(false);
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
rebind();
}
@Override
public void onError(ErrorResponse error) {
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
} else {
item.setTranslationShown(!item.translationShown);
rebind();
}
});
updateTranslation(false);
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
@@ -224,18 +152,19 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
boolean expandable = tooBig && !hasSpoiler;
boolean expandable = tooBig && !item.status.hasSpoiler();
item.parentFragment.onEnableExpandable(Holder.this, expandable);
}
boolean expandButtonShown=item.status.textExpandable && !item.status.textExpanded;
translateWrap.setPadding(0, V.dp(expandButtonShown ? 0 : 4), 0, 0);
if(translationFooter!=null)
translationFooter.setPadding(0, V.dp(expandButtonShown ? 0 : 4), 0, 0);
readMore.setVisibility(expandButtonShown ? View.VISIBLE : View.GONE);
textScrollView.setLayoutParams(item.status.textExpandable && !item.status.textExpanded ? collapseParams : wrapParams);
// compensate for spoiler's bottom margin
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
params.setMargins(params.leftMargin, item.inset && hasSpoiler ? V.dp(-16) : 0,
params.setMargins(params.leftMargin, item.inset && item.status.hasSpoiler() ? V.dp(-16) : 0,
params.rightMargin, params.bottomMargin);
}
@@ -259,5 +188,60 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private CustomEmojiHelper getEmojiHelper(){
return item.emojiHelper;
}
public void updateTranslation(boolean updateText){
if(item.status==null)
return;
boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession());
if(translationFooter==null && translateEnabled){
translationFooter=translationFooterStub.inflate();
translationInfo=findViewById(R.id.translation_info_text);
translationButton=findViewById(R.id.translation_btn);
translationButtonWrap=findViewById(R.id.translation_btn_wrap);
translationProgress=findViewById(R.id.translation_progress);
translationButton.setOnClickListener(v->item.parentFragment.togglePostTranslation(item.status, item.parentID));
}
if(item.status.translationState==Status.TranslationState.HIDDEN){
if(updateText) text.setText(item.text);
if(translationFooter==null) return;
translationFooter.setVisibility(translateEnabled ? View.VISIBLE : View.GONE);
translationProgress.setVisibility(View.GONE);
Translation existingTrans=item.status.getContentStatus().translation;
String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null;
String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language;
String displayLang=Locale.forLanguageTag(lang != null ? lang
: AccountSessionManager.get(item.parentFragment.getAccountID()).preferences.postingDefaultLanguage).getDisplayLanguage();
translationButton.setText(item.parentFragment.getString(R.string.translate_post, !displayLang.isBlank() ? displayLang : lang));
translationButton.setEnabled(true);
translationButton.setAlpha(1);
translationInfo.setVisibility(View.GONE);
UiUtils.beginLayoutTransition((ViewGroup) translationButtonWrap);
}else{
translationFooter.setVisibility(View.VISIBLE);
if(item.status.translationState==Status.TranslationState.SHOWN){
translationProgress.setVisibility(View.GONE);
translationButton.setText(R.string.translation_show_original);
translationButton.setEnabled(true);
translationButton.setAlpha(1);
translationInfo.setVisibility(View.VISIBLE);
translationButton.setVisibility(View.VISIBLE);
String displayLang=Locale.forLanguageTag(item.status.translation.detectedSourceLanguage).getDisplayLanguage();
translationInfo.setText(translationInfo.getContext().getString(R.string.post_translated, !displayLang.isBlank() ? displayLang : item.status.translation.detectedSourceLanguage, item.status.translation.provider));
UiUtils.beginLayoutTransition((ViewGroup) translationButtonWrap);
if(updateText){
if(item.translatedText==null){
item.setTranslatedText(item.status.translation.content);
}
text.setText(item.translatedText);
}
}else{ // LOADING
translationProgress.setVisibility(View.VISIBLE);
translationButton.setEnabled(false);
translationButton.startAnimation(opacityIn);
translationInfo.setVisibility(View.INVISIBLE);
UiUtils.beginLayoutTransition((ViewGroup) translationButton.getParent());
}
}
}
}
}

View File

@@ -99,15 +99,11 @@ public class BlurhashCrossfadeDrawable extends Drawable{
@Override
public int getIntrinsicWidth(){
if(width==0)
return imageDrawable==null ? 1920 : imageDrawable.getIntrinsicWidth();
return width;
}
@Override
public int getIntrinsicHeight(){
if(height==0)
return imageDrawable==null ? 1080 : imageDrawable.getIntrinsicHeight();
return height;
}

View File

@@ -878,6 +878,8 @@ public class PhotoViewer implements ZoomPanView.Listener{
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height){
if(width<=0 || height<=0)
return;
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
params.width=width;
params.height=height;

View File

@@ -119,6 +119,9 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
int width=right-left;
int height=bottom-top;
if(width==0 || height==0)
return;
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
minScale=scale;
maxScale=Math.max(3f, height/(float)child.getHeight());
@@ -306,8 +309,6 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
}, 1f).setMinimumVisibleChange(DynamicAnimation.MIN_VISIBLE_CHANGE_ALPHA));
}
}else{
if(animatingTransition)
Log.w(TAG, "updateViewTransform: ", new Throwable().fillInStackTrace());
child.setScaleX(matrixValues[Matrix.MSCALE_X]);
child.setScaleY(matrixValues[Matrix.MSCALE_Y]);
child.setTranslationX(matrixValues[Matrix.MTRANS_X]);

View File

@@ -100,9 +100,10 @@ public class HtmlParser{
}
}
Map<String, String> idsByUrl=mentions.stream().collect(Collectors.toMap(m->m.url, m->m.id));
Map<String, String> idsByUrl=mentions.stream().distinct().collect(Collectors.toMap(m->m.url, m->m.id));
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
final SpannableStringBuilder ssb=new SpannableStringBuilder();
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
@@ -115,6 +116,7 @@ public class HtmlParser{
}else if(node instanceof Element el){
switch(el.nodeName()){
case "a" -> {
Object linkObject=null;
String href=el.attr("href");
LinkSpan.Type linkType;
String text=el.text();
@@ -122,6 +124,7 @@ public class HtmlParser{
if(text.startsWith("#")){
linkType=LinkSpan.Type.HASHTAG;
href=text.substring(1);
linkObject=tagsByTag.get(text.substring(1).toLowerCase());
}else{
linkType=LinkSpan.Type.URL;
}
@@ -136,7 +139,7 @@ public class HtmlParser{
}else{
linkType=LinkSpan.Type.URL;
}
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID), ssb.length(), el));
openSpans.add(new SpanInfo(new LinkSpan(href, null, linkType, accountID, linkObject, text), ssb.length(), el));
}
case "br" -> ssb.append('\n');
case "span" -> {
@@ -271,7 +274,7 @@ public class HtmlParser{
String url=matcher.group(3);
if(TextUtils.isEmpty(matcher.group(4)))
url="http://"+url;
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null), matcher.start(3), matcher.end(3), 0);
ssb.setSpan(new LinkSpan(url, null, LinkSpan.Type.URL, null, null, null), matcher.start(3), matcher.end(3), 0);
}while(matcher.find()); // Find more URLs
return ssb;
}

View File

@@ -5,6 +5,7 @@ import android.text.TextPaint;
import android.text.style.CharacterStyle;
import android.view.View;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.ui.utils.UiUtils;
public class LinkSpan extends CharacterStyle {
@@ -14,17 +15,15 @@ public class LinkSpan extends CharacterStyle {
private String link;
private Type type;
private String accountID;
private Object linkObject;
private String text;
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID){
this(link, listener, type, accountID, null);
}
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, String text){
public LinkSpan(String link, OnLinkClickListener listener, Type type, String accountID, Object linkObject, String text){
this.listener=listener;
this.link=link;
this.type=type;
this.accountID=accountID;
this.linkObject=linkObject;
this.text=text;
}
@@ -42,7 +41,12 @@ public class LinkSpan extends CharacterStyle {
switch(getType()){
case URL -> UiUtils.openURL(context, accountID, link);
case MENTION -> UiUtils.openProfileByID(context, accountID, link);
case HASHTAG -> UiUtils.openHashtagTimeline(context, accountID, link, null);
case HASHTAG -> {
if(linkObject instanceof Hashtag ht)
UiUtils.openHashtagTimeline(context, accountID, ht);
else
UiUtils.openHashtagTimeline(context, accountID, text);
}
case CUSTOM -> listener.onLinkClick(this);
}
}

View File

@@ -46,7 +46,7 @@ public class DiscoverInfoBannerHelper{
TextView text=banner.findViewById(R.id.banner_text);
text.setText(switch(type){
case TRENDING_POSTS -> list.getResources().getString(R.string.sk_trending_posts_info_banner);
case TRENDING_LINKS -> list.getResources().getString(R.string.trending_links_info_banner);
case TRENDING_LINKS -> list.getResources().getString(R.string.sk_trending_links_info_banner);
case FEDERATED_TIMELINE -> list.getResources().getString(R.string.sk_federated_timeline_info_banner);
case POST_NOTIFICATIONS -> list.getResources().getString(R.string.sk_notify_posts_info_banner);
case BUBBLE_TIMELINE -> list.getResources().getString(R.string.sk_bubble_timeline_info_banner);

View File

@@ -74,24 +74,19 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
// List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
boolean inset=sdi.getItem().inset;
int pos=holder.getAbsoluteAdapterPosition();
// int pos=holder.getAbsoluteAdapterPosition();
if(inset){
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
int pad;
// boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
// boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
// if(holder instanceof MediaGridStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
pad=V.dp(16);
// else
// pad=V.dp(12);
boolean insetPadding=((StatusDisplayItem.Holder<?>) holder).getItem().insetPadding;
if(insetPadding)
outRect.left=pad;
if(insetPadding)
outRect.right=pad;
int pad=V.dp(16);
// else pad=V.dp(12);
outRect.left=pad;
outRect.right=pad;
// had to comment this out because animations with offsets aren't handled properly.
// can be worked around by manually applying top margins to items

View File

@@ -104,6 +104,7 @@ import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.SearchResults;
@@ -217,11 +218,20 @@ public class UiUtils {
if(diff<1000L){
return context.getString(R.string.time_now);
}else if(diff<60_000L){
return context.getString(ago ? R.string.time_seconds_ago_short : R.string.sk_time_seconds, diff/1000L);
long time = diff/1000L;
return ago ?
context.getString(R.string.time_seconds_ago_short, time) :
context.getResources().getQuantityString(R.plurals.sk_time_seconds, (int) time, time);
}else if(diff<3600_000L){
return context.getString(ago ? R.string.time_minutes_ago_short : R.string.sk_time_minutes, diff/60_000L);
long time = diff/60_000L;
return ago ?
context.getString(R.string.time_minutes_ago_short, time) :
context.getResources().getQuantityString(R.plurals.sk_time_minutes, (int) time, time);
}else if(diff<3600_000L*24L){
return context.getString(ago ? R.string.time_hours_ago_short : R.string.sk_time_hours, diff/3600_000L);
long time = diff/3600_000L;
return ago ?
context.getString(R.string.time_hours_ago_short, time) :
context.getResources().getQuantityString(R.plurals.sk_time_hours, (int) time, time);
} else {
int days = (int) (diff / (3600_000L * 24L));
if (ago && days > 30) {
@@ -232,7 +242,7 @@ public class UiUtils {
return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt);
}
}
return context.getString(ago ? R.string.time_days_ago_short : R.string.sk_time_days, days);
return ago ? context.getString(R.string.time_days_ago_short, days) : context.getResources().getQuantityString(R.plurals.sk_time_days, days, days);
}
}
@@ -445,12 +455,18 @@ public class UiUtils {
Nav.go((Activity) context, ProfileFragment.class, args);
}
public static void openHashtagTimeline(Context context, String accountID, String hashtag, @Nullable Boolean following) {
Bundle args = new Bundle();
public static void openHashtagTimeline(Context context, String accountID, Hashtag hashtag){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("hashtag", hashtag);
if (following != null) args.putBoolean("following", following);
Nav.go((Activity) context, HashtagTimelineFragment.class, args);
args.putParcelable("hashtag", Parcels.wrap(hashtag));
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
}
public static void openHashtagTimeline(Context context, String accountID, String hashtag){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("hashtagName", hashtag);
Nav.go((Activity)context, HashtagTimelineFragment.class, args);
}
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed) {
@@ -1157,7 +1173,7 @@ public class UiUtils {
return Optional.empty();
}
return Optional.of(new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() {
return Optional.of(new GetSearchResults(query.getQuery(), type, true, null, 0, 0).setCallback(new Callback<>() {
@Override
public void onSuccess(SearchResults results) {
Optional<T> result = extractResult.apply(results);
@@ -1254,7 +1270,7 @@ public class UiUtils {
}
public static MastodonAPIRequest<SearchResults> lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse(""));
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true)
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true, null, 0, 0)
.setCallback(new Callback<>() {
@Override
public void onSuccess(SearchResults results) {
@@ -1317,7 +1333,7 @@ public class UiUtils {
})
.execNoAuth(uri.getHost()));
} else if (looksLikeFediverseUrl(url)) {
return Optional.of(new GetSearchResults(url, null, true)
return Optional.of(new GetSearchResults(url, null, true, null, 0, 0)
.setCallback(new Callback<>() {
@Override
public void onSuccess(SearchResults results) {

View File

@@ -15,15 +15,12 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.model.viewmodel.AccountViewModel;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FilterChipView;
@@ -96,6 +93,24 @@ public class ComposeAutocompleteViewController{
outRect.right=V.dp(8);
}
});
// Set empty adapter to prevent NPEs
list.setAdapter(new RecyclerView.Adapter<>(){
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
throw new UnsupportedOperationException();
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position){
}
@Override
public int getItemCount(){
return 0;
}
});
contentView.addView(list, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
emptyButton=new FilterChipView(activity);
@@ -222,11 +237,13 @@ public class ComposeAutocompleteViewController{
}
private void doSearchUsers(){
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false)
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
currentRequest=null;
if(mode!=Mode.USERS)
return;
List<AccountViewModel> oldList=users;
users=result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList());
if(isLoading){
@@ -256,7 +273,7 @@ public class ComposeAutocompleteViewController{
}
private void doSearchHashtags(){
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false)
currentRequest=new GetSearchResults(lastText, GetSearchResults.Type.HASHTAGS, false, null, 0, 0)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){

View File

@@ -75,14 +75,14 @@ public class ComposePollViewController{
Instance instance=fragment.instance;
if (!instance.isAkkoma()) {
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
maxPollOptions=instance.configuration.polls.maxOptions;
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
if(instance!=null && instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
maxPollOptionLength=instance.configuration.polls.maxCharactersPerOption;
} else {
if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
maxPollOptions=instance.pollLimits.maxOptions;
if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
if(instance!=null && instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
maxPollOptionLength=instance.pollLimits.maxOptionChars;
}

View File

@@ -15,7 +15,7 @@ public class MediaGridLayout extends ViewGroup{
private static final int GAP=2; // dp
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
private int[] columnStarts, columnEnds, rowStarts, rowEnds;
public MediaGridLayout(Context context){
this(context, null);
@@ -41,6 +41,14 @@ public class MediaGridLayout extends ViewGroup{
width=Math.round(width*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
}
if(rowStarts==null || rowStarts.length<tiledLayout.rowSizes.length){
rowStarts=new int[tiledLayout.rowSizes.length];
rowEnds=new int[tiledLayout.rowSizes.length];
}
if(columnStarts==null || columnStarts.length<tiledLayout.columnSizes.length){
columnStarts=new int[tiledLayout.columnSizes.length];
columnEnds=new int[tiledLayout.columnSizes.length];
}
int offset=0;
for(int i=0;i<tiledLayout.columnSizes.length;i++){
columnStarts[i]=offset;
@@ -73,7 +81,7 @@ public class MediaGridLayout extends ViewGroup{
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b){
if(tiledLayout==null)
if(tiledLayout==null || rowStarts==null)
return;
int maxWidth=UiUtils.MAX_WIDTH;

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item android:gravity="center_vertical" android:height="40dp">
<item>
<shape>
<stroke android:width="1dp" android:color="?colorM3Outline"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item android:id="@android:id/mask" android:gravity="center_vertical" android:height="40dp">
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000"/>
<corners android:radius="20dp"/>

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item android:gravity="center_vertical" android:height="40dp">
<item>
<shape>
<stroke android:width="1dp" android:color="?colorM3Outline"/>
<solid android:color="?colorM3Surface"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item android:id="@android:id/mask" android:gravity="center_vertical" android:height="40dp">
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000"/>
<corners android:radius="20dp"/>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center_vertical" android:height="40dp">
<item>
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
<shape>
<solid android:color="?colorM3SecondaryContainer"/>
@@ -8,7 +8,7 @@
</shape>
</scale>
</item>
<item android:gravity="center_vertical" android:height="40dp">
<item>
<shape>
<stroke android:width="1dp" android:color="?colorM3OutlineVariant"/>
<corners android:radius="20dp"/>

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center_vertical" android:height="40dp">
<item>
<shape>
<solid android:color="?colorM3Surface"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item android:gravity="center_vertical" android:height="40dp">
<item>
<scale android:scaleGravity="start|fill_vertical" android:scaleWidth="100%">
<shape>
<solid android:color="@color/poll_option_progress_inset"/>
@@ -14,7 +14,7 @@
</shape>
</scale>
</item>
<item android:gravity="center_vertical" android:height="40dp">
<item>
<shape>
<stroke android:width="1dp" android:color="?colorM3Outline"/>
<corners android:radius="20dp"/>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11,17H13V11H11ZM12,9Q12.425,9 12.713,8.712Q13,8.425 13,8Q13,7.575 12.713,7.287Q12.425,7 12,7Q11.575,7 11.288,7.287Q11,7.575 11,8Q11,8.425 11.288,8.712Q11.575,9 12,9ZM12,22Q8.525,21.125 6.263,18.012Q4,14.9 4,11.1V5L12,2L20,5V11.1Q20,14.9 17.738,18.012Q15.475,21.125 12,22ZM12,19.9Q14.6,19.075 16.3,16.6Q18,14.125 18,11.1V6.375L12,4.125L6,6.375V11.1Q6,14.125 7.7,16.6Q9.4,19.075 12,19.9ZM12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Q12,12 12,12Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M2,14Q1.175,14 0.588,13.412Q0,12.825 0,12V6Q0,5.7 0.125,5.425Q0.25,5.15 0.45,4.95L5.4,0L6.15,0.75Q6.3,0.9 6.4,1.137Q6.5,1.375 6.5,1.6V1.8L5.8,5H11Q11.425,5 11.713,5.287Q12,5.575 12,6V7.25Q12,7.4 11.975,7.537Q11.95,7.675 11.9,7.8L9.65,13.1Q9.475,13.525 9.088,13.762Q8.7,14 8.25,14ZM7.95,12 L10,7.15V7Q10,7 10,7Q10,7 10,7H3.35L3.95,4.3L2,6.2V12Q2,12 2,12Q2,12 2,12ZM18.6,24 L17.85,23.25Q17.7,23.1 17.6,22.863Q17.5,22.625 17.5,22.4V22.2L18.2,19H13Q12.575,19 12.288,18.712Q12,18.425 12,18V16.75Q12,16.6 12.025,16.462Q12.05,16.325 12.1,16.2L14.35,10.9Q14.55,10.475 14.925,10.238Q15.3,10 15.75,10H22Q22.825,10 23.413,10.587Q24,11.175 24,12V18Q24,18.3 23.888,18.562Q23.775,18.825 23.55,19.05ZM16.05,12 L14,16.85V17Q14,17 14,17Q14,17 14,17H20.65L20.05,19.7L22,17.8V12Q22,12 22,12Q22,12 22,12ZM2,12V6.2V7Q2,7 2,7Q2,7 2,7V7.15V12Q2,12 2,12Q2,12 2,12ZM22,12V17.8V17Q22,17 22,17Q22,17 22,17V16.85V12Q22,12 22,12Q22,12 22,12Z"/>
</vector>

View File

@@ -5,7 +5,9 @@
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:padding="16dp"
android:background="@drawable/rect_12dp"
android:backgroundTint="?colorM3SurfaceVariant">

View File

@@ -5,28 +5,30 @@
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingBottom="4dp"
android:paddingVertical="4dp"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_height="wrap_content"
android:minHeight="40dp"
android:background="@drawable/bg_poll_option_clickable"
android:duplicateParentState="true"
android:layoutDirection="locale">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingVertical="8dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginStart="12dp"
android:layout_gravity="center_vertical"
android:tint="?colorM3OnSecondaryContainer"
android:scaleType="center"
@@ -41,7 +43,6 @@
android:layout_marginEnd="8dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3Primary"
android:singleLine="true"
android:ellipsize="end"
android:paddingEnd="26dp"
tools:text="scream into void jsfdklfjdalskfjdsalkfjdsalkfjdsalkfdjsalkfdsajlk"/>

View File

@@ -45,56 +45,10 @@
android:visibility="gone"
android:importantForAccessibility="no"/>
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout
android:id="@+id/translate_wrap"
<ViewStub
android:id="@+id/translation_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:gravity="center_vertical"
android:visibility="gone">
<FrameLayout
android:id="@+id/action_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:clipToPadding="false">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/translate_btn"
style="@style/Widget.Mastodon.M3.Button.Text"
android:textColor="?colorM3OnSurfaceVariant"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:minWidth="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/sk_translate_post"/>
<ProgressBar
android:id="@+id/translate_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
style="?android:progressBarStyleSmall"
android:elevation="10dp"
android:outlineProvider="none"
android:visibility="gone"/>
</FrameLayout>
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/translate_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:textColor="?colorM3OnSurfaceVariant"
android:textAlignment="textEnd"
tools:text="Translated using TranslateEngine" />
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
android:layout="@layout/footer_text_translation"/>
</LinearLayout>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:gravity="center_vertical">
<FrameLayout
android:id="@+id/translation_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:clipToPadding="false">
<ProgressBar
android:id="@+id/translation_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:layout_marginEnd="8dp"
android:indeterminate="true"
style="?android:progressBarStyleSmall"
android:visibility="gone"/>
<Button
android:id="@+id/translation_btn"
style="@style/Widget.Mastodon.M3.Button.Text"
android:textColor="?colorM3OnSurfaceVariant"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/translation_show_original"/>
</FrameLayout>
<TextView
android:id="@+id/translation_info_text"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:maxLines="1"
android:ellipsize="end"
android:textAppearance="@style/m3_body_small"
android:textColor="?colorM3OnSurfaceVariant"
android:textAlignment="textEnd"
tools:text="Translated using TranslateEngine" />
</LinearLayout>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/follow_btn_wrap"
android:layout_marginEnd="8dp"
android:textAppearance="@style/m3_headline_small"
android:textColor="?colorM3OnSurface"
android:maxLines="4"
android:ellipsize="end"
android:minHeight="48dp"
android:gravity="center_vertical"
tools:text="#CatsOfMastodonButLong"/>
<FrameLayout
android:id="@+id/follow_btn_wrap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/title"
android:layout_alignBottom="@id/title"
android:layout_alignParentEnd="true">
<org.joinmastodon.android.ui.views.ProgressBarButton
android:id="@+id/profile_action_btn"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_gravity="center"
style="@style/Widget.Mastodon.M3.Button.Filled"
android:paddingHorizontal="16dp"
tools:text="@string/button_follow" />
<ProgressBar
android:id="@+id/action_progress"
style="?android:progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:elevation="10dp"
android:indeterminate="true"
android:outlineProvider="none"
android:visibility="gone" />
</FrameLayout>
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_marginTop="4dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3OnSurfaceVariant"
tools:text="123 posts"/>
</RelativeLayout>

View File

@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/pin"
android:title="@string/sk_pin_timeline"
android:icon="@drawable/ic_fluent_pin_24_regular"
android:showAsAction="always" />
<item
android:id="@+id/pin"
android:title="@string/sk_pin_timeline"
android:showAsAction="always"
android:icon="@drawable/ic_fluent_pin_24_regular" />
<item
android:id="@+id/follow_hashtag"
android:icon="@drawable/ic_fluent_person_add_24_regular"
android:showAsAction="always"
android:title="@string/button_follow"/>
</menu>

View File

@@ -7,6 +7,8 @@
</item>
<item android:id="@+id/manage_user_lists" android:title="@string/sk_your_lists" android:icon="@drawable/ic_fluent_people_24_regular"/>
<item android:id="@+id/favorites" android:title="@string/your_favorites" android:icon="@drawable/ic_fluent_star_24_regular"/>
<item android:id="@+id/muted_accounts" android:title="@string/sk_muted_accounts" android:icon="@drawable/ic_fluent_speaker_off_24_regular"/>
<item android:id="@+id/blocked_accounts" android:title="@string/sk_blocked_accounts" android:icon="@drawable/ic_fluent_shield_24_regular"/>
<item android:id="@+id/scheduled" android:title="@string/sk_unsent_posts" android:icon="@drawable/ic_fluent_folder_open_24_regular"/>
<item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/>
</menu>

View File

@@ -8,7 +8,7 @@
<string name="ok">حسنًا</string>
<string name="preparing_auth">جَارٍ الإعدَادُ لِلمُصادَقَة…</string>
<string name="finishing_auth">يُنهي المصادقة…</string>
<string name="user_boosted">%s إعادة نشر</string>
<string name="user_boosted">قام %s بإعادة نشر</string>
<string name="in_reply_to">ردًا على %s</string>
<string name="notifications">الإشعارات</string>
<string name="user_followed_you">%s بَدَأ بِمُتابَعَتِك</string>
@@ -242,7 +242,7 @@
<string name="skip">تخطى</string>
<string name="notification_type_follow">متابعُون جُدُد</string>
<string name="notification_type_favorite">المفضلة</string>
<string name="notification_type_reblog">المشاركات</string>
<string name="notification_type_reblog">المعاد نشرها</string>
<string name="notification_type_mention">الإشارات</string>
<string name="notification_type_poll">استطلاع رأي</string>
<string name="choose_account">اختر حسابًا</string>
@@ -277,7 +277,7 @@
<string name="more_options">مزيد من الخيارات</string>
<string name="new_post">منشور جديد</string>
<string name="button_reply">ردّ</string>
<string name="button_reblog">شارك</string>
<string name="button_reblog">إعادة النشر</string>
<string name="button_favorite">فضّل</string>
<string name="button_share">شارك</string>
<string name="media_no_description">وسائط بدون وصف</string>
@@ -292,8 +292,8 @@
<string name="followed_user">أنت تتابع الآن %s</string>
<string name="following_user_requested">طَلَبَ %s مُتابَعتك</string>
<string name="open_in_browser">افتح في المتصفح</string>
<string name="hide_boosts_from_user">اخف مشاركات %s</string>
<string name="show_boosts_from_user">أظهر مشاركات %s</string>
<string name="hide_boosts_from_user">أخفِ المعاد نشرها مِن %s</string>
<string name="show_boosts_from_user">أظهر ما أعاد %s نشرَه</string>
<string name="signup_reason">لماذا تريد الانضمام؟</string>
<string name="signup_reason_note">هذا سوف يساعدنا في مراجعة تطبيقك.</string>
<string name="clear">امسح</string>
@@ -346,10 +346,10 @@
<item quantity="other">%,d تفضيل</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="zero">%,d إعادة نشر</item>
<item quantity="zero">لم يُعد نشره</item>
<item quantity="one">إعادة نشر واحدة</item>
<item quantity="two">أعيد نشره مرّتان</item>
<item quantity="few">أعيد نشره %,d مرة</item>
<item quantity="few">أعيد نشره %,d مرات</item>
<item quantity="many">أعيد نشره %,d مرات</item>
<item quantity="other">أعيد نشره %,d مرات</item>
</plurals>
@@ -698,4 +698,18 @@
<string name="time_minutes_ago_short">مُنذُ %dد</string>
<string name="time_hours_ago_short">مُنذُ %dسا</string>
<string name="time_days_ago_short">مُنذُ %d أيام</string>
<!-- %s is the name of the post language -->
<string name="translate_post">تُرجِم مِن %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">مُترجَم مِن %1$s باستخدام %2$s</string>
<string name="translation_show_original">إظهار الأصل</string>
<string name="translation_failed">فشِلَت الترجَمة. قد لم يتمكّن مدير الخادم من تفعيل الترجمات على هذا الخادم أو أنّ هذا الخادم يُشغِّل نسخة قديمة من ماستدون حيث الترجمات غير مدعومة بعد.</string>
<plurals name="x_participants">
<item quantity="zero">لا مُشارِك</item>
<item quantity="one">مشارِك واحد</item>
<item quantity="two">مشاركَيْنِ</item>
<item quantity="few">مشاركين</item>
<item quantity="many">مُشارِكًا</item>
<item quantity="other">مُشارك</item>
</plurals>
</resources>

View File

@@ -249,4 +249,71 @@
<string name="sk_notify_poll_results">نتيجة استطلاع الرأي</string>
<string name="sk_filtered">تمت تصفيته: %s</string>
<string name="sk_search_fediverse">البحث في الفديفرس</string>
<string name="sk_suicide_search_terms">انتحار, انتِحار, الانتحار, الإنتحار, أنتحر</string>
<string name="sk_search_suicide_title">إن كنت في مِحنة وبحاجة إلى مساعدة…</string>
<string name="sk_search_suicide_message">إذا كنت تبحث عن إشارة لعدم الانتحار، فها هي. إذا كنت تعاني من ضائقة و/أو تراودك أفكار انتحارية، وإذا كنت بحاجة إلى مساعدة، فيمكنك الاتصال بخط المساعدة للوقاية من الانتحار إن كنت في مِحنة.</string>
<string name="sk_load_missing_posts_above">حمِّل المنشورات الحديثة</string>
<string name="sk_load_missing_posts_below">حمِّل المنشورات القديمة</string>
<string name="sk_time_seconds">%d ثواني</string>
<string name="sk_time_minutes">%d دقائق</string>
<string name="sk_time_hours">%d ساعات</string>
<string name="sk_time_days">%d أيام</string>
<string name="sk_search_suicide_hotlines">للعثور على خط للمساعدة</string>
<string name="sk_confirm_delete_scheduled_post_title">احذف المنشور المُبَرمَج</string>
<string name="sk_compose_scheduled">مُبَرمَج لـ</string>
<string name="sk_do_remove_follower">إزالة</string>
<string name="sk_icon_academic_cap">قبعة جامعية</string>
<string name="sk_icon_tag">ملصقة</string>
<string name="sk_add_timeline_tag_error_empty">لا يجب أن يُترَك الوسم فارغًا</string>
<string name="sk_unfinished_attachments">إصلاح المرفقات؟</string>
<plurals name="sk_posts_count_label">
<item quantity="zero">لا منشور</item>
<item quantity="one">منشور واحد</item>
<item quantity="two">منشورَيْنِ</item>
<item quantity="few">منشورات</item>
<item quantity="many">منشورًا</item>
<item quantity="other">منشور</item>
</plurals>
<string name="sk_notification_mention">لقد قام %s بذِكرك</string>
<string name="sk_settings_emoji_reactions">تمكين الوجوه التعبيرية للتفاعل</string>
<string name="sk_icon_verified">مُتحَقَّق مِنه</string>
<string name="sk_switch_timeline">تبديل الخيط الزمني</string>
<string name="sk_settings_show_emoji_reactions_always">إظهار زر الإضافة دائما</string>
<string name="sk_do_not_show_again">لا تظهر مرة أخرى</string>
<string name="sk_settings_display_pronouns_in_user_listings">إظهار الأسماء في قوائم المستخدِمين</string>
<string name="sk_post_contains_media">يحتوي المنشور على وسائط</string>
<string name="sk_settings_see_new_posts_button">أظهر زِرّ ”إظهار المنشورات الجديدة“</string>
<string name="sk_followed_as">مُتابَع عبر %s</string>
<string name="sk_settings_allow_remote_loading">تحميل المعلومات مِن الخوادم البعيدة</string>
<string name="sk_content_type_html">HTML</string>
<string name="sk_reacted">تَفاعَل %s</string>
<string name="sk_reacted_with">تَفاعَلَ %1$s مع %2$s</string>
<string name="sk_external_share_or_open_title">إعادة نشر أو فتح بحساب آخَر</string>
<string name="sk_content_type_plain">نص عادي</string>
<string name="sk_settings_prefix_replies_always">ردًّا على أي كان</string>
<string name="sk_settings_forward_report_default">إعادة ”تحويل الإبلاغ“ افتراضيا</string>
<string name="sk_content_type_mfm">MFM</string>
<string name="sk_settings_hide_fab">إخفاء زر التحرير مبدئيا</string>
<string name="sk_enter_emoji_hint">اضغط للتفاعل بوجوه تعبيرية</string>
<string name="sk_content_type_markdown">Markdown</string>
<string name="sk_content_type_bbcode">BBCode</string>
<string name="sk_enter_emoji_toast">يُرجى إدخال إيموجي</string>
<string name="sk_in_reply">ردّا على</string>
<string name="sk_open_in_app_failed">لا يمكن فتحه في التطبيق</string>
<string name="sk_settings_enable_delete_notifications">تمكين حذف الإشعارات</string>
<string name="sk_clear_all_notifications_confirm">أتريد حقا حذف كافة الإشعارات؟</string>
<string name="sk_already_bookmarked">موجود بالفعل في الفواصل المرجعية</string>
<string name="sk_resource_not_found">المَورِد غير موجود</string>
<string name="sk_delete_notification_confirm">أتريد حقا حذف هذا الإشعار؟</string>
<string name="sk_draft_or_schedule">تحرير مسودة أو برمجتها</string>
<string name="sk_trending_links_info_banner">هذه هي القصص الإخبارية التي يُتَحدّث عنها في خادمكم.</string>
<string name="sk_notification_type_posts">إشعارات المنشورات</string>
<string name="sk_confirm_delete_draft">هل أنت متأكد من أنك تريد حذف مُسودّة هذا المنشور؟</string>
<string name="sk_compose_no_schedule">لا تُبرمِجه</string>
<string name="sk_confirm_clear_recent_languages">أتريد حقا مسح لغاتك المُستخدَمة حديثا؟</string>
<string name="sk_confirm_delete_scheduled_post">هل أنت متأكد من أنك تريد حذف هذا المنشور المُبرمَج؟</string>
<string name="sk_trending_posts_info_banner">هذه هي المنشورات المثيرة للإهتمام على خادمكم.</string>
<string name="sk_federated_timeline_info_banner">هذه هي أحدث المنشورات للأعضاء في فديراليتك.</string>
<string name="sk_bubble_timeline_info_banner">هذه هي أحدث المنشورات على الشبكة والتي انتقاها مُدراء خادمكم.</string>
<string name="sk_schedule_or_draft">برمجة أو تحرير مُسودّة</string>
</resources>

View File

@@ -527,4 +527,6 @@
<string name="time_minutes_ago_short">%d хв таму</string>
<string name="time_hours_ago_short">%d г таму</string>
<string name="time_days_ago_short">%d дз таму</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -199,4 +199,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -182,4 +182,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -293,4 +293,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -639,4 +639,6 @@
<string name="time_minutes_ago_short">Před %dm</string>
<string name="time_hours_ago_short">Před %dh</string>
<string name="time_days_ago_short">Před %dd</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -503,4 +503,6 @@
<string name="search_open_url">Åbn URL i Mastodon</string>
<string name="posts_matching_hashtag">Indlæg med “%s”</string>
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -582,4 +582,6 @@
<string name="time_minutes_ago_short">vor %d Minuten</string>
<string name="time_hours_ago_short">vor %d Stunden</string>
<string name="time_days_ago_short">vor %d Tagen</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -382,10 +382,6 @@
<item quantity="other">Beiträge</item>
</plurals>
<string name="sk_settings_show_labels_in_navigation_bar">Tab-Bezeichnungen in der Navigationsleiste anzeigen</string>
<string name="sk_time_seconds">%d Sekunden</string>
<string name="sk_time_minutes">%d Minuten</string>
<string name="sk_time_hours">%d Stunden</string>
<string name="sk_time_days">%d Tage</string>
<string name="sk_suicide_helplines_url">https://findahelpline.com</string>
<string name="sk_load_missing_posts_below">Ältere Beiträge laden</string>
<string name="sk_load_missing_posts_above">Neuere Beiträge laden</string>

View File

@@ -387,6 +387,7 @@
<string name="welcome_to_mastodon">Καλώς ήρθες στο Mastodon</string>
<string name="welcome_paragraph1">Το Mastodon είναι ένα αποκεντρωμένο κοινωνικό δίκτυο που σημαίνει ότι καμία εταιρεία δεν το ελέγχει. Αποτελείται από πολλούς ανεξάρτητους διακομιστές, όλοι συνδεδεμένοι μαζί.</string>
<string name="what_are_servers">Τι είναι οι διακομιστές;</string>
<string name="welcome_paragraph2">Κάθε λογαριασμός Mastodon φιλοξενείται σε ένα διακομιστή - ο καθένας με τις δικές του αξίες, κανόνες &amp; διαχειριστές. Ανεξάρτητα από το ποιον μπορεί να επιλέξεις, μπορείς να ακολουθήσεις και να αλληλεπιδράσεις με άτομα από οποιονδήποτε διακομιστή.</string>
<string name="opening_link">Άνοιγμα συνδέσμου…</string>
<string name="link_not_supported">Αυτός ο σύνδεσμος δεν υποστηρίζεται στην εφαρμογή</string>
<string name="log_out_all_accounts">Αποσύνδεση από όλους τους λογαριασμούς</string>
@@ -581,4 +582,21 @@
<string name="time_minutes_ago_short">%dλ πριν</string>
<string name="time_hours_ago_short">%dώ πριν</string>
<string name="time_days_ago_short">%dημ πριν</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Μετάφραση από %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Μεταφράστηκε από %1$s χρησιμοποιώντας %2$s</string>
<string name="translation_show_original">Εμφάνιση αρχικού</string>
<string name="translation_failed">Η μετάφραση απέτυχε. Ίσως ο διαχειριστής δεν έχει ενεργοποιήσει μεταφράσεις σε αυτόν τον διακομιστή ή αυτός ο διακομιστής εκτελεί μια παλαιότερη έκδοση του Mastodon όπου οι μεταφράσεις δεν υποστηρίζονται ακόμα.</string>
<string name="settings_privacy">Ιδιωτικότητα και προσιτότητα</string>
<string name="settings_discoverable">Παροχή προφίλ και δημοσιεύσεων σε αλγορίθμους ανακάλυψης</string>
<string name="settings_indexable">Συμπερίληψη δημόσιων αναρτήσεων στα αποτελέσματα αναζήτησης</string>
<plurals name="x_participants">
<item quantity="one">%,d συμμετέχων</item>
<item quantity="other">%,d συμμετέχοντες</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d ανάρτηση σήμερα</item>
<item quantity="other">%,d αναρτήσεις σήμερα</item>
</plurals>
</resources>

View File

@@ -573,4 +573,6 @@
<string name="time_minutes_ago_short">hace %dm</string>
<string name="time_hours_ago_short">hace %dh</string>
<string name="time_days_ago_short">hace %dd</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -224,8 +224,8 @@
<string name="sk_updater_enable_pre_releases">Activar versiones beta</string>
<string name="sk_timeline_posts">Publicaciones</string>
<string name="sk_timelines_add">Añadir</string>
<string name="sk_timeline">Cronología</string>
<string name="sk_hashtag">Etiqueta</string>
<string name="sk_timeline">Línea de tiempo</string>
<string name="sk_hashtag">Hashtag</string>
<string name="sk_pin_timeline">Anclar cronología</string>
<string name="sk_unpin_timeline">Desanclar cronología</string>
<string name="sk_pinned_timeline">Anclado a inicio</string>
@@ -256,7 +256,7 @@
<string name="sk_settings_collapse_long_posts">Minimizar publicaciones largas</string>
<string name="sk_unfinished_attachments">¿Corregir adjuntos\?</string>
<string name="sk_unfinished_attachments_message">Algunos adjuntos no han terminado de subirse.</string>
<string name="sk_settings_prefix_reply_cw_with_re">Prefijo CW con \"re:\" al responder</string>
<string name="sk_settings_prefix_reply_cw_with_re">Añadir \"re\" a Avisos de Contenido</string>
<string name="sk_spectator_mode">Modo espectador</string>
<string name="sk_settings_hide_interaction">Ocultar los botones de interacción</string>
<string name="sk_follow_as">Seguir desde otra cuenta</string>
@@ -292,7 +292,7 @@
<string name="sk_external_share_or_open_title">Compartir o abrir con una cuenta</string>
<string name="sk_open_in_app">Abrir en la app</string>
<string name="sk_external_share_title">Compartir con una cuenta</string>
<string name="sk_settings_auto_reveal_equal_spoilers">Revelar automáticamente los CW iguales en las respuestas</string>
<string name="sk_settings_auto_reveal_equal_spoilers">Mostrar automáticamente Avisos de Contenido de respuestas</string>
<string name="sk_settings_auto_reveal_nobody">Nunca</string>
<string name="sk_settings_auto_reveal_author">Respuestas del mismo autor</string>
<string name="sk_settings_auto_reveal_anyone">Respuestas de todos</string>
@@ -301,14 +301,14 @@
<string name="sk_error_loading_profile">No se pudo cargar el perfil a través de %s</string>
<string name="sk_settings_allow_remote_loading">Cargar la información desde las instancias remotas</string>
<string name="sk_settings_allow_remote_loading_explanation">Intenta obtener listas más precisas de seguidores, Me gusta y promociones cargando la información desde la instancia de origen.</string>
<string name="sk_settings_prefix_replies_always">Responder a cualquiera</string>
<string name="sk_settings_prefix_replies_always">En cualquier respuesta</string>
<string name="sk_settings_prefix_replies_never">Nunca</string>
<string name="sk_settings_prefix_replies_to_others">Solo en respuesta a otros</string>
<string name="sk_settings_prefix_replies_to_others">Solo es respuestas a otros</string>
<string name="sk_settings_forward_report_default">\"Reenviar denuncia\" activado por defecto</string>
<string name="sk_list_exclusive_switch">Hacer una lista exclusiva</string>
<string name="sk_icon_feed">Cronología</string>
<string name="sk_exclusive_list">Lista exclusiva</string>
<string name="sk_list_exclusive_switch_explanation">Los miembros de una lista exclusiva no aparecerán en tu cronología de inicio - si tu instancia lo admite.</string>
<string name="sk_list_exclusive_switch">Crear una lista excluyente</string>
<string name="sk_icon_feed">Feed</string>
<string name="sk_exclusive_list">Lista excluyente</string>
<string name="sk_list_exclusive_switch_explanation">Los miembros de una lista excluyente no aparecerán en tu cronología de inicio - si tu instancia lo permite.</string>
<string name="sk_icon_verified">Verificado</string>
<string name="sk_icon_doctor">Médico</string>
<string name="sk_icon_diamond">Diamante</string>
@@ -319,8 +319,8 @@
<string name="sk_edit_timeline_tag_none">...pero ninguna de estas</string>
<string name="sk_hashtag_timeline_local_only_switch">¿Mostrar sólo las publicaciones locales\?</string>
<string name="sk_add_timeline_tag_error_empty">La etiqueta no puede quedar vacía</string>
<string name="sk_advanced_options_show">Mostrar las opciones avanzadas</string>
<string name="sk_advanced_options_hide">Ocultar las opciones avanzadas</string>
<string name="sk_advanced_options_show">Mostrar opciones avanzadas</string>
<string name="sk_advanced_options_hide">Ocultar opciones avanzadas</string>
<string name="sk_icon_recycle_bin">Papelera de reciclaje</string>
<string name="sk_add_timeline">Agregar una cronología</string>
<string name="sk_edit_timeline_tag_hint">Introduce la etiqueta…</string>
@@ -357,37 +357,47 @@
<string name="sk_settings_continues_playback_summary">Permitir que los medios que ya se están reproduciendo sigan reproduciéndose, superponiéndose a la nueva reproducción</string>
<string name="sk_settings_unifiedpush">Usar UnifiedPush</string>
<string name="sk_settings_unifiedpush_no_distributor_body">Es necesario instalar un distribuidor para que funcionen las notificaciones de UnifiedPush. Para obtener más información, visita https://unifiedpush.org/</string>
<string name="sk_settings_display_pronouns_in_timelines">Mostrar pronombres en líneas de tiempo</string>
<string name="sk_settings_display_pronouns_in_timelines">Mostrar pronombres en cronologías</string>
<string name="sk_settings_display_pronouns_in_threads">Mostrar pronombres en los hilos</string>
<string name="sk_settings_show_labels_in_navigation_bar">Mostrar las etiquetas de las pestañas en la barra de navegación</string>
<string name="sk_settings_show_labels_in_navigation_bar">Mostrar etiquetas de pestañas en la barra de navegación</string>
<string name="sk_settings_emoji_reactions_in_lists">Mostrar las reacciones con los emoticonos en las líneas de tiempo</string>
<plurals name="sk_users_reacted_with">
<item quantity="one">Un usuario reaccionó con %2$s</item>
<item quantity="many">%1$,d usuarios reaccionaron con %2$s</item>
<item quantity="other">%1$,d usuarios reaccionaron con %2$s</item>
</plurals>
<string name="sk_enter_emoji_toast">Necesitas escribir un emoticono</string>
<string name="sk_enter_emoji_toast">Por favor, introduce un emoticono</string>
<string name="sk_enter_emoji_hint">Escribe para reaccionar con un emoticono</string>
<string name="sk_settings_emoji_reactions">Activar las reacciones con los emoticonos</string>
<string name="sk_settings_emoji_reactions_explanation">Muestra las reacciones con los emoticonos a los mensajes y te permite interactuar con ellos. Algunas versiones modificadas de Mastodon lo soportan, pero Mastodon no.</string>
<string name="sk_settings_emoji_reactions">Activar reacciones con emoticonos</string>
<string name="sk_settings_emoji_reactions_explanation">Muestra las reacciones con emoticonos a las publicaciones y te permite añadir las tuyas. Varias instancias del Fediverso lo soportan, pero Mastodon no.</string>
<string name="sk_settings_emoji_reactions_in_lists_explanation">Las reacciones con los emoticonos deben mostrarse en las líneas de tiempo. Si esta opción está desactivada, las reacciones con los emoticonos solo se mostrarán al ver un hilo.</string>
<string name="sk_button_react">Reacciona con un emoticono</string>
<string name="sk_again_for_system_keyboard">Pulsa de nuevo para el Teclado del sistema</string>
<plurals name="sk_posts_count_label">
<item quantity="one">tema</item>
<item quantity="many">temas</item>
<item quantity="other">temas</item>
<item quantity="one">publicación</item>
<item quantity="many">publicaciones</item>
<item quantity="other">publicaciones</item>
</plurals>
<string name="sk_settings_show_emoji_reactions_hide_empty">Ocultar emoji vacíos</string>
<string name="sk_settings_show_emoji_reactions_only_opened">Sólo cuando se abre un tema</string>
<string name="sk_settings_show_emoji_reactions_hide_empty">Ocultar emoticonos sin reacciones</string>
<string name="sk_settings_show_emoji_reactions_only_opened">Sólo cuando se abre la publicación</string>
<string name="sk_settings_show_emoji_reactions_always">Mostrar siempre el botón de añadir</string>
<string name="sk_search_suicide_hotlines">Encontrar un teléfono de ayuda</string>
<string name="sk_search_suicide_hotlines">Encontrar un teléfono de ayuda (024 en España)</string>
<string name="sk_do_not_show_again">No mostrar de nuevo</string>
<string name="sk_post_contains_media">El tema contiene medios</string>
<string name="sk_post_contains_media">La publicación contiene multimedia</string>
<string name="sk_load_missing_posts_oldest_first">El más antiguo primero (hacia arriba)</string>
<string name="sk_settings_show_emoji_reactions">Mostrar los emoji en las líneas de tiempo</string>
<string name="sk_search_suicide_title">En caso de que estés en apuros</string>
<string name="sk_settings_show_emoji_reactions">Mostrar emoticonos en las cronologías</string>
<string name="sk_search_suicide_title">En caso de que estés en un mal momento</string>
<string name="sk_suicide_helplines_url">https://findahelpline.com</string>
<string name="sk_settings_load_missing_posts">Cargar los mensajes que faltan</string>
<string name="sk_load_missing_posts_newest_first">Lo más nuevo primero (hacia abajo)</string>
<string name="sk_time_minutes">%d minutos</string>
<string name="sk_time_hours">%d horas</string>
<string name="sk_time_days">%d días</string>
<string name="sk_suicide_search_terms">Suicidio</string>
<string name="sk_search_suicide_message">Si buscas una señal para no suicidarte, es ésta. Por favor, piensa el ponerte en contacto con una línea directa para ayuda al suicido si estás angustiado.</string>
<string name="sk_load_missing_posts_below">Cargar publicaciones más antiguas</string>
<string name="sk_time_seconds">%d segundos</string>
<string name="sk_load_missing_posts_above">Cargar publicaciones más recientes</string>
<string name="sk_trending_posts_info_banner">Estos son los posts que ganan popularidad en tu servidor.</string>
<string name="sk_trending_links_info_banner">Estas son las noticias de las que se habla en tu servidor.</string>
</resources>

View File

@@ -441,4 +441,6 @@
<string name="clear_all">Garbitu dena</string>
<string name="search_open_url">Ireki URLa Mastodonen</string>
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -387,6 +387,7 @@
<string name="welcome_to_mastodon">به ماستودون خوش آمدید</string>
<string name="welcome_paragraph1">ماستودون یک شبکه اجتماعی غیر متمرکز است،به این معنی که هیچ شرکتی آن را کنترل نمی کند. این از بسیاری از کارسازهای مستقل تشکیل شده است که همه به هم متصل هستند.</string>
<string name="what_are_servers">کارساز شما کجاست؟</string>
<string name="welcome_paragraph2">هر حساب ماستودون بر روی یک سرور میزبانی می شود — هر کدام با مقادیر، قوانین، &amp; مدیران خاص خود. مهم نیست کدام یک را انتخاب می کنید، می توانید افراد را در هر کارسازی دنبال کنید و با آنها تعامل داشته باشید.</string>
<string name="opening_link">باز کردن پیوند…</string>
<string name="link_not_supported">این پیوند در کاره پشتیبانی نمی شود</string>
<string name="log_out_all_accounts">از همه حساب‌ها خارج شوید</string>
@@ -581,4 +582,21 @@
<string name="time_minutes_ago_short">%d؜دقيقه پيش</string>
<string name="time_hours_ago_short">%dساعت پيش</string>
<string name="time_days_ago_short">%dروز پیش</string>
<!-- %s is the name of the post language -->
<string name="translate_post">ترجمه از %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">ترجمه از %1$s با %2$s</string>
<string name="translation_show_original">نمایش اصلی</string>
<string name="translation_failed">ترجمه ناموفق بود. شاید مدیر ترجمه‌ها را در این کارساز فعال نکرده باشد یا این کارساز نسخه قدیمی ماستودون را اجرا می کند که در آن ترجمه‌ها هنوز پشتیبانی نمی شوند.</string>
<string name="settings_privacy">محرمانگی و دسترسی</string>
<string name="settings_discoverable">مشخص کردن مشخصات و فرسته‌ها در الگوریتم‌های اکتشاف</string>
<string name="settings_indexable">قرار دادن فرسته‌های عمومی در نتایج جستجو</string>
<plurals name="x_participants">
<item quantity="one">%,d شرکت کننده</item>
<item quantity="other">%,d شرکت‌کننده</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d فرسته امروز</item>
<item quantity="other">%,d فرسته امروز</item>
</plurals>
</resources>

View File

@@ -357,12 +357,12 @@
<string name="sk_notification_mention">شما توسط %s نام‌برده شده اید</string>
<string name="sk_settings_unifiedpush">استفاده از UnifiedPush</string>
<string name="sk_duration_days_7">7 روز</string>
<string name="sk_settings_unifiedpush_no_distributor_body">برای کارکردن آگاهی‌های UnifiedPush باید یک توزیع‌کننده نصب کنید. برای اطلاعات بیشتر به https://unifiedpush.org/ مراجعه کنید.</string>
<string name="sk_settings_unifiedpush_no_distributor_body">برای کارکردن آگاهی‌های یونیفیتاید پوش باید یک توزیع‌کننده نصب کنید. برای اطلاعات بیشتر به https://unifiedpush.org/ مراجعه کنید</string>
<string name="sk_settings_continues_playback">پوشش صوتی</string>
<string name="sk_settings_continues_playback_summary">به رسانه‌ای که از قبل پخش می‌شود اجازه دهید به پخش ادامه دهد و پخش جدید را پوشش دهد</string>
<string name="sk_button_react">واکنش با ایموجی</string>
<string name="sk_again_for_system_keyboard">دوباره برای کیبورد سامانه ضربه بزنید</string>
<string name="sk_enter_emoji_toast">باید یک ایموجی تایپ کنید</string>
<string name="sk_enter_emoji_toast">لطفا یک ایموجی تایپ کنید</string>
<string name="sk_enter_emoji_hint">برای واکنش با ایموجی تایپ کنید</string>
<plurals name="sk_users_reacted_with">
<item quantity="one">یک کاربر با %2$s واکنش نشان داد</item>
@@ -371,5 +371,28 @@
<string name="sk_settings_emoji_reactions">به‌کار انداختن واکنش های ایموجی</string>
<string name="sk_settings_emoji_reactions_in_lists">نمایش واکنش های ایموجی در خطوط زمانی</string>
<string name="sk_settings_emoji_reactions_in_lists_explanation">اینکه آیا واکنش های ایموجی باید در خطوط زمانی نمایش داده شود یا خیر. اگر این گزینه خاموش باشد، واکنش های ایموجی فقط هنگام مشاهده یک موضوع نمایش داده می شود.</string>
<string name="sk_settings_emoji_reactions_explanation">واکنش‌های ایموجی را به فرسته‌ها نشان می‌دهد و به شما امکان می‌دهد با آنها تعامل داشته باشید. برخی از نسخه های اصلاح شده ماستودون از این پشتیبانی می کنند، اما ماستودون اینطور نیست.</string>
<string name="sk_settings_emoji_reactions_explanation">واکنش‌های ایموجی را به فرسته‌ها نشان می‌دهد و به شما امکان می‌دهد عکس‌العمل خود را اضافه کنید.. سرور های مختلف فدیورس از این پشتیبانی می کنند، اما ماستودون اینطور نیست.</string>
<string name="sk_settings_show_emoji_reactions_only_opened">فقط زمانی که فرسته باز شود</string>
<string name="sk_settings_show_emoji_reactions_hide_empty">نهفتن واکنش‌های ایموجی خالی</string>
<string name="sk_settings_show_emoji_reactions">نمایش واکنش های ایموجی در خطوط زمانی</string>
<string name="sk_time_seconds">%d ثانیه</string>
<string name="sk_time_hours">%d ساعت</string>
<plurals name="sk_posts_count_label">
<item quantity="one">فرسته</item>
<item quantity="other">فرسته‌ها</item>
</plurals>
<string name="sk_suicide_search_terms">خودکشی</string>
<string name="sk_load_missing_posts_above">بارگذاری فرسته‌های جدیدتر</string>
<string name="sk_time_days">%d روز</string>
<string name="sk_settings_show_emoji_reactions_always">دکمه افزودن همیشه نشان داده شود</string>
<string name="sk_search_suicide_hotlines">یک راه کمکی بیابید</string>
<string name="sk_do_not_show_again">دوباره نمایش نده</string>
<string name="sk_suicide_helplines_url">https://findahelpline.com</string>
<string name="sk_trending_posts_info_banner">اینها فرسته‌هایی هستند که در کارساز شما مورد توجه قرار می گیرند.</string>
<string name="sk_load_missing_posts_below">بارگذاری فرسته‌های قدیمی‌تر</string>
<string name="sk_search_suicide_title">اگر نیازمند کمک هستید…</string>
<string name="sk_time_minutes">%d دقیقه</string>
<string name="sk_search_suicide_message">اگر به دنبال نشانه ای برای خودکشی نیستید، همین است. لطفاً اگر نیازمند کمک هستید با شماره تلفن محلی خودکشی تماس بگیرید.</string>
<string name="sk_trending_links_info_banner">اینها اخباری است که در کارساز شما در مورد آنها صحبت می شود.</string>
<string name="sk_post_contains_media">فرسته حاوی رسانه‌ست</string>
</resources>

View File

@@ -2,13 +2,167 @@
<resources>
<string name="log_in">Kirjaudu sisään</string>
<string name="next">Seuraava</string>
<string name="loading_instance">Haetaan palvelimen tietoja…</string>
<string name="error">Virhe</string>
<string name="not_a_mastodon_instance">%s ei näytä olevan Mastodonin palvelin.</string>
<string name="ok">OK</string>
<string name="preparing_auth">Valmistellaan todennusta…</string>
<string name="finishing_auth">Viimeistellään todennusta…</string>
<string name="user_boosted">%s tehosti</string>
<string name="in_reply_to">Vastauksessa %s</string>
<string name="notifications">Ilmoitukset</string>
<string name="user_followed_you">%s seurasi sinua</string>
<string name="user_sent_follow_request">%s lähetti sinulle seurauspyynnön</string>
<string name="user_favorited">%s tykkäsi julkaisustasi</string>
<string name="notification_boosted">%s tehosti viestiäsi</string>
<string name="poll_ended">Katso tulokset äänestyksestä johon osallistuit</string>
<string name="share_toot_title">Jaa</string>
<string name="settings">Asetukset</string>
<string name="publish">Julkaise</string>
<string name="discard_draft">Hylkää luonnos?</string>
<string name="discard">Hylkää</string>
<string name="cancel">Kumoa</string>
<plurals name="followers">
<item quantity="one">seuraaja</item>
<item quantity="other">seuraajat</item>
</plurals>
<plurals name="following">
<item quantity="one">seurataan</item>
<item quantity="other">seurataan</item>
</plurals>
<string name="posts">Viestit</string>
<string name="posts_and_replies">Viestit ja vastaukset</string>
<string name="media">Media</string>
<string name="profile_about">Tietoja</string>
<string name="button_follow">Seuraa</string>
<string name="button_following">Seurataan</string>
<string name="edit_profile">Muokkaa profiilia</string>
<string name="share_user">Jaa profiili</string>
<string name="mute_user">Mykistä %s</string>
<string name="unmute_user">Poista mykistys tililtä %s</string>
<string name="block_user">Estä %s</string>
<string name="unblock_user">Poista käyttäjän %s esto</string>
<string name="report_user">Raportoi %s</string>
<string name="block_domain">Estä %s</string>
<string name="unblock_domain">Poista käyttäjän %s esto</string>
<plurals name="x_posts">
<item quantity="one">%,d julkaisu</item>
<item quantity="other">%,d julkaisua</item>
</plurals>
<string name="profile_joined">Liittynyt</string>
<string name="done">Valmis</string>
<string name="loading">Ladataan…</string>
<string name="field_label">Nimi</string>
<string name="field_content">Sisältö</string>
<string name="saving">Tallennetaan…</string>
<string name="post_from_user">Julkaisu tililtä %s</string>
<string name="poll_option_hint">Vaihtoehto %d</string>
<plurals name="x_minutes">
<item quantity="one">%d minuutti</item>
<item quantity="other">%d minuuttia</item>
</plurals>
<plurals name="x_hours">
<item quantity="one">%d tunti</item>
<item quantity="other">%d tuntia</item>
</plurals>
<plurals name="x_days">
<item quantity="one">%d päivä</item>
<item quantity="other">%d päivää</item>
</plurals>
<plurals name="x_seconds_left">
<item quantity="one">%d sekunti jäljellä</item>
<item quantity="other">%d sekunttia jäljellä</item>
</plurals>
<plurals name="x_minutes_left">
<item quantity="one">%d minuutti jäljellä</item>
<item quantity="other">%d minuuttia jäljellä</item>
</plurals>
<plurals name="x_hours_left">
<item quantity="one">%d tunti jäljellä</item>
<item quantity="other">%d tuntia jäljellä</item>
</plurals>
<plurals name="x_days_left">
<item quantity="one">%d päivä jäljellä</item>
<item quantity="other">%d päivää jäljellä</item>
</plurals>
<plurals name="x_votes">
<item quantity="one">%d ääni</item>
<item quantity="other">%d ääntä</item>
</plurals>
<string name="poll_closed">Suljettu</string>
<string name="confirm_mute_title">Mykistä tili</string>
<string name="confirm_mute">Vahvista käyttäjän %s mykistys</string>
<string name="do_mute">Mykistä</string>
<string name="confirm_unmute_title">Poista tilin mykistys</string>
<string name="confirm_unmute">Vahvista, että haluat poistaa mykistyksen tililtä %s</string>
<string name="do_unmute">Poista mykistys</string>
<string name="confirm_block_title">Estä tili</string>
<string name="confirm_block_domain_title">Estä verkkotunnus</string>
<string name="confirm_block">Vahvista käyttäjän %s esto</string>
<string name="do_block">Estä</string>
<string name="confirm_unblock_title">Poista tilin esto</string>
<string name="confirm_unblock_domain_title">Poista verkkotunnuksen esto</string>
<string name="confirm_unblock">Vahvista, että haluat poistaa tilin %s eston</string>
<string name="do_unblock">Poista esto</string>
<string name="button_blocked">Estetty</string>
<string name="action_vote">Äänestä</string>
<string name="delete">Poista</string>
<string name="confirm_delete_title">Poista julkaisu</string>
<string name="confirm_delete">Haluatko varmasti poistaa tämän julkaisun?</string>
<string name="deleting">Poistetaan…</string>
<string name="notification_channel_audio_player">Äänen toisto</string>
<string name="play">Toista</string>
<string name="pause">Tauko</string>
<string name="log_out">Kirjaudu ulos</string>
<string name="add_account">Lisää tili</string>
<string name="search_hint">Haku</string>
<string name="hashtags">Aihetunnisteet</string>
<string name="news">Uutiset</string>
<string name="for_you">Sinulle</string>
<string name="all_notifications">Kaikki</string>
<string name="mentions">Maininnat</string>
<plurals name="x_people_talking">
<item quantity="one">%d henkilö puhuu</item>
<item quantity="other">%d henkilöä puhuu</item>
</plurals>
<string name="report_title">Raportoi %s</string>
<string name="report_choose_reason">Mikä on väärin tässä julkaisussa?</string>
<string name="report_choose_reason_account">Mikä on vialla käyttäjässä %s?</string>
<string name="report_choose_reason_subtitle">Valitse se, mikä sopii parhaiten</string>
<string name="report_reason_personal">En pidä siitä</string>
<string name="report_reason_personal_subtitle">Tätä ei halua nähdä</string>
<string name="report_reason_spam">Se on roskapostia</string>
<string name="report_reason_spam_subtitle">Haitalliset linkit, väärennetyt sitoutumiset tai toistuvat vastaukset</string>
<string name="report_reason_violation">Se rikkoo palvelimen sääntöjä</string>
<string name="report_reason_violation_subtitle">Tiedät, että se rikkoo tiettyjä sääntöjä</string>
<string name="report_reason_other">Jotain muuta</string>
<string name="report_reason_other_subtitle">Ongelma ei sovi muihin kategorioihin</string>
<string name="report_choose_rule">Mitä sääntöjä rikotaan?</string>
<string name="report_choose_rule_subtitle">Valitse kaikki sopivat</string>
<string name="report_choose_posts">Onko julkaisuja, jotka tukevat tätä raporttia?</string>
<string name="report_choose_posts_subtitle">Valitse kaikki sopivat</string>
<string name="report_comment_title">Olisiko jotain muuta, mitä meidän pitäisi tietää?</string>
<string name="report_comment_hint">Lisäkommentit</string>
<string name="sending_report">Lähetetään raporttia…</string>
<string name="report_sent_title">Kiitos raportista, tutkimme asiaa.</string>
<string name="report_sent_subtitle">Sillä välin kun tarkistamme tätä, voit ryhtyä toimenpiteisiin käyttäjää @%s vastaan:</string>
<string name="unfollow_user">Lopeta käyttäjän %s seuraaminen</string>
<string name="unfollow">Lopeta seuraaminen</string>
<string name="mute_user_explain">Et näe hänen viestejään. Hän voi silti seurata sinua ja nähdä viestisi. Hän ei tiedä, että on mykistetty.</string>
<string name="block_user_explain">Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkevät, että olet estänyt hänet.</string>
<string name="report_personal_title">Etkö halua nähdä tätä?</string>
<string name="report_personal_subtitle">Tässä on vaihtoehtosi hallita näkemääsi Mastodonissa:</string>
<string name="back">Takaisin</string>
<string name="search_communities">Palvelimen nimi tai URL-osoite</string>
<string name="instance_rules_title">Palvelimen säännöt</string>
<string name="instance_rules_subtitle">Jatkamalla sitoudut noudattamaan seuraavia sääntöjä, jotka %s moderaattorit ovat asettaneet ja valvoneet.</string>
<string name="signup_title">Luo tili</string>
<string name="display_name">Nimi</string>
<string name="username">Käyttäjätunnus</string>
<string name="email">Sähköposti</string>
<string name="password">Salasana</string>
<string name="confirm_password">Vahvista salasana</string>
<string name="password_note">Sisällytä suuraakkoset, erikoismerkit, ja numerot, jotta voit lisätä salasanan voimaa.</string>
<string name="category_academia">Akateeminen</string>
<string name="category_activism">Aktivismi</string>
<string name="category_all">Kaikki</string>
@@ -22,39 +176,424 @@
<string name="category_music">Musiikki</string>
<string name="category_regional">Alueellinen</string>
<string name="category_tech">Teknologia</string>
<string name="confirm_email_title">Tarkista sähköpostisi</string>
<!-- %s is the email address -->
<string name="confirm_email_subtitle">Napauta lähettämäämme linkkiä vahvistaaksesi tunnuksen %s. Odotamme täällä.</string>
<string name="confirm_email_didnt_get">Etkö saanut linkkiä?</string>
<string name="resend">Lähetä uudelleen</string>
<string name="open_email_app">Avaa sähköpostiohjelma</string>
<string name="resent_email">Vahvistusviesti lähetetty</string>
<string name="compose_hint">Kirjoita tai liitä mitä mietit</string>
<string name="content_warning">Sisältövaroitus</string>
<string name="save">Tallenna</string>
<string name="add_alt_text">Lisää selitys</string>
<string name="visibility_public">Julkinen</string>
<string name="visibility_followers_only">Vain seuraajat</string>
<string name="visibility_private">Vain mainitsemani tilit</string>
<string name="recent_searches">Viimeisimmät</string>
<string name="skip">Ohita</string>
<string name="notification_type_follow">Uudet seuraajat</string>
<string name="notification_type_favorite">Suosikit</string>
<string name="notification_type_reblog">Tehostukset</string>
<string name="notification_type_mention">Maininnat</string>
<string name="notification_type_poll">Kyselyt</string>
<string name="choose_account">Valitse tili</string>
<string name="err_not_logged_in">Kirjaudu ensin Mastodoniin</string>
<plurals name="cant_add_more_than_x_attachments">
<item quantity="one">Et voi lisätä enempää kuin %d medialiitteen</item>
<item quantity="other">Et voi lisätä enempää kuin %d medialiitettä</item>
</plurals>
<string name="media_attachment_unsupported_type">Tiedosto %s on tyyppiä jota ei tueta</string>
<string name="media_attachment_too_big">Tiedosto %1$s ylittää %2$s MB kokorajan</string>
<string name="settings_theme">Ulkoasu</string>
<string name="theme_auto">Käytä laitteen ulkoasua</string>
<string name="theme_light">Vaalea</string>
<string name="theme_dark">Tumma</string>
<string name="settings_behavior">Toiminnot</string>
<string name="settings_gif">Toista animoidut käyttäjäkuvat ja emojit</string>
<string name="settings_custom_tabs">Käytä sovelluksen sisäistä selainta</string>
<string name="settings_notifications">Ilmoitukset</string>
<string name="settings_contribute">Osallistu Mastodoniin</string>
<string name="settings_tos">Käyttöehdot</string>
<string name="settings_privacy_policy">Tietosuojakäytäntö</string>
<string name="settings_clear_cache">Tyhjennä median välimuisti</string>
<string name="settings_app_version">Mastodon Android v%1$s (%2$d)</string>
<string name="media_cache_cleared">Median välimuisti tyhjennetty</string>
<string name="confirm_log_out">Kirjaudu ulos %s?</string>
<string name="sensitive_content_explain">Kirjoittaja merkitsi tämän median arkaluontoiseksi.</string>
<string name="avatar_description">Avaa profiili %s</string>
<string name="more_options">Lisää asetuksia</string>
<string name="new_post">Uusi julkaisu</string>
<string name="button_reply">Vastaa</string>
<string name="button_reblog">Tehosta</string>
<string name="button_favorite">Suosikki</string>
<string name="button_share">Jaa</string>
<string name="media_no_description">Kuva ilma kuvausta</string>
<string name="add_media">Lisää mediatiedosto</string>
<string name="add_poll">Lisää kysely</string>
<string name="emoji">Emoji</string>
<string name="home_timeline">Kotiaikajana</string>
<string name="my_profile">Oma profiili</string>
<string name="media_viewer">Median katselin</string>
<string name="follow_user">Follow %s</string>
<string name="unfollowed_user">Käyttäjän %s seuraaminen lopetettu</string>
<string name="followed_user">Seuraat nyt käyttäjää %s</string>
<string name="following_user_requested">Käyttäjän %s seuraamista pyydetty</string>
<string name="open_in_browser">Avaa selaimessa</string>
<string name="hide_boosts_from_user">Piilota käyttäjän @%s tehostukset</string>
<string name="show_boosts_from_user">Näytä tehostukset käyttäjältä @%s</string>
<string name="signup_reason">Miksi haluat liittyä?</string>
<string name="signup_reason_note">Tämä auttaa meitä arvioimaan hakemustasi.</string>
<string name="clear">Tyhjennä</string>
<string name="profile_header">Otsikon kuva</string>
<string name="profile_picture">Profiilikuva</string>
<string name="reorder">Järjestä uudelleen</string>
<string name="download">Lataa</string>
<string name="permission_required">Käyttöoikeus vaaditaan</string>
<string name="storage_permission_to_download">Sovellus tarvitsee pääsyn tallennustilaan, jotta voit tallentaa tämän tiedoston.</string>
<string name="open_settings">Avaa asetukset</string>
<string name="error_saving_file">Virhe tallennettaessa tiedostoa</string>
<string name="file_saved">Tiedosto tallennettu</string>
<string name="downloading">Ladataan…</string>
<string name="no_app_to_handle_action">Tätä toimintoa käsittelevää sovellusta ei ole</string>
<string name="local_timeline">Paikallinen</string>
<string name="trending_posts_info_banner">Nämä julkaisut ovat saamassa vetoa eri puolilla Mastodonia.</string>
<string name="trending_links_info_banner">Näistä uutisista puhutaan Mastodonissa.</string>
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">Nämä ovat viestit kaikilta palvelimesi (%s) käyttäjiltä.</string>
<string name="recommended_accounts_info_banner">Muiden seuraamiesi perusteella saattaisit pitää näistä tileistä.</string>
<string name="see_new_posts">Uusia julkaisuja</string>
<string name="load_missing_posts">Lataa puuttuvat julkaisut</string>
<string name="follow_back">Seuraa takaisin</string>
<string name="button_follow_pending">Pyydetty</string>
<string name="follows_you">Seuraa sinua</string>
<string name="manually_approves_followers">Hyväksyy seuraajat käsin</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<plurals name="x_followers">
<item quantity="one">%d seuraaja</item>
<item quantity="other">%d seuraajaa</item>
</plurals>
<plurals name="x_following">
<item quantity="one">%d seurattu</item>
<item quantity="other">%d seurattua</item>
</plurals>
<plurals name="x_favorites">
<item quantity="one">%,d suosikki</item>
<item quantity="other">%,d suosikkia</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="one">%,d tehostus</item>
<item quantity="other">%,d tehostusta</item>
</plurals>
<string name="timestamp_via_app">%1$s sovelluksella %2$s</string>
<string name="time_now">nyt</string>
<string name="edit_history">Muokkaushistoria</string>
<string name="last_edit_at_x">Muokattiin viimeksi %s</string>
<string name="time_just_now">juuri nyt</string>
<plurals name="x_seconds_ago">
<item quantity="one">%d sekunti sitten</item>
<item quantity="other">%d sekuntia sitten</item>
</plurals>
<plurals name="x_minutes_ago">
<item quantity="one">%d minuutti sitten</item>
<item quantity="other">%d minuuttia sitten</item>
</plurals>
<string name="edited_timestamp">muokattu %s</string>
<string name="edit_original_post">Alkuperäinen viesti</string>
<string name="edit_text_edited">Tekstiä muokattu</string>
<string name="edit_spoiler_added">Sisältövaroitus</string>
<string name="edit_spoiler_edited">Sisältövaroitus muokattu</string>
<string name="edit_spoiler_removed">Sisältövaroitus poistettu</string>
<string name="edit_poll_added">Kysely lisätty</string>
<string name="edit_poll_edited">Kyselyä muokattu</string>
<string name="edit_poll_removed">Kysely poistettu</string>
<string name="edit_media_added">Mediatiedosto lisätty</string>
<string name="edit_media_removed">Mediatiedosto poistettu</string>
<string name="edit_media_reordered">Mediatiedostoja järjestetty</string>
<string name="edit_marked_sensitive">Merkitty arkaluontoiseksi</string>
<string name="edit_marked_not_sensitive">Merkitty ei arkaluontoiseksi</string>
<string name="edit_multiple_changed">Julkaisu muokattu</string>
<string name="edit">Muokkaa</string>
<string name="discard_changes">Hylätäänkö muutokset?</string>
<string name="upload_failed">Lataus epäonnistui</string>
<string name="file_size_bytes">%d tavua</string>
<string name="file_size_kb">%.2f KB</string>
<string name="file_size_mb">%.2f MB</string>
<string name="file_size_gb">%.2f GB</string>
<string name="upload_processing">Käsitellään…</string>
<!-- %s is version like 1.2.3 -->
<!-- %s is version like 1.2.3 -->
<!-- %s is file size -->
<string name="download_update">Lataa (%s)</string>
<string name="install_update">Asenna</string>
<string name="privacy_policy_title">Yksityisyytesi</string>
<string name="privacy_policy_subtitle">Vaikka Mastodon-sovellus ei kerää mitään tietoja, palvelimella, jonka olet rekisteröitynyt, voi olla eri käytäntö.\n\nJos olet eri mieltä käytännöstä %s, voit palata ja valita eri palvelin.</string>
<string name="i_agree">Hyväksyn</string>
<string name="empty_list">Luettelo on tyhjä</string>
<string name="instance_signup_closed">Tämä palvelin ei hyväksy uusia rekisteröintejä.</string>
<string name="text_copied">Kopioitu leikepöydälle</string>
<string name="add_bookmark">Kirjanmerkki</string>
<string name="remove_bookmark">Poista kirjanmerkki</string>
<string name="bookmarks">Kirjanmerkit</string>
<string name="your_favorites">Omat suosikit</string>
<string name="login_title">Tervetuloa takaisin</string>
<string name="login_subtitle">Kirjaudu sisään palvelimella, jossa olet luonut tilisi.</string>
<string name="server_url">Palvelimen URL-osoite</string>
<string name="signup_random_server_explain">Palvelin valitaan kielesi perusteella, jos jatkat ilman valintaa.</string>
<string name="server_filter_any_language">Millä tahansa kielellä</string>
<string name="server_filter_instant_signup">Välitön rekisteröityminen</string>
<string name="server_filter_manual_review">Manuaalinen hyväksyntä</string>
<string name="server_filter_any_signup_speed">Mikä tahansa rekisteröintinopeus</string>
<string name="server_filter_region_europe">Eurooppa</string>
<string name="server_filter_region_north_america">Pohjois-Amerikka</string>
<string name="server_filter_region_south_america">Etelä-Amerikka</string>
<string name="server_filter_region_africa">Afrikka</string>
<string name="server_filter_region_asia">Aasia</string>
<string name="server_filter_region_oceania">Oseania</string>
<string name="not_accepting_new_members">Ei hyväksy uusia jäseniä</string>
<string name="category_special_interests">Erityiset Kiinnostukset</string>
<string name="signup_passwords_dont_match">Salasanat eivät täsmää</string>
<string name="pick_server_for_me">Valitse minulle</string>
<string name="profile_add_row">Lisää rivi</string>
<string name="profile_setup">Profiilin asetukset</string>
<string name="profile_setup_subtitle">Voit myös täyttää tämän myöhemmin Profiili-välilehdellä.</string>
<string name="profile_setup_explanation">Voit lisätä enintään neljä profiilikenttää joissa on mitä haluat. Sijainti, linkit, pronominit — taivas on rajana.</string>
<string name="popular_on_mastodon">Suosittua Mastodonissa</string>
<string name="follow_all">Seuraa kaikkia</string>
<string name="server_rules_disagree">Eri mieltä</string>
<string name="privacy_policy_explanation">TL;DR: Emme kerää tai käsittele mitään.</string>
<!-- %s is server domain -->
<string name="server_policy_disagree">Eri mieltä tästä %s</string>
<string name="profile_bio">Kuvaus</string>
<!-- Shown in a progress dialog when you tap "follow all" -->
<string name="sending_follows">Seurataan käyttäjiä…</string>
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
<string name="signup_email_domain_blocked">%1$s ei salli ilmoittautumisia osoitteesta %2$s. Kokeile toista osoitetta tai &lt;a&gt;valitse toinen palvelin&lt;/a&gt;.</string>
<string name="spoiler_show">Näytä joka tapauksessa</string>
<string name="spoiler_hide">Piilota uudelleen</string>
<string name="poll_multiple_choice">Valitse yksi tai useampi</string>
<string name="save_changes">Tallenna muutokset</string>
<string name="profile_featured">Suositellut</string>
<string name="profile_timeline">Aikajana</string>
<string name="view_all">Näytä kaikki</string>
<string name="profile_endorsed_accounts">Tilit</string>
<string name="verified_link">Vahvistettu linkki</string>
<string name="show">Näytä</string>
<string name="hide">Piilota</string>
<string name="join_default_server">Liity palvelimelle %s</string>
<string name="pick_server">Valitse toinen palvelin</string>
<string name="signup_or_login">tai</string>
<string name="learn_more">Lue lisää</string>
<string name="welcome_to_mastodon">Tervetuloa Mastodoniin</string>
<string name="welcome_paragraph1">Mastodon on hajautettu sosiaalinen verkosto, joka tarkoittaa sitä, ettei sitä hallitse mikään yksittäinen yritys. Se koostuu monista itsenäisesti ylläpidetyistä palvelimista, jotka on liitetty yhteen.</string>
<string name="what_are_servers">Mitä palvelimet ovat?</string>
<string name="welcome_paragraph2">Jokainen Mastodon tili isännöi palvelimella - kullakin on omat arvot, säännöt, &amp; ylläpitäjät. Riippumatta siitä, minkä valitset, voit seurata ja olla vuorovaikutuksessa ihmisten kanssa millä tahansa palvelimella.</string>
<string name="opening_link">Avataan linkki…</string>
<string name="link_not_supported">Tämä linkki ei ole tuettu sovelluksessa</string>
<string name="log_out_all_accounts">Kirjaudu ulos kaikista tileistä</string>
<string name="confirm_log_out_all_accounts">Kirjaudu ulos kaikista tileistä?</string>
<string name="retry">Yritä uudelleen</string>
<string name="post_failed">Viestin lähettäminen epäonnistui</string>
<!-- %s is formatted file size ("467 KB image") -->
<string name="attachment_description_image">%s kuva</string>
<string name="attachment_description_video">%s video</string>
<string name="attachment_description_audio">%s ääni</string>
<string name="attachment_description_unknown">%s tiedosto</string>
<string name="attachment_type_image">Kuva</string>
<string name="attachment_type_video">Video</string>
<string name="attachment_type_audio">Ääni</string>
<string name="attachment_type_gif">GIF</string>
<string name="attachment_type_unknown">Tiedosto</string>
<string name="attachment_x_percent_uploaded">%d%% ladattu</string>
<string name="add_poll_option">Lisää kyselyyn vaihtoehto</string>
<string name="poll_length">Kyselyn kesto</string>
<string name="poll_style">Tyyli</string>
<string name="compose_poll_single_choice">Valitse yksi</string>
<string name="compose_poll_multiple_choice">Monivalinta</string>
<string name="delete_poll_option">Poista kyselyn vaihtoehto</string>
<string name="poll_style_title">Kyselyn tyyli</string>
<string name="alt_text">Selitys</string>
<string name="help">Ohje</string>
<string name="what_is_alt_text">Mikä on selitys?</string>
<string name="alt_text_help">Kuvaselitys auttaa ihmisiä, joilla on näkövamma, hidas yhteys, tai jotka tarvitsevat lisäkontekstia. \n\nVoit parantaa saavutettavuutta ja kaikkien mahdollisuutta ymmärtää kirjoittamalla selkeän, lyhyen ja objektiivisen selityksen. \n\n<ul><li>Mainitse tärkeät elementit</li>\n<li>Anna tiivistelmä kuvissa olevista teksteistä</li>\n<li>Käytä normaalia lauserakennetta</li>\n<li>Vältä turhaa toistoa</li>\n<li>Keskity monimutkaisissa kuvioissa (kuten kartoissa ja taulukoissa) trendeihin ja tärkeimpiin tietoihin</li></ul></string>
<string name="edit_post">Muokkaa julkaisua</string>
<string name="no_verified_link">Ei todennettua linkkiä</string>
<string name="compose_autocomplete_emoji_empty">Selaa emojeita</string>
<string name="compose_autocomplete_users_empty">Löydä etsimäsi henkilöt</string>
<string name="no_search_results">Näille hakusanoille ei löytynyt mitään</string>
<string name="language">Kieli</string>
<string name="language_default">Oletus</string>
<string name="language_system">Järjestelmä</string>
<string name="language_detecting">Tunnista kieli</string>
<string name="language_cant_detect">Ei voi tunnistaa kieltä</string>
<string name="language_detected">Tunnistettu</string>
<string name="media_hidden">Media piilotettu</string>
<string name="post_hidden">Julkaisu piilotettu</string>
<string name="report_title_post">Raportoi julkaisu</string>
<string name="forward_report_explanation">Tämä tili on toisella palvelimella. Haluatko lähettää nimettömän raportin myös sinne?</string>
<!-- %s is the server domain -->
<string name="forward_report_to_server">Välitä kohteeseen %s</string>
<!-- Shown on the "stamp" on the screen that appears after you report a post/user. Please keep the translation short, preferably a single word -->
<string name="reported">Raportoitu</string>
<string name="report_unfollow_explanation">Jos et enää halua nähdä tämän käyttäjän julkaisuja kotiaikajanallasi, lopeta seuraaminen.</string>
<string name="muted_user">Käyttäjä %s mykistetty</string>
<string name="report_sent_already_blocked">Olet jo estänyt tämän käyttäjän, sinun ei tarvitse tehdä muuta sillä aikaa kun tarkastamme raporttisi.</string>
<string name="report_personal_already_blocked">Olet jo estänyt tämän käyttäjän, joten sinun ei tarvitse tehdä mitään muuta.\n\nKiitos, että autat pitämään Mastodonin turvallisena paikkana kaikille!</string>
<string name="blocked_user">Estetty %s</string>
<string name="mark_all_notifications_read">Merkitse kaikki luetuksi</string>
<string name="settings_display">Näyttö</string>
<string name="settings_filters">Suodattimet</string>
<string name="settings_server_explanation">Yleiskatsaus, säännöt, valvojat</string>
<!-- %s is the app name (Mastodon, key app_name). I made it a placeholder so everything Just Works™ with forks -->
<string name="about_app">Tietoa: %s</string>
<string name="default_post_language">Julkaisun oletuskieli</string>
<string name="settings_alt_text_reminders">Lisää muistutus kuvaselityksestä</string>
<string name="settings_confirm_unfollow">Kysy ennen kuin käyttäjän seuraaminen lopetetaan</string>
<string name="settings_confirm_boost">Kysy ennen tehostusta</string>
<string name="settings_confirm_delete_post">Kysy ennen julkaisujen poistamista</string>
<string name="pause_all_notifications">Keskeytä kaikki</string>
<string name="pause_notifications_off">Pois Päältä</string>
<string name="notifications_policy_anyone">Kuka tahansa</string>
<string name="notifications_policy_followed">Seuraajasi</string>
<string name="notifications_policy_follower">Seuraamasi henkilöt</string>
<string name="notifications_policy_no_one">Ei kukaan</string>
<string name="settings_notifications_policy">Ota vastaan ilmoituksia käyttäjältä</string>
<string name="notification_type_mentions_and_replies">Maininnat ja vastaukset</string>
<string name="pause_all_notifications_title">Keskeytä kaikki ilmoitukset</string>
<plurals name="x_weeks">
<item quantity="one">%d viikko</item>
<item quantity="other">%d viikkoa</item>
</plurals>
<!-- %1$s is the date (may be relative, e.g. "today" or "yesterday"), %2$s is the time. You can reorder these placeholders if that works better for your language -->
<string name="date_at_time">%1$s klo %2$s</string>
<string name="today">tänään</string>
<string name="yesterday">eilen</string>
<string name="tomorrow">huomenna</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="pause_notifications_ends">Päättyy %s</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="pause_notifications_banner">Ilmoitukset jatkuvat %s.</string>
<string name="resume_notifications_now">Jatka nyt</string>
<string name="open_system_notification_settings">Siirry ilmoitusasetuksiin</string>
<string name="about_server">Tietoja</string>
<string name="server_rules">Säännöt</string>
<string name="server_administrator">Ylläpitäjä</string>
<string name="send_email_to_server_admin">Viestin ylläpitäjä</string>
<string name="notifications_disabled_in_system">Ota ilmoitukset käyttöön laitteesi asetuksista nähdäksesi päivityksiä mistä tahansa.</string>
<string name="settings_even_more">Vielä enemmän asetuksia</string>
<string name="settings_show_cws">Näytä sisältövaroitukset</string>
<string name="settings_hide_sensitive_media">Piilota arkaluontoiseksi merkitty media</string>
<string name="settings_show_interaction_counts">Näytä reaktiolaskurit</string>
<string name="settings_show_emoji_in_names">Mukautetut emojit näyttönimissä</string>
<plurals name="in_x_seconds">
<item quantity="one">%d sekunnin kuluttua</item>
<item quantity="other">%d sekunnin kuluttua</item>
</plurals>
<plurals name="in_x_minutes">
<item quantity="one">%d minuutin kuluttua</item>
<item quantity="other">%d minuutin kuluttua</item>
</plurals>
<plurals name="in_x_hours">
<item quantity="one">%d tunnin kuluttua</item>
<item quantity="other">%d tunnin kuluttua</item>
</plurals>
<plurals name="x_hours_ago">
<item quantity="one">%d tunti sitten</item>
<item quantity="other">%d tuntia sitten</item>
</plurals>
<string name="alt_text_reminder_title">Mediasta puuttuu selitysteksti</string>
<plurals name="alt_text_reminder_x_images">
<item quantity="one">%s kuvastasi puuttuu selitysteksti. Julkaistaanko silti?</item>
<item quantity="other">%s kuvastasi puuttuu selitysteksti. Julkaistaanko silti?</item>
</plurals>
<plurals name="alt_text_reminder_x_attachments">
<item quantity="one">%s mediatiedostostasi puuttuu selitysteksti. Julkaistaanko silti?</item>
<item quantity="other">%s mediatiedostostasi puuttuu selitysteksti. Julkaistaanko silti?</item>
</plurals>
<string name="count_one">Yksi</string>
<string name="count_two">Kaksi</string>
<string name="count_three">Kolme</string>
<string name="count_four">Neljä</string>
<string name="alt_text_reminder_post_anyway">Julkaise</string>
<!-- %s is the username -->
<string name="unfollow_confirmation">Lopeta käyttäjän %s seuraaminen?</string>
<string name="filter_active">Aktiivinen</string>
<string name="filter_inactive">Ei käytössä</string>
<string name="settings_add_filter">Lisää suodatin</string>
<string name="settings_edit_filter">Muokkaa suodatinta</string>
<string name="settings_filter_duration">Kesto</string>
<string name="settings_filter_muted_words">Mykistetyt sanat</string>
<string name="settings_filter_context">Mykistä alkaen</string>
<string name="settings_filter_show_cw">Näytä sisältövaroituksella</string>
<string name="settings_filter_show_cw_explanation">Näytä vielä viestejä, jotka täsmäävät tähän suodattimeen, mutta sisällönvaroituksen takana</string>
<string name="settings_delete_filter">Poista suodatin</string>
<string name="filter_duration_forever">Ikuisesti</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="settings_filter_ends">Päättyy %s</string>
<plurals name="settings_x_muted_words">
<item quantity="one">%d mykistetty sana tai lause</item>
<item quantity="other">%d mykistettyä sanaa tai lauseita</item>
</plurals>
<string name="selection_2_options">%1$s ja %2$s</string>
<string name="selection_3_options">%1$s, %2$s ja %3$s</string>
<string name="selection_4_or_more">%1$s, %2$s, ja %3$d lisää</string>
<string name="filter_context_home_lists">Koti &amp; listat</string>
<string name="filter_context_notifications">Ilmoitukset</string>
<string name="filter_context_public_timelines">Julkiset aikajanat</string>
<string name="filter_context_threads_replies">Langat &amp; vastaukset</string>
<string name="filter_context_profiles">Profiilit</string>
<string name="settings_filter_title">Otsikko</string>
<string name="settings_delete_filter_title">Poistetaanko suodatin “%s”?</string>
<string name="settings_delete_filter_confirmation">Tämä suodatin poistetaan tililtäsi kaikissa laitteissa.</string>
<string name="add_muted_word">Lisää mykistetty sana</string>
<string name="edit_muted_word">Muokkaa mykistettyä sanaa</string>
<string name="add">Lisää</string>
<string name="filter_word_or_phrase">Sana tai lause</string>
<string name="filter_add_word_help">Sanat ovat tapauskohtaisia ja vastaavat vain kokonaisia sanoja.\n\nJos suodattaa avainsana “Apple”, se piilottaa viestit sisältävät “omena” tai “aPPLe” mutta ei “ananas\".</string>
<string name="settings_delete_filter_word">Poista sana “%s”?</string>
<string name="enter_selection_mode">Valitse</string>
<string name="select_all">Valitse kaikki</string>
<string name="settings_filter_duration_title">Suodattimen kesto</string>
<string name="filter_duration_custom">Mukautettu</string>
<plurals name="settings_delete_x_filter_words">
<item quantity="one">Poista %d sana?</item>
<item quantity="other">Poista %d sanaa?</item>
</plurals>
<plurals name="x_items_selected">
<item quantity="one">%d valittu</item>
<item quantity="other">%d valittu</item>
</plurals>
<string name="required_form_field_blank">Tätä ei voi jättää tyhjäksi</string>
<string name="filter_word_already_in_list">Jo luettelossa</string>
<string name="app_update_ready">Sovelluksen päivitys valmis</string>
<string name="app_update_version">Versio %s</string>
<string name="downloading_update">Ladataan (%d%%)</string>
<!-- Shown like a content warning, %s is the name of the filter -->
<string name="post_matches_filter_x">Sopii suodattimeen ”%s”</string>
<string name="search_mastodon">Etsi Mastodonista</string>
<string name="clear_all">Tyhjennä kaikki</string>
<string name="search_open_url">Avaa URL-osoite Mastodonissa</string>
<string name="posts_matching_hashtag">Julkaisut joissa on \"%s\"</string>
<string name="search_go_to_account">Siirry tiliin %s</string>
<string name="posts_matching_string">Julkaisut joissa on \"%s\"</string>
<string name="accounts_matching_string">Henkilöt jossa on \"%s\"</string>
<!-- Shown in the post header. Please keep it short -->
<string name="time_seconds_ago_short">%ds sitten</string>
<string name="time_minutes_ago_short">%dm sitten</string>
<string name="time_hours_ago_short">%dh sitten</string>
<string name="time_days_ago_short">%dd sitten</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Käännetty kielestä %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Käännetty kielestä %1$s käyttäen %2$s</string>
<string name="translation_show_original">Näytä alkuperäinen</string>
<string name="translation_failed">Käännös epäonnistui. Ehkä järjestelmänvalvoja ei ole ottanut käyttöön käännöksiä tällä palvelimella tai tällä palvelimella on käynnissä vanhempi versio Mastodonista, jossa käännöksiä ei vielä tueta.</string>
<plurals name="x_participants">
<item quantity="one">%d osallistuja</item>
<item quantity="other">%d osallistujaa</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d viesti tänään</item>
<item quantity="other">%,d viestiä tänään</item>
</plurals>
</resources>

View File

@@ -368,7 +368,7 @@
<string name="sk_icon_clapper_board">Klaffitaulu</string>
<string name="sk_icon_pizza">Pitsa</string>
<string name="sk_icon_recycle_bin">Roskakori</string>
<string name="sk_edit_timelines">Muokkaa aikananoja</string>
<string name="sk_edit_timelines">Muokkaa aikajanoja</string>
<string name="sk_edit_timeline_tags_explanation">Ota huomioon, että palvelin tekee nämä operaatiot. Niiden yhdistelyä ei välttämättä tueta.</string>
<string name="sk_notify_update">Tämä muokkaa tehostettua viestiä</string>
<string name="sk_save_draft_message">Haluatko tallentaa muutokset tähän luonnokseen vai julkaista sen nyt\?</string>

View File

@@ -211,12 +211,12 @@
<item quantity="other">%,d tagasunod</item>
</plurals>
<plurals name="x_following">
<item quantity="one">%, d Sumusunod</item>
<item quantity="other">%, d Sumusunod</item>
<item quantity="one">%,d Sumusunod</item>
<item quantity="other">%,d Sumusunod</item>
</plurals>
<plurals name="x_favorites">
<item quantity="one">%, d Paborito</item>
<item quantity="other">%, d paborito</item>
<item quantity="one">%,d Paborito</item>
<item quantity="other">%,d paborito</item>
</plurals>
<string name="timestamp_via_app">%1$s sa pamamagitan ng %2$s</string>
<string name="time_now">ngayon</string>
@@ -284,4 +284,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -582,4 +582,18 @@
<string name="time_minutes_ago_short">il y a %dm</string>
<string name="time_hours_ago_short">Il y a %dh</string>
<string name="time_days_ago_short">Il y a %dj</string>
<!-- %s is the name of the post language -->
<string name="translate_post">Traduire depuis %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Traduit depuis %1$s via %2$s</string>
<string name="translation_show_original">Afficher loriginal</string>
<string name="translation_failed">La traduction a échoué. Peut-être que ladministrateur na pas activé les traductions sur ce serveur ou que ce serveur utilise une ancienne version de Mastodon où les traductions ne sont pas encore prises en charge.</string>
<plurals name="x_participants">
<item quantity="one">%,d participant</item>
<item quantity="other">%,d participants</item>
</plurals>
<plurals name="x_posts_today">
<item quantity="one">%,d message aujourdhui</item>
<item quantity="other">%,d messages aujourdhui</item>
</plurals>
</resources>

View File

@@ -362,7 +362,7 @@
<string name="sk_settings_display_pronouns_in_timelines">Afficher les pronoms dans les timelines</string>
<string name="sk_settings_show_labels_in_navigation_bar">Afficher les étiquettes des onglets dans la barre de navigation</string>
<string name="sk_settings_emoji_reactions">Activer les réactions emoji</string>
<string name="sk_settings_emoji_reactions_explanation">Affiche les réactions emoji aux messages et vous permet d\'interagir avec elles. Certaines versions modifiées de Mastodon le supportent, mais pas Mastodon.</string>
<string name="sk_settings_emoji_reactions_explanation">Affiche les réactions emoji aux messages et vous permet d\'ajouter les vôtres. Divers serveurs du Fediverse le prennent en charge, mais pas Mastodon.</string>
<string name="sk_settings_emoji_reactions_in_lists">Afficher les réactions emoji dans les timelines</string>
<string name="sk_settings_emoji_reactions_in_lists_explanation">Si les réactions emoji doivent être affichées sur les timelines. Si cette option est désactivée, les réactions emoji ne seront affichées que lors de l\'affichage d\'un fil de discussion.</string>
<plurals name="sk_users_reacted_with">
@@ -373,7 +373,7 @@
<string name="sk_button_react">Réagissez avec des emojis</string>
<string name="sk_again_for_system_keyboard">Appuyez à nouveau pour le clavier système</string>
<string name="sk_enter_emoji_hint">Tapez pour réagir avec un emoji</string>
<string name="sk_enter_emoji_toast">Vous devez saisir un emoji</string>
<string name="sk_enter_emoji_toast">Veuillez saisir un emoji</string>
<plurals name="sk_posts_count_label">
<item quantity="one">message</item>
<item quantity="many">messages</item>
@@ -393,4 +393,12 @@
<string name="sk_suicide_search_terms">Suicide</string>
<string name="sk_search_suicide_title">Dans le cas où vous seriez en détresse…</string>
<string name="sk_search_suicide_message">Si vous cherchez un signe pour ne pas vous suicider, le voici. Si vous êtes en détresse et/ou avez des pensées suicidaires, si vous voulez aider une personne en souffrance, vous pouvez contacter une ligne d\'assistance de prévention du suicide.</string>
<string name="sk_trending_links_info_banner">Ce sont les actualités dont on parle sur votre serveur.</string>
<string name="sk_load_missing_posts_above">Charger les messages les plus récents</string>
<string name="sk_load_missing_posts_below">Charger les anciens messages</string>
<string name="sk_time_seconds">%d secondes</string>
<string name="sk_time_minutes">%d minutes</string>
<string name="sk_time_hours">%d heures</string>
<string name="sk_time_days">%d jours</string>
<string name="sk_trending_posts_info_banner">Ce sont les publications qui gagnent du terrain sur votre serveur.</string>
</resources>

View File

@@ -20,4 +20,6 @@
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- Shown like a content warning, %s is the name of the filter -->
<!-- Shown in the post header. Please keep it short -->
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -640,4 +640,6 @@
<string name="time_minutes_ago_short">%dm air ais</string>
<string name="time_hours_ago_short">%du air ais</string>
<string name="time_days_ago_short">%dl air ais</string>
<!-- %s is the name of the post language -->
<!-- %1$s is the language, %2$s is the name of the translation service -->
</resources>

View File

@@ -311,4 +311,88 @@
<string name="sk_edit_timeline_tag_none">… ach gun gin sam bith dhen fheadhainn seo</string>
<string name="sk_edit_timeline_tag_hint">Cuir a-steach taga hais…</string>
<string name="sk_edit_timeline_tags_hint">Cuir a-steach tagaichean hais…</string>
<string name="sk_settings_allow_remote_loading_explanation">Feuch liostaichean nas pongaile air luchd-leantainn, annsachdan is brosnachaidhean fhaighinn le luchdadh an fhiosrachaidh on ionstans tùsail.</string>
<string name="sk_disable_pill_shaped_active_indicator">Cuir à comas taisbeanair an taba ghnìomhaich air a bheil cruth pile</string>
<string name="sk_tab_profile">Pròifil</string>
<string name="sk_time_seconds">%d diog(an)</string>
<string name="sk_mute_label">Faide</string>
<string name="sk_settings_show_emoji_reactions_only_opened">Nuair a bhios am post fosgailte a-mhàin</string>
<string name="sk_time_hours">%d uair(ean) a thìde</string>
<string name="sk_settings_allow_remote_loading">Luchdaich am fiosrachadh o ionstansan cèine</string>
<string name="sk_settings_auto_reveal_anyone">Freagairtean le duine sam bith</string>
<string name="sk_settings_content_types_explanation">Leigidh seo leat seòrsa susbainte ma Markdown a shuidheachadh nuair a chruthaicheas tu post. Thoir an aire nach cuir gach ionstans taic ri seo.</string>
<string name="sk_tab_home">Dachaigh</string>
<string name="sk_settings_show_emoji_reactions_hide_empty">Falaich freagairtean Emoji falamh</string>
<string name="sk_settings_show_emoji_reactions">Seall freagairtean Emoji air loidhnichean-ama</string>
<plurals name="sk_posts_count_label">
<item quantity="one">phost</item>
<item quantity="two">phost</item>
<item quantity="few">postaichean</item>
<item quantity="other">post</item>
</plurals>
<string name="sk_notification_mention">Thug %s iomradh ort</string>
<string name="sk_open_in_app">Fosgail san aplacaid</string>
<string name="sk_settings_emoji_reactions">Cuir an comas freagairtean Emoji</string>
<string name="sk_suicide_search_terms">Fèin-mhurt</string>
<string name="sk_duration_minutes_5">5 mionaidean</string>
<string name="sk_advanced_options_show">Seall na roghainnean adhartach</string>
<string name="sk_spoiler_show">Seall an t-susbaint</string>
<string name="sk_settings_prefix_replies_never">Chan ann idir</string>
<string name="sk_instance_info_unavailable">Chan eil fiosrachadh an ionstans ri fhaighinn an-dràsta fhèin</string>
<string name="sk_settings_default_content_type_explanation">Bheir seo ro-thaghadh air seòrsa na susbainte dhut nuair a chruthaicheas tu post ùr, a tar-àithneadh an luach a chaidh a shuidheachadh sna “Roghainnean postaidh”.</string>
<string name="sk_settings_auto_reveal_nobody">Chan ann idir</string>
<string name="sk_settings_instance">Ionstans</string>
<string name="sk_load_missing_posts_above">Luchdaich na postaichean nas ùire</string>
<string name="sk_settings_display_pronouns_in_timelines">Seall na riochdairean air loidhnichean-ama</string>
<string name="sk_switch_timeline">Geàrr leum gu loidhne-ama eile</string>
<string name="sk_time_days">%d là(ithean)</string>
<string name="sk_settings_show_labels_in_navigation_bar">Seall leubailean thabaichean air bàr na seòladaireachd</string>
<string name="sk_external_share_or_open_title">Co-roinn no fosgail le cunntas</string>
<string name="sk_tab_search">Lorg</string>
<string name="sk_settings_auto_reveal_equal_spoilers">Nochd susbaint ron aon rabhadh susbainte ann am freagairtean gu fèin-obrachail</string>
<string name="sk_settings_show_emoji_reactions_always">Seall am putan “Cuir ris” an-còmhnaidh</string>
<string name="sk_settings_forward_report_default">Bun-roghainn dha “Sìn air adhart an gearan”</string>
<string name="sk_duration_indefinite">Gun chrìoch</string>
<string name="sk_search_suicide_hotlines">Lorg loidhne taice</string>
<string name="sk_advanced_options_hide">Falaich na roghainnean adhartach</string>
<string name="sk_duration_hours_1">Uair a thìde</string>
<string name="sk_duration_hours_6">6 uairean a thìde</string>
<string name="sk_enter_emoji_hint">Sgrìobh rudeigin airson freagairt le Emoji</string>
<string name="sk_duration_days_7">Seachdain</string>
<string name="sk_settings_true_black">Modh dubh dorcha</string>
<string name="sk_no_remote_info_hint">chan eil am fiosrachadh cèin ri fhaighinn</string>
<string name="sk_settings_auto_reveal_author">Freagairtean leis an aon ùghdar</string>
<string name="sk_do_not_show_again">Na seall seo a-rithist</string>
<string name="sk_suicide_helplines_url">https://findahelpline.com</string>
<string name="sk_settings_display_pronouns_in_threads">Seall na riochdairean ann an snàithleanan</string>
<string name="sk_error_loading_profile">Dhfhàillig le luchdadh na pròifil slighe %s</string>
<string name="sk_enter_emoji_toast">Cuir a-steach Emoji</string>
<string name="sk_trending_posts_info_banner">Seo na postaichean fèillmhor air an fhrithealaiche agad.</string>
<string name="sk_duration_minutes_30">Leth-uair a thìde</string>
<string name="sk_settings_display_pronouns_in_user_listings">Seall na riochdairean air liostaichean luchd-cleachdadh</string>
<string name="sk_button_react">Freagair le emoji</string>
<string name="sk_duration_days_1">Latha</string>
<string name="sk_load_missing_posts_below">Luchdaich na postaichean nas sine</string>
<string name="sk_settings_emoji_reactions_explanation">Seallaidh seo freagairtean Emoji do phostaichean is leigidh e leat an fheadhainn agad fhèin a chur ris. Cuiridh grunn fhrithealaichean a cho-shaoghail taic ri seo ach cha chuir Mastodon.</string>
<string name="sk_external_share_title">Co-roinn le cunntas</string>
<string name="sk_list_exclusive_switch">Dèan an liosta às-dùnach</string>
<string name="sk_duration_days_3">3 làithean</string>
<string name="sk_settings_default_content_type">Seòrsa bunaiteach na susbainte</string>
<string name="sk_search_suicide_title">Ma bhios tu iomagain ort…</string>
<string name="sk_open_in_app_failed">Cha b urrainn dhuinn fhosgladh san aplacaid</string>
<string name="sk_time_minutes">%d mionaid(ean)</string>
<string name="sk_exclusive_list">Liosta às-dùnach</string>
<plurals name="sk_users_reacted_with">
<item quantity="one">Fhreagair %1$,d neach-cleachdaidh le %2$s</item>
<item quantity="two">Fhreagair %1$,d luchd-cleachdaidh le %2$s</item>
<item quantity="few">Fhreagair %1$,d luchd-cleachdaidh le %2$s</item>
<item quantity="other">Fhreagair %1$,d luchd-cleachdaidh le %2$s</item>
</plurals>
<string name="sk_search_suicide_message">Ma tha thu a sireadh samhla nach cuir thu às dhiot fhèin, seo e. Saoil am bruidhinn thu ri loidhne theth ionadail mu fhèin-mhurt ma tha iomagain ort\?</string>
<string name="sk_trending_links_info_banner">Seo na sgeulachdan naidheachd air a bheilear a bruidhinn air an fhrithealaiche agad.</string>
<string name="sk_search_fediverse">Lorg air a cho-shaoghal</string>
<string name="sk_post_contains_media">Tha meadhan sa phost</string>
<string name="sk_tab_notifications">Brathan</string>
<string name="sk_list_exclusive_switch_explanation">Cha nochd buill liosta às-dùnaich air loidhne-ama na dachaighe agad ma chuireas an t-ionstans agad taic ri seo.</string>
<string name="sk_pronouns_label">Riochdairean</string>
</resources>

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