Compare commits

..

276 Commits

Author SHA1 Message Date
LucasGGamerM
f1f04375f6 Merge pull request #627 from rcarrillodev/master
Fix a NullPointerException in NotificationsListFragment.java
2025-05-17 07:49:10 -03:00
Rafael Carrillo
b56a922c65 Fix a NullPointerException in NotificationsListFragment.java
- Some notifications will have the status attribute set as null and the if in 128 is trying to access to attributes from a null object, causing a NPE as soon the app is opened.
2025-05-16 18:12:35 -07:00
LucasGGamerM
732a0f9b4c fix: check if status.account is null before accessing it's id
Fixes #625
2025-05-16 05:30:01 -03:00
LucasGGamerM
889762f667 docs: add CSAE-POLICY.md link to readme 2025-05-15 12:48:08 -03:00
LucasGGamerM
baf9536145 docs: make CSAE-POLICY.md 2025-05-15 12:45:51 -03:00
LucasGGamerM
b8b8287f8f docs: add relevant images for CASE-POLICY.md 2025-05-15 12:40:43 -03:00
LucasGGamerM
fb6b9a9bed build: bump version number 2025-05-15 09:04:15 -03:00
LucasGGamerM
bf288f82f4 build(proguard): dont warn when missing android.app.BroadcastOptions
I still don't know how proguard works
2025-05-15 09:03:45 -03:00
LucasGGamerM
41a7ca5487 build(f-droid): turn vcsInfo back on 2025-05-15 09:03:45 -03:00
LucasGGamerM
78219038bd docs: add v109 changelog 2025-05-15 09:03:45 -03:00
Andrewblasco
6671253b05 Translated using Weblate (Spanish)
Currently translated at 100.0% (127 of 127 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2025-05-15 11:36:08 +00:00
SomeTr
18a827708c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/uk/
2025-05-15 11:36:08 +00:00
SomeTr
b2b0182e04 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (127 of 127 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2025-05-15 11:36:08 +00:00
SomeTr
499d425e68 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (127 of 127 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2025-05-15 11:36:08 +00:00
hasan
026f32b03c Translated using Weblate (Turkish)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/tr/
2025-05-15 11:36:08 +00:00
aindriu80
6b4f114e06 Translated using Weblate (Irish)
Currently translated at 100.0% (127 of 127 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ga/
2025-05-15 11:36:08 +00:00
Linerly
7b64d49084 Translated using Weblate (Indonesian)
Currently translated at 100.0% (127 of 127 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/id/
2025-05-15 11:36:08 +00:00
ghose
ad1bc22c6a Translated using Weblate (Galician)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/gl/
2025-05-15 11:36:08 +00:00
ghose
8f571e7d74 Translated using Weblate (Galician)
Currently translated at 100.0% (127 of 127 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/gl/
2025-05-15 11:36:08 +00:00
Outbreak2096
eb10544960 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (127 of 127 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2025-05-15 11:36:08 +00:00
SomeTr
7190b61b94 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (127 of 127 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2025-05-15 11:36:08 +00:00
hasan
8f9f58a0e9 Translated using Weblate (Turkish)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/tr/
2025-05-15 11:36:08 +00:00
hasan
f29598374c Translated using Weblate (Turkish)
Currently translated at 100.0% (41 of 41 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/tr/
2025-05-15 11:36:08 +00:00
hasan
c7f6975d4f Translated using Weblate (Turkish)
Currently translated at 100.0% (124 of 124 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/tr/
2025-05-15 11:36:08 +00:00
hasan
750456820a Translated using Weblate (Turkish)
Currently translated at 64.7% (272 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/tr/
2025-05-15 11:36:08 +00:00
hasan
42f29291b4 Translated using Weblate (Turkish)
Currently translated at 19.5% (82 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/tr/
2025-05-15 11:36:08 +00:00
greyluked
9b7a64d5aa Translated using Weblate (Catalan)
Currently translated at 95.4% (401 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/ca/
2025-05-15 11:36:08 +00:00
greyluked
3d0392db70 Translated using Weblate (Spanish)
Currently translated at 100.0% (41 of 41 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2025-05-15 11:36:08 +00:00
greyluked
08f559e70a Translated using Weblate (Spanish)
Currently translated at 100.0% (124 of 124 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2025-05-15 11:36:08 +00:00
hasan
3d9379a4c0 Translated using Weblate (Turkish)
Currently translated at 5.7% (24 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/tr/
2025-05-15 11:36:08 +00:00
hasan
0da5188900 Translated using Weblate (Turkish)
Currently translated at 14.6% (6 of 41 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/tr/
2025-05-15 11:36:08 +00:00
hasan
3932b9dc11 Translated using Weblate (Turkish)
Currently translated at 4.8% (2 of 41 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/tr/
2025-05-15 11:36:08 +00:00
liilliil
6776f1fb55 Translated using Weblate (Russian)
Currently translated at 92.7% (115 of 124 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ru/
2025-05-15 11:36:07 +00:00
openhuman
f25d731b42 Translated using Weblate (German)
Currently translated at 100.0% (124 of 124 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/de/
2025-05-15 11:36:07 +00:00
Outbreak2096
6dde25009d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (41 of 41 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hans/
2025-05-15 11:36:07 +00:00
Outbreak2096
087c5c1993 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (124 of 124 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2025-05-15 11:36:07 +00:00
ghose
6f1a3e02b0 Translated using Weblate (Galician)
Currently translated at 100.0% (124 of 124 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/gl/
2025-05-15 11:36:07 +00:00
SomeTr
9e7f3a3b2c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (41 of 41 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/uk/
2025-05-15 11:36:07 +00:00
Linerly
c678a37f95 Translated using Weblate (Indonesian)
Currently translated at 100.0% (41 of 41 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/id/
2025-05-15 11:36:07 +00:00
SomeTr
eb451736e9 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (124 of 124 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2025-05-15 11:36:07 +00:00
Linerly
77687cfb4e Translated using Weblate (Indonesian)
Currently translated at 100.0% (124 of 124 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/id/
2025-05-15 11:36:07 +00:00
LucasGGamerM
bdec03514a style(status-display-item): move bracket around to make it more neat
It's in the check for the account parameter existing
2025-05-15 08:34:40 -03:00
LucasGGamerM
2f16e06750 fix(scheduled-posts): only throw account missing error if scheduledStatus is null
This fixes #623
2025-05-14 18:54:47 -03:00
LucasGGamerM
ab2256f90c fix(blocklist): remove example entry from blocks.txt 2025-05-11 16:58:53 -03:00
LucasGGamerM
abe40c08db feat(blocklist): update blocklist to latest FediNuke 2025-05-11 16:16:50 -03:00
LucasGGamerM
2a7e8bac2d refactor(status-emojis-field): make it not a required field. This is for glitch-soc
The glitch-soc people are doing weird stuff, and there is probably an easy way to fix it all... but I am not sure I know it yet
2025-05-08 14:06:32 -03:00
LucasGGamerM
a8da796956 refactor(status-account-field): make it not a required field, but do throw error if it's null
This is for debugging purposes on newer glitch-soc versions. This should be reverted before a release.
2025-05-08 11:29:07 -03:00
LucasGGamerM
0de346c1bc fix(compose-auto-complete): check if the animator is null before doing animations
Fixes #591 #564 #517 #434 #328
2025-04-30 11:46:22 -03:00
LucasGGamerM
8598dc2608 fix(UnifiedPushHelper.java): check if vapidkey parameter is null before proceeding
This will only happen on very unlikely circumstances, but it happened to me, so it's fixed now
2025-04-19 21:47:38 -03:00
LucasGGamerM
ab72435347 Merge pull request #516
Iceshrimp improvements
2025-03-19 10:58:36 -03:00
LucasGGamerM
d5f6852bdc Merge: feat(Timeline/Hashtag): show confirmation sheet when muting 2025-03-17 22:27:07 -03:00
LucasGGamerM
36f96c1ed6 misc(github-actions): create mirror-to-codeberg.yml 2025-03-15 09:05:34 -03:00
LucasGGamerM
058fb62253 fix(unified-push-connector): remove try catches that are no longer needed 2025-03-13 08:26:31 -03:00
LucasGGamerM
bc43e2180f build(unified-push-connector): update the dependency 2025-03-13 08:25:56 -03:00
LucasGGamerM
c1e96d4ff0 fix(enhance-text-size): just enhance the font size, not the whole app's density 2025-03-12 10:40:50 -03:00
LucasGGamerM
42a060e8a6 build(unified-push): update unifiedPush connector dependency 2025-03-11 19:22:08 -03:00
LucasGGamerM
065db923cc fix(unifiedPushHelper): surround by try and catch due to a javax.crypto.AEADBadTagException
cc: @p1gp1g
2025-03-11 14:52:07 -03:00
openhuman
0089a10a81 Translated using Weblate (German)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/de/
2025-03-11 10:21:45 -03:00
notAvi10
c6d4436467 Translated using Weblate (Esperanto)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/eo/
2025-03-11 10:21:45 -03:00
notAvi10
98d58c35dc Translated using Weblate (Esperanto)
Currently translated at 50.0% (20 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/eo/
2025-03-11 10:21:45 -03:00
guille11
3f7c0417c2 Translated using Weblate (Asturian)
Currently translated at 74.5% (313 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/ast/
2025-03-11 10:21:45 -03:00
Ricky-Tigg
64dcb8b387 Translated using Weblate (Finnish)
Currently translated at 5.0% (2 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/fi/
2025-03-11 10:21:45 -03:00
Ricky-Tigg
4145b6451b Translated using Weblate (Finnish)
Currently translated at 9.8% (12 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/fi/
2025-03-11 10:21:45 -03:00
Linerly
188b3fade7 Translated using Weblate (Indonesian)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/id/
2025-03-11 10:21:45 -03:00
dieguitux8623
87800a696a Translated using Weblate (Italian)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/it/
2025-03-11 10:21:45 -03:00
dieguitux8623
f51891f64c Translated using Weblate (Italian)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/it/
2025-03-11 10:21:45 -03:00
guille11
45c822c48d Translated using Weblate (Asturian)
Currently translated at 68.8% (84 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ast/
2025-03-11 10:21:45 -03:00
Eryk Michalak
a1eae5a1a1 Translated using Weblate (Polish)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/pl/
2025-03-11 10:21:45 -03:00
Eryk Michalak
73926e0ac1 Translated using Weblate (Polish)
Currently translated at 99.1% (121 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2025-03-11 10:21:45 -03:00
brli
00333604c9 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 15.0% (6 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hant/
2025-03-11 10:21:45 -03:00
brli
8aa9e99e91 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 12.5% (5 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hant/
2025-03-11 10:21:45 -03:00
Vaclovas Intas
5159e8fbda Translated using Weblate (Lithuanian)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/lt/
2025-03-11 10:21:45 -03:00
GunChleoc
6cf3253d40 Translated using Weblate (Gaelic)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/gd/
2025-03-11 10:21:45 -03:00
GunChleoc
b769bf5ee4 Translated using Weblate (Gaelic)
Currently translated at 99.1% (121 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/gd/
2025-03-11 10:21:45 -03:00
Vaclovas Intas
7adb3c7b39 Translated using Weblate (Lithuanian)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/lt/
2025-03-11 10:21:45 -03:00
Metanoir
4f33194884 Translated using Weblate (Turkish)
Currently translated at 0.7% (3 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/tr/
2025-03-11 10:21:45 -03:00
Metanoir
bfaa6e12a7 Translated using Weblate (Turkish)
Currently translated at 2.5% (1 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/tr/
2025-03-11 10:21:45 -03:00
guille11
e48143585e Translated using Weblate (Asturian)
Currently translated at 57.3% (70 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ast/
2025-03-11 10:21:45 -03:00
guille11
3f60beb999 Translated using Weblate (Asturian)
Currently translated at 0.0% (0 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/ast/
2025-03-11 10:21:45 -03:00
guille11
a952a17a18 Translated using Weblate (Asturian)
Currently translated at 74.5% (313 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/ast/
2025-03-11 10:21:45 -03:00
Languages add-on
ae966cc784 Added translation using Weblate (Asturian) 2025-03-11 10:21:45 -03:00
guille11
5b684bd9b3 Added translation using Weblate (Asturian) 2025-03-11 10:21:45 -03:00
aindriu80
251a518e56 Translated using Weblate (Irish)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/ga/
2025-03-11 10:21:45 -03:00
aindriu80
0156f8a732 Translated using Weblate (Irish)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ga/
2025-03-11 10:21:45 -03:00
aindriu80
20464001b8 Translated using Weblate (Irish)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/ga/
2025-03-11 10:21:45 -03:00
irure
2d4f3b9a88 Translated using Weblate (Basque)
Currently translated at 76.4% (321 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/eu/
2025-03-11 10:21:45 -03:00
SomeTr
66afd9d091 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2025-03-11 10:21:45 -03:00
dampuzakura
69562fa3e4 Translated using Weblate (Japanese)
Currently translated at 99.2% (417 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/ja/
2025-03-11 10:21:45 -03:00
dampuzakura
9e2839c0ae Translated using Weblate (Japanese)
Currently translated at 20.0% (8 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/ja/
2025-03-11 10:21:45 -03:00
dampuzakura
f1b9f110d2 Translated using Weblate (Japanese)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ja/
2025-03-11 10:21:45 -03:00
Linerly
94cb110f99 Translated using Weblate (Indonesian)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/id/
2025-03-11 10:21:45 -03:00
Vaclovas Intas
07a29564d5 Translated using Weblate (Lithuanian)
Currently translated at 47.5% (19 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/lt/
2025-03-11 10:21:45 -03:00
yeyueww
015d416773 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 91.9% (386 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/zh_Hant/
2025-03-11 10:21:45 -03:00
kdh8219
2fea6a2934 Translated using Weblate (Korean)
Currently translated at 95.7% (402 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/ko/
2025-03-11 10:21:45 -03:00
yeyueww
3af59de797 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 7.5% (3 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hant/
2025-03-11 10:21:45 -03:00
yeyueww
543f7ab30a Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 7.3% (9 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hant/
2025-03-11 10:21:45 -03:00
Vaclovas Intas
dacc32dcaa Translated using Weblate (Lithuanian)
Currently translated at 47.5% (19 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/lt/
2025-03-11 10:21:45 -03:00
Vaclovas Intas
45568f600a Translated using Weblate (Lithuanian)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/lt/
2025-03-11 10:21:45 -03:00
butterflyoffire
df96e6af31 Translated using Weblate (French)
Currently translated at 99.0% (416 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/fr/
2025-03-11 10:21:45 -03:00
butterflyoffire
c7b58a0982 Translated using Weblate (Arabic (Algeria))
Currently translated at 43.5% (183 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/ar_DZ/
2025-03-11 10:21:45 -03:00
butterflyoffire
463933b19a Translated using Weblate (Arabic (Algeria))
Currently translated at 90.9% (111 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ar_DZ/
2025-03-11 10:21:45 -03:00
butterflyoffire
53f3a42588 Translated using Weblate (French)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/fr/
2025-03-11 10:21:45 -03:00
Vaclovas Intas
9338fbc246 Translated using Weblate (Lithuanian)
Currently translated at 47.5% (19 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/lt/
2025-03-11 10:21:45 -03:00
lizarion
54d074839c Translated using Weblate (Basque)
Currently translated at 74.5% (313 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/eu/
2025-03-11 10:21:45 -03:00
butterflyoffire
0424dcd6ca Translated using Weblate (French)
Currently translated at 97.1% (408 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/fr/
2025-03-11 10:21:45 -03:00
Vaclovas Intas
dedd1a7b70 Translated using Weblate (Lithuanian)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/lt/
2025-03-11 10:21:45 -03:00
Vaclovas Intas
7684b6705c Translated using Weblate (Lithuanian)
Currently translated at 45.0% (18 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/lt/
2025-03-11 10:21:45 -03:00
gallegonovato
86d01cbb97 Translated using Weblate (Spanish)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2025-03-11 10:21:45 -03:00
gallegonovato
3ca18ed38b Translated using Weblate (Spanish)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2025-03-11 10:21:45 -03:00
Lefteris T
5a5c2fbc69 Translated using Weblate (Greek)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/el/
2025-03-11 10:21:45 -03:00
SomeTr
4bbeb4f198 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/uk/
2025-03-11 10:21:45 -03:00
SomeTr
2133ca7188 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/uk/
2025-03-11 10:21:45 -03:00
SomeTr
33a71e1c46 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/uk/
2025-03-11 10:21:45 -03:00
ghose
0cba8f30a6 Translated using Weblate (Galician)
Currently translated at 55.0% (22 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/gl/
2025-03-11 10:21:45 -03:00
SomeTr
0241628cf5 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2025-03-11 10:21:45 -03:00
alextecplayz
a4bac9c100 Translated using Weblate (Romanian)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/ro/
2025-03-11 10:21:45 -03:00
ghose
915bd41b71 Translated using Weblate (Galician)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/gl/
2025-03-11 10:21:45 -03:00
nethad
32c3b81ec3 Translated using Weblate (German)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/de/
2025-03-11 10:21:44 -03:00
nethad
fad20394ff Translated using Weblate (German)
Currently translated at 95.0% (38 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/de/
2025-03-11 10:21:44 -03:00
Outbreak2096
fba279f43e Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hans/
2025-03-11 10:21:44 -03:00
Outbreak2096
a8fd78b1a8 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2025-03-11 10:21:44 -03:00
SomeTr
19390221ec Translated using Weblate (Ukrainian)
Currently translated at 100.0% (122 of 122 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2025-03-11 10:21:44 -03:00
Vaclovas Intas
ab47192b2c Translated using Weblate (Lithuanian)
Currently translated at 38.4% (15 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/lt/
2025-03-11 10:21:44 -03:00
Outbreak2096
e9c01a5452 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/zh_Hans/
2025-03-11 10:21:44 -03:00
Vaclovas Intas
bc45072542 Translated using Weblate (Lithuanian)
Currently translated at 33.3% (13 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/lt/
2025-03-11 10:21:44 -03:00
Andrewblasco
e24bfb0448 Translated using Weblate (Spanish)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/es/
2025-03-11 10:21:44 -03:00
nethad
673ada4782 Translated using Weblate (German)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/de/
2025-03-11 10:21:44 -03:00
Andrewblasco
ee02634036 Translated using Weblate (Spanish)
Currently translated at 100.0% (39 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2025-03-11 10:21:44 -03:00
Andrewblasco
1c42b9a4e7 Translated using Weblate (Spanish)
Currently translated at 100.0% (121 of 121 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2025-03-11 10:21:44 -03:00
eana
3bd855ed1e Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/zh_Hans/
2025-03-11 10:21:44 -03:00
eana
fba7650918 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (39 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/zh_Hans/
2025-03-11 10:21:44 -03:00
poesty
c3b2e9fdc2 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (121 of 121 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2025-03-11 10:21:44 -03:00
eana
b957f3500b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (121 of 121 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2025-03-11 10:21:44 -03:00
eana
51bf3416bf Translated using Weblate (Chinese (Simplified))
Currently translated at 97.5% (118 of 121 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2025-03-11 10:21:44 -03:00
LucasGGamerM
8bdbde0ea6 Merge pull request #521 from FineFindus/feat/block-domain
Feat/block domain
2025-03-11 10:17:51 -03:00
LucasGGamerM
c50aecdd05 Merge pull request #549 from FineFindus/feat/reblog-mention
feat(Compose): exclude own account from reblog mentions
2025-03-11 10:17:10 -03:00
LucasGGamerM
f4ffd4718e Merge pull request #589 from p1gp1g/unifiedpush/push_settings
Unifiedpush/push settings
2025-03-11 08:54:31 -03:00
LucasGGamerM
6d83453f96 fix(display-settings): move enhanced text size option to correct place 2025-03-10 13:15:31 -03:00
LucasGGamerM
69dbf38e1e fix(compose): hide bottom char counter when not in use 2025-03-10 12:38:57 -03:00
LucasGGamerM
a03297313a feat(accessibility): add display setting to make app's text size bigger 2025-03-10 12:10:33 -03:00
LucasGGamerM
b52f1c156d fix(unofficial-quote-toots): ignore youtube links
Youtube links look a lot like fediverse links, so we added a special rule for it, just in case. This may mitigate the too many requests problems we are facing.
2025-03-09 19:38:18 -03:00
LucasGGamerM
28097554a7 fix(animated-avatars): restart app when this setting is changed
This setting needs the app to be reset for it to apply. This commits does that.
2025-03-07 16:45:49 -03:00
LucasGGamerM
5984783831 Merge pull request #608 from TRMSC/patch-1
Add reply_to_user string
2025-03-05 16:56:30 -03:00
TRMSC
5299cda1ad Add reply_to_user string 2025-03-04 20:10:48 +01:00
LucasGGamerM
65c8906b2a build(github-actions): update upload-artifact dependency on nightly-builds.yml 2025-03-01 18:47:09 -03:00
LucasGGamerM
86e7e7cdc6 Merge pull request #606 from blacklight/346-fix-akkoma-translations
[#346] Fix Akkoma translations.
2025-03-01 18:45:03 -03:00
Fabio Manganiello
fe7f9f14c3 [#346] Fix Akkoma translations.
Not sure if something has changed in the Akkoma API since the latest
version of `AkkomaTranslateStatus`, but it seems that the API expects
the target language to be _lowercase_ (not _uppercase_).

Libretranslate nginx traces from my instance with the current
implementation show a 400 with the current implementation:

```
1.2.3.4 - - [11/Feb/2025:11:40:34 +0100] "POST /translate HTTP/1.1" 400 32 "-" "Akkoma 3.13.2-0-g050bc74; https://myinstance.social <fabio@myinstance.social>"
```

And that's also mirrored on my Akkoma logs:

```
1.2.3.4 - - [11/Feb/2025:11:40:35 +0100] "GET /api/v1/statuses/Ar0PZaoyJwN2GD3UJs/translations/EN HTTP/2.0" 400 54 "-" "MoshidonAndroid/2.3.0+fork.108.moshinda"
```

A direct API call to the endpoint above indeed returns a 400:

```
{"error":"libre_translate: request failed (code 400)"}
```

But everything works when `en` is used instead of `EN`:

```
{"text":"<p>Help me! </p><p>**As a large organization you go to Mastodon because: **<br/>- You want to set a standard<br/>- Guarantee your free access to your posts<br/>- as an anti-reaction against big tech and its associated:<br/> - data collections<br/> - earning models<br/> - algorithms that spread her<br/>- ... </p><p>**You run your own server because: **<br/>- your owner wants to remain over your dates<br/>- you can arrange your own accounts for your employees<br/>- ...</p><p>What arguments would you have for switching to Mastodon and own server?</p>","detected_language":"nl"}
```
2025-02-11 11:43:37 +01:00
LucasGGamerM
1bbfc45bd0 fix(publish-button): update both button states
This is bullshit, I will fix this tomfoolery with the 2 buttons in the rewrite
2025-01-29 13:14:41 -03:00
LucasGGamerM
0b5588515e fix(pixelfed): allow publishing comments without images on pixelfed 2025-01-29 13:10:52 -03:00
LucasGGamerM
2cdc649b7d fix(pleroma-login): use longs for min and max poll expiration values 2025-01-26 15:22:23 -03:00
sim
4e1f7839b3 Allow configuring push notifications with UnifiedPush 2025-01-21 18:00:14 +01:00
sim
281e989749 Use server VAPID keys 2025-01-21 17:59:52 +01:00
sim
7cd756f6b0 Re-register UnifiedPush when application starts 2025-01-14 17:06:19 +01:00
sim
cc4558458c Add Helper for some UnifiedPush functions 2025-01-14 17:06:19 +01:00
sim
66824aadb9 Use standard webpush for UnifiedPush only 2025-01-14 16:13:31 +01:00
sim
00e90e5f21 Use standard webpush when available 2025-01-14 16:04:27 +01:00
sim
376653cb3f Update UnifiedPush lib 2025-01-14 16:00:54 +01:00
LucasGGamerM
082de410e0 build: disable vcsInfo for fdroid builds 2025-01-07 12:12:16 -03:00
LucasGGamerM
899f48b40c build: disable DependencyInfoBlock 2025-01-07 11:55:05 -03:00
LucasGGamerM
dae5989d64 docs: add changelog 2025-01-05 19:34:29 -03:00
LucasGGamerM
1d1c4f2666 build: bump version number 2025-01-05 19:33:11 -03:00
LucasGGamerM
0fecdf345a Merge pull request #570
Upgrade build dependencies for the project
2025-01-04 15:15:24 -03:00
Jacoco
ed5c58b4ea Fix wrong padding under reactions 2024-12-22 12:08:32 +01:00
Jacoco
cbab92ed87 Prevent double long press for reaction buttons 2024-12-21 17:34:28 +01:00
Aurimas
82bcfe3fa8 Update gradle.properties
Co-authored-by: Zongle Wang <wangzongler@gmail.com>
2024-11-17 18:46:51 -08:00
Aurimas
203c43343a Update build.gradle
Co-authored-by: Zongle Wang <wangzongler@gmail.com>
2024-11-17 18:46:43 -08:00
Aurimas Liutikas
4c105acc30 Upgrade build dependencies for the project
- Upgrade to Gradle 8.11
- Upgrade to Android Gradle Plugin 8.7.2
- Remove deprecated android.defaults.buildfeatures.buildconfig=true
  gradle property, it is not needed as mastodon/build.gradle already
  sets android { buildFeatures { buildConfig true } }
- Move plugin repository definition to settings.gradle to match latest
  Gradle practices
- Move to using plugin {} mechanism to add Android Gradle Plugin to
  match the latest Gradle practices
- Remove root project clean task as this project does not produce any
  real artifacts, it seems to be leftover from original Android new
  project template
2024-11-15 17:21:37 -08:00
FineFindus
bd6f739842 feat(Compose): exclude own account from reblog mentions
Excludes the users account from being automatically included when
repplying to a status that has been bosted by the user.

Fixes https://github.com/LucasGGamerM/moshidon/issues/548
2024-09-23 18:44:40 +02:00
Jacocococo
e336f15cb0 Don't hide filter settings on Iceshrimp.NET 2024-09-14 21:28:42 +02:00
Jacocococo
a554059cce Change isAkkoma to be based on version code
Iceshrimp.NET now also implements some APIs using the pleroma field, so
only checking for that would create false positives for Iceshrimp.NET
2024-09-14 21:21:41 +02:00
LucasGGamerM
d81eb6ad0a Merge pull request #538 from FineFindus/fix/hashtag-timeline-open-crash
fix(Timeline/Hashtag): check if hashtag is null
2024-09-09 09:03:54 -03:00
FineFindus
08542cd16f fix(Timeline/Hashtag): check if hashtag is null
Fixes a crash when clicking on hashtags in profiles, since the hashtag
is for some reason null.
2024-09-09 12:03:10 +02:00
LucasGGamerM
f30e12f5c6 Merge pull request #526 from FineFindus/fix/empty-hashtag
feat(Error): disable clicking + crash fix
2024-09-02 09:39:41 -03:00
LucasGGamerM
3a14fb5912 Merge pull request #529 from FineFindus/fix/hashtag-timeline-follow-icon
fix(HashtagTimeline): update follow menu icon
2024-09-02 08:55:15 -03:00
FineFindus
cc64a1b6a2 fix(HashtagTimeline): update follow menu icon
Fixes an issue, where the menu follow icon was not correctly updated,
and would always show the follow state.
2024-08-31 17:49:04 +02:00
FineFindus
7fa079e362 fix(HtmlParser): check if hashtag has text
Fixes a crash, where the text of an hashtag was empty, leading to an oob
string access.
2024-08-31 10:44:53 +02:00
FineFindus
c2e6280a18 feat(ErrorStatusDisplayItem): disable clicking on error item
Disable clicking on the ErrorStatusDisplayItem, since there is no valid
content that can be displayed.
2024-08-31 10:42:09 +02:00
LucasGGamerM
01225b05f2 Merge pull request #515 from collingsr/master
Updated README formatting & content
2024-08-29 20:25:16 -03:00
Jacocococo
f016b87ea0 Fix indent 2024-08-28 16:32:30 +02:00
Jacocococo
9426a9bc59 Code comments clarifying features being hidden on some servers 2024-08-27 22:21:47 +02:00
Jacocococo
f146067cda Code comments to clear up isIceshrimp and isIceshrimpJs 2024-08-27 22:16:59 +02:00
Jacocococo
f28e06d2f5 Code comments about hiding news discovery on Iceshrimp 2024-08-27 22:16:00 +02:00
Jacocococo
87cbffcb06 Better code style for hiding language button 2024-08-27 22:04:14 +02:00
FineFindus
7a103046b4 feat(sheets): add domain block sheet 2024-08-26 13:12:56 +02:00
FineFindus
64c53be990 fix(MuteAccountSheet): set selected mute time 2024-08-26 13:10:30 +02:00
FineFindus
e419c504e4 feat: use mute row
Partially 6c519b3cb9 to use the full row
again, but keep the selection in a dialog. This improves the UI, as the
secondary button is confusing, and feels out-of-place.
2024-08-26 13:10:30 +02:00
FineFindus
1f06e4e8f3 feat(Timeline/Hashtag): show snackbar when unmuting
Gives the user feedback in the form of a snackbar that the unmuting was
sucessful. This is already done for unmuting accounts.
2024-08-25 13:43:26 +02:00
FineFindus
b0f8cbb2e3 feat(Timeline/Hashtag): show confirmation sheet when muting
Shows a MuteHashtagConfirmationSheet when muting a hashtag, similar to
the MuteAccountConfirmationSheet. This also adds the option for the mute
to expire.
2024-08-25 13:35:32 +02:00
Ruth Collings
89f27984b7 Update FAQ.md
Co-authored-by: FineFindus <63370021+FineFindus@users.noreply.github.com>
2024-08-22 14:22:30 -04:00
Jacocococo
e3df5ce0a8 Properly set emoji reactions padding 2024-08-22 14:01:35 +02:00
Jacocococo
fcf7665ab5 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon into moshidon-iceshrimp-improvements 2024-08-22 13:32:48 +02:00
Jacocococo
407844378f Revert some changes specifically for Iceshrimp.NET 2024-08-21 22:56:33 +02:00
Ruth Collings
61b933655c clean up
removing stuff I dropped in there earlier
2024-08-21 14:22:13 -04:00
Ruth Collings
d47e1939d0 formatting 2024-08-21 14:18:42 -04:00
Jacocococo
c834199ee4 Hide filter settings on Iceshrimp 2024-08-19 22:42:13 +02:00
Ruth Collings
00b934dc69 spacing and lines
it was bothering me
2024-08-19 16:34:22 -04:00
Ruth Collings
c86ff1cce4 update
Reformatted header buttons and donate section, rewrote blurb, shortened up features descriptions and moved some to FAQ which I will tidy up later
2024-08-19 16:30:50 -04:00
Jacocococo
893b88350a Allow all content types on Iceshrimp
It's disabled anyways for new users, and allowing all lowers the chance
of crashes from a previously selected option being missing
2024-08-19 22:26:11 +02:00
Jacocococo
96b992025b Disable content type setting on Iceshrimp
We can be sure it doesn't support anything other than MFM
2024-08-19 22:24:06 +02:00
Jacocococo
cd7c546bed Only allow MFM content type on Iceshrimp 2024-08-19 21:59:42 +02:00
Jacocococo
6a46815809 Prevent more reactions if max has been reached
This is specifically for tapping existing reactions on a post if the
user has already reached the instance max
2024-08-19 21:11:57 +02:00
Jacocococo
5d411e842e Place new reaction where server specified it 2024-08-19 17:26:33 +02:00
Jacocococo
309d27242d Fix crash when liking statuses from non-Iceshrimp instances 2024-08-19 17:16:39 +02:00
Jacocococo
5f78cd4a8e Fix unicode reaction not showing up if no reactions were previsouly present 2024-08-19 17:13:16 +02:00
Jacocococo
1ad2257bb1 Add favorite reaction in right place + fix issues 2024-08-19 17:04:47 +02:00
LucasGGamerM
5427b21365 Merge pull request #508 from FineFindus/feat/duration
feat(MuteSheet): revert to row
2024-08-18 17:35:05 -03:00
Ruth Collings
d875edbc23 Update README.md 2024-08-15 14:57:57 -04:00
LucasGGamerM
4aecb17497 Merge pull request #507 from FineFindus/fix/note-save-crash
fix(Profile) note save crash
2024-08-14 15:56:20 -03:00
LucasGGamerM
806db1d09f Merge pull request #496 from FineFindus/fix/share-theme
fix(ExternalShareActivity): set theme before opening compose
2024-08-14 15:55:16 -03:00
FineFindus
49cf100d37 fix(MuteAccountSheet): set selected mute time 2024-08-14 19:12:35 +02:00
FineFindus
259a0ae140 feat: use mute row
Partially 6c519b3cb9 to use the full row
again, but keep the selection in a dialog. This improves the UI, as the
secondary button is confusing, and feels out-of-place.
2024-08-14 18:50:58 +02:00
FineFindus
420233da14 fix(Profile): remove note text before hiding NoteTextField
Fixes an issue, where if the NoteTextField was focussed and the note was
hidden, the note would saved and reshown once the NoteTextField was
hidden.
2024-08-12 07:16:15 +02:00
FineFindus
78ec24ff0c feat(Profile): only show note saved if note has not been hidden
The note text field being hidden is already enough comfirmation that the
note has been hidden.
2024-08-12 07:15:22 +02:00
FineFindus
a6f1d981db fix(Profile): use global context for note toast
Uses the global context for displaying the note saved toast. When using
the local context, it was somehow, sometimes null, leading to crashes.
2024-08-12 07:09:17 +02:00
LucasGGamerM
b07789b346 Merge pull request #505 from FineFindus/fix/scheduled-status-quote-crash
fix(StatusDisplayItem): check if account is null before adding quote
2024-08-11 19:32:08 -03:00
FineFindus
42c55d5eee fix(StatusDisplayItem): check if account is null before adding quote
This should fix crashes in the ScheduledStatusListFragment,
as ScheduledStatus do not contain an account.
2024-08-11 23:48:31 +02:00
LucasGGamerM
13545fd5ef Merge pull request #503 from FineFindus/feat/profile-note-save-feedback
feat(Profile): display Toast when saving note
2024-08-11 09:38:45 -03:00
FineFindus
134513babd feat(ProfileFragment): display toast when saving note
It can be quite unclear if the note has been saved. This adds a toast,
to indicate that the profile note has been saved.
2024-08-11 11:48:51 +02:00
FineFindus
91cb616164 refactor(ProfileFragment): remove duplicated InputType setting
The NoteEdit InputType is already set in the UI file.
2024-08-11 11:47:34 +02:00
Jacocococo
3266a490be Add reaction when favoriting post on Iceshrimp 2024-08-05 18:02:27 +02:00
FineFindus
f3d600282e fix(ExternalShareActivity): set theme before opening compose
Fixes https://github.com/sk22/megalodon/issues/926.
2024-08-05 14:28:20 +02:00
Jacocococo
c26df5762f Still set desired height 2024-08-04 14:44:08 -03:00
Jacocococo
2021c335ac None-square emoji for reactions 2024-08-04 14:44:08 -03:00
Jacocococo
d121f14d30 Non-square emoji in text views 2024-08-04 14:44:08 -03:00
LucasGGamerM
d1a2a70cdc Merge pull request #495 from FineFindus/feat/trending-links-timeline-improvements
feat(Timeline/TrendingLinks): display URL, update icon
2024-08-04 12:16:42 -03:00
FineFindus
89ef482e2e feat(Timeline/TrendingLink): use open icon for open action
The previous icon made it hard to recognize what the action was supposed
to do. Additionally, the new one also indicate that it will take the
user to an external website.
2024-08-04 15:58:35 +02:00
FineFindus
9918649d7c feat(Timeline/TrendingLink): provide WebURL
Since the Web version now has a user-visible timeline, we can provide a
URL to that.
2024-08-04 15:56:32 +02:00
LucasGGamerM
09185faf9a Merge remote-tracking branch 'refs/remotes/FineFindus/feat/quote-filter-hide'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
2024-08-04 09:33:55 -03:00
LucasGGamerM
bed201a2f7 docs: add 107 changelog 2024-08-03 10:01:56 -03:00
LucasGGamerM
5e7a4c0136 build: bump version number 2024-08-03 09:55:17 -03:00
gallegonovato
bcb8717d5f Translated using Weblate (Spanish)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/es/
2024-08-03 12:50:50 +00:00
gallegonovato
ed1c1bd097 Translated using Weblate (Spanish)
Currently translated at 100.0% (39 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2024-08-03 12:50:50 +00:00
joenepraat
f480532fd6 Translated using Weblate (Dutch)
Currently translated at 100.0% (39 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/nl/
2024-08-03 12:50:50 +00:00
Vaclovas Intas
cc056cef08 Translated using Weblate (Lithuanian)
Currently translated at 23.0% (9 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/lt/
2024-08-03 12:50:50 +00:00
Hayny
9e7445b8d8 Translated using Weblate (French)
Currently translated at 5.1% (2 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/fr/
2024-08-03 12:50:50 +00:00
Lefteris T
e2d96d3bc7 Translated using Weblate (Greek)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/el/
2024-08-03 12:50:50 +00:00
Lefteris T
4f5c99be21 Translated using Weblate (Greek)
Currently translated at 100.0% (120 of 120 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/el/
2024-08-03 12:50:50 +00:00
LucasGGamerM
0388f9d9be fix(toggle-expanded): fix crash when headers happen to be empty 2024-08-03 09:40:28 -03:00
LucasGGamerM
c45128ced0 fix(unofficial-quotes): fix crash when results.statuses is null 2024-08-03 09:36:16 -03:00
LucasGGamerM
f404d2f9cd Merge pull request #491 from FineFindus/fix/discover-scroll-regression
fix(Discover): switch post and hashtag fragments everywhere
2024-08-03 08:05:10 -03:00
FineFindus
2dada69eb8 fix(Discover): switch post and hashtag fragments everywhere
Fixes a regression in 5edbe9b826, whcih
did not switch the fragments everywhere. This caused the scroll-to-top
functionality to not work and the posts to not immediatly load.

Closes https://github.com/LucasGGamerM/moshidon/issues/483.
2024-08-03 11:30:58 +02:00
FineFindus
b7e0596014 feat(StatusDisplayItem): do not hide self-quoted posts 2024-08-03 11:22:40 +02:00
FineFindus
dbef984908 feat(StatusDisplayItem): hide statuses with quotes of muted/blocked
accounts

Hides Statuses with non-official quotes of accounts that are
blocked/muted. This is equivalent to how misskey handles muted quotes.

Closes https://github.com/LucasGGamerM/moshidon/issues/488.
2024-08-03 10:59:51 +02:00
FineFindus
55259f103d feat(Quote): hide filtered quotes
Hides quote of that would have been hidden by a filter, essentially
reverting back to the previous behaviour.

(Partially) Closes: https://github.com/LucasGGamerM/moshidon/issues/488
2024-08-03 00:08:45 +02:00
LucasGGamerM
81519fe906 fix(f-droid): remove f-droid version suffix 2024-08-02 16:43:55 -03:00
LucasGGamerM
07ab3c394a Merge pull request #485 from FineFindus/feat/draft-improvements
feat(Draft): display ScheduledStatus highlighted and formatted
2024-08-02 16:15:05 -03:00
LucasGGamerM
620cc94351 fix(pixelfed): make pixelfed login work again 2024-08-02 16:02:04 -03:00
LucasGGamerM
2494918171 fix(self-updater): export receiver for android 13 and plus 2024-08-02 15:30:27 -03:00
Grishka
a0bed5e739 fix: cherrypick a patch for the Sdk 34 from upstream 2024-08-02 15:24:03 -03:00
FineFindus
a42bf86a1e feat: display ScheduledStatus rendered
Fakes the highlighting and formatting of ScheduledStatus by injecting
the correct HTML tags.

Fixes https://github.com/LucasGGamerM/moshidon/issues/478.
2024-08-01 19:32:53 +02:00
LucasGGamerM
9c7ae9653b Merge pull request #487 from FineFindus/fix/uri-crash
fix: check if uri is hierarchical
2024-08-01 14:27:05 -03:00
FineFindus
44473705b9 feat(Settings/About): hide pre-release option in nightly 2024-08-01 14:07:54 +02:00
FineFindus
f1d40f8963 fix(Tacking): check if uri is hierarchical
Checks if the given uri is hierarchical, as otherwise the
`getQueryParameterNames` function will throw an exception.
2024-08-01 14:07:38 +02:00
FineFindus
fbae5d8816 feat(Draft): only hide media preview if status is senstive
Closes https://github.com/LucasGGamerM/moshidon/issues/478.
2024-07-31 22:39:26 +02:00
LucasGGamerM
43afbb7523 Merge pull request #484 from FineFindus/fix/quote
fix: correctly render more unofficial quotes
2024-07-30 20:05:23 -03:00
LucasGGamerM
080815846f Merge pull request #482 from FineFindus/feat/GNOME-icon
feat(Timelines): add GNOME icon
2024-07-30 20:03:43 -03:00
FineFindus
4b6c6cbcfe refactor(Quotes): inlcude URL scheme in quote regex
This should increase the performance, whilst rejecting more incorrect
URLs and allowing more correct ones.
2024-07-30 21:51:36 +02:00
FineFindus
117037e7e8 feat(Quote): only show quotes for status without attachments 2024-07-30 20:47:37 +02:00
FineFindus
05972fc702 fix(Quotes): increase TLD max length 2024-07-30 20:47:08 +02:00
LucasGGamerM
28084b9f9e Merge remote-tracking branch 'refs/remotes/weblate/master' 2024-07-29 19:43:19 -03:00
joenepraat
02010df408 Translated using Weblate (Dutch)
Currently translated at 100.0% (420 of 420 strings)

Translation: Moshidon/megalodon_values
Translate-URL: https://translate.codeberg.org/projects/moshidon/megalodon_values/nl/
2024-07-29 22:34:59 +00:00
joenepraat
38f77c69d1 Translated using Weblate (Dutch)
Currently translated at 100.0% (39 of 39 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/nl/
2024-07-29 22:34:58 +00:00
joenepraat
d0a8c26b65 Translated using Weblate (Dutch)
Currently translated at 100.0% (120 of 120 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/nl/
2024-07-29 22:34:58 +00:00
FineFindus
401602e5bc feat(Timelines): add GNOME icon 2024-07-29 22:38:56 +02:00
Jacocococo
86b6adf228 Move unauthenticatedApiController
The constructor of AccountSessionManager may need this object which
could lead to a situation where it was being used before it had been
created.
Making it static and moving it to MastodonAPIRequest, the only place it
was being used anyways, seems to fix the issue.
2024-03-29 14:38:06 +01:00
Jacocococo
2856e99569 Update favorite when reacting on Iceshrimp 2024-03-17 00:05:47 +01:00
Jacocococo
684164903a Disable add reaction button upon reaching limit when using that button 2024-03-16 23:08:55 +01:00
Jacocococo
2dfb79c828 Disable remote emoji reaction buttons on Iceshrimp 2024-03-16 14:25:30 +01:00
Jacocococo
86f54f5a02 Respect instance max reaction count 2024-03-15 23:00:25 +01:00
Jacocococo
3593d8d80f Announcements fixes on Iceshrimp
- Hide emoji reactions
- Correctly set editedAt
2024-02-16 00:10:20 +01:00
Jacocococo
454660fe89 Hide profile notify button on Iceshrimp 2024-02-15 23:25:09 +01:00
Jacocococo
325eda58cb Fix Iceshrimp quote notification 2024-02-15 22:58:40 +01:00
Jacocococo
0e96e23cfa Hide language selector on Iceshrimp 2024-02-15 00:06:08 +01:00
Jacocococo
2892a31c72 Hide news discovery on Iceshrimp 2024-02-14 23:35:13 +01:00
Jacocococo
c8bb0de7d4 Don't load instance info in background
This information is very useful and it's loaded very quickly anyways
2024-02-14 22:35:32 +01:00
Jacocococo
53e8c0d2f4 Quoting 2024-02-14 19:22:50 +01:00
326 changed files with 4659 additions and 790 deletions

View File

@@ -0,0 +1,16 @@
name: Mirror to Codeberg
on: [push]
jobs:
sync-git:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: yesolutions/mirror-action@master
with:
REMOTE: 'https://codeberg.org/LucasGGamerM/moshidon.git'
GIT_USERNAME: LucasGGamerM
GIT_PASSWORD: ${{ secrets.CODEBERG_GIT_PASSWORD }}

View File

@@ -65,7 +65,7 @@ jobs:
CURRENT_DATE: ${{ steps.date.outputs.date }}
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2
uses: actions/upload-artifact@v4
with:
name: moshidon-nightly.apk
path: ./mastodon/build/outputs/apk/nightly/moshidon-nightly.apk

57
CSAE-POLICY.md Normal file
View File

@@ -0,0 +1,57 @@
# CSAE Policy
## "Moshidon" CSAE Policy
> CSAE refers to child sexual abuse and exploitation, including content or behavior that sexually exploits, abuses, or endangers children. This includes, for example, grooming a child for sexual exploitation, sextorting a child, trafficking of a child for sex, or otherwise sexually exploiting a child. Google Child Safety Standards Policy
## Posting or linking to CSAE
“Moshidon” allows you to create posts on your accounts server.
Using the application to post or link to CSAE is strictly prohibited.
## Reporting accounts posting or linking to CSAE
#### If you suspect a child is in immediate danger in any way, contact the police immediately.
If you see an account posting CSAE you can report it to your servers moderators for further action.
Reporting an account is a four step process.
### 1. Start the report
Tap the “…” button at the bottom of any post from the account to show the per-post menu
![reporting_step](img/CSAE-POLICY/step1.png)
Choose “Report” from the per-post menu.
### 2. Fill in appropriate server rule breakage information
![server_rule_breakage_reporting_step](img/CSAE-POLICY/step2.png)
### 3. Optionally, include additional posts
Choose one or more posts to report.
The post you chose at the previous step is automatically selected.
![add_more_posts_step](img/CSAE-POLICY/step3.png)
Tap the “Continue” button when you have finished selecting posts.
### 4. Finalise the report
Enter any additional information in the space provided.
![finalize_report_step](img/CSAE-POLICY/step4.png)
Finalising the report
Tap the “Report” button.
The report will be sent to your servers moderation team for action according to their published policies.
## Additional reporting
You may also want to report to the relevant organisations in your jurisdiction.
Google maintains a list of organisations to report CSAE organised by country.
If your country is not listed there it may be listed at INHOPE.
## Contact
If you have any questions about this CSAE policy please contact moshidon.app@gmail.com.

54
FAQ.md
View File

@@ -7,3 +7,57 @@ A: There are many, but the most outstanding differences are: the ability to have
Q: Will there ever be a version of Moshidon for iOS?
A: No. As android and iOS apps do not share code, it is incredibly hard to port.
## Detailed changes
### Features
* [Adding the ability to view other server's local timelines](https://github.com/LucasGGamerM/moshidon/tree/feature/local-timelines)
* [Adding the ability to load followers and following from remote instance](https://github.com/LucasGGamerM/moshidon/tree/feature/remote-followers)
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
* Adding a useful private profile note box
* Auto hiding the compose button on scroll
* Adding the ability to remind yourself to add alt text to images
* An indicator for if an image has alt text or not
* Adding the ability to have drafts
* Also adding the ability to view announcements from your instance
* Adding the ability to post for local timeline only (Only on instances that support it!)
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
* [Implement a bookmark button and list](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/bookmarks) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/22))
* [Add “Check for update” button in addition to integrated update checker](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/check-for-update-button)
* [Add “Mark media as sensitive” option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/mark-media-as-sensitive)
* [Add settings to hide replies and reposts from the timeline](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/filter-home-timeline) ([Pull request](https://github.com/mastodon/mastodon-android/pull/317))
* [Follow and unfollow hashtags](https://github.com/sk22/megalodon/commit/7d38f031f197aa6cefaf53e39d929538689c1e4e) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/233))
* [Notification bell for posts](https://github.com/sk22/megalodon/commit/b166ca705eb9169025ef32bbe6315b42491b57ea) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/81))
* [Viewing lists and adding/removing users from lists](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:list-timeline-views) based on [@obstsalatschuessel](https://github.com/obstsalatschuessel)'s [Pull request](https://github.com/mastodon/mastodon-android/pull/286)
* [List favorited posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/favs-list)
* [Accept/reject follow requests](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/follow-requests)
* [Display content warning title above text](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
* [Add notifications tab for posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/posts-notifications-tab)
* [Show visibility of original post when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-reply-visibility)
* [Clickable reply/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
### Behavior
* Ask for confirmation before reblogging
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
* [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
* [Set spoiler height independently to content height](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:spoiler-height-independent) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/166))
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
* [Option to always reveal content warnings](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
### Visual
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
* [Improvements to the true black mode](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:true-black-improvements)
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)

215
README.md
View File

@@ -1,185 +1,91 @@
![MoshidonLogo](mastodon/src/main/res/mipmap-xhdpi/ic_launcher_round.png)
# ![MoshidonLogo](mastodon/src/main/res/mipmap-xhdpi/ic_launcher_round.png) Moshidon, the material you mastodon client!
# Moshidon, the material you mastodon client!
> A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly wont ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
> A fast, highly customizable, up-to-date fork of [megalodon](https://github.com/sk22/megalodon) adding important features such as a fully federated timeline, unlisted posting, drafts, scheduled posts, bookmarks, and alt text warnings.
[![Download latest release](https://img.shields.io/badge/dynamic/json?color=282C37&label=Download%20APK&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2FLucasGGamerM%2Fmoshidon%2Freleases%2Flatest&style=for-the-badge)](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
## Download Now
[![Download nightly release](https://img.shields.io/badge/dynamic/json?color=282C37&label=Download%20Nightly%20APK&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2FLucasGGamerM%2Fmoshidon%2Freleases%2Flatest&style=for-the-badge)](https://github.com/LucasGGamerM/moshidon-nightly/releases/latest/download/moshidon-nightly.apk)
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.moshinda"><img height="35" alt="Get it on Google Play" src="img/google-play-badge.png"></a> <a href="https://f-droid.org/pt_BR/packages/org.joinmastodon.android.moshinda"><img height="35" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a> <a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda"><img height="35" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
[![GitHub Release Download](https://img.shields.io/badge/dynamic/json?color=282C37&label=Download%20APK&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2FLucasGGamerM%2Fmoshidon%2Freleases%2Flatest&style=for-the-badge)](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) [![Translation status](https://translate.codeberg.org/widgets/moshidon/-/svg-badge.svg)](https://translate.codeberg.org/engage/moshidon/) [![GitHub Nightly Download](https://img.shields.io/badge/dynamic/json?color=282C37&label=Download%20Nightly%20APK&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2FLucasGGamerM%2Fmoshidon%2Freleases%2Flatest&style=for-the-badge)](https://github.com/LucasGGamerM/moshidon-nightly/releases/latest/download/moshidon-nightly.apk) [![GitHub Nightly Build Download](https://github.com/LucasGGamerM/moshidon/actions/workflows/nightly-builds.yml/badge.svg)](https://github.com/LucasGGamerM/moshidon/actions/workflows/nightly-builds.yml)
[![Translation status](https://translate.codeberg.org/widgets/moshidon/-/svg-badge.svg)](https://translate.codeberg.org/engage/moshidon/)
&nbsp;
[![Nightly build](https://github.com/LucasGGamerM/moshidon/actions/workflows/nightly-builds.yml/badge.svg)](https://github.com/LucasGGamerM/moshidon/actions/workflows/nightly-builds.yml)
## Donate
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.moshinda"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
&nbsp;
<a href="https://f-droid.org/pt_BR/packages/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a>
&nbsp;
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
<a href="https://github.com/sponsors/LucasGGamerM">Github Sponsors</a> | <a href="https://liberapay.com/LucasGGamerM/donate">Liberapay</a> | Monero Wallet Key: `4886mdarcyB6Yf8Qc6vDJBK1fz6ibHFLZUmHb4GZZz9yLGNhcG3XC64e5UZ8dVQYTLZb82W6P9WhteowW4STJEec97Gf22j`
## Help out the project by donating at: https://github.com/sponsors/LucasGGamerM!
### We also support LiberaPay at: https://liberapay.com/LucasGGamerM/donate
## Key Features
### You can also donate some Monero through this wallet address as well:
4886mdarcyB6Yf8Qc6vDJBK1fz6ibHFLZUmHb4GZZz9yLGNhcG3XC64e5UZ8dVQYTLZb82W6P9WhteowW4STJEec97Gf22j
[ screenshot of full timeline in default colour scheme ]
[ screenshot of full timeline in an alt colour scheme ]
[ screenshot of profile page ]
[ screenshot of compose post window ]
---
### Flexible Timelines
## Key features
[ Home dropdown menu ]
### **The ability to add other server's local timeline to your timelines**
Under the Home menu by default you can see your active account's timeline, your server's local timeline, and your server's federated timeline. You can also pin hashtags, lists, other servers, or make a custom view of just your posts, your bookmarks, or your favourites for quick access. Then sort these timelines to prioritize the ones you visit most often.
It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts!
### Multiple Accounts & Crossposting
### **View remote profiles**
Sign in to multiple accounts in the same app and easily switch between them. Press and hold on the boost or fave button to boost or fave a post to a different account than the one you are currently browsing with.
You can now see all of a profile follows and followers, by directly loading them from the profile's home instance. In case of a failed lookup, the app will automatically fall back to the older method.
[ boost icon pop up select profile ]
### **Translate posts easily**
### Drafts & Scheduled Posts
Allows you to easily translate posts in another language with a translate button! Your instance must support translation, otherwise it will not work.
Write posts and save them, or schedule them to post later. Edit and delete your drafts.
### **Show posts filtered with a warning**
### Alt Text Tag & Reminder
Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:
An unobtrusive ALT tag appears on images with alt text. Clicking on the icon makes the alt text appear. By default, Moshidon will show a warning to add alt text if your post has any attachments lacking alt text. This is for better accessibility, and it can be disabled in settings. You can also hide from your feed all posts that are lacking in alt text.
Before | After
:-------------------------:|:-------------------------:
![Screenshot_20230205-100200edited](https://user-images.githubusercontent.com/71328265/216820539-20802dc5-e433-4511-b2d9-291d810e4ef2.png) | ![Screenshot_20230205-100203edited](https://user-images.githubusercontent.com/71328265/216820544-231b2966-f38f-4ec6-b555-d39c62433839.png)
[ image with alt text icon higlighted ]
[ alt text expanded ]
### Themes & Customization
### **Color themes**
Moshidon is designed according to Material Design principles. Follow your device's light or dark mode settings or change colour palette - your system's default, purple, black & white, "pitch black" (battery saving) and more. Customize your experience by moving or renaming the publish button, show or hide sensitive media by default, reduce motion, collapse long posts, add haptic feedback, or making the fave button a heart &hearts; or a star &starf;.
Allows you to change theme within the app. Supports Material You, purple, pink, green, blue, red, orange, yellow and Nord!
### Not Just For Mastodon
### **Unlisted posting**
Supports features available on other types of fediverse servers such as admin announcements, showing pronouns in user names, post translation, emoji reactions, local-only posting, and markdown or html in posts.
**Allows you to post publicly without having your post show up in trends, hashtags or public timelines (i.e., in the tabs “Local”, “Community” and “Posts”).**
### Fully Federated Feed & Profiles
When posting with Unlisted visibility, your posts will still be publicly accessible in your profile. They will also be shown in peoples Home timelines, but only if they follow you or someone they follow reposted/replied to your post.
The Mastodon documentation has some more information about [Unlisted posting](https://docs.joinmastodon.org/user/posting/#unlisted) and [Public timelines](https://docs.joinmastodon.org/user/network/#timelines).
See all public posts from servers your server federates with and fetch profiles from a user's local server for accurate up to date information.
### **Federated timeline**
## And more...
- quote-posts - links to fediverse posts in other posts will be loaded inline like quote-tweets
- manage pinned posts and bookmarks
- manage lists, filters, and most privacy settings
- display pronouns in timelines, threads, and user listings
- get only specific types of notifications (no more finished polls!), limit who you get notifications from, or group all notifications into one.
- automatically add "re:" to beginning of replies with content warnings
- ask before boosting or deleting posts
- when replying to a boosted post automatically mention the person who boosted it
- overlay audio from posts, allowing your existing media to keep playing
- auto-reveal CWs that are the same as ones you've already opened, or always reveal content warnings and sensitive media
- hide media previews in timelines (save data)
- show post interaction counts in timeline
- allow custom emoji in display names
- enable scrolling text for long display names
- hide interaction buttons
- show post dividers
**This allows you to chronologically see all Public posts from people on all other Fediverse neighborhoods your home instance is connected to.**
Despite being one of the main features of federated social media, the Federated timeline wasnt included in the official Mastodon app supposedly, because this conflicts with Googles safety requirements for apps on the Play Store.
Thats one of the reasons why choosing a small, **well-moderated instance is important**. Instance admins and moderators should always make sure to ban abusive users and stop federating with instances who platform them. On well-moderated instances, the Federated timeline can be a welcoming place to meet new people!
## Installation & Releases
### **Image description viewer**
Moshidon is available on GitHub, Google Play, F-Droid, and the IzzyOnDroid repo. All sources provide the same ` moshidon.apk ` stable release. Older releases are available on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
**Allows you to quickly check whether an image or video has an alternative text attached to it.**
### How to Install from GitHub
[Download the latest stable release from Github](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) and open it. You might have to accept installing APK files from your browser. Moshidon will automatically check for new updates available on GitHub and offer to download and install them within the app. You can also manually press “Check for updates” at the bottom of the settings page.
This is important to **ensure the content youre sharing is as accessible as possible** to people who cant see the images and rely on software to read back the provided content descriptions. Thankfully, its quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way!
### Nightly Version
All ` moshidon-night.apk ` nightly builds can be downloaded on the [Nightly Releases](https://github.com/LucasGGamerM/moshidon-nightly/releases) page. This is an unstable version with an integrated updater for development and testing purposes. If you find any bugs with it, please file a bug report on our [Issues](https://github.com/LucasGGamerM/moshidon/issues) page.
### **Reminder to add alt text to attached media**
By default, Moshidon will show a warning to add alt text if your post has any attachments without any alt text. This is for better accessibility, and it can easily be bypassed and disabled in settings.
### **Pinning posts**
**This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in peoples profiles shows all the posts they pinned.**
On the Fediverse, its quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
### **Bookmarks**
**They allow for quickly saving posts and viewing them through the Bookmarks button on the top right of your profile.**
To bookmark a post, press the button between the Favorite and Share buttons on the bottom of the post. Bookmarks are saved privately, so the post authors wont know you saved their post the list of bookmarked posts is only visible to you.
## Installation
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Moshidon will automatically notify you about new updates inside the app.**
To install this app on your Android device, download the [latest release from GitHub](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
Moshidon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)s automatic update checker. Moshidon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
Moshidon is also available in [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda), compatible with all F-Droid clients. The APK provided here is the same as the one included in the Releases.
## Release variants
### Stable variant
All stable version downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
**`moshidon.apk`**
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
### Nightly variant
All nightly builds can be downloaded at [Nightly Releases](https://github.com/LucasGGamerM/moshidon-nightly/releases) page.
**`moshidon-nightly.apk`**
Unstable variant with an integrated updater. It's for development and testing purposes. If you find any bugs with it, please file a bug report at our [issues](https://github.com/LucasGGamerM/moshidon/issues) page.
---
## Detailed changes
### Features
* [Adding the ability to view other server's local timelines](https://github.com/LucasGGamerM/moshidon/tree/feature/local-timelines)
* [Adding the ability to load followers and following from remote instance](https://github.com/LucasGGamerM/moshidon/tree/feature/remote-followers)
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
* Adding a useful private profile note box
* Auto hiding the compose button on scroll
* Adding the ability to remind yourself to add alt text to images
* An indicator for if an image has alt text or not
* Adding the ability to have drafts
* Also adding the ability to view announcements from your instance
* Adding the ability to post for local timeline only (Only on instances that support it!)
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
* [Implement a bookmark button and list](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/bookmarks) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/22))
* [Add “Check for update” button in addition to integrated update checker](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/check-for-update-button)
* [Add “Mark media as sensitive” option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/mark-media-as-sensitive)
* [Add settings to hide replies and reposts from the timeline](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/filter-home-timeline) ([Pull request](https://github.com/mastodon/mastodon-android/pull/317))
* [Follow and unfollow hashtags](https://github.com/sk22/megalodon/commit/7d38f031f197aa6cefaf53e39d929538689c1e4e) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/233))
* [Notification bell for posts](https://github.com/sk22/megalodon/commit/b166ca705eb9169025ef32bbe6315b42491b57ea) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/81))
* [Viewing lists and adding/removing users from lists](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:list-timeline-views) based on [@obstsalatschuessel](https://github.com/obstsalatschuessel)'s [Pull request](https://github.com/mastodon/mastodon-android/pull/286)
* [List favorited posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/favs-list)
* [Accept/reject follow requests](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/follow-requests)
* [Display content warning title above text](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
* [Add notifications tab for posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/posts-notifications-tab)
* [Show visibility of original post when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-reply-visibility)
* [Clickable reply/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
### Behavior
* Allow for confirmation before reblogging
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
* [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
* [Set spoiler height independently to content height](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:spoiler-height-independent) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/166))
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
* [Option to always reveal content warnings](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
### Visual
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
* [Improvements to the true black mode](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:true-black-improvements)
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)
## Building
## Building & Contributing
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:
@@ -191,14 +97,15 @@ As this app is using Java 17 features, you need JDK 17 or newer to build it. Oth
This project is released under the [GPL-3 License](./LICENSE).
## Links
## Contact & Support
[F.A.Q](FAQ.md)
**<a rel="me" href="https://floss.social/@moshidon">@moshidon@floss.social</a>**
[Official matrix chatroom:](https://matrix.to/#/#moshidon:floss.social) https://matrix.to/#/#moshidon:floss.social
[Official Matrix Chatroom](https://matrix.to/#/#moshidon:floss.social)
[Moshidon roadmap](https://github.com/users/LucasGGamerM/projects/1)
[F.A.Q.](FAQ.md)
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
[Moshidon's CSAE policy](CSAE-POLICY.md)
[Moshidon Roadmap](https://github.com/users/LucasGGamerM/projects/1)
---

View File

@@ -1,23 +1,3 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
maven {
url "https://www.jitpack.io"
content {
includeModule 'com.github.UnifiedPush', 'android-connector'
}
}
mavenLocal()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
plugins {
id("com.android.application") version "8.7.2" apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -17,7 +17,5 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=false
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=true
android.nonFinalResIds=false
org.gradle.configuration-cache=true
org.gradle.configuration-cache=true

View File

@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionSha256Sum=57dafb5c2622c6cc08b993c85b7c06956a2f53536432a30ead46166dbca0f1e9
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

BIN
img/CSAE-POLICY/step1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
img/CSAE-POLICY/step2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
img/CSAE-POLICY/step3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
img/CSAE-POLICY/step4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -16,12 +16,19 @@ android {
applicationId "org.joinmastodon.android.moshinda"
minSdk 23
targetSdk 34
versionCode 106
versionName "2.3.0+fork.106.moshinda"
versionCode 109
versionName "2.3.0+fork.109.moshinda"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
}
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
signingConfigs {
nightly{
storeFile = file("keystore/nightly_keystore.jks")
@@ -108,7 +115,9 @@ android {
}
fdroidRelease {
initWith release
versionNameSuffix '-fdroid'
vcsInfo.include true
// The F-droid build system doesn't like this at all for some reason.
// versionNameSuffix '-fdroid'
// signingConfig signingConfigs.release
}
}
@@ -155,7 +164,7 @@ dependencies {
implementation 'com.github.bottom-software-foundation:bottom-java:2.1.0'
annotationProcessor 'org.parceler:parceler:1.1.12'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
implementation 'com.github.UnifiedPush:android-connector:2.1.1'
implementation 'org.unifiedpush.android:connector:3.0.7'
androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'

View File

@@ -33,6 +33,9 @@
# i don't know how proguard works
-keep class org.joinmastodon.android.** { *; }
# i still don't know how proguard works
-dontwarn android.app.BroadcastOptions
# Keep all enums for debugging purposes
-keepnames public enum * {
*;

View File

@@ -211,7 +211,13 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
if(state==UpdateState.DOWNLOADING)
throw new IllegalStateException();
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED);
}else{
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
downloadID=dm.enqueue(
new DownloadManager.Request(Uri.parse(getPrefs().getString("apkURL", null)))
.setDestinationUri(Uri.fromFile(getUpdateApkFile()))

View File

@@ -5,7 +5,8 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>

View File

@@ -17,8 +17,7 @@ brainsoap.net
breastmilk.club
brighteon.social
cachapa.xyz
canary.fedinuke.example.com
catgirl.life
caekis.love
cawfee.club
childlove.su
clew.lol
@@ -34,6 +33,7 @@ decayable.ink
dembased.xyz
detroitriotcity.com
djsumdog.com
drinkanddrive.africa
eientei.org
eveningzoo.club
fluf.club
@@ -50,7 +50,7 @@ geofront.rocks
gleasonator.com
glee.li
glindr.org
goyim.app
goyim.social
h5q.net
haeder.net
handholding.io
@@ -78,7 +78,6 @@ morale.ch
mouse.services
mugicha.club
narrativerry.xyz
natehiggers.online
nationalist.social
needs.vodka
neenster.org
@@ -86,14 +85,13 @@ nicecrew.digital
nightshift.social
nnia.space
noagendasocial.com
noagendasocial.nl
noagendatube.com
noauthority.social
nobodyhasthe.biz
norwoodzero.net
nyanide.com
onionfarms.org
parcero.bond
parcero.casa
pawlicker.com
pawoo.net
pedo.school
@@ -138,10 +136,10 @@ sonichu.com
spinster.xyz
springbo.cc
strelizia.net
subs4social.xyz
taihou.website
tastingtraffic.net
teci.world
theapex.social
theblab.org
thechimp.zone
thenobody.club
@@ -152,7 +150,6 @@ truthsocial.co.in
usualsuspects.lol
vampiremaid.cafe
varishangout.net
vtuberfan.social
wolfgirl.bar
xn--p1abe3d.xn--80asehdb
yggdrasil.social

View File

@@ -88,8 +88,13 @@ public class AudioPlayerService extends Service{
nm=getSystemService(NotificationManager.class);
// registerReceiver(receiver, new IntentFilter(Intent.ACTION_MEDIA_BUTTON));
registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE));
registerReceiver(receiver, new IntentFilter(ACTION_STOP));
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE), RECEIVER_EXPORTED);
registerReceiver(receiver, new IntentFilter(ACTION_STOP), RECEIVER_EXPORTED);
}else{
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE));
registerReceiver(receiver, new IntentFilter(ACTION_STOP));
}
instance=this;
}

View File

@@ -86,6 +86,8 @@ public class ExternalShareActivity extends FragmentStackActivity{
}
private void openComposeFragment(String accountID){
AccountSession session=AccountSessionManager.get(accountID);
UiUtils.setUserPreferredTheme(this, session);
getWindow().setBackgroundDrawable(null);
Intent intent=getIntent();

View File

@@ -81,6 +81,7 @@ public class GlobalUserPreferences{
public static boolean showPostsWithoutAlt;
public static boolean showMediaPreview;
public static boolean removeTrackingParams;
public static boolean enhanceTextSize;
public static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
@@ -162,10 +163,10 @@ public class GlobalUserPreferences{
showPostsWithoutAlt=prefs.getBoolean("showPostsWithoutAlt", true);
showMediaPreview=prefs.getBoolean("showMediaPreview", true);
removeTrackingParams=prefs.getBoolean("removeTrackingParams", true);
enhanceTextSize=prefs.getBoolean("enhanceTextSize", false);
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
if (prefs.contains("prefixRepliesWithRe")) {
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
? PrefixRepliesMode.TO_OTHERS : PrefixRepliesMode.NEVER;
@@ -237,6 +238,7 @@ public class GlobalUserPreferences{
.putBoolean("showPostsWithoutAlt", showPostsWithoutAlt)
.putBoolean("showMediaPreview", showMediaPreview)
.putBoolean("removeTrackingParams", removeTrackingParams)
.putBoolean("enhanceTextSize", enhanceTextSize)
.apply();
}

View File

@@ -7,8 +7,10 @@ import android.Manifest;
import android.app.Activity;
import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.Uri;
@@ -16,7 +18,9 @@ import android.os.BadParcelableException;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.Toast;
@@ -338,4 +342,20 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
}
}
}
@Override
protected void attachBaseContext(Context base) {
if (!GlobalUserPreferences.enhanceTextSize) {
super.attachBaseContext(base);
return;
}
final Configuration override = new Configuration(base.getResources().getConfiguration());
// This is the font multiplier, which should be multiplied by, because the system settings also play a role here
override.fontScale *= 1.15f;
final Context newBase = base.createConfigurationContext(override);
super.attachBaseContext(newBase);
}
}

View File

@@ -6,6 +6,7 @@ import android.content.Context;
import android.webkit.WebView;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.utils.UnifiedPushHelper;
import me.grishka.appkit.imageloader.ImageCache;
import me.grishka.appkit.utils.NetworkUtils;
@@ -27,7 +28,11 @@ public class MastodonApp extends Application{
ImageCache.setParams(params);
NetworkUtils.setUserAgent("MoshidonAndroid/"+BuildConfig.VERSION_NAME);
PushSubscriptionManager.tryRegisterFCM();
if (UnifiedPushHelper.isUnifiedPushEnabled(this)){
UnifiedPushHelper.registerAllAccounts(this);
} else {
PushSubscriptionManager.tryRegisterFCM();
}
GlobalUserPreferences.load();
if(BuildConfig.DEBUG){
WebView.setWebContentsDebuggingEnabled(true);

View File

@@ -163,7 +163,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
PushNotificationReceiver.this.notify(context, PushNotification.fromNotification(context, account, notification), account.getID(), notification);
}
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
NotificationManager nm=context.getSystemService(NotificationManager.class);
AccountSession session=AccountSessionManager.get(accountID);
Account self=session.self;

View File

@@ -5,14 +5,22 @@ import android.util.Log;
import org.jetbrains.annotations.NotNull;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.requests.notifications.GetNotificationByID;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.PushNotification;
import org.unifiedpush.android.connector.FailedReason;
import org.unifiedpush.android.connector.MessagingReceiver;
import org.unifiedpush.android.connector.data.PublicKeySet;
import org.unifiedpush.android.connector.data.PushEndpoint;
import org.unifiedpush.android.connector.data.PushMessage;
import java.util.List;
import java.util.function.Function;
import kotlin.text.Charsets;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -24,16 +32,23 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
}
@Override
public void onNewEndpoint(@NotNull Context context, @NotNull String endpoint, @NotNull String instance) {
public void onNewEndpoint(@NotNull Context context, @NotNull PushEndpoint endpoint, @NotNull String instance) {
// Called when a new endpoint be used for sending push messages
Log.d(TAG, "onNewEndpoint: New Endpoint " + endpoint + " for "+ instance);
Log.d(TAG, "onNewEndpoint: New Endpoint " + endpoint.getUrl() + " for "+ instance);
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
if (account != null)
account.getPushSubscriptionManager().registerAccountForPush(null, endpoint);
if (account != null) {
PublicKeySet ks = endpoint.getPubKeySet();
if (ks != null){
account.getPushSubscriptionManager().registerAccountForPush(account.pushSubscription, true, endpoint.getUrl(), ks.getPubKey(), ks.getAuth());
} else {
// ks should never be null on new endpoint
account.getPushSubscriptionManager().registerAccountForPush(account.pushSubscription, endpoint.getUrl());
}
}
}
@Override
public void onRegistrationFailed(@NotNull Context context, @NotNull String instance) {
public void onRegistrationFailed(@NotNull Context context, @NotNull FailedReason reason, @NotNull String instance) {
// called when the registration is not possible, eg. no network
Log.d(TAG, "onRegistrationFailed: " + instance);
//re-register for gcm
@@ -53,26 +68,46 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
}
@Override
public void onMessage(@NotNull Context context, @NotNull byte[] message, @NotNull String instance) {
public void onMessage(@NotNull Context context, @NotNull PushMessage message, @NotNull String instance) {
Log.d(TAG, "New message for " + instance);
// Called when a new message is received. The message contains the full POST body of the push message
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
if (account == null)
return;
//this is stupid
// Mastodon stores the info to decrypt the message in the HTTP headers, which are not accessible in UnifiedPush,
// thus it is not possible to decrypt them. SO we need to re-request them from the server and transform them later on
// The official uses fcm and moves the headers to extra data, see
// https://github.com/mastodon/webpush-fcm-relay/blob/cac95b28d5364b0204f629283141ac3fb749e0c5/webpush-fcm-relay.go#L116
// https://github.com/tuskyapp/Tusky/pull/2303#issue-1112080540
if (message.getDecrypted()) {
// If the mastodon server supports the standard webpush, we can directly use the content
Log.d(TAG, "Push message correctly decrypted");
PushNotification pn = MastodonAPIController.gson.fromJson(new String(message.getContent(), Charsets.UTF_8), PushNotification.class);
new GetNotificationByID(pn.notificationId)
.setCallback(new Callback<>(){
@Override
public void onSuccess(org.joinmastodon.android.model.Notification result){
MastodonAPIController.runInBackground(()->new PushNotificationReceiver().notify(context, pn, instance, result));
}
@Override
public void onError(ErrorResponse error){
MastodonAPIController.runInBackground(()-> new PushNotificationReceiver().notify(context, pn, instance, null));
}
})
.exec(instance);
} else {
// else, we have to sync with the server
Log.d(TAG, "Server doesn't support standard webpush, fetching one notification");
fetchOneNotification(context, account, (notif) -> () -> new PushNotificationReceiver().notifyUnifiedPush(context, account, notif));
}
}
private void fetchOneNotification(@NotNull Context context, @NotNull AccountSession account, @NotNull Function<Notification, Runnable> callback) {
account.getCacheController().getNotifications(null, 1, false, false, true, new Callback<>(){
@Override
public void onSuccess(PaginatedResponse<List<Notification>> result){
result.items
.stream()
.findFirst()
.ifPresent(value->MastodonAPIController.runInBackground(()->new PushNotificationReceiver().notifyUnifiedPush(context, account, value)));
.ifPresent(value->MastodonAPIController.runInBackground(callback.apply(value)));
}
@Override

View File

@@ -37,6 +37,8 @@ import okhttp3.Response;
public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
private static final String TAG="MastodonAPIRequest";
private static MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null);
private String domain;
private AccountSession account;
private String path;
@@ -95,14 +97,14 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
public MastodonAPIRequest<T> execNoAuth(String domain){
this.domain=domain;
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
unauthenticatedApiController.submitRequest(this);
return this;
}
public MastodonAPIRequest<T> exec(String domain, Token token){
this.domain=domain;
this.token=token;
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
unauthenticatedApiController.submitRequest(this);
return this;
}

View File

@@ -166,12 +166,23 @@ public class PushSubscriptionManager{
//work-around for adding the randomAccountId
String newEndpoint = endpoint;
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
newEndpoint += pushAccountID;
Boolean standard = true;
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/")){
newEndpoint+=pushAccountID;
standard = false;
}
new RegisterForPushNotifications(newEndpoint,
encodedPublicKey,
encodedAuthKey,
registerAccountForPush(subscription, standard, newEndpoint, encodedPublicKey, encodedAuthKey);
});
}
public void registerAccountForPush(PushSubscription subscription, Boolean standard, String endpoint, String p256dh, String auth){
MastodonAPIController.runInBackground(()->{
Log.d(TAG, "registerAccountForPush: started for "+accountID);
new RegisterForPushNotifications(endpoint,
standard,
p256dh,
auth,
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
subscription==null ? PushSubscription.Policy.ALL : subscription.policy)
.setCallback(new Callback<>(){

View File

@@ -8,14 +8,23 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.EmojiReaction;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import me.grishka.appkit.api.Callback;
@@ -42,6 +51,9 @@ public class StatusInteractionController{
if(!Looper.getMainLooper().isCurrentThread())
throw new IllegalStateException("Can only be called from main thread");
AccountSession session=AccountSessionManager.get(accountID);
Instance instance=session.getInstance().get();
SetStatusFavorited current=runningFavoriteRequests.remove(status.id);
if(current!=null){
current.cancel();
@@ -54,6 +66,7 @@ public class StatusInteractionController{
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
cb.accept(result);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
if(instance.isIceshrimpJs()) E.post(new EmojiReactionsUpdatedEvent(status.id, result.reactions, false, null));
}
@Override
@@ -63,12 +76,58 @@ public class StatusInteractionController{
status.favourited=!favorited;
cb.accept(status);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(instance.isIceshrimpJs()) E.post(new EmojiReactionsUpdatedEvent(status.id, status.reactions, false, null));
}
})
.exec(accountID);
runningFavoriteRequests.put(status.id, req);
status.favourited=favorited;
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(instance.configuration==null || instance.configuration.reactions==null)
return;
String defaultReactionEmojiRaw=instance.configuration.reactions.defaultReaction;
if(!instance.isIceshrimpJs() || defaultReactionEmojiRaw==null)
return;
boolean reactionIsCustom=defaultReactionEmojiRaw.startsWith(":");
String defaultReactionEmoji=reactionIsCustom ? defaultReactionEmojiRaw.substring(1, defaultReactionEmojiRaw.length()-1) : defaultReactionEmojiRaw;
ArrayList<EmojiReaction> reactions=new ArrayList<>(status.reactions.size());
for(EmojiReaction reaction:status.reactions){
reactions.add(reaction.copy());
}
Optional<EmojiReaction> existingReaction=reactions.stream().filter(r->r.me).findFirst();
Optional<EmojiReaction> existingDefaultReaction=reactions.stream().filter(r->r.name.equals(defaultReactionEmoji)).findFirst();
if(existingReaction.isPresent() && !favorited){
existingReaction.get().me=false;
existingReaction.get().count--;
existingReaction.get().pendingChange=true;
}else if(existingDefaultReaction.isPresent() && favorited){
existingDefaultReaction.get().count++;
existingDefaultReaction.get().me=true;
existingDefaultReaction.get().pendingChange=true;
}else if(favorited){
EmojiReaction reaction=null;
if(reactionIsCustom){
List<EmojiCategory> customEmojis=AccountSessionManager.getInstance().getCustomEmojis(session.domain);
for(EmojiCategory category:customEmojis){
for(Emoji emoji:category.emojis){
if(emoji.shortcode.equals(defaultReactionEmoji)){
reaction=EmojiReaction.of(emoji, session.self);
break;
}
}
}
if(reaction==null)
reaction=EmojiReaction.of(defaultReactionEmoji, session.self);
}else{
reaction=EmojiReaction.of(defaultReactionEmoji, session.self);
}
reaction.pendingChange=true;
reactions.add(reaction);
}
E.post(new EmojiReactionsUpdatedEvent(status.id, reactions, false, null));
}
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){

View File

@@ -4,10 +4,11 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.PushSubscription;
public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscription>{
public RegisterForPushNotifications(String endpoint, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy){
public RegisterForPushNotifications(String endpoint, Boolean standard, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy){
super(HttpMethod.POST, "/push/subscription", PushSubscription.class);
Request r=new Request();
r.subscription.endpoint=endpoint;
r.subscription.standard = standard;
r.data.alerts=alerts;
r.policy=policy;
r.subscription.keys.p256dh=encryptionKey;
@@ -27,6 +28,8 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
private static class Subscription{
public String endpoint;
// Use standard push notifications if available
public Boolean standard;
public Keys keys=new Keys();
}

View File

@@ -5,7 +5,6 @@ import org.joinmastodon.android.model.AkkomaTranslation;
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
public AkkomaTranslateStatus(String id, String lang){
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toLowerCase(), AkkomaTranslation.class);
}
}

View File

@@ -16,6 +16,7 @@ import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type;
@@ -23,6 +24,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class AccountLocalPreferences{
private final SharedPreferences prefs;
@@ -72,19 +74,20 @@ public class AccountLocalPreferences{
// preReplySheet=prefs.getBoolean("preReplySheet", false);
// MEGALODON
Optional<Instance> instance=session.getInstance();
showReplies=prefs.getBoolean("showReplies", true);
showBoosts=prefs.getBoolean("showBoosts", true);
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new ArrayList<>());
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", ContentType.PLAIN.name()));
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", true);
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", instance.map(Instance::isIceshrimp).orElse(false) ? ContentType.MISSKEY_MARKDOWN.name() : ContentType.PLAIN.name()));
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", instance.map(i->!i.isIceshrimp()).orElse(false));
timelines=fromJson(prefs.getString("timelines", null), timelinesType, TimelineDefinition.getDefaultTimelines(session.getID()));
localOnlySupported=prefs.getBoolean("localOnlySupported", false);
glitchInstance=prefs.getBoolean("glitchInstance", false);
publishButtonText=prefs.getString("publishButtonText", null);
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", instance.map(i->i.isAkkoma() || i.isIceshrimp()).orElse(false));
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());

View File

@@ -34,6 +34,7 @@ import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Token;
import org.joinmastodon.android.utils.UnifiedPushHelper;
import org.unifiedpush.android.connector.UnifiedPush;
import java.io.File;
@@ -70,7 +71,6 @@ public class AccountSessionManager{
private HashMap<String, List<EmojiCategory>> customEmojis=new HashMap<>();
private HashMap<String, Long> instancesLastUpdated=new HashMap<>();
private HashMap<String, Instance> instances=new HashMap<>();
private MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null);
private Instance authenticatingInstance;
private Application authenticatingApp;
private String lastActiveAccountID;
@@ -109,7 +109,7 @@ public class AccountSessionManager{
Log.e(TAG, "Error loading accounts", x);
}
lastActiveAccountID=prefs.getString("lastActiveAccount", null);
MastodonAPIController.runInBackground(()->readInstanceInfo(domains));
readInstanceInfo(domains);
maybeUpdateShortcuts();
}
@@ -127,12 +127,12 @@ public class AccountSessionManager{
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
updateMoreInstanceInfo(instance, instance.uri);
if (!UnifiedPush.getDistributor(context).isEmpty()) {
UnifiedPush.registerApp(
if (UnifiedPushHelper.isUnifiedPushEnabled(context)) {
UnifiedPush.register(
context,
session.getID(),
new ArrayList<>(),
context.getPackageName()
null,
session.app.vapidKey.replaceAll("=","")
);
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null);
@@ -247,11 +247,6 @@ public class AccountSessionManager{
maybeUpdateShortcuts();
}
@NonNull
public MastodonAPIController getUnauthenticatedApiController(){
return unauthenticatedApiController;
}
public void authenticate(Activity activity, Instance instance){
authenticatingInstance=instance;
new CreateOAuthApp()

View File

@@ -68,14 +68,14 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
instanceUser.url = "https://"+session.domain+"/about";
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
instanceUser.emojis = List.of();
Status fakeStatus = a.toStatus();
Status fakeStatus = a.toStatus(isInstanceIceshrimp());
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
textItem.textSelectable = true;
List<StatusDisplayItem> items=new ArrayList<>();
items.add(HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead));
items.add(textItem);
if(!isInstanceAkkoma()) items.add(new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true));
if(!isInstanceAkkoma() && !isInstanceIceshrimp()) items.add(new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true));
return items;
}

View File

@@ -9,6 +9,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -709,26 +710,17 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
public void updateStatusWithQuote(DisplayItemsParent parent) {
int startIndex=-1;
int endIndex=-1;
for(int i=0; i<displayItems.size(); i++){
StatusDisplayItem item = displayItems.get(i);
if(item.parentID.equals(parent.getID())) {
startIndex= startIndex==-1 ? i : startIndex;
endIndex=i;
}
}
if (startIndex==-1 || endIndex==-1)
Pair<Integer, Integer> items=findAllItemsOfParent(parent);
if (items==null)
return;
// Only StatusListFragments/NotificationsListFragments can display status with quotes
assert (this instanceof StatusListFragment) || (this instanceof NotificationsListFragment);
List<StatusDisplayItem> oldItems = displayItems.subList(startIndex, endIndex+1);
List<StatusDisplayItem> oldItems = displayItems.subList(items.first, items.second+1);
List<StatusDisplayItem> newItems=this.buildDisplayItems((T) parent);
int prevSize=oldItems.size();
oldItems.clear();
displayItems.addAll(startIndex, newItems);
displayItems.addAll(items.first, newItems);
// Update the cache
final CacheController cache=AccountSessionManager.get(accountID).getCacheController();
@@ -738,8 +730,19 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
cache.updateNotification((Notification) parent);
}
adapter.notifyItemRangeRemoved(startIndex, prevSize);
adapter.notifyItemRangeInserted(startIndex, newItems.size());
adapter.notifyItemRangeRemoved(items.first, prevSize);
adapter.notifyItemRangeInserted(items.first, newItems.size());
}
public void removeStatus(DisplayItemsParent parent) {
Pair<Integer, Integer> items=findAllItemsOfParent(parent);
if (items==null)
return;
List<StatusDisplayItem> statusDisplayItems = displayItems.subList(items.first, items.second+1);
int prevSize=statusDisplayItems.size();
statusDisplayItems.clear();
adapter.notifyItemRangeRemoved(items.first, prevSize);
}
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
@@ -815,6 +818,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
}
List<HeaderStatusDisplayItem.Holder> headers=findAllHoldersOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if (headers.isEmpty())
return;
HeaderStatusDisplayItem.Holder header=headers.size() > 1 && isForQuote ? headers.get(1) : headers.get(0);
if(header!=null) header.animateExpandToggle();
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
@@ -833,6 +838,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
list.invalidateItemDecorations();
}
public void onFavoriteChanged(Status status, String itemID) {
FooterStatusDisplayItem.Holder footer=findHolderOfType(itemID, FooterStatusDisplayItem.Holder.class);
if(footer!=null){
footer.getItem().status=status;
footer.onFavoriteClick();
}
}
@Override
public String getAccountID(){
return accountID;
@@ -943,6 +956,23 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null;
}
@Nullable
protected Pair<Integer, Integer> findAllItemsOfParent(DisplayItemsParent parent){
int startIndex=-1;
int endIndex=-1;
for(int i=0; i<displayItems.size(); i++){
StatusDisplayItem item = displayItems.get(i);
if(item.parentID.equals(parent.getID())) {
startIndex= startIndex==-1 ? i : startIndex;
endIndex=i;
}
}
if(startIndex==-1 || endIndex==-1)
return null;
return Pair.create(startIndex, endIndex);
}
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> List<H> findAllHoldersOfType(String id, Class<H> type){
ArrayList<H> holders=new ArrayList<>();
for(int i=0;i<list.getChildCount();i++){

View File

@@ -787,7 +787,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
if(!status.account.id.equals(ownID))
mentions.add('@'+status.account.acct);
if(status.rebloggedBy != null && GlobalUserPreferences.mentionRebloggerAutomatically)
if(GlobalUserPreferences.mentionRebloggerAutomatically && status.rebloggedBy != null && !status.rebloggedBy.id.equals(ownID))
mentions.add('@'+status.rebloggedBy.acct);
for(Mention mention:status.mentions){
if(mention.id.equals(ownID))
@@ -927,6 +927,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
return false;
});
if(instance.isIceshrimpJs())
languageButton.setVisibility(View.GONE); // hide language selector on Iceshrimp-JS because the feature is not supported
if (!GlobalUserPreferences.relocatePublishButton)
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
@@ -1052,12 +1055,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public void updatePublishButtonState(){
uuid=null;
if(GlobalUserPreferences.relocatePublishButton && publishButtonRelocated != null){
publishButtonRelocated.setEnabled((!isInstancePixelfed() || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
publishButtonRelocated.setEnabled(((!isInstancePixelfed() || replyTo != null) || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
}
if(publishButton==null)
return;
publishButton.setEnabled((!isInstancePixelfed() || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
publishButton.setEnabled(((!isInstancePixelfed() || replyTo != null) || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
}
private void onCustomEmojiClick(Emoji emoji){

View File

@@ -297,8 +297,8 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
cover.setImageDrawable(image);
}else{
item.emojiHelper.setImageDrawable(index-2, image);
name.invalidate();
bio.invalidate();
name.setText(name.getText());
bio.setText(bio.getText());
}
if(image instanceof Animatable a && !a.isRunning())
a.start();

View File

@@ -22,6 +22,14 @@ public interface HasAccountID {
return getInstance().map(Instance::isPixelfed).orElse(false);
}
default boolean isInstanceIceshrimp() {
return getInstance().map(Instance::isIceshrimp).orElse(false);
}
default boolean isInstanceIceshrimpJs() {
return getInstance().map(Instance::isIceshrimpJs).orElse(false);
}
default Optional<Instance> getInstance() {
return getSession().getInstance();
}

View File

@@ -11,7 +11,6 @@ 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;
@@ -32,21 +31,21 @@ import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterKeyword;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.Snackbar;
import org.joinmastodon.android.ui.sheets.MuteHashtagConfirmationSheet;
import org.joinmastodon.android.ui.text.SpacerSpan;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.time.Duration;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.concurrent.atomic.AtomicReference;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
@@ -105,15 +104,40 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
muteMenuItem.setIcon(newMute ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
}
private void showMuteDialog(boolean mute) {
UiUtils.showConfirmationAlert(getContext(),
mute ? R.string.mo_unmute_hashtag : R.string.mo_mute_hashtag,
mute ? R.string.mo_confirm_to_unmute_hashtag : R.string.mo_confirm_to_mute_hashtag,
mute ? R.string.do_unmute : R.string.do_mute,
mute ? R.drawable.ic_fluent_speaker_2_28_regular : R.drawable.ic_fluent_speaker_off_28_regular,
mute ? this::unmuteHashtag : this::muteHashtag
);
private void updateFollowState(boolean following) {
followMenuItem.setTitle(getString(following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
followMenuItem.setIcon(following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
}
private void showMuteDialog(boolean currentlyMuted) {
if (currentlyMuted) {
unmuteHashtag();
return;
}
//pass a references, so they can be changed inside the confirmation sheet
AtomicReference<Duration> muteDuration=new AtomicReference<>(Duration.ZERO);
new MuteHashtagConfirmationSheet(getContext(), null, muteDuration, hashtag, (onSuccess, onError)->{
FilterKeyword hashtagFilter=new FilterKeyword();
hashtagFilter.wholeWord=true;
hashtagFilter.keyword="#"+hashtagName;
new CreateFilter("#"+hashtagName, EnumSet.of(FilterContext.HOME), FilterAction.HIDE, (int) muteDuration.get().getSeconds(), List.of(hashtagFilter)).setCallback(new Callback<>(){
@Override
public void onSuccess(Filter result){
filter=Optional.of(result);
updateMuteState(true);
onSuccess.run();
}
@Override
public void onError(ErrorResponse error){
error.showToast(getContext());
onError.run();
}
}).exec(accountID);
}).show();
}
private void unmuteHashtag() {
//safe to get, this only called if filter is present
new DeleteFilter(filter.get().id).setCallback(new Callback<>(){
@@ -121,6 +145,9 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
public void onSuccess(Void result){
filter=Optional.empty();
updateMuteState(false);
new Snackbar.Builder(getContext())
.setText(getContext().getString(R.string.unmuted_user_x, '#'+hashtagName))
.show();
}
@Override
@@ -130,26 +157,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
}).exec(accountID);
}
private void muteHashtag() {
FilterKeyword hashtagFilter=new FilterKeyword();
hashtagFilter.wholeWord=true;
hashtagFilter.keyword="#"+hashtagName;
new CreateFilter("#"+hashtagName, EnumSet.of(FilterContext.HOME), FilterAction.HIDE, 0 , List.of(hashtagFilter)).setCallback(new Callback<>(){
@Override
public void onSuccess(Filter result){
filter=Optional.of(result);
updateMuteState(true);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getContext());
}
}).exec(accountID);
}
@Override
protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofHashtag(hashtagName);
@@ -292,6 +299,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
pinMenuItem=optionsMenu.findItem(R.id.pin);
followMenuItem.setVisible(toolbarContentVisible);
updateFollowState(hashtag!=null && hashtag.following);
// pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
super.updatePinButton(pinMenuItem);
@@ -388,8 +396,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
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);
updateFollowState(hashtag.following);
}
if(muteMenuItem!=null){
muteMenuItem.setTitle(getString(filter.isPresent() ? R.string.unmute_user : R.string.mute_user, "#" + hashtag));
@@ -429,6 +436,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
return;
hashtag=result;
updateHeader();
updateFollowState(result.following);
followRequestRunning=false;
}

View File

@@ -24,6 +24,7 @@ import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.Status;
@@ -122,7 +123,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
}
NotificationHeaderStatusDisplayItem titleItem;
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
Account self=AccountSessionManager.get(accountID).self;
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS
|| (n.type==Notification.Type.REBLOG && n.status != null && n.status.account != null && !n.status.account.id.equals(self.id))){ // Iceshrimp quote
titleItem=null;
}else{
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
@@ -316,13 +319,16 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
for(Notification n : data){
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
n.status.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
for(int i=0; i<list.getChildCount(); i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
reactions.rebind();
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
reactions.updateReactions(ev.reactions);
}
}
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
text.rebind();
}
}

View File

@@ -58,6 +58,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
@@ -287,11 +288,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
noteEdit.setOnFocusChangeListener((v, hasFocus)->{
if(hasFocus){
hideFab();
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
}else{
showFab();
savePrivateNote(noteEdit.getText().toString());
return;
}
showFab();
savePrivateNote(noteEdit.getText().toString());
});
FrameLayout sizeWrapper=new FrameLayout(getActivity()){
@@ -454,8 +454,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
private void hidePrivateNote(){
noteWrap.setVisibility(View.GONE);
noteEdit.setText(null);
noteWrap.setVisibility(View.GONE);
}
private void savePrivateNote(String note){
@@ -469,6 +469,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
public void onSuccess(Relationship result) {
updateRelationship(result);
invalidateOptionsMenu();
if(!TextUtils.isEmpty(result.note))
Toast.makeText(MastodonApp.context, R.string.mo_personal_note_saved, Toast.LENGTH_SHORT).show();
}
@Override
@@ -875,7 +877,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}else if(id==R.id.open_in_browser){
UiUtils.launchWebBrowser(getActivity(), account.url);
}else if(id==R.id.block_domain){
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account, relationship.domainBlocking, ()->{
relationship.domainBlocking=!relationship.domainBlocking;
updateRelationship();
});
@@ -989,7 +991,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
else hidePrivateNote();
invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
notifyButton.setVisibility(relationship.following && !isInstanceIceshrimpJs() ? View.VISIBLE : View.GONE); // always hide notify button on Iceshrimp-JS because it's unsupported on the server
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
@@ -1587,8 +1589,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
public void setImage(int index, Drawable image){
CustomEmojiSpan span=index>=item.nameEmojis.length ? item.valueEmojis[index-item.nameEmojis.length] : item.nameEmojis[index];
span.setDrawable(image);
title.invalidate();
value.invalidate();
title.setText(title.getText());
value.setText(value.getText());
toolbarTitleView.setText(toolbarTitleView.getText());
}
@Override

View File

@@ -85,7 +85,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
@Override
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, null,
return StatusDisplayItem.buildItems(this, s.toFormattedStatus(accountID), accountID, s, knownAccounts, null,
StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS |
StatusDisplayItem.FLAG_NO_FOOTER |
StatusDisplayItem.FLAG_NO_TRANSLATE);

View File

@@ -327,13 +327,16 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
for(Status s:data){
if(s.getContentStatus().id.equals(ev.id)){
s.getContentStatus().update(ev);
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
reactions.rebind();
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
reactions.updateReactions(ev.reactions);
}
}
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
text.rebind();
}
}

View File

@@ -279,8 +279,8 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
cover.setImageDrawable(image);
}else{
item.emojiHelper.setImageDrawable(index-2, image);
name.invalidate();
bio.invalidate();
name.setText(name.getText());
bio.setText(bio.getText());
}
if(image instanceof Animatable a && !a.isRunning())
a.start();

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments.discover;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.assist.AssistContent;
import android.os.Build;
import android.os.Bundle;
@@ -31,6 +32,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import java.util.Optional;
import me.grishka.appkit.Nav;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
@@ -60,6 +64,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private String currentQuery;
private boolean disableDiscover;
private boolean isIceshrimp;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -78,13 +83,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
tabLayout=view.findViewById(R.id.tabbar);
pager=view.findViewById(R.id.pager);
tabViews=new FrameLayout[4];
Optional<Instance> instance=AccountSessionManager.get(accountID).getInstance();
disableDiscover=instance.map(Instance::isAkkoma).orElse(false);
isIceshrimp=instance.map(Instance::isIceshrimp).orElse(false);
tabViews=new FrameLayout[isIceshrimp ? 3 : 4]; // reduce array size on Iceshrimp to hide news feed because it's unsupported and always returns an empty list
for(int i=0;i<tabViews.length;i++){
FrameLayout tabView=new FrameLayout(getActivity());
tabView.setId(switch(i){
case 0 -> R.id.discover_posts;
case 1 -> R.id.discover_hashtags;
case 2 -> R.id.discover_news;
case 2 -> isIceshrimp ? R.id.discover_users : R.id.discover_news; // skip unsupported news discovery on Iceshrimp
case 3 -> R.id.discover_users;
default -> throw new IllegalStateException("Unexpected value: "+i);
});
@@ -126,12 +135,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
accountsFragment=new DiscoverAccountsFragment();
accountsFragment.setArguments(args);
getChildFragmentManager().beginTransaction()
.add(R.id.discover_posts, postsFragment)
.add(R.id.discover_hashtags, hashtagsFragment)
.add(R.id.discover_news, newsFragment)
.add(R.id.discover_users, accountsFragment)
.commit();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction
.add(R.id.discover_posts, postsFragment)
.add(R.id.discover_hashtags, hashtagsFragment);
if(!isIceshrimp) // skip unsupported news discovery on Iceshrimp
transaction.add(R.id.discover_news, newsFragment);
transaction
.add(R.id.discover_users, accountsFragment)
.commit();
}
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@@ -140,7 +152,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
tab.setText(switch(position){
case 0 -> R.string.posts;
case 1 -> R.string.hashtags;
case 2 -> R.string.news;
case 2 -> isIceshrimp ? R.string.for_you : R.string.news; // skip unsupported news discovery on Iceshrimp
case 3 -> R.string.for_you;
default -> throw new IllegalStateException("Unexpected value: "+position);
});
@@ -160,7 +172,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
});
disableDiscover=AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false);
searchView=view.findViewById(R.id.search_fragment);
if(searchFragment==null){
searchFragment=new SearchFragment();
@@ -260,9 +271,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private Fragment getFragmentForPage(int page){
return switch(page){
case 0 -> hashtagsFragment;
case 1 -> postsFragment;
case 2 -> newsFragment;
case 0 -> postsFragment;
case 1 -> hashtagsFragment;
case 2 -> isIceshrimp ? accountsFragment : newsFragment; // skip unsupported news discovery on Iceshrimp
case 3 -> accountsFragment;
default -> throw new IllegalStateException("Unexpected value: "+page);
};

View File

@@ -131,8 +131,7 @@ public class DiscoverTrendingLinkTimelineFragment extends StatusListFragment{
@Override
public Uri getWebUri(Uri.Builder base) {
//TODO: add URL link once web version implements a UI
return base.path("/explore/links").build();
return base.path("/links").appendPath(trendingLink.url).build();
}
@Override

View File

@@ -94,7 +94,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> impleme
copyCrashLogItem=new ListItem<>(getString(R.string.sk_settings_copy_crash_log), lastModified, 0, this::onCopyCrashLog)
));
if(GithubSelfUpdater.needSelfUpdating()){
if(GithubSelfUpdater.needSelfUpdating() && !BuildConfig.BUILD_TYPE.equals("nightly") ){
items.add(enablePreReleasesItem=new CheckableListItem<>(R.string.sk_updater_enable_pre_releases, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enablePreReleases, i->toggleCheckableItem(enablePreReleasesItem)));
}

View File

@@ -54,7 +54,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
languageResolver.from(s.preferences.postingDefaultLanguage).orElse(null);
List<ListItem<Void>> items = new ArrayList<>(List.of(
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick),
customTabsItem=new ListItem<>(getString(R.string.settings_custom_tabs), getString(GlobalUserPreferences.useCustomTabs ? R.string.in_app_browser : R.string.system_browser), R.drawable.ic_fluent_open_24_regular, this::onCustomTabsClick),
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, i->toggleCheckableItem(altTextItem)),
showPostsWithoutAltItem=new CheckableListItem<>(R.string.mo_settings_show_posts_without_alt, R.string.mo_settings_show_posts_without_alt_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showPostsWithoutAlt, R.drawable.ic_fluent_eye_tracking_on_24_regular, i->toggleCheckableItem(showPostsWithoutAltItem)),
@@ -73,6 +72,11 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
showRepliesItem=new CheckableListItem<>(R.string.sk_settings_show_replies, 0, CheckableListItem.Style.SWITCH, lp.showReplies, R.drawable.ic_fluent_arrow_reply_24_regular, i->toggleCheckableItem(showRepliesItem))
));
if(!isInstanceIceshrimpJs()) items.add(
0,
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick)
);
if(isInstanceAkkoma()) items.add(
replyVisibilityItem=new ListItem<>(R.string.sk_settings_reply_visibility, getReplyVisibilityString(), R.drawable.ic_fluent_chat_24_regular, this::onReplyVisibilityClick)
);
@@ -203,7 +207,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
@Override
protected void onHidden(){
super.onHidden();
GlobalUserPreferences.playGifs=playGifsItem.checked;
GlobalUserPreferences.overlayMedia=overlayMediaItem.checked;
GlobalUserPreferences.altTextReminders=altTextItem.checked;
GlobalUserPreferences.confirmUnfollow=confirmUnfollowItem.checked;
@@ -215,13 +218,14 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
GlobalUserPreferences.mentionRebloggerAutomatically=mentionRebloggerAutomaticallyItem.checked;
GlobalUserPreferences.hapticFeedback=hapticFeedbackItem.checked;
GlobalUserPreferences.showPostsWithoutAlt=showPostsWithoutAltItem.checked;
GlobalUserPreferences.save();
AccountLocalPreferences lp=getLocalPrefs();
boolean restartPlease=lp.showBoosts!=showBoostsItem.checked
|| lp.showReplies!=showRepliesItem.checked;
|| lp.showReplies!=showRepliesItem.checked || GlobalUserPreferences.playGifs!=playGifsItem.checked;
lp.showBoosts=showBoostsItem.checked;
lp.showReplies=showRepliesItem.checked;
GlobalUserPreferences.playGifs=playGifsItem.checked;
lp.save();
GlobalUserPreferences.save();
if(newPostLanguage!=null){
AccountSession s=AccountSessionManager.get(accountID);
if(s.preferences==null)

View File

@@ -47,7 +47,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem;
// MOSHIDON
private CheckableListItem<Void> enableDoubleTapToSwipeItem, relocatePublishButtonItem, showPostDividersItem, enableDoubleTapToSearchItem, showMediaPreviewItem;
private CheckableListItem<Void> enableDoubleTapToSwipeItem, relocatePublishButtonItem, showPostDividersItem, enableDoubleTapToSearchItem, showMediaPreviewItem, enhanceTextSizeItem;
private AccountLocalPreferences lp;
@@ -63,6 +63,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
trueBlackModeItem=new CheckableListItem<>(R.string.sk_settings_true_black, R.string.mo_setting_true_black_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.trueBlackTheme, R.drawable.ic_fluent_dark_theme_24_regular, i->onTrueBlackModeClick(), true),
publishTextItem=new ListItem<>(getString(R.string.sk_settings_publish_button_text), getPublishButtonText(), R.drawable.ic_fluent_send_24_regular, this::onPublishTextClick),
autoRevealCWsItem=new ListItem<>(R.string.sk_settings_auto_reveal_equal_spoilers, getAutoRevealSpoilersText(), R.drawable.ic_fluent_eye_24_regular, this::onAutoRevealSpoilersClick),
enhanceTextSizeItem=new CheckableListItem<>(R.string.mo_settings_enhance_text_size, R.string.mo_settings_enhance_text_size_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enhanceTextSize, R.drawable.ic_fluent_text_more_24_regular, i->onEnhanceTextSizeClick()),
relocatePublishButtonItem=new CheckableListItem<>(R.string.mo_relocate_publish_button, R.string.mo_setting_relocate_publish_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.relocatePublishButton, R.drawable.ic_fluent_arrow_autofit_down_24_regular, i->toggleCheckableItem(relocatePublishButtonItem)),
revealCWsItem=new CheckableListItem<>(R.string.sk_settings_always_reveal_content_warnings, 0, CheckableListItem.Style.SWITCH, lp.revealCWs, R.drawable.ic_fluent_chat_warning_24_regular, i->toggleCheckableItem(revealCWsItem)),
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_fluent_flag_24_regular, i->toggleCheckableItem(hideSensitiveMediaItem)),
@@ -141,6 +142,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
GlobalUserPreferences.displayPronounsInThreads=pronounsInThreadsItem.checked;
GlobalUserPreferences.displayPronounsInUserListings=pronounsInUserListingsItem.checked;
GlobalUserPreferences.showMediaPreview=showMediaPreviewItem.checked;
GlobalUserPreferences.enhanceTextSize=enhanceTextSizeItem.checked;
GlobalUserPreferences.save();
if(restartPlease) restartActivityToApplyNewTheme();
else E.post(new StatusDisplaySettingsChangedEvent(accountID));
@@ -182,6 +184,11 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
maybeApplyNewThemeRightNow(null, null, prev);
}
private void onEnhanceTextSizeClick(){
toggleCheckableItem(enhanceTextSizeItem);
restartActivityToApplyNewTheme();
}
private void onAppearanceClick(ListItem<?> item_){
int selected=switch(GlobalUserPreferences.theme){
case LIGHT -> 0;

View File

@@ -17,6 +17,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -35,24 +36,27 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
setTitle(R.string.sk_settings_instance);
AccountSession s=AccountSessionManager.get(accountID);
lp=s.getLocalPreferences();
onDataLoaded(List.of(
ArrayList<ListItem<Void>> items=new ArrayList<>(List.of(
new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_fluent_server_24_regular, this::onServerClick),
new ListItem<>(R.string.sk_settings_profile, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/profile")),
new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")),
new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true),
contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, i->onContentTypeClick()),
defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true),
emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, i->onEmojiReactionsClick()),
showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true),
localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, i->onLocalOnlyClick()),
glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, i->toggleCheckableItem(glitchModeItem))
));
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
if(!isInstanceIceshrimp()){
items.add(4, contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, i->onContentTypeClick()));
items.add(5, defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true));
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
}
emojiReactionsItem.checkedChangeListener=checked->onEmojiReactionsClick();
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
localOnlyItem.checkedChangeListener=checked->onLocalOnlyClick();
glitchModeItem.isEnabled=localOnlyItem.checked;
onDataLoaded(items);
}
@Override
@@ -61,7 +65,8 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
@Override
protected void onHidden(){
super.onHidden();
lp.contentTypesEnabled=contentTypesItem.checked;
if(contentTypesItem!=null)
lp.contentTypesEnabled=contentTypesItem.checked;
lp.emojiReactionsEnabled=emojiReactionsItem.checked;
lp.localOnlySupported=localOnlyItem.checked;
lp.glitchInstance=glitchModeItem.checked;
@@ -84,7 +89,8 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
private void resetDefaultContentType(){
lp.defaultContentType=defaultContentTypeItem.isEnabled
? ContentType.PLAIN : ContentType.UNSPECIFIED;
? isInstanceIceshrimp() ? ContentType.MISSKEY_MARKDOWN
: ContentType.PLAIN : ContentType.UNSPECIFIED;
defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName();
}

View File

@@ -64,7 +64,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
));
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if(!instance.isAkkoma()){
if(!instance.isAkkoma() && !instance.isIceshrimpJs()){ // hide filter settings on Akkoma and Iceshrimp-JS because the servers don't support the feature
data.add(3, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
}

View File

@@ -26,6 +26,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.UnifiedPushHelper;
import org.unifiedpush.android.connector.UnifiedPush;
import java.time.Instant;
@@ -57,6 +58,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
// MEGALODON
private boolean useUnifiedPush = false;
private boolean hasAnyUnifiedPushDistrib = false;
private CheckableListItem<Void> uniformIconItem, deleteItem, onlyLatestItem, unifiedPushItem;
private CheckableListItem<Void> postsItem, updateItem;
@@ -72,7 +74,8 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
lp=AccountSessionManager.get(accountID).getLocalPreferences();
getPushSubscription();
useUnifiedPush=!UnifiedPush.getDistributor(getContext()).isEmpty();
useUnifiedPush=UnifiedPushHelper.isUnifiedPushEnabled(getContext());
hasAnyUnifiedPushDistrib=UnifiedPushHelper.hasAnyDistributorInstalled(getContext());
onDataLoaded(List.of(
pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_fluent_alert_snooze_24_regular, i->onPauseNotificationsClick(false)),
@@ -94,7 +97,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
));
//only enable when distributors, who can receive notifications, are available
unifiedPushItem.isEnabled=!UnifiedPush.getDistributors(getContext(), new ArrayList<>()).isEmpty();
unifiedPushItem.isEnabled=hasAnyUnifiedPushDistrib;
if (!unifiedPushItem.isEnabled) {
unifiedPushItem.subtitleRes=R.string.sk_settings_unifiedpush_no_distributor_body;
}
@@ -124,7 +127,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
GlobalUserPreferences.save();
lp.keepOnlyLatestNotification=onlyLatestItem.checked;
lp.save();
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
if(needUpdateNotificationSettings && (PushSubscriptionManager.arePushNotificationsAvailable() || useUnifiedPush)){
ps.alerts.mention=mentionsItem.checked;
ps.alerts.reblog=boostsItem.checked;
ps.alerts.favourite=favoritesItem.checked;
@@ -316,12 +319,12 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
bannerText.setText(R.string.notifications_disabled_in_system);
bannerButton.setText(R.string.open_system_notification_settings);
bannerButton.setOnClickListener(v->openSystemNotificationSettings());
}else if(BuildConfig.BUILD_TYPE.equals("fdroidRelease") && UnifiedPush.getDistributor(getContext()).isEmpty()){
}else if(BuildConfig.BUILD_TYPE.equals("fdroidRelease") && useUnifiedPush){
bannerAdapter.setVisible(true);
bannerIcon.setImageResource(R.drawable.ic_fluent_warning_24_filled);
bannerTitle.setVisibility(View.VISIBLE);
bannerTitle.setText(R.string.mo_settings_unifiedpush_warning);
if(UnifiedPush.getDistributors(getContext(), new ArrayList<>()).isEmpty()) {
if(!hasAnyUnifiedPushDistrib) {
bannerText.setText(R.string.mo_settings_unifiedpush_warning_no_distributors);
bannerButton.setText(R.string.info);
bannerButton.setOnClickListener(v->UiUtils.launchWebBrowser(getContext(), "https://unifiedpush.org/"));
@@ -342,23 +345,15 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
}
private void onUnifiedPushClick(){
if(UnifiedPush.getDistributor(getContext()).isEmpty()){
List<String> distributors = UnifiedPush.getDistributors(getContext(), new ArrayList<>());
if(!useUnifiedPush){
List<String> distributors = UnifiedPush.getDistributors(getContext());
showUnifiedPushRegisterDialog(distributors);
return;
}
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()) {
UnifiedPush.unregisterApp(
getContext(),
accountSession.getID()
);
//re-register to fcm
accountSession.getPushSubscriptionManager().registerAccountForPush(getPushSubscription());
}
UnifiedPushHelper.unregisterAllAccounts(getContext());
unifiedPushItem.toggle();
rebindItem(unifiedPushItem);
useUnifiedPush = false;
}
private void showUnifiedPushRegisterDialog(List<String> distributors){
@@ -366,16 +361,10 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
(dialog, which)->{
String userDistrib = distributors.get(which);
UnifiedPush.saveDistributor(getContext(), userDistrib);
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
UnifiedPush.registerApp(
getContext(),
accountSession.getID(),
new ArrayList<>(),
getContext().getPackageName()
);
}
UnifiedPushHelper.registerAllAccounts(getContext());
unifiedPushItem.toggle();
rebindItem(unifiedPushItem);
useUnifiedPush = true;
}).setOnCancelListener(d->rebindItem(unifiedPushItem)).show();
}

View File

@@ -50,11 +50,11 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
if(reactions==null) reactions=new ArrayList<>();
}
public Status toStatus() {
public Status toStatus(boolean isIceshrimp) {
Status s=Status.ofFake(id, content, publishedAt);
s.createdAt=startsAt != null ? startsAt : publishedAt;
s.reactions=reactions;
if(updatedAt != null) s.editedAt=updatedAt;
if(updatedAt != null && (!isIceshrimp || !updatedAt.equals(publishedAt))) s.editedAt=updatedAt;
return s;
}

View File

@@ -34,6 +34,6 @@ public enum ContentType {
}
public boolean supportedByInstance(Instance i) {
return i.isAkkoma() || (this!=BBCODE && this!=MISSKEY_MARKDOWN);
return i.isAkkoma() || i.isIceshrimp() || (this!=BBCODE && this!=MISSKEY_MARKDOWN);
}
}

View File

@@ -22,6 +22,7 @@ public class EmojiReaction {
public String staticUrl;
public transient ImageLoaderRequest request;
public transient boolean pendingChange=false;
public String getUrl(boolean playGifs){
String idealUrl=playGifs ? url : staticUrl;
@@ -38,7 +39,7 @@ public class EmojiReaction {
reaction.staticUrl=info.staticUrl;
reaction.accounts=new ArrayList<>(Collections.singleton(me));
reaction.accountIds=new ArrayList<>(Collections.singleton(me.id));
reaction.request=new UrlImageLoaderRequest(info.url, V.sp(24), V.sp(24));
reaction.request=new UrlImageLoaderRequest(info.url, 0, V.sp(24));
return reaction;
}
@@ -60,4 +61,18 @@ public class EmojiReaction {
accounts.add(self);
accountIds.add(self.id);
}
public EmojiReaction copy() {
EmojiReaction r=new EmojiReaction();
r.accounts=accounts;
r.accountIds=accountIds;
r.count=count;
r.me=me;
r.name=name;
r.url=url;
r.staticUrl=staticUrl;
r.request=request;
r.pendingChange=pendingChange;
return r;
}
}

View File

@@ -146,14 +146,28 @@ public class Instance extends BaseModel{
return ci;
}
// This method has almost exclusively been used to improve support for
// Akkoma with no regard for Pleroma, hence its name. However, it is
// more likely than not that most uses should also apply to Pleroma,
// so checking for that too probably causes more good than harm.
public boolean isAkkoma() {
return pleroma != null;
return version.contains("compatible; Akkoma") || version.contains("compatible; Pleroma");
}
public boolean isPixelfed() {
return version.contains("compatible; Pixelfed");
}
// For both Iceshrimp-JS and Iceshrimp.NET
public boolean isIceshrimp() {
return version.contains("compatible; Iceshrimp");
}
// Only for Iceshrimp-JS
public boolean isIceshrimpJs() {
return version.contains("compatible; Iceshrimp "); // Iceshrimp.NET will not have a space immediately after
}
public boolean hasFeature(Feature feature) {
Optional<List<String>> pleromaFeatures = Optional.ofNullable(pleroma)
.map(p -> p.metadata)
@@ -219,6 +233,7 @@ public class Instance extends BaseModel{
public StatusesConfiguration statuses;
public MediaAttachmentsConfiguration mediaAttachments;
public PollsConfiguration polls;
public ReactionsConfiguration reactions;
}
@Parcel
@@ -242,8 +257,14 @@ public class Instance extends BaseModel{
public static class PollsConfiguration{
public int maxOptions;
public int maxCharactersPerOption;
public int minExpiration;
public int maxExpiration;
public long minExpiration;
public long maxExpiration;
}
@Parcel
public static class ReactionsConfiguration {
public int maxReactions;
public String defaultReaction;
}
@Parcel

View File

@@ -1,17 +1,27 @@
package org.joinmastodon.android.model;
import android.util.Patterns;
import androidx.annotation.NonNull;
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.model.Poll.Option;
import org.parceler.Parcel;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Parcel
public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
private static final Pattern HIGHLIGHT_PATTER=Pattern.compile("(?<!\\w)(?:@([a-z0-9_]+)(@[a-z0-9_\\.\\-]*)?|#([^\\s.]+)|:([a-z0-9_]+))|" +Patterns.WEB_URL, Pattern.CASE_INSENSITIVE);
@RequiredField
public String id;
@RequiredField
@@ -87,7 +97,61 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
s.visibility=params.visibility;
s.language=params.language;
s.sensitive=params.sensitive;
// hide media preview only if status is marked as sensitive
s.sensitiveRevealed=!params.sensitive;
if(params.poll!=null) s.poll=params.poll.toPoll();
return s;
}
/**
* Creates a fake status, which has (somewhat) correctly formatted mentions, hashtags and URLs.
*
* @param accountID the ID of the account
* @return the formatted Status object
*/
public Status toFormattedStatus(String accountID){
AccountSession self=AccountSessionManager.get(accountID);
Status s=this.toStatus();
// the mastodon api does not return formatted (html) content, only the raw content, so we modify it
s.content=s.content.replace("\n", "<br>");
if(!s.content.contains("@") && !s.content.contains("#") && !s.content.contains(":"))
return s;
StringBuffer sb=new StringBuffer();
Matcher matcher=HIGHLIGHT_PATTER.matcher(s.content);
// I'm sure this will cause problems at some point...
while(matcher.find()){
String content=matcher.group();
String href="";
// add relevant links, so on-click actions work
// hashtags are done by the parser
if(content.startsWith("@"))
href=" href=\""+formatMention(content, self.domain)+"\" class=\"u-url mention\"";
else if(content.startsWith("https://"))
href=" href=\""+content+"\"";
matcher.appendReplacement(sb, "<a"+href+">"+content+"</a>");
}
matcher.appendTail(sb);
s.content=sb.toString();
return s;
}
/**
* Converts a string mention into a URL of the account.
* @param mention Mention in the form a of user name with an optional instance URL
* @param instanceURL URL of the home instance of the user
* @return Formatted HTML or the mention
*/
@NonNull
private static String formatMention(@NonNull String mention, @NonNull String instanceURL){
String[] parts=mention.split("@");
if(parts.length>1){
String username=parts[1];
String domain=parts.length==3 ? parts[2] : instanceURL;
return "https://"+domain+"/@"+username;
}
return mention;
}
}

View File

@@ -47,7 +47,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public String uri;
// @RequiredField // sometimes null on calckey
public Instant createdAt;
@RequiredField
// @RequiredField // sometimes null? Gonna make sure to check everytime. TODO: make account field required again
public Account account;
// @RequiredField
public String content;
@@ -62,8 +62,8 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public List<Mention> mentions;
@RequiredField
public List<Hashtag> tags;
@RequiredField
public List<Emoji> emojis;
// @RequiredField // sometimes null on glitch-soc? TODO: make this field required again
public List<Emoji> emojis = new ArrayList<>();
public long reblogsCount;
public long favouritesCount;
public long repliesCount;

View File

@@ -336,6 +336,7 @@ public class TimelineDefinition {
THUNDERSTORM(R.drawable.ic_fluent_weather_thunderstorm_24_regular, R.string.sk_icon_thunderstorm),
RAIN(R.drawable.ic_fluent_weather_rain_24_regular, R.string.sk_icon_rain),
SNOWFLAKE(R.drawable.ic_fluent_weather_snowflake_24_regular, R.string.sk_icon_snowflake),
GNOME(R.drawable.ic_gnome_logo, R.string.mo_icon_gnome),
HOME(R.drawable.ic_fluent_home_24_regular, R.string.sk_timeline_home, true),
LOCAL(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),

View File

@@ -1,15 +1,15 @@
package org.joinmastodon.android.model;
import org.joinmastodon.android.api.AllFieldsAreRequired;
import org.joinmastodon.android.api.RequiredField;
/**
* Represents an OAuth token used for authenticating with the API and performing actions.
*/
@AllFieldsAreRequired
public class Token extends BaseModel{
/**
* An OAuth token to be used for authorization.
*/
@RequiredField
public String accessToken;
/**
* The OAuth token type. Mastodon uses Bearer tokens.
@@ -23,5 +23,6 @@ public class Token extends BaseModel{
* When the token was generated.
* (unixtime)
*/
@RequiredField
public long createdAt;
}

View File

@@ -225,8 +225,8 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
cover.setImageDrawable(image);
}else{
item.emojiHelper.setImageDrawable(index-2, image);
name.invalidate();
bio.invalidate();
name.setText(name.getText());
bio.setText(bio.getText());
}
if(image instanceof Animatable && !((Animatable) image).isRunning())
((Animatable) image).start();

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.ui.displayitems;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Paint;
@@ -33,16 +34,24 @@ import org.joinmastodon.android.api.requests.statuses.PleromaDeleteStatusReactio
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.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiReaction;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.CustomEmojiPopupKeyboard;
import org.joinmastodon.android.ui.utils.TextDrawable;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.joinmastodon.android.ui.views.EmojiReactionButton;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
@@ -62,6 +71,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
private final boolean hideEmpty, forAnnouncement, playGifs;
private final String accountID;
private static final float ALPHA_DISABLED=0.55f;
private boolean forceShow=false;
public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideEmpty, boolean forAnnouncement) {
super(parentID, parentFragment);
@@ -90,6 +100,10 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
}
public boolean isHidden(){
if(forceShow){
forceShow=false;
return false;
}
return status.reactions.isEmpty() && hideEmpty;
}
@@ -101,7 +115,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
vh.btn.setAlpha(visible ? ALPHA_DISABLED : 1);
}
private MastodonAPIRequest<?> createRequest(String name, int count, boolean delete, Holder.EmojiReactionViewHolder vh, Runnable cb, Runnable err){
private MastodonAPIRequest<?> createRequest(String name, int count, boolean delete, Holder.EmojiReactionViewHolder vh, Consumer<Status> cb, Runnable err){
setActionProgressVisible(vh, true);
boolean ak=parentFragment.isInstanceAkkoma();
boolean keepSpinning=delete && count == 1;
@@ -113,7 +127,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
@Override
public void onSuccess(Object result){
if(!keepSpinning) setActionProgressVisible(vh, false);
cb.run();
cb.accept(null);
}
@Override
public void onError(ErrorResponse error){
@@ -130,7 +144,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
@Override
public void onSuccess(Status result){
if(!keepSpinning) setActionProgressVisible(vh, false);
cb.run();
cb.accept(result);
}
@Override
public void onError(ErrorResponse error){
@@ -151,6 +165,8 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
private final ProgressBar progress;
private final EmojiReactionsAdapter adapter;
private final ListImageLoaderWrapper imgLoader;
private int meReactionCount=0;
private Instance instance;
public Holder(Activity activity, ViewGroup parent) {
super(activity, R.layout.display_item_emoji_reactions, parent);
@@ -171,8 +187,15 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
if(emojiKeyboard != null) root.removeView(emojiKeyboard.getView());
addButton.setSelected(false);
AccountSession session=item.parentFragment.getSession();
instance=item.parentFragment.getInstance().get();
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){
meReactionCount=(int) item.status.reactions.stream().filter(r->r.me).count();
boolean canReact=meReactionCount<instance.configuration.reactions.maxReactions;
addButton.setClickable(canReact);
addButton.setAlpha(canReact ? 1 : ALPHA_DISABLED);
}
item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), V.sp(24), V.sp(24))
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), 0, V.sp(24))
: null);
emojiKeyboard=new CustomEmojiPopupKeyboard(
(Activity) item.parentFragment.getContext(),
@@ -182,18 +205,34 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
emojiKeyboard.setListener(this);
space.setVisibility(View.GONE);
root.addView(emojiKeyboard.getView());
boolean hidden=item.isHidden();
root.setVisibility(hidden ? View.GONE : View.VISIBLE);
line.setVisibility(hidden ? View.GONE : View.VISIBLE);
updateVisibility(item.isHidden(), true);
imgLoader.updateImages();
adapter.notifyDataSetChanged();
if(!GlobalUserPreferences.showDividers || item.isHidden())
return;
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
if(next!=null && !next.parentID.equals(item.parentID)) next=null;
if(next instanceof ExtendedFooterStatusDisplayItem)
itemView.setPadding(0, 0, 0, V.dp(12));
else
itemView.setPadding(0, 0, 0, 0);
}
private void updateVisibility(boolean hidden, boolean force){
int visibility=hidden ? View.GONE : View.VISIBLE;
if(!force && visibility==root.getVisibility())
return;
root.setVisibility(visibility);
line.setVisibility(visibility);
line.setPadding(
list.getPaddingLeft(),
hidden ? 0 : V.dp(8),
list.getPaddingRight(),
item.forAnnouncement ? V.dp(8) : 0
);
imgLoader.updateImages();
adapter.notifyDataSetChanged();
}
}
private void hideEmojiKeyboard(){
space.setVisibility(View.GONE);
@@ -244,19 +283,32 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
}
}
EmojiReaction finalExisting=existing;
item.createRequest(emoji, existing==null ? 1 : existing.count, false, null, ()->{
item.createRequest(emoji, existing==null ? 1 : existing.count, false, null, (status)->{
resetBtn.run();
if(finalExisting==null){
int pos=item.status.reactions.size();
int pos=status.reactions.stream()
.filter(r->r.name.equals(info!=null ? info.shortcode : emoji))
.findFirst()
.map(r->status.reactions.indexOf(r))
.orElse(item.status.reactions.size());
boolean previouslyEmpty=item.status.reactions.isEmpty();
item.status.reactions.add(pos, info!=null ? EmojiReaction.of(info, me) : EmojiReaction.of(emoji, me));
adapter.notifyItemRangeInserted(pos, 1);
if(previouslyEmpty)
adapter.notifyItemChanged(pos);
else
adapter.notifyItemInserted(pos);
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
scroller.setTargetPosition(pos);
list.getLayoutManager().startSmoothScroll(scroller);
updateMeReactionCount(false);
}else{
finalExisting.add(me);
adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting));
}
if(instance.isIceshrimpJs() && status!=null){
item.parentFragment.onFavoriteChanged(status, getItemID());
E.post(new StatusCountersUpdatedEvent(status));
}
E.post(new EmojiReactionsUpdatedEvent(item.status.id, item.status.reactions, countBefore==0, adapter.parentHolder));
}, resetBtn).exec(item.accountID);
}
@@ -278,6 +330,99 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
}
}
private void updateAddButtonClickable() {
if(instance==null || instance.configuration==null || instance.configuration.reactions==null || instance.configuration.reactions.maxReactions==0)
return;
boolean canReact=meReactionCount<instance.configuration.reactions.maxReactions;
addButton.setClickable(canReact);
ObjectAnimator anim=ObjectAnimator.ofFloat(
addButton, View.ALPHA,
canReact ? ALPHA_DISABLED : 1,
canReact ? 1 : ALPHA_DISABLED);
anim.setDuration(200);
anim.start();
}
private void updateMeReactionCount(boolean deleting) {
meReactionCount=Math.max(0, meReactionCount + (deleting ? -1 : 1));
updateAddButtonClickable();
}
public void updateReactions(List<EmojiReaction> reactions){
item.status.reactions=new ArrayList<>(item.status.reactions); // I don't know how, but this seemingly fixes a bug
List<EmojiReaction> toRemove=new ArrayList<>();
for(int i=0;i<item.status.reactions.size();i++){
EmojiReaction reaction=item.status.reactions.get(i);
Optional<EmojiReaction> newReactionOptional=reactions.stream().filter(r->r.name.equals(reaction.name)).findFirst();
if(newReactionOptional.isEmpty()){ // deleted reactions
toRemove.add(reaction);
continue;
}
// changed reactions
EmojiReaction newReaction=newReactionOptional.get();
if(reaction.count!=newReaction.count || reaction.me!=newReaction.me || reaction.pendingChange!=newReaction.pendingChange){
if(newReaction.pendingChange){
View holderView=list.getChildAt(i);
if(holderView!=null){
EmojiReactionViewHolder reactionHolder=(EmojiReactionViewHolder) list.getChildViewHolder(holderView);
item.setActionProgressVisible(reactionHolder, true);
}
}else{
item.status.reactions.set(i, newReaction);
adapter.notifyItemChanged(i);
}
}
}
Collections.reverse(toRemove);
for(EmojiReaction r:toRemove){
int index=item.status.reactions.indexOf(r);
item.status.reactions.remove(index);
adapter.notifyItemRemoved(index);
}
boolean pendingAddReaction=false;
for(int i=0;i<reactions.size();i++){
EmojiReaction reaction=reactions.get(i);
if(item.status.reactions.stream().anyMatch(r->r.name.equals(reaction.name)))
continue;
// new reactions
if(reaction.pendingChange){
pendingAddReaction=true;
item.forceShow=true;
continue;
}
boolean previouslyEmpty=item.status.reactions.isEmpty();
item.status.reactions.add(i, reaction);
if(previouslyEmpty)
adapter.notifyItemChanged(i);
else
adapter.notifyItemInserted(i);
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
scroller.setTargetPosition(i);
list.getLayoutManager().startSmoothScroll(scroller);
}
if(pendingAddReaction){
progress.setVisibility(View.VISIBLE);
addButton.setClickable(false);
addButton.setAlpha(ALPHA_DISABLED);
}else{
progress.setVisibility(View.GONE);
}
int newMeReactionCount=(int) reactions.stream().filter(r->r.me || r.pendingChange).count();
if (newMeReactionCount!=meReactionCount){
meReactionCount=newMeReactionCount;
updateAddButtonClickable();
}
updateVisibility(reactions.isEmpty() && item.hideEmpty, false);
}
@Override
public void setImage(int index, Drawable image){
View child=list.getChildAt(index);
@@ -330,7 +475,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
}
private static class EmojiReactionViewHolder extends BindableViewHolder<Pair<EmojiReactionsStatusDisplayItem, EmojiReaction>> implements ImageLoaderViewHolder{
private final ProgressBarButton btn;
private final EmojiReactionButton btn;
private final ProgressBar progress;
public EmojiReactionViewHolder(Context context, RecyclerView list){
@@ -342,7 +487,9 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
@Override
public void setImage(int index, Drawable drawable){
drawable.setBounds(0, 0, V.sp(24), V.sp(24));
int height=V.sp(24);
int width=drawable.getIntrinsicWidth()*height/drawable.getIntrinsicHeight();
drawable.setBounds(0, 0, width, height);
btn.setCompoundDrawablesRelative(drawable, null, null, null);
if(drawable instanceof Animatable) ((Animatable) drawable).start();
}
@@ -354,6 +501,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
@Override
public void onBind(Pair<EmojiReactionsStatusDisplayItem, EmojiReaction> item){
if(item.second.pendingChange){
itemView.setVisibility(View.GONE);
return;
}else{
itemView.setVisibility(View.VISIBLE);
}
item.first.setActionProgressVisible(this, false);
EmojiReactionsStatusDisplayItem parent=item.first;
EmojiReaction reaction=item.second;
@@ -369,10 +522,25 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null);
}
btn.setSelected(reaction.me);
if(parent.parentFragment.isInstanceIceshrimpJs() && reaction.name.contains("@")){
btn.setEnabled(false);
btn.setClickable(false);
btn.setLongClickable(true);
}else{
btn.setEnabled(true);
btn.setClickable(true);
}
btn.setOnClickListener(e->{
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
Instance instance = adapter.parentHolder.instance;
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0 &&
adapter.parentHolder.meReactionCount >= instance.configuration.reactions.maxReactions &&
!reaction.me){
return;
}
boolean deleting=reaction.me;
parent.createRequest(reaction.name, reaction.count, deleting, this, ()->{
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
parent.createRequest(reaction.name, reaction.count, deleting, this, (status)->{
for(int i=0; i<parent.status.reactions.size(); i++){
EmojiReaction r=parent.status.reactions.get(i);
if(!r.name.equals(reaction.name)) continue;
@@ -392,6 +560,14 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
adapter.parentHolder.root.setVisibility(View.GONE);
adapter.parentHolder.line.setVisibility(View.GONE);
}
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){
adapter.parentHolder.updateMeReactionCount(deleting);
}
if(instance.isIceshrimpJs() && status!=null){
parent.parentFragment.onFavoriteChanged(status, adapter.parentHolder.getItemID());
E.post(new StatusCountersUpdatedEvent(status));
}
E.post(new EmojiReactionsUpdatedEvent(parent.status.id, parent.status.reactions, parent.status.reactions.isEmpty(), adapter.parentHolder));
adapter.parentHolder.imgLoader.updateImages();
}, null).exec(parent.parentFragment.getAccountID());

View File

@@ -39,6 +39,16 @@ public class ErrorStatusDisplayItem extends StatusDisplayItem{
findViewById(R.id.button_copy_error_details).setOnClickListener(this::copyErrorDetails);
}
@Override
public void onClick(){
// explicitly do nothing when clicked
}
@Override
public boolean isEnabled(){
return false;
}
@Override
public void onBind(ErrorStatusDisplayItem item) {
openInBrowserButton.setEnabled(item.status!=null && item.status.url!=null);

View File

@@ -46,7 +46,6 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class FooterStatusDisplayItem extends StatusDisplayItem{
public final Status status;
private final String accountID;
public boolean hideCounts;
@@ -316,17 +315,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
UiUtils.opacityIn(v);
Bundle args=new Bundle();
args.putString("account", item.accountID);
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(item.accountID);
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
if(instance.pleroma == null){
Instance instance=AccountSessionManager.get(item.accountID).getInstance().get();
if(instance.isAkkoma() || instance.isIceshrimp()){
args.putParcelable("quote", Parcels.wrap(item.status));
}else{
StringBuilder prefilledText = new StringBuilder().append("\n\n");
String ownID = AccountSessionManager.getInstance().getAccount(item.accountID).self.id;
if (!item.status.account.id.equals(ownID)) prefilledText.append('@').append(item.status.account.acct).append(' ');
prefilledText.append(item.status.url);
args.putString("prefilledText", prefilledText.toString());
args.putInt("selectionStart", 0);
}else{
args.putParcelable("quote", Parcels.wrap(item.status));
}
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
});
@@ -335,6 +333,20 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
return true;
}
public void onFavoriteClick() {
favorite.setSelected(item.status.favourited);
favorite.animate().scaleX(0.95f).scaleY(0.95f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
UiUtils.opacityOut(favorite);
favorite.postDelayed(() -> {
favorite.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
UiUtils.opacityIn(favorite);
if(item.status.favourited && !GlobalUserPreferences.reduceMotion && !GlobalUserPreferences.likeIcon) {
favorite.startAnimation(spin);
}
}, 300);
bindText(favorites, item.status.favouritesCount);
}
private void onFavoriteClick(View v){
if(item.status.preview) return;
applyInteraction(v, status -> {

View File

@@ -113,7 +113,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
public static HeaderStatusDisplayItem fromAnnouncement(Announcement a, Status fakeStatus, Account instanceUser, BaseStatusListFragment parentFragment, String accountID, Consumer<String> consumeReadID) {
HeaderStatusDisplayItem item = new HeaderStatusDisplayItem(a.id, instanceUser, a.startsAt, parentFragment, accountID, fakeStatus, null, null, null);
HeaderStatusDisplayItem item = new HeaderStatusDisplayItem(a.id, instanceUser, a.startsAt!=null ? a.startsAt : fakeStatus.createdAt, parentFragment, accountID, fakeStatus, null, null, null);
item.announcement = a;
item.consumeReadAnnouncement = consumeReadID;
return item;
@@ -279,7 +279,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : rel.requested ? R.string.following_user_requested : R.string.unfollowed_user, account.getDisplayUsername()), Toast.LENGTH_SHORT).show();
});
}else if(id==R.id.block_domain){
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account, relationship!=null && relationship.domainBlocking, ()->{});
}else if(id==R.id.bookmark){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
}else if(id==R.id.manage_user_lists){
@@ -422,7 +422,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public void setImage(int index, Drawable drawable){
if(index>0){
item.emojiHelper.setImageDrawable(index-1, drawable);
name.invalidate();
name.setText(name.getText());
}else{
avatar.setImageDrawable(drawable);
}

View File

@@ -141,7 +141,7 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
avatar.setImageDrawable(image);
}else{
item.emojiHelper.setImageDrawable(index-1, image);
text.invalidate();
text.setText(text.getText());
}
if(image instanceof Animatable)
((Animatable) image).start();

View File

@@ -136,7 +136,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
@Override
public void setImage(int index, Drawable image){
item.emojiHelper.setImageDrawable(index, image);
text.invalidate();
text.setText(text.getText());
if(image instanceof Animatable){
((Animatable) image).start();
}
@@ -145,7 +145,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
@Override
public void clearImage(int index){
item.emojiHelper.setImageDrawable(index, null);
text.invalidate();
text.setText(text.getText());
}
private void onButtonClick(View v){

View File

@@ -152,8 +152,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
int firstHelperCount=item.emojiHelper.getImageCount();
CustomEmojiHelper helper=index<firstHelperCount ? item.emojiHelper : item.extra.emojiHelper;
helper.setImageDrawable(firstHelperCount>0 ? index%firstHelperCount : index, image);
text.invalidate();
extraText.invalidate();
text.setText(text.getText());
extraText.setText(extraText.getText());
}
@Override

View File

@@ -114,7 +114,7 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
@Override
public void setImage(int index, Drawable image){
item.emojiHelper.setImageDrawable(index, image);
title.invalidate();
title.setText(title.getText());
}
@Override

View File

@@ -17,8 +17,10 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
@@ -35,6 +37,7 @@ import org.joinmastodon.android.model.FilterResult;
import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.model.Status;
@@ -87,7 +90,7 @@ public abstract class StatusDisplayItem{
private final static Pattern QUOTE_MENTION_PATTERN=Pattern.compile("(?:<p>)?\\s?(?:RE:\\s?(<br\\s?\\/?>)?)?<a href=\"https:\\/\\/[^\"]+\"[^>]*><span class=\"invisible\">https:\\/\\/<\\/span><span class=\"ellipsis\">[^<]+<\\/span><span class=\"invisible\">[^<]+<\\/span><\\/a>(?:<\\/p>)?$");
private final static Pattern QUOTE_PATTERN=Pattern.compile("[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$");
private final static Pattern QUOTE_PATTERN=Pattern.compile("https://[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,8}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$");
public void setAncestryInfo(
boolean hasDescendantNeighbor,
@@ -180,6 +183,11 @@ public abstract class StatusDisplayItem{
try{
ScheduledStatus scheduledStatus=parentObject instanceof ScheduledStatus s ? s : null;
// Check if account is null. This should never happen, but it seems to do in latest versions of glitch-soc
if (scheduledStatus == null && status.account == null || (status.reblog != null && status.reblog.account == null) || (status.quote != null && status.quote.account == null)) {
throw new Exception("status " + status.url + " has null account field");
}
HeaderStatusDisplayItem header=null;
boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts;
@@ -271,8 +279,10 @@ public abstract class StatusDisplayItem{
contentItems=items;
}
if(statusForContent.quote!=null){
if(statusForContent.quote!=null) {
int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:");
if(quoteInlineIndex==-1)
quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br><br>RE:");
if(quoteInlineIndex!=-1)
statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
else {
@@ -338,8 +348,8 @@ public abstract class StatusDisplayItem{
if(!statusForContent.mediaAttachments.isEmpty() && statusForContent.poll==null) // add spacing if immediately preceded by attachment
contentItems.add(new DummyStatusDisplayItem(parentID, fragment));
contentItems.addAll(buildItems(fragment, statusForContent.quote, accountID, parentObject, knownAccounts, filterContext, FLAG_NO_FOOTER|FLAG_INSET|FLAG_NO_EMOJI_REACTIONS|FLAG_IS_FOR_QUOTE));
} else if((flags & FLAG_INSET)==0){
tryAddNonOfficialQuote(statusForContent, fragment, accountID);
} else if((flags & FLAG_INSET)==0 && statusForContent.mediaAttachments.isEmpty() && statusForContent.account!=null){
tryAddNonOfficialQuote(statusForContent, fragment, accountID, filterContext);
}
if(contentItems!=items && statusForContent.spoilerRevealed){
items.addAll(contentItems);
@@ -421,29 +431,55 @@ public abstract class StatusDisplayItem{
* Tries to adds a non-official quote to a status.
* A non-official quote is a quote on an instance that does not support quotes officially.
*/
private static void tryAddNonOfficialQuote(Status status, BaseStatusListFragment fragment, String accountID) {
private static void tryAddNonOfficialQuote(Status status, BaseStatusListFragment fragment, String accountID, FilterContext filterContext) {
Matcher matcher=QUOTE_PATTERN.matcher(status.getStrippedText());
if(!matcher.find())
return;
String quoteURL="https://"+matcher.group();
String quoteURL=matcher.group();
if (UiUtils.looksLikeFediverseUrl(quoteURL)) {
new GetSearchResults(quoteURL, GetSearchResults.Type.STATUSES, true, null, 0, 0).setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults results){
if (!results.statuses.isEmpty()){
status.quote=results.statuses.get(0);
fragment.updateStatusWithQuote(status);
}
}
// account may be null for scheduled posts
if (!UiUtils.looksLikeFediverseUrl(quoteURL))
return;
@Override
public void onError(ErrorResponse error){
Log.w("StatusDisplayItem", "onError: failed to find quote status with URL: " + quoteURL + " " + error);
}
}).exec(accountID);
}
new GetSearchResults(quoteURL, GetSearchResults.Type.STATUSES, true, null, 0, 0).setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults results){
AccountSessionManager.get(accountID).filterStatuses(results.statuses, filterContext);
if (results.statuses == null || results.statuses.isEmpty())
return;
Status quote=results.statuses.get(0);
new GetAccountRelationships(Collections.singletonList(quote.account.id))
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Relationship> relationships){
if(relationships.isEmpty())
return;
Relationship relationship=relationships.get(0);
String selfId=AccountSessionManager.get(accountID).self.id;
if(!status.account.id.equals(selfId) && (relationship.domainBlocking || relationship.muting || relationship.blocking)) {
// do not show posts that are quoting a muted/blocked user
fragment.removeStatus(status);
return;
}
status.quote=results.statuses.get(0);
fragment.updateStatusWithQuote(status);
}
@Override
public void onError(ErrorResponse error){}
})
.exec(accountID);
}
@Override
public void onError(ErrorResponse error){
Log.w("StatusDisplayItem", "onError: failed to find quote status with URL: " + quoteURL + " " + error);
}
}).exec(accountID);
}
public enum Type{

View File

@@ -173,7 +173,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public void setImage(int index, Drawable image){
getEmojiHelper().setImageDrawable(index, image);
text.invalidate();
text.setText(text.getText());
if(image instanceof Animatable){
((Animatable) image).start();
if(image instanceof MovieDrawable)
@@ -184,7 +184,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public void clearImage(int index){
getEmojiHelper().setImageDrawable(index, null);
text.invalidate();
text.setText(text.getText());
}
private CustomEmojiHelper getEmojiHelper(){

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.ui.sheets;
import android.app.AlertDialog;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
@@ -15,6 +16,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.drawables.EmptyDrawable;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.AutoOrientationLinearLayout;
@@ -23,6 +25,11 @@ import org.joinmastodon.android.ui.views.ProgressBarButton;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.BottomSheet;
@@ -108,6 +115,51 @@ public abstract class AccountRestrictionConfirmationSheet extends BottomSheet{
addRow(icon, getContext().getString(text));
}
public void addDurationRow(@NonNull Context context, AtomicReference<Duration> muteDuration) {
//Moshidon: add row to choose a duration, e.g. for muting accounts
Button muteDurationBtn=new Button(getContext());
muteDurationBtn.setOnClickListener(v->getMuteDurationDialog(context, muteDuration, muteDurationBtn).show());
muteDurationBtn.setText(R.string.sk_duration_indefinite);
addRow(R.drawable.ic_fluent_clock_20_regular, R.string.sk_mute_label, muteDurationBtn);
}
@NonNull
private M3AlertDialogBuilder getMuteDurationDialog(@NonNull Context context, AtomicReference<Duration> muteDuration, Button button){
M3AlertDialogBuilder builder=new M3AlertDialogBuilder(context);
builder.setTitle(R.string.sk_mute_label);
builder.setIcon(R.drawable.ic_fluent_clock_20_regular);
List<Duration> durations =List.of(Duration.ZERO,
Duration.ofMinutes(5),
Duration.ofMinutes(30),
Duration.ofHours(1),
Duration.ofHours(6),
Duration.ofDays(1),
Duration.ofDays(3),
Duration.ofDays(7),
Duration.ofDays(7));
String[] choices = {context.getString(R.string.sk_duration_indefinite),
context.getString(R.string.sk_duration_minutes_5),
context.getString(R.string.sk_duration_minutes_30),
context.getString(R.string.sk_duration_hours_1),
context.getString(R.string.sk_duration_hours_6),
context.getString(R.string.sk_duration_days_1),
context.getString(R.string.sk_duration_days_3),
context.getString(R.string.sk_duration_days_7)};
builder.setSingleChoiceItems(choices, durations.indexOf(muteDuration.get()), (dialog, which) -> {});
builder.setPositiveButton(R.string.ok, (dialog, which)->{
int selected = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
muteDuration.set(durations.get(selected));
button.setText(choices[selected]);
});
builder.setNegativeButton(R.string.cancel, null);
return builder;
}
public interface ConfirmCallback{
void onConfirmed(Runnable onSuccess, Runnable onError);
}

View File

@@ -0,0 +1,36 @@
package org.joinmastodon.android.ui.sheets;
import android.content.Context;
import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import androidx.annotation.NonNull;
public class BlockDomainConfirmationSheet extends AccountRestrictionConfirmationSheet{
public BlockDomainConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback, ConfirmCallback blockUserConfirmCallback){
super(context, user, confirmCallback);
titleView.setText(R.string.block_domain_confirm_title);
confirmBtn.setText(R.string.do_block_server);
secondaryBtn.setText(context.getString(R.string.block_user_x_instead, user.getDisplayUsername()));
icon.setImageResource(R.drawable.ic_fluent_shield_24_regular);
subtitleView.setText(user.getDomain());
addRow(R.drawable.ic_campaign_24px, R.string.users_cant_see_blocked);
addRow(R.drawable.ic_fluent_eye_off_24_regular, R.string.you_wont_see_server_posts);
addRow(R.drawable.ic_fluent_person_delete_24_regular, R.string.server_followers_will_be_removed);
addRow(R.drawable.ic_fluent_arrow_reply_24_regular, R.string.server_cant_mention_or_follow_you);
addRow(R.drawable.ic_fluent_history_24_regular, R.string.server_can_interact_with_older);
secondaryBtn.setOnClickListener(v->{
if(loading)
return;
loading=true;
secondaryBtn.setProgressBarVisible(true);
blockUserConfirmCallback.onConfirmed(this::dismiss, ()->{
secondaryBtn.setProgressBarVisible(false);
loading=false;
});
});
}
}

View File

@@ -1,19 +1,10 @@
package org.joinmastodon.android.ui.sheets;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.views.M3Switch;
import java.time.Duration;
@@ -44,59 +35,6 @@ public class MuteAccountConfirmationSheet extends AccountRestrictionConfirmation
addRow(R.drawable.ic_fluent_alert_off_24_regular, R.string.mo_mute_notifications, m3Switch);
// add mute duration (Moshidon)
secondaryBtn.setVisibility(View.VISIBLE);
secondaryBtn.setOnClickListener(v->getMuteDurationDialog(context, muteDuration, secondaryBtn).show());
secondaryBtn.setText(R.string.sk_duration_indefinite);
secondaryBtn.setTypeface(null, Typeface.BOLD_ITALIC);
addDurationRow(context, muteDuration);
}
@NonNull
private M3AlertDialogBuilder getMuteDurationDialog(@NonNull Context context, AtomicReference<Duration> muteDuration, Button button){
M3AlertDialogBuilder builder=new M3AlertDialogBuilder(context);
builder.setTitle(R.string.sk_mute_label);
builder.setIcon(R.drawable.ic_fluent_clock_20_regular);
String[] choices = {context.getString(R.string.sk_duration_indefinite),
context.getString(R.string.sk_duration_minutes_5),
context.getString(R.string.sk_duration_minutes_30),
context.getString(R.string.sk_duration_hours_1),
context.getString(R.string.sk_duration_hours_6),
context.getString(R.string.sk_duration_days_1),
context.getString(R.string.sk_duration_days_3),
context.getString(R.string.sk_duration_days_7)};
builder.setSingleChoiceItems(choices, 0, (dialog, which) -> {});
builder.setPositiveButton(R.string.ok, (dialog, which)->{
int selected = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
if(selected==0){
muteDuration.set(Duration.ZERO);
}else if(selected==1){
muteDuration.set(Duration.ofMinutes(5));
}else if(selected==2){
muteDuration.set(Duration.ofMinutes(30));
}else if(selected==3){
muteDuration.set(Duration.ofHours(1));
}else if(selected==4){
muteDuration.set(Duration.ofHours(6));
}else if(selected==5){
muteDuration.set(Duration.ofDays(1));
}else if(selected==6){
muteDuration.set(Duration.ofDays(3));
}else if(selected==7){
muteDuration.set(Duration.ofDays(7));
}
if(selected >= 0 && selected <= 7){
button.setText(choices[selected]);
} else {
Toast.makeText(context, "" + selected, Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton(R.string.cancel, ((dialogInterface, i) -> {}));
return builder;
}
}

View File

@@ -0,0 +1,30 @@
package org.joinmastodon.android.ui.sheets;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Hashtag;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;
// MOSHIDON
public class MuteHashtagConfirmationSheet extends AccountRestrictionConfirmationSheet{
public MuteHashtagConfirmationSheet(@NonNull Context context, Account user, AtomicReference<Duration> muteDuration, Hashtag hashtag, ConfirmCallback confirmCallback){
super(context, user, confirmCallback);
titleView.setText(R.string.mo_mute_hashtag);
confirmBtn.setText(R.string.do_mute);
secondaryBtn.setVisibility(View.GONE);
icon.setImageResource(R.drawable.ic_fluent_speaker_off_24_regular);
subtitleView.setText("#"+hashtag.name);
addRow(R.drawable.ic_fluent_number_symbol_24_regular, R.string.mo_mute_hashtag_explanation_muted_home);
addRow(R.drawable.ic_fluent_eye_off_24_regular, R.string.mo_mute_hashtag_explanation_discreet);
addRow(R.drawable.ic_fluent_search_24_regular, R.string.mo_mute_hashtag_explanation_search);
addDurationRow(context, muteDuration);
}
}

View File

@@ -24,7 +24,8 @@ public class CustomEmojiSpan extends ReplacementSpan{
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
return Math.round(paint.descent()-paint.ascent());
int size = Math.round(paint.descent()-paint.ascent());
return drawable!=null ? (int) (drawable.getIntrinsicWidth()*(size/(float) drawable.getIntrinsicHeight())) : size;
}
@Override
@@ -45,7 +46,8 @@ public class CustomEmojiSpan extends ReplacementSpan{
}
canvas.save();
canvas.translate(x, top);
canvas.scale(size/(float)dw, size/(float)dh, 0f, 0f);
float scale = size/(float)dh;
canvas.scale(scale, scale, 0f, 0f);
drawable.draw(canvas);
canvas.restore();
}
@@ -56,7 +58,6 @@ public class CustomEmojiSpan extends ReplacementSpan{
}
public UrlImageLoaderRequest createImageLoaderRequest(){
int size=V.dp(20);
return new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? emoji.url : emoji.staticUrl, size, size);
return new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? emoji.url : emoji.staticUrl, 0, V.dp(20));
}
}

View File

@@ -140,7 +140,7 @@ public class HtmlParser{
String href=el.attr("href");
LinkSpan.Type linkType;
String text=el.text();
if(el.hasClass("hashtag") || text.startsWith("#")){
if(!TextUtils.isEmpty(text) && (el.hasClass("hashtag") || text.startsWith("#"))){
// MOSHIDON: we have slightly refactored this so that the hashtags properly work in akkoma
// TODO: upstream this
linkType=LinkSpan.Type.HASHTAG;

View File

@@ -123,6 +123,7 @@ import org.joinmastodon.android.ui.Snackbar;
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
import org.joinmastodon.android.ui.sheets.BlockAccountConfirmationSheet;
import org.joinmastodon.android.ui.sheets.MuteAccountConfirmationSheet;
import org.joinmastodon.android.ui.sheets.BlockDomainConfirmationSheet;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.utils.Tracking;
@@ -417,7 +418,6 @@ public class UiUtils {
CustomEmojiSpan[] spans = text.getSpans(0, text.length(), CustomEmojiSpan.class);
if (spans.length == 0)
return;
int emojiSize = V.dp(20);
Map<Emoji, List<CustomEmojiSpan>> spansByEmoji = Arrays.stream(spans).collect(Collectors.groupingBy(s -> s.emoji));
for (Map.Entry<Emoji, List<CustomEmojiSpan>> emoji : spansByEmoji.entrySet()) {
ViewImageLoader.load(new ViewImageLoader.Target() {
@@ -428,14 +428,14 @@ public class UiUtils {
for (CustomEmojiSpan span : emoji.getValue()) {
span.setDrawable(d);
}
view.invalidate();
view.setText(view.getText());
}
@Override
public View getView() {
return view;
}
}, null, new UrlImageLoaderRequest(emoji.getKey().url, emojiSize, emojiSize), null, false, true);
}, null, new UrlImageLoaderRequest(emoji.getKey().url, 0, V.dp(20)), null, false, true);
}
}
@@ -575,27 +575,61 @@ public class UiUtils {
);
}
public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback) {
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title),
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain),
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
R.drawable.ic_fluent_shield_28_regular,
() -> {
new SetDomainBlocked(domain, !currentlyBlocked)
.setCallback(new Callback<>() {
@Override
public void onSuccess(Object result) {
resultCallback.run();
}
public static void confirmToggleBlockDomain(Activity activity, String accountID, Account account, boolean currentlyBlocked, Runnable resultCallback){
if(!currentlyBlocked){
new BlockDomainConfirmationSheet(activity, account, (onSuccess, onError)->{
new SetDomainBlocked(account.getDomain(), true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
resultCallback.run();
onSuccess.run();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
});
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
onError.run();
}
})
.exec(accountID);
}, (onSuccess, onError)->{
new SetAccountBlocked(account.id, true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.run();
onSuccess.run();
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
onError.run();
}
})
.exec(accountID);
}).show();
}else{
new SetDomainBlocked(account.getDomain(), false)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
resultCallback.run();
new Snackbar.Builder(activity)
.setText(activity.getString(R.string.unblocked_domain_x, account.getDomain()))
.show();
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
}
}
public static void confirmToggleMuteUser(Context context, String accountID, Account account, boolean currentlyMuted, Consumer<Relationship> resultCallback){
if(!currentlyMuted){
@@ -974,6 +1008,9 @@ public class UiUtils {
}
public static <T> void updateList(List<T> oldList, List<T> newList, RecyclerView list, RecyclerView.Adapter<?> adapter, BiPredicate<T, T> areItemsSame) {
RecyclerView.ItemAnimator animator=list.getItemAnimator();
if(animator!=null)
animator.endAnimations();
// Save topmost item position and offset because for some reason RecyclerView would scroll the list to weird places when you insert items at the top
int topItem, topItemOffset;
if (list.getChildCount() == 0) {
@@ -1184,6 +1221,10 @@ public class UiUtils {
return false;
}
// Akkoma somehow makes this necessary, because youtube links look like posts. And because it may trigger too many requests.
if (uri.getHost().toLowerCase().contains("youtube.com") || uri.getHost().toLowerCase().contains("youtu.be"))
return false;
if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null)
return false;

View File

@@ -185,8 +185,8 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
avatar.setImageDrawable(image);
}else{
item.emojiHelper.setImageDrawable(index-1, image);
name.invalidate();
bio.invalidate();
name.setText(name.getText());
bio.setText(bio.getText());
}
if(image instanceof Animatable a && !a.isRunning())
@@ -312,7 +312,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
}else if(id==R.id.open_in_browser){
UiUtils.launchWebBrowser(fragment.getActivity(), account.url);
}else if(id==R.id.block_domain){
UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account, relationship.domainBlocking, ()->{
relationship.domainBlocking=!relationship.domainBlocking;
bindRelationship();
});

View File

@@ -0,0 +1,34 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
public class EmojiReactionButton extends ProgressBarButton {
private final Handler handler=new Handler();
public EmojiReactionButton(Context context){
super(context);
}
public EmojiReactionButton(Context context, AttributeSet attrs){
super(context, attrs);
}
public EmojiReactionButton(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// allow long click even if button is disabled
int action=event.getAction();
if(action==MotionEvent.ACTION_DOWN && !isEnabled())
handler.postDelayed(this::performLongClick, ViewConfiguration.getLongPressTimeout());
if(action==MotionEvent.ACTION_UP)
handler.removeCallbacksAndMessages(null);
return super.onTouchEvent(event);
}
}

View File

@@ -64,7 +64,7 @@ public class Tracking{
@NonNull
public static String removeTrackingParameters(@NonNull String url){
Uri uri=Uri.parse(url);
if(uri==null)
if(uri==null || !uri.isHierarchical())
return url;
Uri.Builder uriBuilder=uri.buildUpon().clearQuery();

View File

@@ -0,0 +1,59 @@
package org.joinmastodon.android.utils;
import android.content.Context;
import android.widget.Toast;
import androidx.annotation.NonNull;
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.unifiedpush.android.connector.UnifiedPush;
public class UnifiedPushHelper {
/**
* @param context
* @return `true` if UnifiedPush is used
*/
public static boolean isUnifiedPushEnabled(@NonNull Context context) {
return UnifiedPush.getAckDistributor(context) != null;
}
/**
* If any distributor is installed on the device
* @param context
* @return `true` if at least one is installed
*/
public static boolean hasAnyDistributorInstalled(@NonNull Context context) {
return !UnifiedPush.getDistributors(context).isEmpty();
}
public static void registerAllAccounts(@NonNull Context context) {
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
// Sometimes this is null when the account's server has died (don't ask me how I know this)
if (accountSession.app.vapidKey == null) {
// TODO: throw this on a translatable string and tell the user to log out and back in
Toast.makeText(context, "Error on unified push subscription: no valid vapid key for account " + accountSession.getFullUsername(), Toast.LENGTH_LONG).show();
break;
}
UnifiedPush.register(
context,
accountSession.getID(),
null,
accountSession.app.vapidKey.replaceAll("=","")
);
}
}
public static void unregisterAllAccounts(@NonNull Context context) {
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
UnifiedPush.unregister(
context,
accountSession.getID()
);
// use FCM again
accountSession.getPushSubscriptionManager().registerAccountForPush(null);
}
}
}

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M17.75,2.007a2.25,2.25 0,0 1,2.245 2.096l0.005,0.154v15.498A2.25,2.25 0,0 1,17.904 22l-0.154,0.005H6.25a2.25,2.25 0,0 1,-2.245 -2.096L4,19.755V4.257a2.25,2.25 0,0 1,2.096 -2.245l0.154,-0.005h11.5ZM7.75,7a0.75,0.75 0,1 0,0 1.5h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5ZM7,11.75c0,0.414 0.336,0.75 0.75,0.75h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5a0.75,0.75 0,0 0,-0.75 0.75ZM7.75,15a0.75,0.75 0,1 0,0 1.5h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5Z"
android:fillColor="#212121"/>
</vector>

View File

@@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="103.97" android:viewportWidth="85.6" android:width="19.759546dp">
<path android:fillColor="@color/fluent_default_icon_tint" android:pathData="m74.46,0c-11.52,0 -18.66,8.37 -20.99,16.54 -1.17,4.08 -1.28,8.19 -0.1,11.64 1.18,3.45 4.25,6.36 8.26,6.36 4,0 7.83,-2.21 11.44,-5.17 3.61,-2.95 6.93,-6.79 9.28,-10.75 2.35,-3.96 3.91,-8.07 2.96,-12.02 -0.48,-1.97 -1.78,-3.83 -3.68,-4.96 -1.91,-1.13 -4.29,-1.63 -7.17,-1.63zM40.1,4c-2.84,0.56 -5.36,2.67 -6.65,5.04 -1.48,2.71 -1.87,5.83 -1.68,8.83 0.19,3 0.98,5.9 2.28,8.28 1.29,2.38 3.22,4.58 6.17,4.76 1.58,0.1 2.97,-0.57 4.03,-1.41 1.06,-0.84 1.92,-1.9 2.67,-3.11 1.52,-2.4 2.64,-5.4 3.26,-8.47 0.62,-3.08 0.76,-6.22 -0.2,-9.03 -0.96,-2.81 -4,-5.3 -7.22,-5.3h-0c-1.04,0.07 -1.77,0.19 -2.65,0.42zM74.46,5.29c2.25,0 3.68,0.42 4.46,0.88 0.78,0.47 1.06,0.89 1.24,1.66 0.37,1.53 -0.39,4.73 -2.37,8.08 -1.98,3.35 -4.99,6.82 -8.08,9.35 -2.76,2.26 -5.64,3.65 -7.48,3.92 -0.22,0.03 -0.42,0.05 -0.6,0.05 -1.77,0 -2.55,-0.75 -3.25,-2.78 -0.7,-2.04 -0.74,-5.24 0.18,-8.46 1.84,-6.45 6.89,-12.7 15.9,-12.7zM42.74,8.87c1.55,0 1.76,0.34 2.24,1.72 0.47,1.38 0.51,3.81 0.02,6.28 -0.49,2.47 -1.48,5 -2.55,6.69 -0.53,0.84 -1.09,1.47 -1.48,1.78 -0.39,0.31 -0.52,0.27 -0.42,0.28 -0.12,-0.01 -1.01,-0.46 -1.85,-2 -0.84,-1.54 -1.5,-3.83 -1.64,-6.09 -0.15,-2.26 0.23,-4.46 1.04,-5.95 0.81,-1.49 1.85,-2.38 3.97,-2.66 0.26,-0.03 0.48,-0.05 0.68,-0.05zM22.67,11.53c-0.99,0.02 -1.98,0.25 -2.95,0.66 -3.04,1.25 -4.97,3.81 -5.62,6.49 -0.65,2.68 -0.26,5.43 0.59,7.93 0.85,2.5 2.17,4.78 3.82,6.51 1.65,1.74 3.85,3.23 6.54,2.69 2.76,-0.56 3.98,-2.91 4.74,-5.16 0.76,-2.25 1.07,-4.85 0.98,-7.46 -0.08,-2.61 -0.54,-5.22 -1.7,-7.46 -1.16,-2.24 -3.49,-4.26 -6.4,-4.2zM22.79,16.82c0.69,-0.02 0.98,0.18 1.59,1.34 0.6,1.16 1.04,3.15 1.11,5.19 0.06,2.04 -0.23,4.17 -0.71,5.61 -0.48,1.44 -1.14,1.73 -0.78,1.66h-0v0c0.09,-0.02 -0.65,-0.1 -1.64,-1.15 -1,-1.05 -2.04,-2.77 -2.65,-4.57 -0.61,-1.8 -0.78,-3.65 -0.46,-4.98 0.32,-1.33 0.89,-2.18 2.49,-2.85 0.48,-0.2 0.82,-0.25 1.05,-0.26zM8.02,23.93c-1.45,-0.05 -2.92,0.46 -4.2,1.35 -2.49,1.72 -3.76,4.44 -3.82,6.99 -0.06,2.55 0.82,4.93 2.06,7 1.24,2.07 2.86,3.84 4.68,5.08 1.82,1.24 4.13,2.19 6.52,1.14 2.34,-1.03 3.02,-3.29 3.3,-5.32 0.28,-2.03 0.12,-4.26 -0.35,-6.46 -0.47,-2.2 -1.26,-4.37 -2.49,-6.19 -1.23,-1.82 -3.14,-3.5 -5.69,-3.58zM7.85,29.22c0.28,0.01 0.79,0.24 1.48,1.26 0.69,1.02 1.34,2.66 1.7,4.34 0.36,1.69 0.45,3.44 0.29,4.63 -0.16,1.13 -0.54,1.34 -0.2,1.19 0.19,-0.09 -0.37,0.03 -1.4,-0.67 -1.04,-0.7 -2.26,-1.99 -3.12,-3.43 -0.86,-1.43 -1.34,-2.99 -1.31,-4.15 0.03,-1.16 0.31,-1.92 1.54,-2.77 0.58,-0.4 0.86,-0.42 1.02,-0.42zM50.43,33.42c-8.43,-0.14 -18.01,1.86 -26.16,6.06 -8.15,4.21 -15,10.78 -17.01,19.79 -2.21,9.88 2.23,20.9 9.71,29.56 7.47,8.66 18.2,15.14 29.59,15.14 11.79,0 24.28,-9.92 26.76,-23.02v-0c0.3,-1.6 0.09,-3.25 -0.67,-4.57 -0.76,-1.32 -1.94,-2.22 -3.18,-2.8 -2.48,-1.16 -5.34,-1.3 -8.18,-1.01 -2.84,0.29 -5.66,1.05 -7.96,2.31 -1.15,0.63 -2.2,1.38 -3.03,2.42 -0.83,1.04 -1.41,2.53 -1.21,4.06 0.36,2.73 -0.54,4.08 -1.64,4.68 -1.1,0.6 -2.9,0.67 -5.28,-1.14 -2.11,-1.61 -2.94,-2.91 -3.16,-3.72 -0.22,-0.82 -0.11,-1.43 0.61,-2.51 1.44,-2.17 5.65,-5.13 10.54,-8.16 4.88,-3.02 10.41,-6.27 14.7,-10.25 4.29,-3.98 7.5,-9.12 6.48,-15.17 -0.68,-4.07 -3.67,-7.1 -7.43,-8.92 -3.76,-1.82 -8.41,-2.66 -13.47,-2.74zM50.34,38.71c4.5,0.08 8.5,0.88 11.26,2.22 2.76,1.33 4.16,2.94 4.51,5.03 0.65,3.84 -1.21,7.02 -4.86,10.41 -3.65,3.39 -8.94,6.56 -13.88,9.63 -4.95,3.06 -9.59,5.86 -12.16,9.73 -1.29,1.93 -1.97,4.42 -1.31,6.84 0.67,2.43 2.42,4.52 5.06,6.53 3.59,2.73 7.82,3.34 11.04,1.57 3.22,-1.77 4.9,-5.68 4.34,-10.01 0.01,0.06 -0.08,0.15 0.11,-0.09 0.19,-0.24 0.68,-0.67 1.41,-1.06 1.45,-0.79 3.75,-1.47 5.96,-1.69 2.21,-0.23 4.36,0.05 5.4,0.54 0.52,0.24 0.74,0.48 0.83,0.64 0.09,0.16 0.18,0.34 0.06,0.97 -1.93,10.22 -12.95,18.71 -21.56,18.71 -9.34,0 -18.91,-5.57 -25.58,-13.3 -6.67,-7.73 -10.22,-17.45 -8.54,-24.95 1.57,-7.04 7.03,-12.5 14.27,-16.24 7.24,-3.74 16.14,-5.6 23.64,-5.47z"/>
</vector>

View File

@@ -567,6 +567,7 @@
android:layout_marginEnd="16dp"
android:textAppearance="@style/m3_label_large"
android:textColor="?colorM3OnSurface"
android:visibility="gone"
tools:text="500"/>
<ImageButton

View File

@@ -15,7 +15,7 @@
android:indeterminate="true"
android:outlineProvider="none"
android:visibility="gone"/>
<org.joinmastodon.android.ui.views.ProgressBarButton
<org.joinmastodon.android.ui.views.EmojiReactionButton
android:id="@+id/btn"
style="@style/Widget.Mastodon.M3.Button.Outlined.Icon"
android:layout_width="wrap_content"

View File

@@ -2,7 +2,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/open_link"
android:icon="@drawable/ic_fluent_document_one_page_24_filled"
android:icon="@drawable/ic_fluent_open_24_regular"
android:showAsAction="always"
android:title="@string/mo_trending_link_read"/>
</menu>

View File

@@ -94,4 +94,15 @@
<string name="mo_swap_bookmark_with_reblog">استبدال إجراء الإضافة إلى الفواصل المرجعية بإعادة النشر</string>
<string name="mo_setting_haptic_feedback_summary">اهتز عند التفاعل مع المنشورات</string>
<string name="mo_notification_filter_reset">إعادة التعيين إلى الإفتراضية</string>
<string name="mo_instance_view_info">إظهار معلومات الخادم</string>
<string name="mo_settings_remove_tracking_params">روابط ذات خصوصية</string>
<string name="mo_personal_note_saved">تم حفظ الملاحظة</string>
<string name="mo_settings_unifiedpush_enable">تمكين</string>
<string name="import_settings_failed">فشل في استيراد الإعدادات</string>
<string name="export_settings_share">تصدير الإعدادات</string>
<string name="export_settings_fail">فشل في تصدير الإعدادات</string>
<string name="export_settings_title">تصدير الإعدادات</string>
<string name="import_settings_title">استيراد الإعدادات</string>
<string name="mo_error_display_copy_error_details">نسخ التفاصيل</string>
<string name="mo_trending_link_read">قراءة</string>
</resources>

View File

@@ -170,4 +170,23 @@
<string name="sk_icon_microscope">مجهر</string>
<string name="sk_delete_notification_confirm_action">احذف الإشعار</string>
<string name="sk_notification_mention">أشار إليك %s</string>
<string name="sk_settings_show_interaction_counts">إظهار عدد التفاعلات</string>
<string name="sk_timeline_bubble">فقاعة</string>
<string name="sk_do_remove_follower">حذف</string>
<string name="sk_remove">حذف</string>
<string name="sk_icon_bot">آلي</string>
<string name="sk_icon_location">الموضع</string>
<string name="sk_icon_laugh">ضحك</string>
<string name="sk_icon_code">شفرة</string>
<string name="sk_icon_bug">حشرة</string>
<string name="sk_icon_pizza">بيتزا</string>
<string name="sk_icon_gavel">مطرقة</string>
<string name="sk_icon_feed">موجز حي</string>
<string name="sk_icon_diamond">ألماس</string>
<string name="sk_icon_umbrella">مطارية</string>
<string name="sk_icon_water">ماء</string>
<string name="sk_icon_sun">شمس</string>
<string name="sk_icon_sunset">شروق الشمس</string>
<string name="sk_icon_cloud">سحاب</string>
<string name="sk_icon_rain">مطر</string>
</resources>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="mo_hide_compose_button_while_scrolling_setting">Ancultar el botón d\'escritura mientres te desplaces</string>
<string name="mo_settings_contribute">Contribuyir a Moshidon</string>
<string name="mo_instance_status">Estáu</string>
<string name="mo_mute_label">Duración:</string>
<string name="mo_share_open_url">Abrir na aplicación</string>
<string name="mo_duration_hours_6">6 hores</string>
<string name="mo_instance_users">Usuarios</string>
<string name="mo_setting_uniform_summary">Usar l\'iconu de la aplicación pa toles notificaciones</string>
<string name="mo_settings_show_posts_without_alt">Amosar artículos con multimedia que-yos falta\'l testu alternativu</string>
<string name="mo_settings_unifiedpush_warning_disabled">UnifiedPush nun ta habilitáu. Nun vas recibir nenguna notificación.</string>
<string name="mo_trending_link_read">Leer</string>
<string name="mo_settings_show_posts_without_alt_summary">Los artículos tarán ancultos en toles cronoloxíes, pero puen amosase nos filos y los avisos</string>
<string name="mo_color_palette_nord">Nord</string>
<string name="mo_color_palette_black_and_white">Blancu y Negru</string>
<string name="mo_enable_dividers">Amosar divisores ente artículos</string>
<string name="mo_relocate_publish_button">Mover el botón d\'espublizar</string>
<string name="mo_welcome_text">Pa entamar, introduz el nome del dominiu de la to instancia embaxo, por favor.</string>
<string name="mo_personal_note">Amestar una nota sobre esti perfil</string>
<string name="mo_personal_note_saved">Nota guardada</string>
<string name="mo_personal_note_confirm">Confirmar los cambeos a la nota</string>
<string name="mo_update_available">Moshidon %s ta llistu pa descargar.</string>
<string name="mo_update_ready">Moshidon %s ta descargáu y llistu pa instalar.</string>
<string name="mo_no_image_desc_title">Ensin descripción de la semeya</string>
<string name="mo_disable_reminder_to_add_alt_text">Desactivar el recordatoriu pa amestar el testu alternativu</string>
<string name="mo_personal_note_update_failed">Fallu al guardar la nota</string>
<string name="mo_emoji_recent">Recién usaos</string>
<string name="mo_open_camera">Facer una semeya</string>
<string name="mo_camera_not_available">Nun hai una cámara disponible!</string>
<string name="mo_poll_option_add">Amestar una nueva opción a la encuesta</string>
<string name="mo_fab_compose">Redactar</string>
<string name="mo_sending_error">Erru espublizando</string>
<string name="mo_duration_indefinite">Indefiníu</string>
<string name="mo_duration_minutes_5">5 minutos</string>
<string name="mo_duration_minutes_30">30 minutos</string>
<string name="mo_duration_hours_1">1 hora</string>
<string name="mo_duration_days_1">1 día</string>
<string name="mo_duration_days_3">3 díes</string>
<string name="mo_duration_days_7">7 díes</string>
<string name="mo_instance_view_info">Ver versión del servidor</string>
<string name="mo_instance_admin">Alministráu por</string>
<string name="mo_instance_contact">Contautu</string>
<string name="mo_instance_registration">Rexistru</string>
<string name="mo_instance_registration_open">Abiertu</string>
<string name="mo_instance_registration_approval">Requier aprobación</string>
<string name="mo_instance_info_open_timeline">Cronoloxía llocal</string>
<string name="mo_instance_info_moderated_servers">Servidores moderaos</string>
<string name="mo_severity_suspend">Bloquiáu</string>
<string name="mo_setting_true_black_summary">Podría aforrar enerxía en pantalles AMOLED</string>
<string name="mo_setting_remote_follower_summary">Amosar siguidores dende otres instancies</string>
<string name="mo_setting_relocate_publish_summary">Mover el botón d\'espublizar a la barra inferior</string>
<string name="mo_setting_interaction_count_summary">Amosar cuánta xente interactuó con un artículu na cronoloxía</string>
<string name="mo_setting_disable_swipe_summary">Eslizar pa camudar de cronoloxía</string>
<string name="mo_swap_bookmark_with_reblog_summary">Meter en marcadores o impulsar artículos dende l\'avisu</string>
<string name="mo_setting_haptic_feedback_summary">Vibrar cuando interactues colos artículos</string>
<string name="mo_double_tap_to_swipe_between_tabs">Doble toque pa camudar ente pestañes</string>
<string name="mo_haptic_feedback">Respuesta táctil</string>
<string name="mo_show_media_preview">Amosar previsualización multimedia nes cronoloxíes</string>
<string name="mo_settings_unifiedpush_warning">UnifiedPush nun ta habilitáu</string>
<string name="mo_confirm_unfollow_title">Dexar de siguir</string>
<string name="mo_confirm_unfollow">Confirma pa dexar de siguir a %s</string>
<string name="mo_settings_unifiedpush_enable">Habilitar</string>
<string name="mo_blocked_accounts">Cuentes bloquiaes</string>
<string name="mo_settings_remove_tracking_params">Enllaces privaos</string>
<string name="mo_add_custom_server_local_timeline">Amestar la cronoloxía llocal d\'otru sirvidor</string>
<string name="mo_notification_action_replied">Respondisti al artículu de %s</string>
<string name="mo_change_default_reply_visibility_to_unlisted">Responder como \'Non llistáu\' por defeutu</string>
<string name="mo_notification_management_settings">Xestionar avisos</string>
<string name="mo_composer_behavior">Comportamientu del editor</string>
<string name="mo_load_remote_followers">Cargar los siguíos y siguidores del perfil remotu</string>
<string name="export_settings_title">Esportar axustes</string>
<string name="import_settings_title">Importar axustes</string>
<string name="mo_error_display_title">Nun se pudo amosar l\'artículu</string>
<string name="mo_error_display_copy_error_details">Copiar detalles</string>
<string name="import_settings_confirm">¿Confirmes la importación de los axustes?</string>
<string name="export_settings_share">Esportar Axustes</string>
<string name="export_settings_fail">Nun se pudieron esportar los axustes</string>
<string name="import_settings_failed">Nun se pudieron importar los axustes</string>
</resources>

View File

@@ -0,0 +1,336 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="sk_icon_weather">Tiempu</string>
<string name="sk_settings_allow_remote_loading">Cargando información dende instancies remotes</string>
<string name="sk_exclusive_list">Llista escluyente</string>
<string name="sk_icon_bicycle">Bicicleta</string>
<string name="sk_save_draft">¿Guardar el borrador?</string>
<string name="sk_settings_default_content_type">Tipu de conteníu per defeutu</string>
<string name="sk_search_fediverse">Buscar nel Fediversu</string>
<string name="sk_settings_display_pronouns_in_user_listings">Amosar los pronomes nos llistaos d\'usuarios</string>
<string name="sk_edit_alt_text">Editar el testu alternativu</string>
<string name="sk_hashtag_timeline_local_only_switch">¿Amosar namás los artículos llocales?</string>
<string name="sk_follow_as">Siguir dende otra cuenta</string>
<string name="sk_content_type_markdown">Markdown</string>
<plurals name="sk_posts_count_label">
<item quantity="one">artículu</item>
<item quantity="other">artículos</item>
</plurals>
<string name="sk_timeline_home">Aniciu</string>
<string name="sk_settings_show_labels_in_navigation_bar">Amosar etiquetes de pestañes na barra de navegación</string>
<plurals name="sk_time_minutes">
<item quantity="one">%d minutu</item>
<item quantity="other">%d minutos</item>
</plurals>
<string name="sk_poll_show_results">Amosar resultaos</string>
<string name="sk_announcements">Anuncios</string>
<string name="sk_your_lists">Les tos llistes</string>
<string name="sk_confirm_unpin_post">¿De xuru que quies desapegar esti artículu?</string>
<string name="sk_create">Crear</string>
<string name="sk_pinned_posts">Fixáu</string>
<string name="sk_delete_and_redraft">Desaniciar y reeditar</string>
<string name="sk_confirm_delete_and_redraft_title">Desaniciar y reeditar artículu</string>
<string name="sk_pinning">Fixando l\'artículu…</string>
<string name="sk_image_description">Descripción de la semeya</string>
<string name="sk_settings_show_replies">Amosar respuestes</string>
<string name="sk_quoting_user">Mentando %s</string>
<string name="sk_settings_reply_visibility">Visibilidá de la respuesta</string>
<string name="sk_settings_reply_visibility_all">Toles respuestes</string>
<string name="sk_settings_reply_visibility_self">Respuestes pa mi</string>
<string name="sk_settings_load_new_posts">Cargar artículos nuevos automáticamente</string>
<string name="sk_settings_show_interaction_counts">Amosar la cuenta d\'interaiciones</string>
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
<string name="sk_visibility_unlisted">Non llistáu</string>
<string name="sk_settings_show_boosts">Amosar impulsos</string>
<string name="sk_unpin_post">Desapegar del perfil</string>
<string name="sk_confirm_unpin_post_title">Desapegar artículu del perfil</string>
<string name="sk_unpinning">Desapegando artículu…</string>
<string name="sk_settings_continues_playback">Superposición d\'audiu</string>
<string name="sk_user_post_notifications_on">Activaes les notificaciones d\'artículos pa %s</string>
<string name="sk_user_post_notifications_off">Desactivaes les notificaciones d\'artículos pa %s</string>
<string name="sk_federated_timeline">Federación</string>
<string name="sk_trending_posts_info_banner">Estos artículos tan ganando tracción nel Fediversu.</string>
<string name="sk_update_available">Megalodon %s ta llistu pa descargar.</string>
<string name="sk_update_ready">Megalodon %s ta descargáu y llistu pa instalar.</string>
<string name="sk_check_for_update">Buscar actualizaciones</string>
<string name="sk_no_update_available">Nun hai actualizaciones disponibles</string>
<string name="sk_accept_follow_request">Aceptar solicitúes de siguimientu</string>
<string name="sk_reject_follow_request">Refugar solicitúes de siguimientu</string>
<string name="sk_lists_with_user">Llistes con %s</string>
<string name="sk_list_timelines">Llistes</string>
<string name="sk_follow_requests">Solicitúes de siguimientu</string>
<string name="sk_notification_type_status">Artículos</string>
<string name="sk_notification_type_posts">Avisos d\'artículos</string>
<string name="sk_settings_color_palette">Paleta de color</string>
<string name="sk_settings_show_federated_timeline">Amosar la llínea del tiempu federada</string>
<string name="sk_color_palette_material3">Sistema</string>
<string name="sk_color_palette_pink">Rosa</string>
<string name="sk_color_palette_purple">Morao</string>
<string name="sk_color_palette_brown">Marrón</string>
<string name="sk_color_palette_red">Coloráu</string>
<string name="sk_color_palette_yellow">Mariellu</string>
<string name="sk_translate_post">Traducir</string>
<string name="sk_translate_show_original">Amosar l\'orixinal</string>
<string name="sk_post_language">Idioma: %s</string>
<string name="sk_language_name">%1$s (%2$s)</string>
<string name="sk_clear_recent_languages">Desaniciar llingües usaes apocayá</string>
<string name="sk_welcome_title">¡Bienveníu!</string>
<string name="sk_welcome_text">¡El tiburón te saluda! Pa entamar, introduz el nome del dominiu de la to instancia embaxo.</string>
<string name="sk_example_domain">exemplu.social</string>
<string name="sk_settings_posting">Preferencies d\'espublizamientu</string>
<string name="sk_settings_filters">Configurar filtros</string>
<string name="sk_settings_auth">Configuraciones de seguridá</string>
<string name="sk_settings_rules">Regles</string>
<string name="sk_settings_profile">Configurar el perfil</string>
<string name="sk_delete_notification_confirm_action">Desaniciar l\'avisu</string>
<string name="sk_settings_donate">Donar</string>
<string name="sk_settings_translation_availability_note_available">¡%s almite traducción!</string>
<string name="sk_settings_translation_availability_note_unavailable">%s paez que nun almite traducción.</string>
<string name="sk_undo_reblog">Desfaer impulsu</string>
<string name="sk_clear_all_notifications">Desaniciar tolos avisos</string>
<string name="sk_clear_all_notifications_confirm">¿De xuru que quies desaniciar tolos avisos?</string>
<string name="sk_loading_fediverse_resource_title">Buscando nel Fediversu</string>
<string name="sk_loading_resource_on_instance_title">Buscando en %s</string>
<string name="sk_settings_publish_button_text_title">Personalizar el testu del botón d\'Espublizar</string>
<string name="sk_reblog_with_visibility">Impulsa con visibilidá</string>
<string name="sk_hashtags_you_follow">Etiquetes que sigues</string>
<string name="sk_copy_link_to_post">Copiar l\'enllaz al artículu</string>
<string name="sk_open_with_account">Abrir con otra cuenta</string>
<string name="sk_resource_not_found">Nun pudo atopase el recursu</string>
<string name="sk_quote_post">Espublizar sobre esto</string>
<string name="sk_bookmark_as">Meter en marcadores con otra cuenta</string>
<string name="sk_reblogged_as">Impulsáu como %s</string>
<string name="sk_already_reblogged">Yá impulsáu</string>
<string name="sk_reply_as">Responder con otra cuenta</string>
<string name="sk_unsent_posts">Artículos non espublizáos</string>
<string name="sk_draft">Borrador</string>
<string name="sk_schedule">Programar</string>
<string name="sk_confirm_delete_draft_title">Desaniciar borrador</string>
<string name="sk_confirm_delete_draft">¿De xuru que quies desaniciar esti borrador?</string>
<string name="sk_confirm_delete_scheduled_post">¿De xuru que quies desaniciar esti artículu programáu?</string>
<string name="sk_draft_or_schedule">Borrador o programar</string>
<string name="sk_compose_scheduled">Programáu para</string>
<string name="sk_draft_saved">Borrador guardáu</string>
<string name="sk_post_scheduled">Artículu programáu</string>
<string name="sk_confirm_save_draft">¿Guardar el borrador?</string>
<string name="sk_confirm_save_changes">¿Guardar cambeos?</string>
<string name="sk_mark_as_draft">Marcar como borrador</string>
<string name="sk_mark_as_read">Marcar como lleíu</string>
<string name="sk_settings_single_notification">Amosar namás un avisu</string>
<string name="sk_settings_unifiedpush">Usar UnifiedPush</string>
<string name="sk_create_list_title">Crear llista</string>
<string name="sk_list_name_hint">Nome de la llista</string>
<string name="sk_list_replies_policy_list">miembros de la llista</string>
<string name="sk_list_replies_policy_followed">usuarios siguíos</string>
<string name="sk_list_replies_policy_none">nengunu</string>
<string name="sk_delete_list">Desaniciar llista</string>
<string name="sk_delete_list_confirm">¿De xuru que quies desaniciar la llista “%s”?</string>
<string name="sk_edit_list_title">Editar llista</string>
<string name="sk_timeline_local">Llocal</string>
<string name="sk_timeline_federated">Federada</string>
<string name="sk_timeline_bubble">Burbuya</string>
<string name="sk_recent_searches_placeholder">Escribe pa entamar a buscar</string>
<string name="sk_do_remove_follower">Desaniciar</string>
<string name="sk_remove_follower_success">Siguidor desaniciáu con ésitu</string>
<string name="sk_changelog">Llista de cambeos</string>
<string name="sk_alt_text_missing_title">Falta\'l testu alternativu</string>
<string name="sk_alt_text_missing">A lo menos falta-y la descripción a un archivu axuntu.</string>
<string name="sk_publish_anyway">Espublizar de toes formes</string>
<string name="sk_settings_disable_alt_text_reminder">Desactivar el recordatoriu pa amestar el testu alternativu</string>
<string name="sk_timelines">Cronoloxíes</string>
<string name="sk_timeline_posts">Artículos</string>
<string name="sk_list">Llista</string>
<string name="sk_pin_timeline">Fixar cronoloxía</string>
<string name="sk_remove">Desaniciar</string>
<string name="sk_timeline_icon">Iconu</string>
<string name="sk_icon_heart">Corazón</string>
<string name="sk_icon_star">Estrella</string>
<string name="sk_icon_city">Ciudá</string>
<string name="sk_icon_cat">Gatu</string>
<string name="sk_icon_dog">Perru</string>
<string name="sk_icon_rabbit">Conexu</string>
<string name="sk_icon_turtle">Tortuga</string>
<string name="sk_icon_balloon">Globu</string>
<string name="sk_icon_bot">Robó</string>
<string name="sk_icon_language">Llingüa</string>
<string name="sk_icon_megaphone">Megáfonu</string>
<string name="sk_icon_microphone">Micrófonu</string>
<string name="sk_icon_microscope">Microscopiu</string>
<string name="sk_icon_keyboard">Tecláu</string>
<string name="sk_icon_location">Llocalización</string>
<string name="sk_icon_news">Noticies</string>
<string name="sk_icon_tag">Etiqueta</string>
<string name="sk_icon_games">Xuegos</string>
<string name="sk_icon_light_bulb">Bombilla</string>
<string name="sk_icon_train">Tren</string>
<string name="sk_icon_leaves">Fueyes</string>
<string name="sk_icon_sport">Deporte</string>
<string name="sk_icon_music">Música</string>
<string name="sk_icon_people">Xente</string>
<string name="sk_icon_health">Salú</string>
<string name="sk_icon_important">Importante</string>
<string name="sk_icon_shield">Escudu</string>
<string name="sk_icon_book">Llibru</string>
<string name="sk_icon_code">Códigu</string>
<string name="sk_icon_map">Mapa</string>
<string name="sk_icon_backpack">Mochila</string>
<string name="sk_icon_fire">Fueu</string>
<string name="sk_icon_bug">Bichu</string>
<string name="sk_icon_pizza">Pizza</string>
<string name="sk_icon_gavel">Mazu</string>
<string name="sk_icon_verified">Verificáu</string>
<string name="sk_icon_water">Agua</string>
<string name="sk_icon_sun">Sol</string>
<string name="sk_icon_cloud">Nube</string>
<string name="sk_icon_thunderstorm">Tormenta</string>
<string name="sk_icon_rain">Lluvia</string>
<string name="sk_add_timeline_tag_error_empty">L\'etiqueta nun pue tar vacía</string>
<string name="sk_icon_human">Persona</string>
<string name="sk_icon_globe">Planeta</string>
<string name="sk_icon_bed">Cama</string>
<string name="sk_icon_diamond">Diamante</string>
<string name="sk_icon_umbrella">Paragües</string>
<string name="sk_edit_timeline">Editar cronoloxía</string>
<string name="sk_add_timeline">Amestar cronoloxía</string>
<string name="sk_edit_timelines">Editar cronoloxíes</string>
<string name="sk_edit_timeline_tag_any">...o dalguna d\'estes</string>
<string name="sk_edit_timeline_tag_all">...y toes estes</string>
<string name="sk_edit_timeline_tag_none">...pero nenguna d\'estes</string>
<string name="sk_edit_timeline_tag_hint">Introduz l\'etiqueta…</string>
<string name="sk_edit_timeline_tags_hint">Introduz les etiquetes…</string>
<string name="sk_alt_button">ALT</string>
<string name="sk_gif_badge">GIF</string>
<string name="sk_posted">%s espublizáu</string>
<string name="sk_post_edited">editáu</string>
<string name="sk_notification_type_update">Artículos editáos</string>
<string name="sk_notify_update">Editáu un artículu impulsáu</string>
<string name="sk_attach_file">Axuntar ficheru</string>
<string name="sk_searching">Buscando…</string>
<string name="sk_no_results">Ensin resultaos</string>
<string name="sk_no_alt_text">Nun hai testu alternativu disponible</string>
<string name="sk_settings_see_new_posts_button">Botón “Ver artículos nuevos”</string>
<string name="sk_separator">·</string>
<string name="sk_local_only">Namás l\'instancia llocal</string>
<string name="sk_settings_support_local_only">El servidor namás almite espublizar llocalmente</string>
<string name="sk_settings_show_alt_indicator">Indicador pa los testos alternativos</string>
<string name="sk_inline_local_only">Namás llocal</string>
<string name="sk_instance_features">Carauterístiques de l\'instancia</string>
<string name="sk_signed_up">rexistráu</string>
<string name="sk_reported">denunciáu</string>
<string name="sk_reacted_with">%1$s reaccionó con %2$s</string>
<string name="sk_reacted">%s reaccionó</string>
<string name="sk_settings_server_version">Versión de servidor: %s</string>
<string name="sk_notify_poll_results">Resultáos de la encuesta</string>
<string name="sk_sign_ups">Rexistru d\'usuarios</string>
<string name="sk_new_reports">Denuncies nueves</string>
<string name="sk_expand">Espandir</string>
<string name="sk_collapse">Encoyer</string>
<string name="sk_settings_collapse_long_posts">Encoyer artículos mui llargos</string>
<string name="sk_unfinished_attachments">Subiendo los archivos axuntos</string>
<string name="sk_unfinished_attachments_message">Dalgunos archivos axuntos nun acabaron de xubir.</string>
<string name="sk_settings_hide_interaction">Ancultar botones d\'interaición</string>
<string name="sk_followed_as">Siguíu dende %s</string>
<string name="sk_show_thread">Amosar filu</string>
<string name="sk_content_type">Tipu de conteníu</string>
<string name="sk_content_type_unspecified">Ensin especificar</string>
<string name="sk_content_type_plain">Testu planu</string>
<string name="sk_content_type_html">HTML</string>
<string name="sk_content_type_bbcode">BBCode</string>
<string name="sk_content_type_mfm">MFM</string>
<string name="sk_notification_action_replied">Respuesta unviada a %s</string>
<string name="sk_in_reply">En respuesta</string>
<string name="sk_settings_confirm_before_reblog">Confirma antes d\'impulsar</string>
<string name="sk_instance_info_unavailable">L\'información de la instancia nun ta disponible temporalmente</string>
<string name="sk_open_in_app">Abrir na aplicación</string>
<string name="sk_open_in_app_failed">Nun se pudo abrir na aplicación</string>
<string name="sk_settings_auto_reveal_nobody">Nunca</string>
<string name="sk_settings_auto_reveal_author">Respuestes del mesmu autor</string>
<string name="sk_settings_auto_reveal_anyone">Respuestes de toos</string>
<string name="sk_settings_prefix_replies_never">Nunca</string>
<string name="sk_advanced_options_show">Amosar opciones avanzaes</string>
<string name="sk_advanced_options_hide">Ancultar opciones avanzaes</string>
<string name="sk_spoiler_show">Amosar el conteníu</string>
<string name="sk_pronouns_label">Pronomes</string>
<string name="sk_settings_instance">Instancia</string>
<string name="sk_settings_display_pronouns_in_timelines">Amosar los pronomes nes cronoloxíes</string>
<string name="sk_settings_display_pronouns_in_threads">Amosar los pronomes nos filos</string>
<string name="sk_list_exclusive_switch">Facer que la llista seya esclusiva</string>
<string name="sk_switch_timeline">Camudar cronoloxía</string>
<string name="sk_tab_home">Aniciu</string>
<string name="sk_tab_notifications">Avisos</string>
<string name="sk_tab_profile">Perfil</string>
<string name="sk_settings_show_emoji_reactions_only_opened">Namás cuando s\'abre l\'artículu</string>
<plurals name="sk_users_reacted_with">
<item quantity="one">Un usuariu reaccionó con %2$s</item>
<item quantity="other">%1$,d usuarios reaccionaron con %2$s</item>
</plurals>
<string name="sk_duration_indefinite">Indefiníu</string>
<string name="sk_duration_minutes_5">5 minutos</string>
<string name="sk_duration_minutes_30">30 minutos</string>
<string name="sk_duration_hours_1">1 hora</string>
<string name="sk_duration_hours_6">6 hores</string>
<string name="sk_duration_days_1">1 día</string>
<string name="sk_duration_days_3">3 díes</string>
<string name="sk_duration_days_7">7 díes</string>
<string name="sk_suicide_search_terms">Suicidiu</string>
<string name="sk_tab_search">Busca</string>
<string name="sk_mute_label">Duración</string>
<string name="sk_load_missing_posts_above">Cargar artículos nuevos</string>
<string name="sk_load_missing_posts_below">Cargar artículos vieyos</string>
<plurals name="sk_time_seconds">
<item quantity="one">%d segundu</item>
<item quantity="other">%d segundos</item>
</plurals>
<plurals name="sk_time_hours">
<item quantity="one">%d hora</item>
<item quantity="other">%d hores</item>
</plurals>
<plurals name="sk_time_days">
<item quantity="one">%d día</item>
<item quantity="other">%d díes</item>
</plurals>
<string name="sk_blocked_accounts">Cuentes bloquiaes</string>
<string name="sk_post_contains_media">L\'artículu contien conteníu multimedia</string>
<string name="sk_poll_hide_results">Ancultar resultaos</string>
<string name="sk_open_post_preview">Vista previa del artículu</string>
<string name="sk_post_preview">Vista previa</string>
<string name="sk_app_name">Megalodon</string>
<string name="sk_confirm_delete_and_redraft">¿De xuru que quies desaniciar y reeditar esti artículu?</string>
<string name="sk_confirm_pin_post_title">Fixar l\'artículu nel perfil</string>
<string name="sk_color_palette_blue">Azul</string>
<string name="sk_translated_using">Traducíu usando %s</string>
<string name="sk_pin_post">Fixar nel perfil</string>
<string name="sk_delete_notification">Desaniciar avisu</string>
<string name="sk_confirm_pin_post">¿Quies fixar esti artículu al to perfil?</string>
<string name="sk_settings_reply_visibility_following">Respuestes a los mios siguidores</string>
<string name="sk_federated_timeline_info_banner">Estos son los artículos más recientes de la xente de la to federación.</string>
<string name="sk_settings_color_palette_default">Por defeutu (%s)</string>
<string name="sk_color_palette_green">Verde</string>
<string name="sk_settings_publish_button_text">Testu del botón d\'espublizamientu</string>
<string name="sk_confirm_delete_scheduled_post_title">Desaniciar artículu programáu</string>
<string name="sk_hashtag">Etiqueta</string>
<string name="sk_icon_image">Semeya</string>
<string name="sk_available_languages">Llingües disponibles</string>
<string name="sk_confirm_clear_recent_languages">¿De xuru que quies desaniciar les llingües usaes apocayá?</string>
<string name="sk_icon_coffee">Café</string>
<string name="sk_icon_color_palette">Paleta de colores</string>
<string name="sk_clear_all_notifications_confirm_action">Desaniciar tou</string>
<string name="sk_settings_uniform_icon_for_notifications">Mesmu icono pa tolos avisos</string>
<string name="sk_compose_draft">L\'artículu va guardase como borrador.</string>
<string name="sk_timelines_add">Amestar</string>
<string name="sk_icon_briefcase">Maletu</string>
<string name="sk_reblog_as">Impulsar con otra cuenta</string>
<string name="sk_pinned_timeline">Fixáu a aniciu</string>
<string name="sk_icon_headphones">Auriculares</string>
<string name="sk_save_draft_message">¿Quies guardar los tos cambios nesti borrador o espublizalo agora?</string>
<string name="sk_delete_notification_confirm">¿De xuru que quies desaniciar esti avisu?</string>
<string name="sk_already_bookmarked">Yá ta metíu en marcadores</string>
<string name="sk_compose_no_draft">Nun guardar borrador</string>
<string name="sk_remove_follower">Desaniciar como siguidor</string>
<string name="sk_bookmarked_as">Metíu en marcadores como %s</string>
<string name="sk_notification_mention">Fuisti mentáu por %s</string>
<string name="sk_settings_translate_only_opened">Namás traducir artículos abiertos</string>
<string name="sk_list_replies_policy">Amosar respuestes a</string>
<string name="sk_timeline">Cronoloxía</string>
<string name="sk_edit_timeline_tag_main">Artículos que contienen l\'etiqueta…</string>
</resources>

View File

@@ -375,4 +375,55 @@
<item quantity="one">publicació</item>
<item quantity="other">publicacions</item>
</plurals>
</resources>
<string name="sk_trending_links_info_banner">Aquestes notícies s\'estan comentant per tot el Fedivers.</string>
<string name="sk_icon_sunset">Foscant</string>
<string name="sk_icon_cloud">Núvol</string>
<string name="sk_icon_water">Aigua</string>
<string name="sk_icon_sun">Sol</string>
<string name="sk_icon_thunderstorm">Tempesta</string>
<string name="sk_icon_rain">Pluja</string>
<string name="sk_icon_snowflake">Floc de neu</string>
<string name="sk_posted">%s publicat/s</string>
<string name="sk_settings_show_emoji_reactions">Mostrar reaccions d\'emoji a la línia de temps</string>
<string name="sk_settings_show_emoji_reactions_hide_empty">Ocultar reaccions d\'emoji buides</string>
<string name="sk_settings_show_emoji_reactions_only_opened">Només quan s\'obri la publicació</string>
<string name="sk_suicide_search_terms">Suïcidi</string>
<string name="sk_search_suicide_title">Si estàs sofrint…</string>
<string name="sk_search_suicide_hotlines">Trobar una línia d\'ajuda</string>
<string name="sk_do_not_show_again">No tornar a mostrar</string>
<string name="sk_suicide_helplines_url">https://findahelpline.com</string>
<string name="sk_post_contains_media">La publicació conté contingut multimedia</string>
<string name="sk_load_missing_posts_below">Carregar publicacions més antigues</string>
<plurals name="sk_time_seconds">
<item quantity="one">%d segon</item>
<item quantity="many">%d segons</item>
<item quantity="other">%d segons</item>
</plurals>
<plurals name="sk_time_minutes">
<item quantity="one">%d minut</item>
<item quantity="many">%d minuts</item>
<item quantity="other">%d minuts</item>
</plurals>
<plurals name="sk_time_hours">
<item quantity="one">%d hora</item>
<item quantity="many">%d hores</item>
<item quantity="other">%d hores</item>
</plurals>
<plurals name="sk_time_days">
<item quantity="one">%d dia</item>
<item quantity="many">%d díes</item>
<item quantity="other">%d díes</item>
</plurals>
<string name="sk_muted_accounts">Comptes silenciats</string>
<string name="sk_blocked_accounts">Comptes bloquejats</string>
<string name="sk_settings_like_icon">Utilitzar un cor com a icona de favorit</string>
<string name="sk_recently_used">Utilitzat recentment</string>
<string name="sk_settings_underlined_links">Enllaços subratllats</string>
<string name="sk_set_as_default">Posar per defecte</string>
<string name="sk_edit_alt_text">Editar text alternatiu</string>
<string name="sk_settings_color_palette_default">Per defecte (%s)</string>
<string name="sk_search_suicide_message">Si estàs buscant una senyal per a no suicidar-te, ací està. Per favor, planteja\'t cridar a una línia d\'ajuda de prevenció del suïcidi si estàs sofrint.</string>
<string name="sk_load_missing_posts_above">Carregar publicacions més recents</string>
<string name="sk_trending_posts_info_banner">Aquestes publicacions estan guanyant popularitat en el Fedivers.</string>
<string name="sk_settings_show_emoji_reactions_always">Mostrar sempre el botó d\'afegir</string>
</resources>

View File

@@ -606,6 +606,7 @@
<string name="manage_list_members">Listenmitglieder verwalten</string>
<string name="list_no_members">Noch keine Mitglieder</string>
<string name="dont_remind_again">Nicht erneut erinnern</string>
<string name="reply_to_user">Antwort an %s</string>
<!-- %s is a time interval ("5 months") -->
<string name="old_post_sheet_title">Dieser Beitrag ist %s alt</string>
</resources>

View File

@@ -121,4 +121,7 @@
<string name="export_settings_share">Einstellungen exportieren</string>
<string name="export_settings_fail">Exportieren der Einstellungen fehlgeschlagen</string>
<string name="export_settings_title">Einstellungen exportieren</string>
</resources>
<string name="mo_personal_note_saved">Notiz gespeichert</string>
<string name="mo_settings_enhance_text_size_summary">Macht den Text der App größer</string>
<string name="mo_settings_enhance_text_size">Textgröße verändern</string>
</resources>

View File

@@ -185,7 +185,7 @@
<string name="sk_icon_location">Standort</string>
<string name="sk_icon_microphone">Mikrofon</string>
<string name="sk_icon_microscope">Mikroskop</string>
<string name="sk_icon_keyboard">Keyboard</string>
<string name="sk_icon_keyboard">Tastatur</string>
<string name="sk_icon_coffee">Kaffee</string>
<string name="sk_icon_laugh">Lachen</string>
<string name="sk_icon_news">Nachrichten</string>

View File

@@ -53,7 +53,7 @@
<string name="mo_swap_bookmark_with_reblog">Εναλλαγή σελιδοδείκτη με ενέργεια αναδημοσίευσης</string>
<string name="mo_show_media_preview">Εμφάνιση προεπισκόπησης πολυμέσων στις ροές</string>
<string name="mo_settings_unifiedpush_warning">UnifiedPush μη ενεργό</string>
<string name="mo_mention_reblogger_automatically">Αυτόματη αναφορά λογαριασμού που αναδημοσίευσε την ανάρτηση, στις απαντήσεις</string>
<string name="mo_mention_reblogger_automatically">Αυτόματη επισήμανση λογαριασμού που αναδημοσίευσε την ανάρτηση, στις απαντήσεις</string>
<string name="mo_confirm_unfollow_title">Άρση ακολούθησης Λογαριασμού</string>
<string name="mo_settings_unifiedpush_warning_no_distributors">Δεν έχουν εγκατασταθεί διανομείς UnifiedPush. Δεν θα λάβεις ειδοποιήσεις.</string>
<string name="mo_mute_notifications">Απόκρυψη ειδοποιήσεων από αυτόν τον χρήστη;</string>
@@ -113,4 +113,5 @@
<string name="mo_trending_link_read">Ανάγνωση</string>
<string name="mo_settings_remove_tracking_params_summary">Απομάκρυνση πληροφοριών ανίχνευσης από συνδέσμους</string>
<string name="mo_settings_remove_tracking_params">Ιδιωτικοί Σύνδεσμοι</string>
<string name="mo_personal_note_saved">Η σημείωση αποθηκεύτηκε</string>
</resources>

View File

@@ -249,7 +249,7 @@
<string name="sk_add_timeline_tag_error_empty">Η ετικέτα δε μπορεί να είναι κενή</string>
<string name="sk_updater_enable_pre_releases">Ενεργοποίηση προ-κυκολοφορίας</string>
<string name="sk_inline_local_only">Μόνο τοπικά</string>
<string name="sk_inline_direct">Μόνο με αναφορά</string>
<string name="sk_inline_direct">Μόνο με επισήμανση</string>
<string name="sk_separator">·</string>
<string name="sk_local_only">Μόνο τοπική οντότητα</string>
<string name="sk_instance_features">Δυνατότητες οντότητας</string>
@@ -374,7 +374,7 @@
<string name="sk_duration_hours_6">6 ώρες</string>
<string name="sk_duration_days_1">1 ημέρα</string>
<string name="sk_duration_days_3">3 ημέρες</string>
<string name="sk_notification_mention">Αναφέρθηκες από τον χρήστη %s</string>
<string name="sk_notification_mention">Επισημάνθηκες από τον χρήστη %s</string>
<string name="sk_suicide_search_terms">Αυτοκτονία</string>
<string name="sk_search_suicide_title">Σε περίπτωση που είσαι σε κρίση…</string>
<string name="sk_search_suicide_hotlines">Βρες γραμμή βοήθειας</string>

View File

@@ -96,4 +96,22 @@
<string name="mo_trending_link_read">Legi</string>
<string name="mo_error_display_copy_error_details">Kopii informojn</string>
<string name="mo_settings_unifiedpush_enable">Ebligi</string>
</resources>
<string name="mo_settings_remove_tracking_params">Malpublikaj Ligiloj</string>
<string name="import_settings_failed">Malsukcesis importi agordojn</string>
<string name="export_settings_share">Eksporti agordojn</string>
<string name="export_settings_fail">Malsukcesis eksporti agordojn</string>
<string name="export_settings_title">Eksportu agordojn</string>
<string name="export_settings_summary">Eksporti la agordojn kaj templinioj de ĉiuj ensalutitaj kontoj</string>
<string name="mo_settings_remove_tracking_params_summary">Foru gvitidintan informacion el ligiloj</string>
<string name="mo_personal_note_saved">Noto savita</string>
<string name="import_settings_confirm_body">Ĉiu nunaj agordoj kaj templinioj estos anstataŭitaj! Ĉi tiu ago ne povas malfarita.</string>
<string name="mo_settings_unifiedpush_warning">UnifiedPush ne estas ŝaltita</string>
<string name="mo_settings_unifiedpush_warning_no_distributors">Neniom da instalitaj distribuantoj de UnifiedPush. Vi ne recivos iajn sciigojn.</string>
<string name="mo_mute_notifications">Ĉu malmontru sciigojn de ĉi tiu uzanto?</string>
<string name="import_settings_confirm">Ĉu akceptu importi agordojn?</string>
<string name="mo_settings_unifiedpush_warning_disabled">UnifiedPush ne estas ŝaltita. Vi ne recivos iajn sciigojn.</string>
<string name="import_settings_title">Importi agordojn</string>
<string name="import_settings_summary">Importi antaŭitajn eksportitajn agordojn kaj templiniojn</string>
<string name="mo_error_display_title">Malsukcesis montri afiŝon</string>
<string name="mo_error_display_text">Io malsukcesis dum ŝarĝas ĉi tiun afiŝon. Se la problemo daŭras, bonvole raportu ĝin en nia paĝo de Issues kune kun la detalojn.</string>
</resources>

View File

@@ -14,7 +14,7 @@
<string name="mo_emoji_recent">Usados recientemente</string>
<string name="mo_clear_recent_emoji">Borrar emojis usados recientemente</string>
<string name="mo_disable_relocate_publish_button_to_enable_customization">Desactiva \"Recolocar botón de publicación\" para poder personalizar</string>
<string name="mo_poll_option_add">Añadir nueva opción en la encuesta</string>
<string name="mo_poll_option_add">Añadir opción en la encuesta</string>
<string name="mo_fab_compose">Redactar</string>
<string name="mo_sending_error">Error al publicar</string>
<string name="mo_settings_contribute">Contribuir en Moshidon</string>
@@ -70,7 +70,7 @@
<string name="mo_setting_relocate_publish_summary">Mueve el botón de publicación a la barra inferior</string>
<string name="mo_swap_bookmark_with_reblog_summary">Añade un marcador o impulsa publicaciones desde la notificación</string>
<string name="mo_open_camera">Hacer foto</string>
<string name="mo_camera_not_available">¡No hay cámara disponible!</string>
<string name="mo_camera_not_available">¡No detecto la cámara!</string>
<string name="mo_setting_haptic_feedback_summary">Vibra cuando interactúas con publicaciones</string>
<string name="mo_haptic_feedback">Vibración</string>
<string name="mo_color_palette_black_and_white">Blanco y Negro</string>
@@ -81,7 +81,7 @@
<string name="mo_show_media_preview">Mostrar previsualización de multimedia en la cronología</string>
<string name="mo_recent_emoji_cleared">Emojis recientes eliminados</string>
<string name="mo_double_tap_to_search">Doble toque para abrir la búsqueda</string>
<string name="mo_unmuted_conversation_successfully">Conversación sin silencio</string>
<string name="mo_unmuted_conversation_successfully">Conversación no silenciada</string>
<string name="mo_confirm_to_unmute_conversation">¿Quieres dejar de silenciar esta conversación\?</string>
<string name="mo_confirm_to_mute_conversation">¿Quieres silenciar esta conversación\?</string>
<string name="mo_muted_conversation_successfully">Conversación silenciada correctamente</string>
@@ -101,16 +101,16 @@
<string name="mo_mute_notifications">¿Ocultar las notificaciones de este usuario?</string>
<string name="mo_settings_unifiedpush_warning">UnifiedPush no habilitado</string>
<string name="mo_settings_unifiedpush_warning_disabled">UnifiedPush no está habilitado. No recibirás ninguna notificación.</string>
<string name="export_settings_share">Exportar configuración</string>
<string name="mo_error_display_text">Algo salió mal al cargar esta publicación. Si el problema persiste, infórmelo en nuestra página de Problemas junto con los detalles del error.</string>
<string name="export_settings_share">Exportar ajustes</string>
<string name="mo_error_display_text">Algo salió mal al cargar esta publicación. Si el problema continúa, infórmanos en nuestra página de soporte junto con los detalles del error.</string>
<string name="import_settings_summary">Importar configuraciones y líneas de tiempo previamente exportadas</string>
<string name="mo_settings_unifiedpush_warning_no_distributors">No hay distribuidores de UnifiedPush instalados. No recibirás ninguna notificación.</string>
<string name="mo_settings_unifiedpush_enable">Permitir</string>
<string name="import_settings_confirm">¿Confirmar para importar la configuración?</string>
<string name="import_settings_confirm_body">¡Se sobrescribirán todas las configuraciones y líneas de tiempo actuales! Esta acción no se puede deshacer.</string>
<string name="import_settings_confirm_body">¡Se sobrescribirán todas las configuraciones y cronologías actuales! Esta acción no se puede deshacer.</string>
<string name="import_settings_failed">No se pudo importar la configuración</string>
<string name="export_settings_fail">No se pudo exportar la configuración</string>
<string name="export_settings_title">Exportar configuración</string>
<string name="export_settings_title">Exportar ajustes</string>
<string name="export_settings_summary">Exportar configuraciones y cronogramas para todas las cuentas</string>
<string name="import_settings_title">Importar ajustes</string>
<string name="mo_error_display_title">No se pudo mostrar la publicación</string>
@@ -118,4 +118,10 @@
<string name="mo_trending_link_read">Leer</string>
<string name="mo_settings_remove_tracking_params">Enlaces Privados</string>
<string name="mo_settings_remove_tracking_params_summary">Eliminar información de rastreo de los enlaces</string>
</resources>
<string name="mo_personal_note_saved">Nota guardada</string>
<string name="mo_settings_enhance_text_size">Aumentar tamaño del texto</string>
<string name="mo_settings_enhance_text_size_summary">Agranda el texto de la app</string>
<string name="mo_mute_hashtag_explanation_discreet">Los demás no sabrán que has silenciado esta etiqueta.</string>
<string name="mo_mute_hashtag_explanation_muted_home">No verás publicaciones con esta etiqueta en tu Inicio.</string>
<string name="mo_mute_hashtag_explanation_search">Todavía verás publicaciones con esta etiqueta buscándolas o en otras cronologías.</string>
</resources>

View File

@@ -16,7 +16,7 @@
<string name="sk_image_description">Descripción de la imagen</string>
<string name="sk_visibility_unlisted">No listada</string>
<string name="sk_settings_show_replies">Mostrar respuestas</string>
<string name="sk_settings_show_boosts">Mostrar boosts</string>
<string name="sk_settings_show_boosts">Mostrar mejoras</string>
<string name="sk_settings_load_new_posts">Cargar publicaciones nuevas automáticamente</string>
<string name="sk_settings_show_interaction_counts">Mostrar recuento de interacciones</string>
<string name="sk_mark_media_as_sensitive">Marcar contenido como delicado</string>
@@ -224,7 +224,7 @@
<string name="sk_timeline_posts">Publicaciones</string>
<string name="sk_timelines_add">Añadir</string>
<string name="sk_timeline">Línea de tiempo</string>
<string name="sk_hashtag">Hashtag</string>
<string name="sk_hashtag">Etiqueta</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>

View File

@@ -147,7 +147,7 @@
<string name="sk_publish_anyway">Argitaratu hala ere</string>
<string name="sk_settings_disable_alt_text_reminder">Desgaitu gogorarazlea testu alternatiboa gehitzeko</string>
<string name="sk_timelines">Denbora-lerroak</string>
<string name="sk_timeline_posts">Bidalketak</string>
<string name="sk_timeline_posts">Argitalpenak</string>
<string name="sk_timelines_add">Gehitu</string>
<string name="sk_timeline">Denbora-lerroa</string>
<string name="sk_list">Zerrenda</string>
@@ -218,7 +218,7 @@
<string name="sk_alt_button">ALT</string>
<string name="sk_post_edited">Editatua</string>
<string name="sk_notification_type_update">Editatutako argitalpenak</string>
<string name="sk_notify_update">Bultzatutako bidalketa editatu da</string>
<string name="sk_notify_update">Bultzatutako argitalpena editatu da</string>
<string name="sk_no_results">Emaitzarik ez</string>
<string name="sk_save_draft">Zirriborroa gorde\?</string>
<string name="sk_no_alt_text">Ez dago testu alternatiborik eskuragarri</string>
@@ -270,7 +270,7 @@
<string name="sk_quoting_user">%s aipatzen</string>
<string name="sk_settings_reply_visibility_self">Niri eginiko erantzunak</string>
<string name="sk_notification_action_replied">%s-(r)i erantzun</string>
<string name="sk_bubble_timeline_info_banner">Hauexek dira zure instantziako administratzaileek saretik aukeratutako azken bidalketak.</string>
<string name="sk_bubble_timeline_info_banner">Hauexek dira zure instantziako administratzaileek saretik aukeratutako azken argitalpenak.</string>
<string name="sk_content_type">Edukiaren mota</string>
<string name="sk_content_type_unspecified">Zehaztu gabea</string>
<string name="sk_content_type_plain">Testu arrunta</string>
@@ -278,8 +278,8 @@
<string name="sk_content_type_markdown">Markdown</string>
<string name="sk_content_type_bbcode">BBCode</string>
<string name="sk_content_type_mfm">MFM</string>
<string name="sk_settings_content_types">Gaitu bidalketaren formateatzea</string>
<string name="sk_settings_content_types_explanation">Markdown bezalako eduki-mota ezartzea ahalbidetzen du bidalketa bat sortzerakoan. Gogoan izan instantzia guztiek ez dutela hau baimentzen.</string>
<string name="sk_settings_content_types">Gaitu argitalpenaren formateatzea</string>
<string name="sk_settings_content_types_explanation">Markdown bezalako eduki-mota ezartzea ahalbidetzen du argitalpen bat sortzerakoan. Gogoan izan instantzia guztiek ez dutela hau baimentzen.</string>
<string name="sk_settings_default_content_type">Eduki-mota lehenetsia</string>
<string name="sk_settings_confirm_before_reblog">Berretsi bultzatu aurretik</string>
<string name="sk_reacted_with">%1$s-(e)k honela erreakzionatu du: %2$s</string>
@@ -290,9 +290,9 @@
<string name="sk_edit_timeline_tags_explanation">Jakin eragiketa hauek zerbitzariak kudeatzen dituela. Litekeena da konbinazioetarako euskarririk ez izatea.</string>
<string name="sk_settings_unifiedpush_no_distributor_body">Banatzaile bat instalatu behar duzu UnifiedPush jakinarazpenek funtzionatzeko. Informazio gehiago: https://unifiedpush.org/</string>
<string name="sk_icon_feed">Jarioa</string>
<string name="sk_edit_timeline_tag_main">Traola hau duten tootak…</string>
<string name="sk_edit_timeline_tag_main">Traola hau duten argitalpenak…</string>
<string name="sk_icon_verified">Egiaztatua</string>
<string name="sk_hashtag_timeline_local_only_switch">Erakutsi toot lokalak soilik\?</string>
<string name="sk_hashtag_timeline_local_only_switch">Erakutsi argitalpen lokalak soilik?</string>
<string name="sk_icon_umbrella">Euritakoa</string>
<string name="sk_settings_unifiedpush_no_distributor">Ez da banatzailerik aurkitu</string>
<string name="sk_edit_timeline_tag_any">...edo hauetako edozein</string>
@@ -304,4 +304,23 @@
<string name="sk_icon_recycle_bin">Zakarrontzia</string>
<string name="sk_trending_links_info_banner">Hauek dira zure zerbitzarian komentatzen ari diren albisteak</string>
<string name="sk_add_timeline_tag_error_empty">Traola ezin da hutsik egon</string>
<string name="sk_icon_sun">Eguzkia</string>
<string name="sk_icon_sunset">Ilunabarra</string>
<string name="sk_icon_cloud">Lainoa</string>
<string name="sk_icon_rain">Euria</string>
<string name="sk_icon_snowflake">Elur maluta</string>
<string name="sk_gif_badge">GIF</string>
<string name="sk_posted">%s(e)k argitaratua</string>
<string name="sk_icon_thunderstorm">Ekaitza</string>
<string name="sk_trending_posts_info_banner">Argitalpen hauek garrantzia hartzen ari dira Fedibertsoan.</string>
<plurals name="sk_posts_count_label">
<item quantity="one">Argitalpena</item>
<item quantity="other">Argitalpenak</item>
</plurals>
<string name="sk_post_contains_media">Multimediadun argitalpena</string>
<string name="sk_load_missing_posts_below">Argitalpen zaharragoak kargatu</string>
<string name="sk_load_missing_posts_above">Argitalpen berriagoak kargatu</string>
<string name="sk_settings_default_visibility">Lehenetsitako argitalpenen ikusgarritasuna</string>
<string name="sk_open_post_preview">Aurreikusi argitalpena</string>
<string name="sk_post_preview">Aurrebista</string>
</resources>

View File

@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>
<string name="mo_relocate_publish_button">Siirrä julkaisupainike uudelleen</string>
<string name="mo_color_palette_nord">Nord</string>
<string name="mo_color_palette_black_and_white">Mustavalkoinen</string>
<string name="mo_enable_dividers">Näytä postin jakajat</string>
</resources>

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