Compare commits

...

471 Commits

Author SHA1 Message Date
LucasGGamerM
32da4f8e09 feat: use targetAccount domain name in lookup dialog 2023-04-16 18:27:29 -03:00
LucasGGamerM
1098c855c4 fix: this fixes the bug where the app would only load 10-20 remote followers 2023-04-16 18:07:49 -03:00
LucasGGamerM
ee8ca09e0e Merge pull request #170 from FineFindus/refactor/remote-followers
Refactor remote follower
2023-04-16 17:32:35 -03:00
FineFindus
ed75a62228 refactor(follower): move remote check to own method 2023-04-16 21:44:05 +02:00
FineFindus
c3c76126a3 feat(follower): show own domain accounts as username 2023-04-16 21:32:26 +02:00
FineFindus
4da6016e06 refactor(follower): move null check in called method 2023-04-16 21:19:24 +02:00
FineFindus
e4f4ca5392 refactor: move request to their own methods 2023-04-16 21:13:20 +02:00
FineFindus
9d690e7670 fix: own instance accounts not loading 2023-04-16 21:07:22 +02:00
FineFindus
e1acfef1bf refactor: use useful variable names 2023-04-16 21:00:07 +02:00
LucasGGamerM
29267dacb4 feat: add onClick lookup for remote accounts 2023-04-16 15:31:47 -03:00
LucasGGamerM
7c8698521d feat: add remote follower and following lookup
Clicking the regenerated accounts wont do anything for now though
2023-04-16 15:25:55 -03:00
LucasGGamerM
fb02689c30 Revert "feat: add remote lookups to the PaginatedAccountListFragment"
This reverts commit 74f2bb4707.
2023-04-16 14:13:25 -03:00
LucasGGamerM
74f2bb4707 feat: add remote lookups to the PaginatedAccountListFragment 2023-04-13 16:08:26 -03:00
LucasGGamerM
76a85af0d7 fix: make remoteLookups not resolve other instance's data
This was done because unauthenticated users cannot access them.
2023-04-13 16:04:19 -03:00
LucasGGamerM
5d7363585a feat: add remote lookup functions in UiUtils
This will help when getting remote status's interactions, and remote accounts' followers and follows
2023-04-13 15:08:41 -03:00
LucasGGamerM
46398f7bad fix: maybe fixing the nightly updater issue 2023-04-12 16:32:15 -03:00
LucasGGamerM
7c6087c1f5 Merge remote-tracking branch 'weblate/master' 2023-04-12 14:20:16 -03:00
nitrogenez47ab3e44720c4675
ed7bcdb761 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (273 of 273 strings)

Translation: Moshidon/values_sk
Translate-URL: https://translate.codeberg.org/projects/moshidon/values_sk/uk/
2023-04-12 17:19:02 +00:00
LucasGGamerM
50f9ebe4f7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.2% (271 of 273 strings)

Translation: Moshidon/values_sk
Translate-URL: https://translate.codeberg.org/projects/moshidon/values_sk/pt_BR/
2023-04-12 17:19:02 +00:00
Weblate
2daf94f91e Added translation using Weblate (Portuguese) 2023-04-12 17:05:04 +00:00
Weblate
d3b7a60f18 Added translation using Weblate (Arabic) 2023-04-12 17:05:03 +00:00
LucasGGamerM
1d5bf452ab Merge remote-tracking branch 'megalodon_weblate/main'
# Conflicts:
#	metadata/es/changelogs/83.txt
2023-04-12 14:00:28 -03:00
LucasGGamerM
51063b65ae Merge remote-tracking branch 'weblate/master' 2023-04-12 13:58:52 -03:00
Weblate
198dfc067e Added translation using Weblate (Arabic (Algeria)) 2023-04-12 16:58:51 +00:00
nitrogenez47ab3e44720c4675
ab0eed88c4 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (44 of 44 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2023-04-12 16:58:49 +00:00
Eryk Michalak
7a036a52ae Translated using Weblate (Polish)
Currently translated at 100.0% (44 of 44 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2023-04-12 16:58:49 +00:00
gallegonovato
aa5404842b Translated using Weblate (Spanish)
Currently translated at 100.0% (44 of 44 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-12 16:58:49 +00:00
nitrogenez47ab3e44720c4675
a33009a297 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (44 of 44 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/uk/
2023-04-12 16:55:29 +00:00
LucasGGamerM
8b27e6de33 fix: long click boost button actions on customlocaltimelines
Before, you just couldnt reblog something with the unlisted visibility directly from the custom local timelines, now you can! And there are some small adjustments to the behavior.
2023-04-12 13:32:22 -03:00
LucasGGamerM
4c698cf217 feat: make confirm reblog setting consistent on customlocaltimelines
Make it so there is also a confirmation to reblog on custom local timelines
2023-04-12 13:12:57 -03:00
LucasGGamerM
05217b7712 refactor: move confirm reblog setting to behavior tab 2023-04-12 13:10:21 -03:00
LucasGGamerM
64e681c227 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	metadata/de-DE/changelogs/83.txt
#	metadata/en-US/changelogs/83.txt
2023-04-12 13:04:31 -03:00
Espasant3
a4515ea360 Translated using Weblate (Galician)
Currently translated at 100.0% (17 of 17 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/gl/
2023-04-11 10:37:30 +00:00
LucasGGamerM
2cea6a9df5 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-10 15:17:14 -03:00
LucasGGamerM
d6d6155f2c refactor: move LatestNotificationID preference away from GlobalUserPreferences
This is solely for the purpose of organization, as I think GlobalUserPreferences should only directly contain Global user preferences
2023-04-10 15:14:47 -03:00
LucasGGamerM
88f498409f chore: refactor deprecated action 2023-04-10 07:57:59 -03:00
LucasGGamerM
f39690b7e5 Merge pull request #160 from FineFindus/fix/sign-in-flow
fix: use BuilType defined REDIRECT_URI
2023-04-10 07:34:19 -03:00
LucasGGamerM
0a1c42f90b feat: add nightly auto updater 2023-04-09 10:56:41 -03:00
FineFindus
a59587eb62 fix(auth): use BuildType depended redirect uri 2023-04-09 15:46:30 +02:00
FineFindus
7615723d4f refactor(auth): use REDIRECT_URI 2023-04-09 15:07:22 +02:00
Eryk Michalak
804129271b Translated using Weblate (Polish)
Currently translated at 100.0% (44 of 44 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2023-04-09 12:37:29 +00:00
gallegonovato
ef1b1e5d1f Translated using Weblate (Spanish)
Currently translated at 100.0% (44 of 44 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-09 12:37:29 +00:00
LucasGGamerM
fe21a95766 fix: wrong property name in build.gradle 2023-04-08 21:09:43 -03:00
LucasGGamerM
6f3cb14dc9 chore: adding CURRENT_DATE environment variable to workflow 2023-04-08 21:00:55 -03:00
LucasGGamerM
1698a32d75 chore: tweak build.gradle to check properties for current date
Check the local.properties file for date in case its null. This should be useful for testing the new nightly updater
2023-04-08 20:58:23 -03:00
LucasGGamerM
1e54b21842 fix: gradle not compiling on nightly build 2023-04-08 20:39:43 -03:00
LucasGGamerM
99ca3b7acb Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-08 20:22:40 -03:00
LucasGGamerM
828617f7d7 feat: change nightly release versionName 2023-04-08 20:22:12 -03:00
ihor_ck
c4d8baf8a2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (17 of 17 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-04-08 20:37:30 +00:00
gallegonovato
118a073961 Translated using Weblate (Spanish)
Currently translated at 100.0% (17 of 17 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/es/
2023-04-08 20:37:29 +00:00
Choukajohn
4b39011497 Translated using Weblate (French)
Currently translated at 17.6% (3 of 17 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/fr/
2023-04-08 20:37:29 +00:00
LucasGGamerM
470a693a1f chore: update nightly badge 2023-04-08 16:51:09 -03:00
LucasGGamerM
1f4970c0d2 fix: NPE when instance is null and attempts to get new notifications
For some weird reason, someone saw a pixelfed instance return null as the instance, causing a crash on the updateNotificationsBadge method. This reminds me of why java is such a shit language
2023-04-08 15:53:40 -03:00
LucasGGamerM
e9833baaa4 Merge pull request #157 from FineFindus/fix/notification-id
fix(notification): overwriting IDs
2023-04-07 21:30:21 -03:00
LucasGGamerM
325d962bbb chore: update build.gradle for easier testing of nightly release 2023-04-07 19:13:06 -03:00
LucasGGamerM
0aa432bf6e chore: update .gitignore 2023-04-07 18:59:18 -03:00
LucasGGamerM
1ed891ced8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (44 of 44 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pt_BR/
2023-04-07 21:39:06 +00:00
Eryk Michalak
2ebf52d156 Translated using Weblate (Polish)
Currently translated at 100.0% (43 of 43 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2023-04-07 21:36:57 +00:00
Andrewblasco
9d45fc1c84 Translated using Weblate (Spanish)
Currently translated at 100.0% (43 of 43 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-07 21:36:57 +00:00
Hiajen
a436573f25 Translated using Weblate (German)
Currently translated at 100.0% (43 of 43 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/de/
2023-04-07 21:36:57 +00:00
dontobi
76980244ec Translated using Weblate (German)
Currently translated at 100.0% (43 of 43 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/de/
2023-04-07 21:36:57 +00:00
LucasGGamerM
0e6a3ccd72 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (43 of 43 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pt_BR/
2023-04-07 21:36:57 +00:00
gallegonovato
b8869e6460 Translated using Weblate (Spanish)
Currently translated at 100.0% (43 of 43 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-07 21:36:57 +00:00
sk
6ec43a6f86 slightly smaller collapsed height
closes sk22#480
2023-04-07 18:45:11 +02:00
sk
df93a1a845 increase max height 2023-04-07 18:42:18 +02:00
sk
41a70a353c distinct default languages
closes sk22#487
2023-04-07 18:21:23 +02:00
sk
8d69bcfd4b new profile counters for account card
closes sk22#483
2023-04-07 18:08:57 +02:00
sk
0ef30f82a7 fix disappearing no-alt indicator
closes sk22#484
2023-04-07 17:16:45 +02:00
sk
be60e78ea6 improve external share behavior 2023-04-07 16:58:02 +02:00
sk
5434325fa8 fix header alignments… again 2023-04-07 16:30:49 +02:00
sk
0a04c9357c Revert "display reblog popup by default"
This reverts commit 21c4cef397.

okay, so, i think i'll keep reblog as a default. i fear that exposing everyone
to an overwhelming menu (you literally have to *decide* for a visibility!)
when just pressing reblog might not be a good idea. i'll just have "confirm
before reblogging" as an option in the settings instead
https://floss.social/@megalodon/110157968813469351
2023-04-07 16:20:29 +02:00
sk
21c4cef397 display reblog popup by default 2023-04-07 16:04:35 +02:00
sk
4b2fcd760a add option to confirm before reblog
closes sk22#456
2023-04-07 15:29:43 +02:00
Pegasus89
39ea51c317 Translated using Weblate (Croatian)
Currently translated at 12.5% (2 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/hr/
2023-04-07 10:51:45 +00:00
Pegasus89
cd668e1bba Translated using Weblate (Croatian)
Currently translated at 97.4% (265 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/hr/
2023-04-07 10:51:45 +00:00
AiOO
916bd68a16 Translated using Weblate (Korean)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ko/
2023-04-07 10:51:45 +00:00
sk22
4aa7ff6f9c Translated using Weblate (Spanish)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-04-07 10:51:45 +00:00
Anonymous
38fb05a38b Translated using Weblate (Russian)
Currently translated at 92.2% (251 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ru/
2023-04-07 10:51:45 +00:00
poesty
aa919867c8 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-04-07 10:51:45 +00:00
sk
9824b5fb56 allow boosting with every visibility
closes sk22#486
2023-04-06 20:24:40 +02:00
sk
78fcf31e34 remove unused crowdin.yml 2023-04-06 20:16:33 +02:00
sk
eadb62d3a8 fix wrong rel=me link on website 2023-04-06 20:16:26 +02:00
sk
f6279fcc0c Merge branch 'l10n_fr' of codeberg.org:butterflyoffire/megalodon 2023-04-06 20:08:46 +02:00
LucasGGamerM
7a350c2685 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-05 20:48:53 -03:00
LucasGGamerM
7da2c06670 chore: remove on PR nightly build 2023-04-05 15:11:27 -03:00
FineFindus
dd4bad706b fix(notification): saved used ids 2023-04-05 19:54:26 +02:00
LucasGGamerM
5d253a39b2 refactor: move the auto-updater settings to match megalodon 2023-04-03 17:10:27 -03:00
LucasGGamerM
e84a92fedc feat: add link to up-to-date nightly build in settings 2023-04-03 17:09:07 -03:00
LucasGGamerM
6bc6aca17e refactor: remove the auto updater from nightly builds 2023-04-03 16:49:25 -03:00
LucasGGamerM
a7261bec47 chore: update nightly builds link on readme 2023-04-03 10:13:33 -03:00
LucasGGamerM
6f52e5de76 chore: change nightly builds action name 2023-04-03 09:30:05 -03:00
LucasGGamerM
f329f7a241 feat: provide apk instead of zip in nightly builds 2023-04-03 09:28:50 -03:00
LucasGGamerM
3eaff4cb4c Revert "fix: maybe fix crashes on the change of themes on nightly build"
This reverts commit cf0b1cf035.
2023-04-02 20:00:15 -03:00
LucasGGamerM
9bcae3f893 Revert "fix: actually fixing the crash on change of themes on nightly builds"
This reverts commit 6d094af6d5.
2023-04-02 20:00:15 -03:00
LucasGGamerM
6d094af6d5 fix: actually fixing the crash on change of themes on nightly builds 2023-04-02 19:52:44 -03:00
LucasGGamerM
cf0b1cf035 fix: maybe fix crashes on the change of themes on nightly build 2023-04-02 19:45:44 -03:00
LucasGGamerM
7c11a12e9a fix: padding inconsistencies on status footer
Fixed padding inconsistencies on the Status footer
2023-04-02 19:04:12 -03:00
LucasGGamerM
e244f63949 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-02 14:08:20 -03:00
LucasGGamerM
c4a390820d Merge remote-tracking branch 'weblate/master' 2023-04-02 14:06:29 -03:00
LucasGGamerM
1c0b62ac30 chore: still debugging the signing issue 2023-04-02 13:27:29 -03:00
LucasGGamerM
eacaf214e8 chore: debugging the signing process for nightly 2023-04-02 13:14:14 -03:00
LucasGGamerM
1369756e1e chore maybe fixing this again 2023-04-02 12:26:19 -03:00
LucasGGamerM
20803df255 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-02 12:14:25 -03:00
LucasGGamerM
80931b8948 chore: debugging the issue with github actions 2023-04-02 12:13:57 -03:00
LucasGGamerM
167597bc9f chore: still in the process of fixing github actions 2023-04-02 12:07:41 -03:00
LucasGGamerM
93dcc12f8d chore: trying to fix android.yml
I forgot that chore is the keyword for this stuff
2023-04-02 12:02:42 -03:00
LucasGGamerM
1287dff879 refactor: changing back the location of the keystore
This is bullshit. Why is this not working?
2023-04-02 11:56:06 -03:00
LucasGGamerM
6bf6e38384 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (42 of 42 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pt_BR/
2023-04-02 11:39:56 +00:00
gallegonovato
a1f8cdd76b Translated using Weblate (Spanish)
Currently translated at 100.0% (33 of 33 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2023-04-02 11:39:55 +00:00
gallegonovato
de48c48b49 Translated using Weblate (Spanish)
Currently translated at 21.2% (7 of 33 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2023-04-02 11:39:55 +00:00
ewm
27f0af4f0b Translated using Weblate (Polish)
Currently translated at 100.0% (41 of 41 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2023-04-02 11:39:55 +00:00
poesty
4226f776c1 Translated using Weblate (Chinese (Simplified))
Currently translated at 97.5% (40 of 41 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/zh_Hans/
2023-04-02 11:39:55 +00:00
gallegonovato
f1e4eae858 Translated using Weblate (Spanish)
Currently translated at 100.0% (41 of 41 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-02 11:39:55 +00:00
gallegonovato
4647fd6ee7 Translated using Weblate (Spanish)
Currently translated at 100.0% (33 of 33 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2023-04-02 11:39:55 +00:00
ewm
3a965500e8 Translated using Weblate (Polish)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2023-04-02 11:39:55 +00:00
Oliebol
7a9bb807f0 Translated using Weblate (Dutch)
Currently translated at 80.0% (32 of 40 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/nl/
2023-04-02 11:39:55 +00:00
gallegonovato
694f0044cc Translated using Weblate (Spanish)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-02 11:39:55 +00:00
dontobi
27696e4f0f Translated using Weblate (German)
Currently translated at 100.0% (40 of 40 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/de/
2023-04-02 11:39:55 +00:00
dontobi
7ec3d4d50b Translated using Weblate (German)
Currently translated at 100.0% (33 of 33 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/de/
2023-04-02 11:39:55 +00:00
ewm
803b1cd21a Translated using Weblate (Polish)
Currently translated at 100.0% (38 of 38 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/pl/
2023-04-02 11:39:55 +00:00
Andrewblasco
125d39b019 Translated using Weblate (Spanish)
Currently translated at 100.0% (38 of 38 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-02 11:39:55 +00:00
tygyh
63d1d9f49a Translated using Weblate (Swedish)
Currently translated at 12.5% (4 of 32 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/sv/
2023-04-02 11:39:55 +00:00
gallegonovato
6832cc8de2 Translated using Weblate (Spanish)
Currently translated at 100.0% (32 of 32 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2023-04-02 11:39:55 +00:00
Andrewblasco
f97effb042 Translated using Weblate (Spanish)
Currently translated at 100.0% (32 of 32 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2023-04-02 11:39:55 +00:00
Andrewblasco
3e233d1f72 Translated using Weblate (Spanish)
Currently translated at 100.0% (32 of 32 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2023-04-02 11:39:55 +00:00
gallegonovato
f07f1bf0e2 Translated using Weblate (Spanish)
Currently translated at 100.0% (32 of 32 strings)

Translation: Moshidon/metadata
Translate-URL: https://translate.codeberg.org/projects/moshidon/metadata/es/
2023-04-02 11:39:55 +00:00
Andrewblasco
8eb66a049e Translated using Weblate (Spanish)
Currently translated at 100.0% (38 of 38 strings)

Translation: Moshidon/values
Translate-URL: https://translate.codeberg.org/projects/moshidon/values/es/
2023-04-02 11:39:55 +00:00
LucasGGamerM
9578b139a9 Revert "refactor: build.gradle tinkering"
This reverts commit 4299e15ad2.
2023-04-01 21:27:11 -03:00
LucasGGamerM
4299e15ad2 refactor: build.gradle tinkering
Work plz i want to sleep
2023-04-01 21:24:19 -03:00
LucasGGamerM
1dceb02e09 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-01 21:20:19 -03:00
LucasGGamerM
32bfdd231a refactor: changing signing key location
Why am I doing this to myself?
2023-04-01 21:19:57 -03:00
LucasGGamerM
05b2d603f1 fix: maybe fix nightly builds again
Whyyyyyyy
2023-04-01 21:15:19 -03:00
LucasGGamerM
e7967eadc5 feat: using proper signing config for nightly flavor 2023-04-01 21:06:46 -03:00
LucasGGamerM
001873998a feat: updating .gitignore 2023-04-01 21:04:30 -03:00
LucasGGamerM
3d834a0c44 fix: maybe fixing gradle build error on nightly build
Github actions is such a pain
2023-04-01 21:00:30 -03:00
LucasGGamerM
35dcc44fe8 Revert "feat: add build.gradle parameters for nightly builds"
This reverts commit 14775aeb67.
2023-04-01 20:57:18 -03:00
LucasGGamerM
81f4934282 style: remove whitespace on strings_mo.xml 2023-04-01 20:48:49 -03:00
LucasGGamerM
f2335ff2d3 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-01 20:47:14 -03:00
LucasGGamerM
75161fc694 feat: add proper nightly signing to android.yml 2023-04-01 20:40:10 -03:00
LucasGGamerM
14775aeb67 feat: add build.gradle parameters for nightly builds 2023-04-01 20:38:51 -03:00
LucasGGamerM
5979341fb9 Update android.yml 2023-04-01 18:23:46 -03:00
LucasGGamerM
d86509233f fix: maybe fix the signing process 2023-04-01 18:18:36 -03:00
LucasGGamerM
d6d45cd9a5 Update android.yml 2023-04-01 18:15:06 -03:00
LucasGGamerM
21e940c94d fx: maybe fix the error on nightly builds 2023-04-01 18:00:36 -03:00
LucasGGamerM
f3ad600c94 feat: add signing key to the nightly build 2023-04-01 17:58:13 -03:00
LucasGGamerM
72e0a9c32e feat: make nightly builds require signing 2023-04-01 16:32:15 -03:00
LucasGGamerM
c791833fbe Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-04-01 15:32:40 -03:00
LucasGGamerM
bcac023d33 feat: add a toggle for swapping between reblog and bookmark notification actions 2023-04-01 15:32:01 -03:00
LucasGGamerM
7459181fa9 feat: make uniform notification icons setting false by default
I set these as true by default when merging because I liked the Mo, now lets make it false by default because Its cooler
2023-04-01 14:22:18 -03:00
LucasGGamerM
ef19a1ad09 Update README.md 2023-04-01 11:39:20 -03:00
LucasGGamerM
41446cde38 Update README.md 2023-04-01 11:34:09 -03:00
LucasGGamerM
44509bb274 feat: change Android CI to Nightly Builds
This is in android.yml in github workflows
2023-03-31 20:37:07 -03:00
LucasGGamerM
54d9c315e5 Update README.md 2023-03-31 20:35:34 -03:00
LucasGGamerM
9f8f7adb26 Revert "refactor: removing name from release"
This reverts commit c2341070f5.
2023-03-31 20:30:17 -03:00
LucasGGamerM
c2341070f5 refactor: removing name from release 2023-03-31 20:26:35 -03:00
LucasGGamerM
20af9eb6f4 fix: syntax error 2023-03-31 20:23:06 -03:00
LucasGGamerM
36bb1ea8af feat: add nightly download button
I have no idea if it will work, but hopefully it does!
2023-03-31 20:21:43 -03:00
LucasGGamerM
7d43105925 refactor: remove unused imports in MainActivity 2023-03-31 16:24:09 -03:00
LucasGGamerM
ebb3f3c5ae feat: add on push build workflow 2023-03-31 16:21:14 -03:00
LucasGGamerM
271815fa53 Merge pull request #145 from Mattis142/MonochromeNightly
New monochrome icon and removal of duplicate debug icons
2023-03-31 14:05:04 -03:00
LucasGGamerM
8570bdb4c0 Merge pull request #144 from Mattis142/CenterDebugIcon
feat: Small changes to the debug icon
2023-03-31 14:01:02 -03:00
Mattis
092a089580 New monochrome icon and removal of duplicate debug icons 2023-03-31 17:17:36 +02:00
Mattis142
f2af2cd9af Merge branch 'LucasGGamerM:master' into CenterDebugIcon 2023-03-31 15:19:52 +02:00
Mattis
2a8e4231b4 Small changes to the debug icon 2023-03-31 15:17:27 +02:00
LucasGGamerM
c3cb082de8 feat: add new nightly icon 2023-03-30 18:05:01 -03:00
LucasGGamerM
20b6d15c2e feat: add auto-updater to the nightly flavor 2023-03-30 14:37:05 -03:00
LucasGGamerM
5ec762ac0d feat: add nightly flavor to the app 2023-03-30 14:20:10 -03:00
LucasGGamerM
7f44c48c12 Merge pull request #140 from Mattis142/NewDebugIcon
feat: new Debug Icon
2023-03-29 11:19:27 -03:00
Mattis
2a071bc5de New Debug Icon 2023-03-29 12:55:11 +02:00
LucasGGamerM
ded9b70483 refactor: moving settings around and fixing a small bug with the publish button text button
Yes, I am doing 2 things in the same commit. Anyhow, this should make for a settings page reordering and a better cohesion with the settings sub parts. This should be a stepping stone to the new settings subfragments system
2023-03-27 15:59:38 -03:00
LucasGGamerM
0019f2183a style: add some tabs in fragment_compose.xml 2023-03-26 10:27:16 -03:00
LucasGGamerM
68f77a6c8a fix: wrong padding values for the schedule icon in ComposeFragment 2023-03-26 10:26:44 -03:00
FineFindus
a4d07d4307 fix: recents URL not showing after open in share menu 2023-03-25 20:24:34 +01:00
LucasGGamerM
903e844aad Merge pull request #136 from FineFindus/fix/missing-recents-url
fix(recent-url): show settings URL
2023-03-25 14:48:06 -03:00
LucasGGamerM
a6b299b029 Merge pull request #135 from FineFindus/fix/show-new-posts
fix(hometimeline): scroll up on tapping new post button
2023-03-25 14:46:05 -03:00
FineFindus
05f9ae8d76 fix(hometimeline): scroll up on tapping new post button 2023-03-25 17:55:15 +01:00
FineFindus
314afed9de fix(recent-url): show settings URL 2023-03-25 17:32:10 +01:00
sk
ebdd1a0b03 rename version 2023-03-24 20:05:42 +01:00
sk
6c44575f7a bump version 2023-03-24 20:04:35 +01:00
sk
8be832239a Merge remote-tracking branch 'upstream/l10n_master' 2023-03-24 20:03:31 +01:00
sk
c7be202430 Merge remote-tracking branch 'weblate/main' 2023-03-24 20:03:21 +01:00
sk
244c5dc6b4 bump version 2023-03-24 19:58:10 +01:00
poesty
6bbb9a638e Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/zh_Hans/
2023-03-24 18:40:14 +00:00
Espasant3
44041b136a Translated using Weblate (Galician)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-03-24 18:40:14 +00:00
ihor_ck
7e17c30ce2 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-03-24 18:40:14 +00:00
McKris
c4d3d1b409 Translated using Weblate (Polish)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pl/
2023-03-24 18:40:14 +00:00
Linerly
03de63754b Translated using Weblate (Indonesian)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-03-24 18:40:14 +00:00
Choukajohn
0e506f0b1a Translated using Weblate (French)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-03-24 18:40:14 +00:00
gallegonovato
a2ab752870 Translated using Weblate (Spanish)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-03-24 18:40:14 +00:00
sk22
eaa032828a Translated using Weblate (German)
Currently translated at 100.0% (272 of 272 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-03-24 18:40:14 +00:00
sk
cceb0b4c6c Merge remote-tracking branch 'upstream/master' 2023-03-24 19:33:40 +01:00
sk
a58640a718 fix crash when instance not yet loaded
closes sk22#474
2023-03-24 19:04:13 +01:00
Eugen Rochko
920384b26c New translations strings.xml (Dutch) 2023-03-24 16:26:08 +01:00
LucasGGamerM
880c34c3f0 fix: NPE in onHidden method
Why is this the one single method that I have commented in and out 5 times?
2023-03-23 14:09:18 -03:00
LucasGGamerM
6d7bf34f51 Merge pull request #131
fix(home-double-tab): scroll up for new posts
2023-03-23 13:52:39 -03:00
FineFindus
bc10a9fcf5 fix(home-double-tab): scroll up for new posts
Fixes an issue, where tapping the home tab would cycle instead of showing new posts
2023-03-23 16:03:15 +01:00
Eugen Rochko
193a2c4f70 New translations strings.xml (German) 2023-03-23 13:42:28 +01:00
Eugen Rochko
793dec98b2 New translations strings.xml (Vietnamese) 2023-03-23 07:36:02 +01:00
Eugen Rochko
11ddf8015d New translations strings.xml (Galician) 2023-03-23 05:17:18 +01:00
LucasGGamerM
b6d969bc7b feat: re-adds markers to the home timeline 2023-03-22 21:29:13 -03:00
LucasGGamerM
b799b54c9e fix: re-adds the Settings toggle for the no-alt-indicator 2023-03-22 21:09:03 -03:00
LucasGGamerM
5389f05ea8 fix: reimplements the no-alt-text badge and fixes crashes 2023-03-22 21:07:13 -03:00
LucasGGamerM
b4bf4a5bf5 fix: notification badge always reloading. Fixes #130 2023-03-22 21:02:30 -03:00
LucasGGamerM
6d9c87c9bc style: cleaning up old useless comments in the HomeFragment 2023-03-22 20:48:40 -03:00
Eugen Rochko
1753bdbd8b New translations strings.xml (Thai) 2023-03-22 17:21:08 +01:00
Grishka
d6e563486b Fix alt text button 2023-03-22 02:46:48 +03:00
LucasGGamerM
e8abc0bbd2 feat: make it compile again
I am tired as hell someone donates plz
2023-03-21 20:40:55 -03:00
Grishka
0112bfa9c4 Fix #547 2023-03-22 02:38:58 +03:00
Grishka
5951611fb0 Fix #551 2023-03-22 02:34:25 +03:00
LucasGGamerM
0589612df9 Merge remote-tracking branch 'megalodon_main/main'
# Conflicts:
#	README.md
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java
#	mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverAccountsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverNewsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/model/NotificationAction.java
#	mastodon/src/main/java/org/joinmastodon/android/model/Status.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/LinkSpan.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/MediaAttachmentViewController.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/res/layout/display_item_gifv.xml
#	mastodon/src/main/res/layout/display_item_photo.xml
#	mastodon/src/main/res/layout/display_item_video.xml
#	mastodon/src/main/res/menu/post.xml
#	mastodon/src/main/res/menu/profile.xml
#	mastodon/src/main/res/values-es-rES/strings_sk.xml
#	mastodon/src/main/res/values-fr-rFR/strings_sk.xml
#	mastodon/src/main/res/values-gl-rES/strings_sk.xml
#	mastodon/src/main/res/values-in-rID/strings_sk.xml
#	mastodon/src/main/res/values-pl-rPL/strings_sk.xml
#	mastodon/src/main/res/values-uk-rUA/strings_sk.xml
#	metadata/it-IT/full_description.txt
#	metadata/zh-Hans/short_description.txt
2023-03-21 20:25:51 -03:00
LucasGGamerM
af2d951b50 style: suppress lint on addMediaAttachment 2023-03-21 20:03:15 -03:00
Jacoco
132b672441 Improvements for Pleroma/Akkoma (#445)
* Reply Visibility on Plemora

* Sort statuses in thread

* Get default visibility and language from account if preferences fail

* Fix for Mentions tab in notifications on Pleroma

* Mark status as sensitive if not already when spoilertext is present

* Integrating Pleroma quoting for new posts

* move string to strings_sk

* use null instead of empty string

* change string

* fix crash due to null value

* update string

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-03-21 20:01:47 -03:00
sk
ffb7cc5c18 don't remove badge if loaded from cache 2023-03-21 19:55:34 -03:00
sk
5a9a3eb140 session-specific notification badge
fix sk22#470
2023-03-21 19:55:34 -03:00
sk
5395855775 feat: implement notification badge using markers from @sk22.
Thank you a lot man! This improvement is amazing
2023-03-21 19:55:34 -03:00
LucasGGamerM
9d60924512 refactor: removing unused piece of code 2023-03-21 19:54:20 -03:00
LucasGGamerM
1680c635dc Revert "feat(tabs/notifications): add unread badge"
This reverts commit 4ecd525f
2023-03-21 19:47:51 -03:00
LucasGGamerM
898fbcc52b Revert "refactor(notifications-tab/badge): use improved implementation"
This reverts commit ac561549
2023-03-21 19:40:11 -03:00
LucasGGamerM
d0e4578af0 fix: NPE when sharing an image and the URL is null 2023-03-21 18:51:15 -03:00
sk
2d31b726ac Merge branch 'main' of github.com:sk22/megalodon 2023-03-21 16:04:43 +01:00
sk22
d2f295ef88 Update README.md 2023-03-21 16:03:35 +01:00
sk
d1b53554ce bump version 2023-03-21 15:59:40 +01:00
sk
ec5db122d0 full text can have emojis, too 2023-03-21 15:56:44 +01:00
sk
0216e22fcc don't remove badge if loaded from cache 2023-03-21 15:25:01 +01:00
sk
5734b19d8c remove unused variable 2023-03-21 15:14:25 +01:00
Eugen Rochko
cc8c818e13 New translations strings.xml (Slovenian) 2023-03-21 11:52:06 +01:00
sk
e58aeec097 query notifications on load posts
closes sk22#471
2023-03-21 10:22:09 +01:00
sk
58b000927a session-specific notification badge
fix sk22#470
2023-03-21 10:03:31 +01:00
sk
797642b972 fix profile fragment crash
closes sk22#469
2023-03-21 09:56:44 +01:00
Eugen Rochko
2e1f273d78 New translations strings.xml (Chinese Traditional) 2023-03-21 06:38:54 +01:00
Eugen Rochko
35d6800877 New translations strings.xml (Chinese Traditional) 2023-03-21 05:17:21 +01:00
sk
781856b822 bump version 2023-03-21 00:55:11 +01:00
sk
ff272179e7 implement notification badge using markers 2023-03-21 00:52:20 +01:00
sk
bec47f40f7 colorful swipe-to-refresh spinner
closes sk22#455
2023-03-20 23:25:29 +01:00
sk
f9607a434a use accent color for notif title
closes sk22#461
2023-03-20 22:19:15 +01:00
sk
b650ca85bc Merge remote-tracking branch 'upstream/master' 2023-03-20 21:51:15 +01:00
LucasGGamerM
4ceacae954 refactor: simplify code in 56e5877040 2023-03-20 16:36:42 -03:00
LucasGGamerM
56e5877040 fix(custom-local-timelines): fix NPE in ended polls. Fixes #110 2023-03-20 16:09:06 -03:00
Eugen Rochko
91e154bbee New translations strings.xml (Italian) 2023-03-20 20:01:49 +01:00
sk
f4365ed163 fix context menu resizing
closes sk22#467
2023-03-20 19:17:22 +01:00
Eugen Rochko
0c02b0cb68 New translations strings.xml (Dutch) 2023-03-20 18:49:16 +01:00
Gregory K
b0aaa58fa7 Merge pull request #550 from sk22/fix/header-follow-protected-account
Fix following protected account from header
2023-03-20 20:36:11 +03:00
sk
054ab774d5 Merge branch 'fix/header-follow-protected-account' 2023-03-20 18:28:27 +01:00
sk
8ca33b552d fix following protected account from header
closes mastodon#549
2023-03-20 18:23:49 +01:00
sk
6734c2b9f7 add null check for source object
closes sk22#460
2023-03-20 14:22:46 +01:00
sk
c484477d6a persist translation between display items
closes sk22#466
2023-03-20 11:24:47 +01:00
sk
d9b5223749 collapse regardless of in timeline
re: sk22#462
2023-03-20 10:48:00 +01:00
sk
55856450b3 hopefully fix broken compact reply line 2023-03-20 10:22:43 +01:00
sk
bb28a3bf83 Merge remote-tracking branch 'upstream/master' 2023-03-19 18:51:10 +01:00
LucasGGamerM
c0484de506 feat: add toggle to disable double tap to swipe action 2023-03-19 11:20:46 -03:00
LucasGGamerM
addb1b27bb Merge remote-tracking branch 'mastodon/master' 2023-03-18 22:39:08 -03:00
LucasGGamerM
96ecdcd33c fix(custom-local-timelines): translate button now works 2023-03-18 22:36:51 -03:00
LucasGGamerM
e3f445cbed fix(custom-local-timelines): polls are now votable 2023-03-18 22:21:09 -03:00
LucasGGamerM
8ccb505165 Revert "refactor(account-switcher-sheet): remove duplicated code"
This reverts commit 9c01f7a522.
2023-03-18 17:03:15 -03:00
LucasGGamerM
98c193ac51 Merge pull request #125
Feat: share URL to open
2023-03-18 16:49:52 -03:00
LucasGGamerM
b966d5626b feat: adding nord to readme 2023-03-18 16:43:37 -03:00
FineFindus
9c01f7a522 refactor(account-switcher-sheet): remove duplicated code 2023-03-18 20:09:07 +01:00
FineFindus
f3c9f52d37 feat(share): add option open URL 2023-03-18 20:02:26 +01:00
LucasGGamerM
1df61780a3 Update README.md 2023-03-18 15:32:57 -03:00
LucasGGamerM
2be3bca70e Update README.md 2023-03-18 15:31:06 -03:00
LucasGGamerM
18ec63b9b4 fix: add missing red color 2023-03-18 15:09:03 -03:00
LucasGGamerM
6781fb6e25 fix: typo 2023-03-18 15:08:23 -03:00
LucasGGamerM
852bc6ba24 Update README.md 2023-03-18 15:06:21 -03:00
LucasGGamerM
223aa7043b fix: MediaGrid and LinkCard inset values too low, causing items to be cut by borders 2023-03-18 14:52:24 -03:00
LucasGGamerM
3178b5f41c fix: NPE when fragment is empty and HomeTabFragment is checking isScrolledToTop() 2023-03-18 13:53:02 -03:00
LucasGGamerM
14665c9a24 Merge pull request #124 from FineFindus/feat/tab-double-tab
Feat: Add tab triple click action
2023-03-18 13:06:43 -03:00
FineFindus
ea823ef0cf feat(NotificationsFragment): switch tab when triple clicking 2023-03-18 16:03:54 +01:00
FineFindus
62d5c7a492 feat(HomeTabFragment): switch timeline when triple clicking
Closes #83. When clicking the HomeTab and the timeline is already scrolled up, the timeline is cycled.
2023-03-18 15:57:13 +01:00
FineFindus
287e75e54a feat(Scrollable): add isScrolledToTop
Checks if the list is already scrolled to the top. Can be used to change the interaction depending on it.
2023-03-18 15:43:14 +01:00
LucasGGamerM
02af385853 feat: redesign account picker sheet 2023-03-17 17:44:17 -03:00
LucasGGamerM
ede2a3e0b5 Revert "feat(external-share): start re-design"
This reverts commit 6e5fa34be6.
2023-03-17 17:16:40 -03:00
LucasGGamerM
66dab9194a Merge pull request #121
Feat: redesign external share sheet
2023-03-17 17:12:12 -03:00
FineFindus
6e5fa34be6 feat(external-share): start re-design 2023-03-17 20:41:12 +01:00
FineFindus
8ce782b578 feat(external-share): use AccountSwitcherSheet 2023-03-17 20:41:07 +01:00
FineFindus
05168b9826 feat(external-share): use transparent background 2023-03-17 20:38:56 +01:00
LucasGGamerM
5208195f72 Merge pull request #120
Feat:  Add recents URL sharing (Pixel exclusive)
2023-03-17 15:29:33 -03:00
Gregory K
5e4e56bd2c Merge pull request #545 from FineFindus/fix/empty-search-query
fix(search): check for empty queries
2023-03-17 20:10:02 +03:00
FineFindus
82fac1d4e7 fix(search): check for empty queries
Fixes an error message, which would appear, if the search query was blank.
2023-03-17 16:19:38 +01:00
FineFindus
1dc90f930a fix(recent-url): remove error log message 2023-03-17 16:11:10 +01:00
FineFindus
aa56d160e6 feat(recent-url): add domain to selected status 2023-03-17 15:46:32 +01:00
FineFindus
452eac6fd7 feat(recent-url): add domain to discover fragments 2023-03-17 15:34:45 +01:00
FineFindus
d37a2b9db7 feat(recent-url): add domain to timelines 2023-03-17 15:07:30 +01:00
FineFindus
14e2747599 fix(recent-url): add support for own profile 2023-03-17 13:49:50 +01:00
sk
bf9afba590 bump version 2023-03-17 02:31:23 +01:00
sk
b667afc7cd some barebones calckey compatibility
re: sk22#429
2023-03-17 02:30:21 +01:00
sk
21b0736842 fix null pointer exception
closes sk22#442
2023-03-17 01:28:38 +01:00
sk
8bd3c7cc28 fix crash when navigating while sending
fix sk22#446
2023-03-17 01:23:14 +01:00
sk
4db90b87f4 compare status lang to device locale
re: sk22#458
2023-03-17 01:06:43 +01:00
sk
46fd0be0eb Merge remote-tracking branch 'upstream/l10n_master' 2023-03-17 00:56:38 +01:00
sk
05a966869b Merge remote-tracking branch 'weblate/main' 2023-03-17 00:52:26 +01:00
sk
6fef51fcbb measure text manually
hopefully fix sk22#422
2023-03-17 00:51:22 +01:00
FineFindus
ae7b2f3fc7 feat(recent-url): add url to profiles 2023-03-16 23:21:34 +01:00
sk
d5b6c02b22 content descriptions for compact reblog/reply line 2023-03-16 19:55:56 +01:00
sk
6cd722dbef improve pre draw listener 2023-03-16 19:22:58 +01:00
sk
8dfc1ecae8 reply part before reblog part 2023-03-16 19:14:28 +01:00
sk
fdca799a5f add todo 2023-03-16 18:49:40 +01:00
sk22
def79e591b Translated using Weblate (German)
Currently translated at 96.7% (268 of 277 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-03-16 00:19:12 +00:00
ihor_ck
093a27970d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (263 of 263 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-03-16 00:19:12 +00:00
McKris
9a1e33a776 Translated using Weblate (Polish)
Currently translated at 100.0% (263 of 263 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pl/
2023-03-16 00:19:12 +00:00
Linerly
a4a206eae6 Translated using Weblate (Indonesian)
Currently translated at 100.0% (263 of 263 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-03-16 00:19:12 +00:00
Espasant3
21ee5fc2bf Translated using Weblate (Galician)
Currently translated at 100.0% (263 of 263 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-03-16 00:19:12 +00:00
Choukajohn
7df72e6bc6 Translated using Weblate (French)
Currently translated at 100.0% (263 of 263 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-03-16 00:19:12 +00:00
gallegonovato
9ceea8de99 Translated using Weblate (Spanish)
Currently translated at 100.0% (263 of 263 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-03-16 00:19:12 +00:00
sk22
79d48a12a9 Translated using Weblate (German)
Currently translated at 100.0% (263 of 263 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/de/
2023-03-16 00:19:12 +00:00
ling0412
473d3a5196 Translated using Weblate (Chinese (Simplified))
Currently translated at 87.5% (14 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/zh_Hans/
2023-03-16 00:19:12 +00:00
Oliebol
43717634f6 Translated using Weblate (Dutch)
Currently translated at 95.8% (251 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/nl/
2023-03-16 00:19:11 +00:00
Andrewblasco
6ba8555adf Translated using Weblate (Spanish)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-03-16 00:19:11 +00:00
AiOO
722ad8c53e Translated using Weblate (Korean)
Currently translated at 100.0% (16 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/ko/
2023-03-16 00:19:11 +00:00
AiOO
b0b497ed46 Translated using Weblate (Korean)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/ko/
2023-03-16 00:19:11 +00:00
Espasant3
6c881eccd2 Translated using Weblate (Galician)
Currently translated at 100.0% (16 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/gl/
2023-03-16 00:19:11 +00:00
gicorada
94b92bd7c1 Translated using Weblate (Italian)
Currently translated at 100.0% (16 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/it/
2023-03-16 00:19:11 +00:00
gicorada
676b195aee Translated using Weblate (Italian)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/it/
2023-03-16 00:19:11 +00:00
a_mento
4591731cdc Translated using Weblate (Basque)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/eu/
2023-03-16 00:19:11 +00:00
Espasant3
fc5eeae9e7 Translated using Weblate (Galician)
Currently translated at 93.7% (15 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/gl/
2023-03-16 00:19:11 +00:00
ihor_ck
aaa88c45f7 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (16 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/uk/
2023-03-16 00:19:11 +00:00
McKris
5d91e8030c Translated using Weblate (Polish)
Currently translated at 100.0% (16 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/pl/
2023-03-16 00:19:11 +00:00
gallegonovato
c9be188d19 Translated using Weblate (Spanish)
Currently translated at 100.0% (16 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/es/
2023-03-16 00:19:11 +00:00
Linerly
ecc8daa8f1 Translated using Weblate (Indonesian)
Currently translated at 100.0% (16 of 16 strings)

Translation: Megalodon/metadata
Translate-URL: https://translate.codeberg.org/projects/megalodon/metadata/id/
2023-03-16 00:19:11 +00:00
ihor_ck
5bbe11bf45 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/uk/
2023-03-16 00:19:11 +00:00
McKris
e52170abea Translated using Weblate (Polish)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/pl/
2023-03-16 00:19:11 +00:00
Linerly
0f87fc7924 Translated using Weblate (Indonesian)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/id/
2023-03-16 00:19:11 +00:00
Espasant3
cc7c4fc95f Translated using Weblate (Galician)
Currently translated at 99.6% (261 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/gl/
2023-03-16 00:19:11 +00:00
Choukajohn
4a2dcdf701 Translated using Weblate (French)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/fr/
2023-03-16 00:19:11 +00:00
gallegonovato
ede47af962 Translated using Weblate (Spanish)
Currently translated at 100.0% (262 of 262 strings)

Translation: Megalodon/values
Translate-URL: https://translate.codeberg.org/projects/megalodon/values/es/
2023-03-16 00:19:11 +00:00
sk
70e4cb2286 change inline reply notification 2023-03-16 01:18:46 +01:00
LucasGGamerM
f4007dba90 Merge pull request #117
fix(custom-timelines): lookup profile by url
2023-03-15 21:10:20 -03:00
sk
c5e8460516 change notification action names 2023-03-16 01:02:33 +01:00
Jacoco
f852dac1e5 Improvements for Pleroma/Akkoma (#445)
* Reply Visibility on Plemora

* Sort statuses in thread

* Get default visibility and language from account if preferences fail

* Fix for Mentions tab in notifications on Pleroma

* Mark status as sensitive if not already when spoilertext is present

* Integrating Pleroma quoting for new posts

* move string to strings_sk

* use null instead of empty string

* change string

* fix crash due to null value

* update string

---------

Co-authored-by: sk <sk22@mailbox.org>
2023-03-16 00:49:11 +01:00
sk
374626cb32 Merge branch 'main' into pr/FineFindus/449 2023-03-15 23:42:02 +01:00
sk
aefa0b89ae Merge branch 'main' into pr/FineFindus/439 2023-03-15 23:38:33 +01:00
sk
9cc8b2668c fix show thread not working for reposts 2023-03-15 23:31:51 +01:00
sk
94b862a3ff improve reblog/reply line render 2023-03-15 23:27:43 +01:00
sk
f71bb6b78c use pre draw listener instead of post
hopefully fixes sk22#422
2023-03-15 22:36:30 +01:00
sk
499ac8f727 improve compact reply/reblog header 2023-03-15 22:30:57 +01:00
sk
938ae97cac remove click handler for reply line 2023-03-15 21:26:41 +01:00
sk
e2193e8e3c reply line below, compact above 2023-03-15 21:24:25 +01:00
FineFindus
d687abcdc1 fix(custom-timelines): lookup profile by url
Closes #107. Instead of using the (wrong) profileID on CustomTimelines, use the URL, which will be correctly resolved.
2023-03-15 20:05:16 +01:00
sk
16f907c91f show thread reply line 2023-03-15 19:23:20 +01:00
sk
584700225c reply line below avatar 2023-03-15 17:36:07 +01:00
sk
bad72985cb bump version 2023-03-15 12:49:09 +01:00
sk
56b24420d1 add local-only string 2023-03-14 15:37:06 +01:00
sk
92beac8dff fuck it, indented header line
closes #448
2023-03-14 15:34:05 +01:00
sk
ed1fdba9a5 Merge remote-tracking branch 'upstream/master' 2023-03-14 15:01:30 +01:00
sk
5e194e3079 display both reply and reblog
re: sk22#448
2023-03-14 14:59:11 +01:00
sk
27c2791d6c add reblogged account to known accounts 2023-03-14 14:58:21 +01:00
Grishka
d6bcc9c156 Fix button color 2023-03-14 07:31:01 +03:00
sk
4c85fd4387 use custom string for anonymous reply 2023-03-13 20:06:05 +01:00
sk
80d529d503 display reply header for unknown original poster
re: mastodon#342
2023-03-13 20:00:56 +01:00
sk
c5a19a2334 fix duplicate notification status header 2023-03-13 19:29:40 +01:00
sk
16857bebd9 add tooltip
closes sk22#436
2023-03-13 19:05:22 +01:00
sk
1c340b7c66 fix extra text padding in compose 2023-03-13 18:56:06 +01:00
sk
7d9d8f0aae Merge remote-tracking branch 'upstream/master' 2023-03-13 18:52:52 +01:00
sk
21fc35230c Merge remote-tracking branch 'upstream/master' 2023-03-13 18:51:26 +01:00
Grishka
fc67c82040 Fix #544 2023-03-13 20:46:29 +03:00
Eugen Rochko
a9b828001c New translations strings.xml (Burmese) 2023-03-13 15:08:41 +01:00
Eugen Rochko
be050abf7e New translations strings.xml (Burmese) 2023-03-13 14:01:13 +01:00
Eugen Rochko
2d071ca252 New translations strings.xml (Burmese) 2023-03-13 12:53:22 +01:00
Eugen Rochko
abf94e1e70 New translations strings.xml (Burmese) 2023-03-13 10:01:18 +01:00
Eugen Rochko
2991c7421c New translations strings.xml (Burmese) 2023-03-13 08:59:13 +01:00
Eugen Rochko
834f84f995 New translations strings.xml (Burmese) 2023-03-13 07:58:41 +01:00
Eugen Rochko
6bf713c96c New translations strings.xml (Polish) 2023-03-13 00:35:08 +01:00
Eugen Rochko
72b16be297 New translations strings.xml (Swedish) 2023-03-12 21:53:57 +01:00
Eugen Rochko
0c7f27b3ac New translations strings.xml (Swedish) 2023-03-12 20:47:06 +01:00
LucasGGamerM
6fa930aa9f Revert "feat: add android 14s predictive back gesture. Fixes #116"
This reverts commit 736ec4dbf9.
2023-03-11 20:18:27 -03:00
LucasGGamerM
736ec4dbf9 feat: add android 14s predictive back gesture. Fixes #116 2023-03-11 20:14:58 -03:00
LucasGGamerM
04ed9e76f2 feat: increase corner radius of search box and login box 2023-03-11 20:01:58 -03:00
LucasGGamerM
811304c0c7 fix: login with some pleroma instances. Fixes #114 2023-03-11 19:47:29 -03:00
Eugen Rochko
ca101748eb New translations strings.xml (Belarusian) 2023-03-11 23:37:17 +01:00
Eugen Rochko
3584c123d6 New translations strings.xml (Belarusian) 2023-03-11 22:33:13 +01:00
LucasGGamerM
9a991b8f33 fix: make show alt text indicator settings toggle work 2023-03-11 12:10:19 -03:00
Eugen Rochko
a2fd4be339 New translations title.txt (Urdu (India)) 2023-03-11 14:05:49 +01:00
Eugen Rochko
656364db60 New translations short_description.txt (Urdu (India)) 2023-03-11 14:05:48 +01:00
Eugen Rochko
884347da12 New translations full_description.txt (Urdu (India)) 2023-03-11 14:05:47 +01:00
Eugen Rochko
675c49a922 New translations strings.xml (Urdu (India)) 2023-03-11 14:05:46 +01:00
FineFindus
4d04741fe0 feat(welcome): use URI InputType (#454) 2023-03-11 13:01:11 +01:00
FineFindus
5c7fe9dcb5 fix: disable group divider on EMUI (#453)
Fixes an issues, where the forth menu item does not show up, when the divider is enabled on EMUI devices
2023-03-11 13:00:36 +01:00
Eugen Rochko
a3146d6cdd New translations full_description.txt (Belarusian) 2023-03-11 09:12:18 +01:00
Eugen Rochko
d769f757ed New translations full_description.txt (Belarusian) 2023-03-11 08:04:43 +01:00
LucasGGamerM
b28b70c6b8 feat: add different debug icon and label 2023-03-10 16:43:58 -03:00
LucasGGamerM
c2c5b08659 refactor: remove duplicate function in UiUtils 2023-03-10 16:07:22 -03:00
LucasGGamerM
7460b24fb0 Merge remote-tracking branch 'FineFindus/fix/home-overflow-menu-visibility'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
2023-03-10 16:06:01 -03:00
LucasGGamerM
2531015094 Merge pull request #115 from FineFindus/feat/uri-type
fix: apply URI InputType correctly
2023-03-10 15:55:50 -03:00
FineFindus
2efdf09333 fix: disable group divider on EMUI
Fixes an issues, where the forth menu item does not show up, when the divider is enabled on EMUI devices
2023-03-10 19:41:45 +01:00
FineFindus
30582bf8e3 fix: apply URI InputType correctly 2023-03-10 19:08:45 +01:00
Eugen Rochko
654e3eb36b New translations strings.xml (Japanese) 2023-03-09 23:29:35 +01:00
LucasGGamerM
67e3244655 refactor: remove missing alt text indicator setting until upstream reimplements it 2023-03-09 17:30:16 -03:00
LucasGGamerM
f793bbc711 Merge remote-tracking branch 'mastodon/l10n_master'
# Conflicts:
#	fastlane/metadata/android/ja-JP/full_description.txt
#	fastlane/metadata/android/ru-RU/full_description.txt
#	fastlane/metadata/android/ru-RU/short_description.txt
#	fastlane/metadata/android/tr-TR/full_description.txt
2023-03-09 17:29:40 -03:00
LucasGGamerM
94634781c4 style: add comma 2023-03-09 15:18:07 -03:00
LucasGGamerM
0fbbdb6621 refactor: remove a broken layout import 2023-03-09 15:17:33 -03:00
LucasGGamerM
d3f2049c7d Merge remote-tracking branch 'mastodon/master'
# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/ClickableLinksDelegate.java
#	mastodon/src/main/res/layout/display_item_gifv.xml
#	mastodon/src/main/res/layout/display_item_photo.xml
#	mastodon/src/main/res/layout/display_item_video.xml
2023-03-09 15:14:14 -03:00
LucasGGamerM
134b1edb19 fix: make reply by notification visibility consistent with Unlisted by default setting 2023-03-09 15:06:00 -03:00
LucasGGamerM
2489b57e42 refactor: change Unlisted by Default settings item icon to an open lock 2023-03-09 15:02:05 -03:00
LucasGGamerM
ee56ad07e9 Merge pull request #112 from FineFindus/patch-1
fix(translation/de): replace indefinite
2023-03-09 15:00:43 -03:00
LucasGGamerM
e1853cb1f0 Merge branch 'master' of https://github.com/LucasGGamerM/moshidon 2023-03-09 14:59:34 -03:00
LucasGGamerM
ee79916104 fix(#113): disableAltTextReminder setting being reset 2023-03-09 14:59:05 -03:00
Eugen Rochko
f62ba685d1 New translations strings.xml (Japanese) 2023-03-09 14:07:24 +01:00
Grishka
c3aa3af650 Fix #540 2023-03-08 22:46:24 +03:00
LucasGGamerM
fb44015402 docs: Update FAQ 2023-03-08 07:33:29 -03:00
Eugen Rochko
8cc5678b38 New translations strings.xml (Japanese) 2023-03-08 11:02:08 +01:00
Eugen Rochko
592eb2d589 New translations strings.xml (Japanese) 2023-03-08 02:55:58 +01:00
FineFindus
84713eb3b7 fix(translation/de): replace indefinite 2023-03-07 19:44:48 +01:00
Eugen Rochko
0bf31de1f1 New translations strings.xml (Japanese) 2023-03-06 21:23:36 +01:00
Eugen Rochko
7fd32cab3d New translations strings.xml (Japanese) 2023-03-06 19:38:06 +01:00
Grishka
4a695b2a83 Use a single display item for the image attachment grid 2023-03-06 02:25:13 +03:00
Grishka
a8ba50e762 Merge branch 'dev_clickable_links_hold_to_copy' 2023-03-05 22:33:35 +03:00
Grishka
f79fc66578 Fix 2023-03-05 22:33:18 +03:00
Eugen Rochko
0c63b99e6c New translations strings.xml (Japanese) 2023-03-05 02:15:44 +01:00
Eugen Rochko
5881f5fa05 New translations strings.xml (Japanese) 2023-03-05 01:20:11 +01:00
FineFindus
5e8ede6ab8 style(notifications-actions/reply): clean-up 2023-03-04 13:18:22 +01:00
FineFindus
1124bab96b style(notifications-actions/reply): clean-up 2023-03-04 13:16:27 +01:00
FineFindus
cbcdf09bfe Merge branch 'feat/notification-actions' 2023-03-04 13:16:17 +01:00
Eugen Rochko
575ca6251c New translations strings.xml (Arabic) 2023-03-03 20:35:51 +01:00
Eugen Rochko
9c83a5aaeb New translations strings.xml (Portuguese, Brazilian) 2023-03-02 15:21:36 +01:00
Eugen Rochko
bc3e46eae1 New translations strings.xml (Portuguese, Brazilian) 2023-03-02 14:20:26 +01:00
FineFindus
6756b36e87 style(notifications/action): fix whitespaces 2023-03-01 21:02:09 +01:00
FineFindus
36808e4e8e fix(notifications/action): remove unused string 2023-03-01 21:02:09 +01:00
FineFindus
1c38570609 fix(notifications/action): remove notification after tapping action 2023-03-01 21:02:09 +01:00
FineFindus
2dc6deb93a fix(notifications/action): use radom request code
Fixes the issue with pendingIntents overwriting each other. Probability of equal same request code should be near 0 with 2^32 ints available
2023-03-01 21:02:09 +01:00
FineFindus
0b58d19811 feat(notifications/action): remove notfication after action 2023-03-01 21:02:09 +01:00
FineFindus
50362d630b feat(notifications/action): add unboost action 2023-03-01 21:02:08 +01:00
FineFindus
bfcd67cbaf feat(notifications/action): only show actions if necessary 2023-03-01 21:02:08 +01:00
FineFindus
ea190c0597 feat(notifications/action): use string titles 2023-03-01 21:02:08 +01:00
FineFindus
e68160800d feat(notifications/action): add boost action 2023-03-01 21:02:08 +01:00
FineFindus
73481f4a1f Merge branch 'feat/notification-actions' 2023-03-01 20:59:54 +01:00
Eugen Rochko
0303e59fc1 New translations strings.xml (Bengali) 2023-02-28 16:38:40 +01:00
Eugen Rochko
0c0d36da62 New translations strings.xml (Bengali) 2023-02-28 13:32:43 +01:00
Eugen Rochko
e7e0b8841c New translations strings.xml (Bengali) 2023-02-28 12:21:52 +01:00
Eugen Rochko
79d9abe7f7 New translations full_description.txt (Turkish) 2023-02-27 17:26:40 +01:00
Eugen Rochko
684fbc0050 New translations strings.xml (Turkish) 2023-02-27 17:26:39 +01:00
Eugen Rochko
03973d41be New translations strings.xml (Turkish) 2023-02-27 16:25:31 +01:00
Eugen Rochko
06c533bf5a New translations full_description.txt (Japanese) 2023-02-27 08:35:37 +01:00
Eugen Rochko
cc64a20e53 New translations full_description.txt (Japanese) 2023-02-27 07:28:37 +01:00
Torge Rosendahl
4144639b75 docu 2023-02-26 13:59:39 -05:00
Eugen Rochko
d633b6f1d4 New translations strings.xml (Bengali) 2023-02-25 16:06:21 +01:00
Eugen Rochko
7b68baef0d New translations strings.xml (Bengali) 2023-02-25 13:06:26 +01:00
Eugen Rochko
fb5ef921f7 New translations strings.xml (Spanish) 2023-02-24 21:22:35 +01:00
Eugen Rochko
da44e50679 New translations strings.xml (Spanish) 2023-02-24 19:50:53 +01:00
Eugen Rochko
93d89f93b2 New translations strings.xml (Bengali) 2023-02-24 09:17:38 +01:00
Eugen Rochko
cce64f9a76 New translations strings.xml (Bengali) 2023-02-24 08:19:59 +01:00
Eugen Rochko
afb396acd8 New translations strings.xml (Chinese Traditional) 2023-02-22 18:07:34 +01:00
Eugen Rochko
0943908173 New translations strings.xml (Kabyle) 2023-02-20 08:34:11 +01:00
Eugen Rochko
b018788cd1 New translations strings.xml (Galician) 2023-02-19 22:35:48 +01:00
Eugen Rochko
4a315e73eb New translations strings.xml (Galician) 2023-02-19 21:34:24 +01:00
FineFindus
d0a9ba041d fix(fab): completly hide 2023-02-17 22:21:56 +01:00
Eugen Rochko
b01ef6d5a7 New translations short_description.txt (Russian) 2023-02-17 10:01:43 +01:00
Eugen Rochko
604e581139 New translations full_description.txt (Russian) 2023-02-17 10:01:42 +01:00
Eugen Rochko
082f697b40 New translations strings.xml (Russian) 2023-02-17 10:01:41 +01:00
Torge Rosendahl
0a8d73dc0b cleanup, resolved some warnings 2023-02-15 20:19:10 -05:00
Torge Rosendahl
fd99f3caa1 changed url longclick implementation to GestureListener 2023-02-15 20:16:20 -05:00
Torge Rosendahl
794c4e5227 removed longClickHandler and moved to view itself 2023-02-15 19:54:08 -05:00
Torge Rosendahl
f5df8225d1 whitespace corrections 2023-02-15 18:20:02 -05:00
Torge Rosendahl
42c6446125 refactoring: moved runnable and made it private, added copy toast localization. 2023-02-15 18:04:52 -05:00
Torge Rosendahl
e3486ebf7c added clickable link type switch for copy, to not copy hashtags and user IDs 2023-02-15 18:00:45 -05:00
Torge Rosendahl
c0115f068c implemented copy service 2023-02-15 17:40:43 -05:00
Torge Rosendahl
41682d1147 added press-and-hold listener to ClickableLinks 2023-02-15 17:30:31 -05:00
Eugen Rochko
8c61660cfc New translations strings.xml (Arabic) 2023-02-15 15:17:22 +01:00
Eugen Rochko
d6933be3cd New translations strings.xml (Arabic) 2023-02-15 13:12:38 +01:00
Eugen Rochko
ca6cfd2b91 New translations strings.xml (French) 2023-02-14 20:11:52 +01:00
Eugen Rochko
20960bdd57 New translations strings.xml (Belarusian) 2023-02-14 14:14:56 +01:00
Weblate Admin
c97a7e5158 Translated using Weblate (French)
Currently translated at 98.8% (351 of 355 strings)

Co-authored-by: Weblate Admin <butterflyoffire@todz.ynh.fr>
Translate-URL: https://rosette.todz.ynh.fr/projects/megalodon-app/app-strings/fr/
Translation: Megalodon app/Megalodon app
2022-12-06 09:19:24 +01:00
Weblate Admin
32e2d24b15 Translated using Weblate (Arabic (Algeria))
Currently translated at 94.0% (334 of 355 strings)

Translated using Weblate (French)

Currently translated at 96.9% (344 of 355 strings)

Co-authored-by: Weblate Admin <butterflyoffire@todz.ynh.fr>
Translate-URL: https://rosette.todz.ynh.fr/projects/megalodon-app/app-strings/ar_DZ/
Translate-URL: https://rosette.todz.ynh.fr/projects/megalodon-app/app-strings/fr/
Translation: Megalodon app/Megalodon app
2022-12-04 07:10:53 +01:00
Weblate Admin
c102aae819 Translated using Weblate (French)
Currently translated at 95.4% (339 of 355 strings)

Co-authored-by: Weblate Admin <butterflyoffire@todz.ynh.fr>
Translate-URL: https://rosette.todz.ynh.fr/projects/megalodon-app/app-strings/fr/
Translation: Megalodon app/Megalodon app
2022-12-03 18:22:49 +01:00
Weblate
bed72cb5ed Added translation using Weblate (Occitan)
Co-authored-by: Weblate <noreply@weblate.org>
2022-12-03 04:15:12 +01:00
Weblate
f0c1046fe9 Added translation using Weblate (Kabyle)
Co-authored-by: Weblate <noreply@weblate.org>
2022-12-03 04:15:08 +01:00
Weblate
d88104d105 Added translation using Weblate (French)
Added translation using Weblate (Arabic (Algeria))

Co-authored-by: Weblate <noreply@weblate.org>
2022-12-03 04:15:03 +01:00
Weblate
f3e21e5a82 Added translation using Weblate (Occitan)
Co-authored-by: Weblate <noreply@weblate.org>
2022-12-03 04:14:56 +01:00
Weblate
a77bee8664 Added translation using Weblate (Kabyle)
Co-authored-by: Weblate <noreply@weblate.org>
2022-12-03 04:14:51 +01:00
Weblate
b5d0aed59e Added translation using Weblate (French)
Added translation using Weblate (Arabic (Algeria))

Co-authored-by: Weblate <noreply@weblate.org>
2022-12-03 04:14:47 +01:00
Weblate Admin
2440cc6af5 Translated using Weblate (Arabic (Algeria))
Currently translated at 91.2% (324 of 355 strings)

Co-authored-by: Weblate Admin <butterflyoffire@todz.ynh.fr>
Translate-URL: https://rosette.todz.ynh.fr/projects/megalodon-app/app-strings/ar_DZ/
Translation: Megalodon app/Megalodon app
2022-12-02 22:40:00 +01:00
butterflyoffire
90114dfbe0 Supprimer 'mastodon/src/main/res/values-ar-DZ/strings.xml' 2022-12-02 21:34:54 +00:00
Weblate Admin
64b8cdf7dc Translated using Weblate (Arabic (Algeria))
Currently translated at 89.8% (319 of 355 strings)

Translated using Weblate (Arabic (Algeria))

Currently translated at 0.2% (1 of 355 strings)

Added translation using Weblate (Arabic (Algeria))

Added translation using Weblate (Arabic (Algeria))

Co-authored-by: Weblate Admin <butterflyoffire@todz.ynh.fr>
Translate-URL: https://rosette.todz.ynh.fr/projects/megalodon-app/app-strings/ar_DZ/
Translation: Megalodon app/Megalodon app
2022-12-02 19:14:49 +01:00
284 changed files with 5287 additions and 1440 deletions

48
.github/workflows/nightly-builds.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Nightly builds
on:
push:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Decode Keystore
id: decode_keystore
uses: timheuer/base64-to-file@v1
with:
fileName: 'nightly_keystore.jks'
fileDir: './mastodon/keystore/'
encodedString: ${{ secrets.KEYSTORE }}
- name: Build with Gradle
run: ./gradlew assembleNightly
env:
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
CURRENT_DATE: ${{ steps.date.outputs.date }}
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2
with:
name: moshidon-nightly.apk
path: ./mastodon/build/outputs/apk/nightly/moshidon-nightly.apk

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@
.cxx
local.properties
*.jks
*.keystore
/mastodon/keystore/nightly_keystore.keystore

View File

@@ -7,8 +7,12 @@
[![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 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://nightly.link/LucasGGamerM/moshidon/workflows/nightly-builds/master/moshidon-nightly.apk.zip)
[![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/android.yml)
<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;
@@ -20,7 +24,7 @@
### Q: What are the main differences between Moshidon and Megalodon?
### A: There are many, but the most outstanding differences are: the ability to have other server's local timeline inside the app. It can be acessed in the "Add community" option in the top right corner of the Edit timelines screen. Most other features are pretty minor, such as profile notes directly available in the person's profile. Other features are quite minor usability and visibility improvements. All of which can be found in the settings page.
### A: There are many, but the most outstanding differences are: the ability to have other server's local timeline inside the app. It can be acessed in the "Add community" option in the top right corner of the Edit timelines screen. Other outstanding features that Moshidon has are some quality of life improvements, such as notification actions and allowing for unlisted replies by default. Most other features are pretty minor, such as profile notes directly available in the person's profile. Other features are quite minor usability and visibility improvements. All of which can be found in the settings page.
---
@@ -28,6 +32,8 @@
### **The ability to add new custom local timelines!**
#### It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts!
### **Material you theme support on Android 12+ devices!**
### **Show posts filtered with a warning!**
@@ -41,7 +47,7 @@ Before | After
### **Color themes**
**Allows you to change theme within the app. Supports Purple, pink, green, blue, orange and yellow!**
**Allows you to change theme within the app. Supports Purple, pink, green, blue, red, orange, yellow and Nord!**
### **Unlisted posting**

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Moshidon</title>
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
<link rel="me" href="https://floss.social/@mastodon">
<link rel="me" href="https://floss.social/@megalodon">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
</head>
<body class="markdown-body">

View File

@@ -1,5 +0,0 @@
files:
- source: /mastodon/src/main/res/values/strings.xml
translation: /mastodon/src/main/res/values-%android_code%/strings.xml
- source: /fastlane/metadata/android/en-US/*.txt
translation: /fastlane/metadata/android/%locale%/%original_file_name%

View File

@@ -5,6 +5,7 @@ plugins {
android {
compileSdk 33
defaultConfig {
manifestPlaceholders = [oAuthScheme:"moshidon-android-auth"]
archivesBaseName = "moshidon"
applicationId "org.joinmastodon.android.moshinda"
minSdk 23
@@ -15,6 +16,30 @@ android {
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
}
signingConfigs {
nightly{
storeFile = file("keystore/nightly_keystore.jks")
storePassword System.getenv("SIGNING_STORE_PASSWORD")
if (storePassword == null) {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
storePassword = properties.getProperty('SIGNING_STORE_PASSWORD')
}
keyAlias System.getenv("SIGNING_KEY_ALIAS")
if (keyAlias == null) {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
keyAlias = properties.getProperty('SIGNING_KEY_ALIAS')
}
keyPassword System.getenv("SIGNING_KEY_PASSWORD")
if (keyPassword == null) {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
keyPassword = properties.getProperty('SIGNING_KEY_PASSWORD')
}
}
}
buildTypes {
release {
// minifyEnabled true
@@ -25,10 +50,24 @@ android {
debuggable true
versionNameSuffix '-debug'
applicationIdSuffix '.debug'
manifestPlaceholders = [oAuthScheme:"moshidon-android-debug-auth"]
}
githubRelease{
initWith release
}
nightly{
initWith release
if(System.getenv("CURRENT_DATE") != null){
versionNameSuffix '-nightly+@' + System.getenv("CURRENT_DATE")
} else {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
versionNameSuffix '-nightly+@' + properties.getProperty('CURRENT_DATE')
}
applicationIdSuffix '.nightly'
signingConfig signingConfigs.nightly
manifestPlaceholders = [oAuthScheme:"moshidon-android-nightly-auth"]
}
playRelease{
initWith release
minifyEnabled true

View File

@@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.joinmastodon.android">
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application>
<application
tools:replace="android:label"
android:label="@string/mo_app_name_debug">
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$InstallerStatusReceiver" android:exported="false"/>-->
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$AfterUpdateRestartReceiver" android:exported="true" android:enabled="false">-->
<!-- <intent-filter>-->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FF000000"
android:pathData="M54,90L54,90c-19.9,0 -36,-16.1 -36,-36v0c0,-19.9 16.1,-36 36,-36h0c19.9,0 36,16.1 36,36v0C90,73.9 73.9,90 54,90z"
android:strokeAlpha="0"
android:fillAlpha="0"/>
<path
android:pathData="M52.5,41.6c-2.4,0 -4.3,0.9 -5.5,2.8l-1.2,2l-1.2,-2c-1.2,-1.9 -3.1,-2.8 -5.5,-2.8c-2.1,0 -3.8,0.8 -5.1,2.2c-1.2,1.4 -1.9,3.4 -1.9,5.9v12h4.7V50c0,-2.4 1.1,-3.7 3.1,-3.7c2.3,0 3.4,1.4 3.4,4.4v6.4h4.7v-6.4c0,-2.9 1.1,-4.4 3.4,-4.4c2.1,0 3.1,1.2 3.1,3.7v11.7h4.7v-12c0,-2.4 -0.6,-4.4 -1.9,-5.9C56.2,42.3 54.6,41.6 52.5,41.6z"
android:fillColor="#33D17A"/>
<path
android:pathData="M65.9,58.1h0.8c0,0 0,0 -0.1,0c-0.6,-0.3 -1.1,-0.8 -1.4,-1.4c-0.3,-0.6 -0.5,-1.4 -0.5,-2.1c0,-0.8 0.2,-1.5 0.5,-2.1c0.4,-0.6 0.8,-1.1 1.4,-1.4c0.6,-0.3 1.2,-0.5 1.9,-0.5c0.7,0 1.3,0.2 1.9,0.5s1.1,0.8 1.4,1.4s0.5,1.3 0.5,2.1c0,0.2 0,0.4 0,0.6l0.7,0.7c0.4,0 0.8,0 1.1,0l1.5,-1.5l0.2,-0.2c-0.1,-1.2 -0.4,-2.3 -0.9,-3.4c-0.6,-1.1 -1.4,-2 -2.6,-2.6c-1.1,-0.6 -2.4,-1 -3.7,-1c-1.4,0 -2.7,0.3 -3.8,1c-1.1,0.6 -2,1.5 -2.6,2.6c-0.6,1.1 -0.9,2.4 -0.9,3.7s0.3,2.7 0.9,3.7c0.6,1.1 1.5,2 2.6,2.6c0.4,0.2 0.8,0.4 1.1,0.5v-1.8V58.1z"
android:fillColor="#33D17A"/>
<path
android:pathData="M76,58.3l1.2,-1.2L76.2,56l-1.7,1.7c-0.4,-0.1 -0.7,-0.2 -1.1,-0.2s-0.8,0.1 -1.1,0.2L70.7,56l-1,1.1l1.2,1.2c-0.5,0.4 -1.1,0.9 -1.4,1.5h-2.1v1.5H69c0,0.2 -0.1,0.5 -0.1,0.8v0.8h-1.5v1.5h1.5v0.8c0,0.2 0,0.5 0.1,0.8h-1.6v1.5h2.1c0.8,1.4 2.3,2.3 4,2.3s3.1,-0.9 4,-2.3h2.1v-1.5H78c0,-0.2 0.1,-0.5 0.1,-0.8v-0.8h1.5v-1.5h-1.5v-0.8c0,-0.2 0,-0.5 -0.1,-0.8h1.6v-1.5h-2.1C77.1,59.2 76.6,58.8 76,58.3zM75,65.9H72v-1.5H75V65.9zM75,62.9H72v-1.5H75V62.9z"
android:fillColor="#33D17A"/>
</vector>

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FF000000"
android:pathData="M54,90L54,90c-19.9,0 -36,-16.1 -36,-36v0c0,-19.9 16.1,-36 36,-36h0c19.9,0 36,16.1 36,36v0C90,73.9 73.9,90 54,90z"
android:strokeAlpha="0"
android:fillAlpha="0"/>
<path
android:pathData="M52.5,41.6c-2.4,0 -4.3,0.9 -5.5,2.8l-1.2,2l-1.2,-2c-1.2,-1.9 -3.1,-2.8 -5.5,-2.8c-2.1,0 -3.8,0.8 -5.1,2.2c-1.2,1.4 -1.9,3.4 -1.9,5.9v12h4.7V50c0,-2.4 1.1,-3.7 3.1,-3.7c2.3,0 3.4,1.4 3.4,4.4v6.4h4.7v-6.4c0,-2.9 1.1,-4.4 3.4,-4.4c2.1,0 3.1,1.2 3.1,3.7v11.7h4.7v-12c0,-2.4 -0.6,-4.4 -1.9,-5.9C56.2,42.3 54.6,41.6 52.5,41.6z"
android:fillColor="#33D17A"/>
<path
android:pathData="M65.9,58.1h0.8c0,0 0,0 -0.1,0c-0.6,-0.3 -1.1,-0.8 -1.4,-1.4c-0.3,-0.6 -0.5,-1.4 -0.5,-2.1c0,-0.8 0.2,-1.5 0.5,-2.1c0.4,-0.6 0.8,-1.1 1.4,-1.4c0.6,-0.3 1.2,-0.5 1.9,-0.5c0.7,0 1.3,0.2 1.9,0.5s1.1,0.8 1.4,1.4s0.5,1.3 0.5,2.1c0,0.2 0,0.4 0,0.6l0.7,0.7c0.4,0 0.8,0 1.1,0l1.5,-1.5l0.2,-0.2c-0.1,-1.2 -0.4,-2.3 -0.9,-3.4c-0.6,-1.1 -1.4,-2 -2.6,-2.6c-1.1,-0.6 -2.4,-1 -3.7,-1c-1.4,0 -2.7,0.3 -3.8,1c-1.1,0.6 -2,1.5 -2.6,2.6c-0.6,1.1 -0.9,2.4 -0.9,3.7s0.3,2.7 0.9,3.7c0.6,1.1 1.5,2 2.6,2.6c0.4,0.2 0.8,0.4 1.1,0.5v-1.8V58.1z"
android:fillColor="#33D17A"/>
<path
android:pathData="M76,58.3l1.2,-1.2L76.2,56l-1.7,1.7c-0.4,-0.1 -0.7,-0.2 -1.1,-0.2s-0.8,0.1 -1.1,0.2L70.7,56l-1,1.1l1.2,1.2c-0.5,0.4 -1.1,0.9 -1.4,1.5h-2.1v1.5H69c0,0.2 -0.1,0.5 -0.1,0.8v0.8h-1.5v1.5h1.5v0.8c0,0.2 0,0.5 0.1,0.8h-1.6v1.5h2.1c0.8,1.4 2.3,2.3 4,2.3s3.1,-0.9 4,-2.3h2.1v-1.5H78c0,-0.2 0.1,-0.5 0.1,-0.8v-0.8h1.5v-1.5h-1.5v-0.8c0,-0.2 0,-0.5 -0.1,-0.8h1.6v-1.5h-2.1C77.1,59.2 76.6,58.8 76,58.3zM75,65.9H72v-1.5H75V65.9zM75,62.9H72v-1.5H75V62.9z"
android:fillColor="#33D17A"/>
</vector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_debug"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground_monochrome_debug"/>
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground_debug"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground_monochrome_debug"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#000000</color>
</resources>

View File

@@ -17,6 +17,9 @@
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
<intent>
<action android:name="android.intent.action.TRANSLATE" />
</intent>
</queries>
<application
@@ -41,10 +44,11 @@
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="moshidon-android-auth" android:host="callback"/>
<data android:scheme="${oAuthScheme}" android:host="callback"/>
</intent-filter>
</activity>
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize">
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize"
android:theme="@style/TransparentDialog">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>

View File

@@ -0,0 +1,83 @@
package org.joinmastodon.android;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsIntent;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
import org.joinmastodon.android.api.session.AccountActivationInfo;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiUpdatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.Token;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class DomainManager {
private static final String TAG="DomainManager";
private static final DomainManager instance=new DomainManager();
private String currentDomain = "";
public static DomainManager getInstance(){
return instance;
}
private DomainManager(){
}
public String getCurrentDomain() {
return currentDomain;
}
public void setCurrentDomain(String domain) {
this.currentDomain = domain;
}
}

View File

@@ -1,9 +1,9 @@
package org.joinmastodon.android;
import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.ClipData;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
@@ -12,7 +12,9 @@ import android.widget.Toast;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.jsoup.internal.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
@@ -27,18 +29,23 @@ public class ExternalShareActivity extends FragmentStackActivity{
UiUtils.setUserPreferredTheme(this);
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
boolean isMastodonURL = UiUtils.looksLikeMastodonUrl(text);
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
if(sessions.isEmpty()){
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
finish();
}else if(sessions.size()==1){
}else if(sessions.size()==1 && !isMastodonURL){
openComposeFragment(sessions.get(0).getID());
}else{
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
UiUtils.pickAccount(this, null, R.string.choose_account, 0,
session -> openComposeFragment(session.getID()),
b -> b.setOnCancelListener(d -> finish())
);
new AccountSwitcherSheet(this, false, false, isMastodonURL, accountSession -> {
if(accountSession!=null)
openComposeFragment(accountSession.getID());
else
UiUtils.openURL(this, AccountSessionManager.getInstance().getLastActiveAccountID(), text);
}).show();
}
}
}
@@ -51,9 +58,15 @@ public class ExternalShareActivity extends FragmentStackActivity{
String subject = "";
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
if (!subject.isBlank()) builder.append(subject).append("\n\n");
if (!StringUtil.isBlank(subject)) builder.append(subject).append("\n\n");
}
if (intent.hasExtra(Intent.EXTRA_TEXT)) {
String extra = intent.getStringExtra(Intent.EXTRA_TEXT);
if (!StringUtil.isBlank(extra)) {
if (extra.startsWith(subject)) extra = extra.substring(subject.length()).trim();
builder.append(extra).append("\n\n");
}
}
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
String text=builder.toString();
List<Uri> mediaUris;
if(Intent.ACTION_SEND.equals(intent.getAction())){
@@ -80,8 +93,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
args.putString("account", accountID);
if(!TextUtils.isEmpty(text))
args.putString("prefilledText", text);
if(!subject.isBlank())
args.putInt("selectionEnd", subject.length());
args.putInt("selectionStart", StringUtil.isBlank(subject) ? 0 : subject.length());
if(mediaUris!=null && !mediaUris.isEmpty())
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
Fragment fragment=new ComposeFragment();
@@ -96,4 +108,11 @@ public class ExternalShareActivity extends FragmentStackActivity{
return null;
return new ArrayList<>(l);
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
}
}

View File

@@ -47,8 +47,13 @@ public class GlobalUserPreferences{
public static boolean collapseLongPosts;
public static boolean spectatorMode;
public static boolean autoHideFab;
public static boolean unreadNotifications;
public static boolean defaultToUnlistedReplies;
public static boolean disableDoubleTapToSwipe;
public static boolean compactReblogReplyLine;
public static boolean confirmBeforeReblog;
public static boolean replyLineAboveHeader;
public static boolean swapBookmarkWithBoostAction;
public static boolean loadRemoteAccountFollowers;
public static String publishButtonText;
public static ThemePreference theme;
public static ColorPreference color;
@@ -63,7 +68,13 @@ public class GlobalUserPreferences{
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
public static Map<String, Integer> recentEmojis;
private static SharedPreferences getPrefs(){
/**
* Pleroma
*/
public static String replyVisibility;
public static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
}
@@ -82,7 +93,7 @@ public class GlobalUserPreferences{
showBoosts=prefs.getBoolean("showBoosts", true);
loadNewPosts=prefs.getBoolean("loadNewPosts", true);
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", true);
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
disableMarquee=prefs.getBoolean("disableMarquee", false);
@@ -103,8 +114,14 @@ public class GlobalUserPreferences{
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
spectatorMode=prefs.getBoolean("spectatorMode", false);
autoHideFab=prefs.getBoolean("autoHideFab", true);
unreadNotifications=prefs.getBoolean("unreadNotifications", false);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
disableDoubleTapToSwipe=prefs.getBoolean("disableDoubleTapToSwipe", false);
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
swapBookmarkWithBoostAction=prefs.getBoolean("swapBookmarkWithBoostAction", false);
loadRemoteAccountFollowers=prefs.getBoolean("loadRemoteAccountFollowers", true);
publishButtonText=prefs.getString("publishButtonText", "");
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
@@ -113,6 +130,7 @@ public class GlobalUserPreferences{
pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
replyVisibility=prefs.getString("replyVisibility", null);
try {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
@@ -154,10 +172,15 @@ public class GlobalUserPreferences{
.putBoolean("collapseLongPosts", collapseLongPosts)
.putBoolean("spectatorMode", spectatorMode)
.putBoolean("autoHideFab", autoHideFab)
.putBoolean("unreadNotifications", unreadNotifications)
.putString("publishButtonText", publishButtonText)
.putBoolean("bottomEncoding", bottomEncoding)
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
.putBoolean("disableDoubleTapToSwipe", disableDoubleTapToSwipe)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
.putBoolean("swapBookmarkWithBoostAction", swapBookmarkWithBoostAction)
.putBoolean("loadRemoteAccountFollowers", loadRemoteAccountFollowers)
.putInt("theme", theme.ordinal())
.putString("color", color.name())
.putString("recentLanguages", gson.toJson(recentLanguages))
@@ -165,6 +188,7 @@ public class GlobalUserPreferences{
.putString("recentEmojis", gson.toJson(recentEmojis))
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
.putString("replyVisibility", replyVisibility)
.apply();
}

View File

@@ -2,40 +2,33 @@ package org.joinmastodon.android;
import android.Manifest;
import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.requests.accounts.SetPrivateNote;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.NotificationAction;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.parceler.Parcels;
import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class MainActivity extends FragmentStackActivity{
@Override
protected void onCreate(@Nullable Bundle savedInstanceState){
UiUtils.setUserPreferredTheme(this);
@@ -90,6 +83,7 @@ public class MainActivity extends FragmentStackActivity{
AccountSession accountSession;
try{
accountSession=AccountSessionManager.getInstance().getAccount(accountID);
DomainManager.getInstance().setCurrentDomain(accountSession.domain);
}catch(IllegalStateException x){
return;
}
@@ -177,4 +171,11 @@ public class MainActivity extends FragmentStackActivity{
super.onBackPressed();
}
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
}
}

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android;
import static org.joinmastodon.android.GlobalUserPreferences.getPrefs;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@@ -10,6 +12,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.opengl.Visibility;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
@@ -23,10 +26,13 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.NotificationReceivedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.NotificationAction;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
@@ -50,10 +56,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
private static final String ACTION_KEY_TEXT_REPLY = "ACTION_KEY_TEXT_REPLY";
private static final int SUMMARY_ID = 791;
private static int notificationId = 0;
private static int notificationId;
@Override
public void onReceive(Context context, Intent intent){
UiUtils.setUserPreferredTheme(context);
if(BuildConfig.DEBUG){
Log.e(TAG, "received: "+intent);
Bundle extras=intent.getExtras();
@@ -83,13 +90,12 @@ public class PushNotificationReceiver extends BroadcastReceiver{
}
String accountID=account.getID();
PushNotification pn=AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().decryptNotification(k, p, s);
E.post(new NotificationReceivedEvent(accountID, pn.notificationId+""));
new GetNotificationByID(pn.notificationId+"")
.setCallback(new Callback<>(){
@Override
public void onSuccess(org.joinmastodon.android.model.Notification result){
MastodonAPIController.runInBackground(()->PushNotificationReceiver.this.notify(context, pn, accountID, result));
GlobalUserPreferences.unreadNotifications = true;
GlobalUserPreferences.save();
}
@Override
@@ -139,6 +145,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
NotificationManager nm=context.getSystemService(NotificationManager.class);
notificationId=getPrefs().getInt("latestNotificationId", 0);
Account self=AccountSessionManager.getInstance().getAccount(accountID).self;
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
Notification.Builder builder;
@@ -210,6 +217,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
}
int id = GlobalUserPreferences.keepOnlyLatestNotification ? NOTIFICATION_ID : notificationId++;
getPrefs().edit().putInt("latestNotificationId", notificationId).apply();
if (notification != null){
switch (pn.notificationType){
@@ -218,9 +226,15 @@ public class PushNotificationReceiver extends BroadcastReceiver{
builder.addAction(buildReplyAction(context, id, accountID, notification));
}
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.button_favorite), NotificationAction.FAVORITE));
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.add_bookmark), NotificationAction.BOOKMARK));
if(GlobalUserPreferences.swapBookmarkWithBoostAction){
if(notification.status.visibility != StatusPrivacy.DIRECT) {
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.button_reblog), NotificationAction.BOOST));
}else{
// This is just so there is a bookmark action if you cannot reblog the toot
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.add_bookmark), NotificationAction.BOOKMARK));
}
} else {
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.add_bookmark), NotificationAction.BOOKMARK));
}
}
case UPDATE -> {
@@ -274,7 +288,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
CreateStatus.Request req=new CreateStatus.Request();
req.status = input.toString() + "\n\n" + "@" + notification.status.account.acct;
req.language = notification.status.language;
req.visibility = notification.status.visibility;
req.visibility = (notification.status.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : notification.status.visibility);
req.inReplyToId = notification.status.id;
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
req.spoilerText = "re: " + notification.status.spoilerText;

View File

@@ -13,9 +13,11 @@ import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.SearchResult;
@@ -126,11 +128,12 @@ public class CacheController{
});
}
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<PaginatedResponse<List<Notification>>> callback){
public void getNotifications(String maxID, int count, boolean onlyMentions, boolean onlyPosts, boolean forceReload, Callback<CacheablePaginatedResponse<List<Notification>>> callback){
cancelDelayedClose();
databaseThread.postRunnable(()->{
try{
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.NOTIFICATIONS)).collect(Collectors.toList());
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(accountID);
List<Filter> filters=accountSession.wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.NOTIFICATIONS)).collect(Collectors.toList());
if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase();
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
@@ -153,18 +156,19 @@ public class CacheController{
result.add(ntf);
}while(cursor.moveToNext());
String _newMaxID=newMaxID;
uiHandler.post(()->callback.onSuccess(new PaginatedResponse<>(result, _newMaxID)));
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
return;
}
}catch(IOException x){
Log.w(TAG, "getNotifications: corrupted notification object in database", x);
}
}
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class))
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.pleroma != null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Notification> result){
callback.onSuccess(new PaginatedResponse<>(result.stream().filter(ntf->{
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
if(ntf.status!=null){
for(Filter filter:filters){
if(filter.matches(ntf.status)){
@@ -173,7 +177,7 @@ public class CacheController{
}
}
return true;
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id));
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
putNotifications(result, onlyMentions, onlyPosts, maxID==null);
}

View File

@@ -16,6 +16,7 @@ import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.model.Status;
import java.io.BufferedReader;
import java.io.IOException;
@@ -40,12 +41,15 @@ import okhttp3.ResponseBody;
public class MastodonAPIController{
private static final String TAG="MastodonAPIController";
public static final Gson gson=new GsonBuilder()
public static final Gson gsonWithoutDeserializer = new GsonBuilder()
.disableHtmlEscaping()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Instant.class, new IsoInstantTypeAdapter())
.registerTypeAdapter(LocalDate.class, new IsoLocalDateTypeAdapter())
.create();
public static final Gson gson = gsonWithoutDeserializer.newBuilder()
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
.create();
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
@@ -81,7 +85,7 @@ public class MastodonAPIController{
final boolean isBad = host == null || badDomains.stream().anyMatch(h -> h.equalsIgnoreCase(host) || host.toLowerCase().endsWith("." + h));
thread.postRunnable(()->{
try{
if (isBad) throw new IllegalArgumentException();
// if (isBad) throw new IllegalArgumentException();
if(req.canceled)
return;
Request.Builder builder=new Request.Builder()

View File

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

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.api.requests.notifications;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.ApiUtils;
@@ -11,19 +10,25 @@ import java.util.EnumSet;
import java.util.List;
public class GetNotifications extends MastodonAPIRequest<List<Notification>>{
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> includeTypes){
public GetNotifications(String maxID, int limit, EnumSet<Notification.Type> includeTypes, boolean isPleromaInstance){
super(HttpMethod.GET, "/notifications", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", ""+limit);
if(includeTypes!=null){
if(!isPleromaInstance) {
for(String type:ApiUtils.enumSetToStrings(includeTypes, Notification.Type.class)){
addQueryParameter("types[]", type);
}
for(String type:ApiUtils.enumSetToStrings(EnumSet.complementOf(includeTypes), Notification.Type.class)){
addQueryParameter("exclude_types[]", type);
}
}else{
for(String type:ApiUtils.enumSetToStrings(includeTypes, Notification.Type.class)){
addQueryParameter("include_types[]", type);
}
}
}
removeUnsupportedItems=true;
}

View File

@@ -45,6 +45,8 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public Instant scheduledAt;
public String language;
public String quoteId;
public static class Poll{
public ArrayList<String> options=new ArrayList<>();
public int expiresIn;

View File

@@ -2,6 +2,7 @@ package org.joinmastodon.android.api.requests.timelines;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
@@ -18,5 +19,7 @@ public class GetHomeTimeline extends MastodonAPIRequest<List<Status>>{
addQueryParameter("since_id", sinceID);
if(limit>0)
addQueryParameter("limit", ""+limit);
if(GlobalUserPreferences.replyVisibility != null)
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
}
}

View File

@@ -7,6 +7,7 @@ import org.joinmastodon.android.api.StatusInteractionController;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.model.Token;
@@ -31,6 +32,7 @@ public class AccountSession{
public String pushAccountID;
public Preferences preferences;
public AccountActivationInfo activationInfo;
public Markers markers;
private transient MastodonAPIController apiController;
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
private transient CacheController cacheController;

View File

@@ -25,6 +25,7 @@ import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.requests.markers.GetMarkers;
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
import org.joinmastodon.android.events.EmojiUpdatedEvent;
import org.joinmastodon.android.model.Account;
@@ -33,6 +34,8 @@ import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Marker;
import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.Token;
@@ -46,6 +49,7 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -61,7 +65,7 @@ import me.grishka.appkit.api.ErrorResponse;
public class AccountSessionManager{
private static final String TAG="AccountSessionManager";
public static final String SCOPE="read write follow push";
public static final String REDIRECT_URI="moshidon-android-auth://callback";
public static final String REDIRECT_URI = getRedirectURI();
private static final AccountSessionManager instance=new AccountSessionManager();
@@ -80,6 +84,17 @@ public class AccountSessionManager{
return instance;
}
public static String getRedirectURI() {
StringBuilder builder = new StringBuilder();
builder.append("moshidon-android-");
if (BuildConfig.BUILD_TYPE.equals("debug") || BuildConfig.BUILD_TYPE.equals("nightly")) {
builder.append(BuildConfig.BUILD_TYPE);
builder.append('-');
}
builder.append("auth://callback");
return builder.toString();
}
private AccountSessionManager(){
prefs=MastodonApp.context.getSharedPreferences("account_manager", Context.MODE_PRIVATE);
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
@@ -211,7 +226,7 @@ public class AccountSessionManager{
.path("/oauth/authorize")
.appendQueryParameter("response_type", "code")
.appendQueryParameter("client_id", result.clientId)
.appendQueryParameter("redirect_uri", "moshidon-android-auth://callback")
.appendQueryParameter("redirect_uri", REDIRECT_URI)
.appendQueryParameter("scope", SCOPE)
.build();
@@ -255,6 +270,7 @@ public class AccountSessionManager{
// if(now-session.filtersLastUpdated>3600_000L){
updateSessionWordFilters(session);
// }
updateSessionMarkers(session);
}
if(loadedInstances){
maybeUpdateCustomEmojis(domains);
@@ -271,6 +287,15 @@ public class AccountSessionManager{
}
}
private void preferencesFromSource(AccountSession session, Account account) {
if (account != null && account.source != null && session.preferences != null) {
if (account.source.privacy != null)
session.preferences.postingDefaultVisibility = account.source.privacy;
if (account.source.language != null)
session.preferences.postingDefaultLanguage = account.source.language;
}
}
private void updateSessionLocalInfo(AccountSession session){
new GetOwnAccount()
.setCallback(new Callback<>(){
@@ -278,13 +303,12 @@ public class AccountSessionManager{
public void onSuccess(Account result){
session.self=result;
session.infoLastUpdated=System.currentTimeMillis();
preferencesFromSource(session, result);
writeAccountsFile();
}
@Override
public void onError(ErrorResponse error){
}
public void onError(ErrorResponse error){}
})
.exec(session.getID());
}
@@ -294,10 +318,14 @@ public class AccountSessionManager{
@Override
public void onSuccess(Preferences preferences) {
session.preferences=preferences;
preferencesFromSource(session, session.self);
}
@Override
public void onError(ErrorResponse error) {}
public void onError(ErrorResponse error) {
session.preferences = new Preferences();
preferencesFromSource(session, session.self);
}
}).exec(session.getID());
}
@@ -319,6 +347,21 @@ public class AccountSessionManager{
.exec(session.getID());
}
private void updateSessionMarkers(AccountSession session) {
new GetMarkers(EnumSet.allOf(Marker.Type.class)).setCallback(new Callback<>() {
@Override
public void onSuccess(Markers markers) {
session.markers = markers;
writeAccountsFile();
}
@Override
public void onError(ErrorResponse error) {
}
}).exec(session.getID());
}
public void updateInstanceInfo(String domain){
new GetInstance()
.setCallback(new Callback<>(){

View File

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

View File

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

View File

@@ -13,6 +13,7 @@ import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -20,11 +21,14 @@ import android.view.animation.TranslateAnimation;
import android.widget.ImageButton;
import android.widget.Toolbar;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.DisplayItemsParent;
@@ -32,12 +36,10 @@ import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.TileGridLayoutManager;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
@@ -45,8 +47,10 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout;
import org.joinmastodon.android.ui.views.MediaGridLayout;
import org.joinmastodon.android.utils.TypedObjectPool;
import java.util.ArrayList;
import java.util.Collections;
@@ -57,7 +61,6 @@ import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
@@ -71,7 +74,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop{
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, DomainDisplay{
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
protected DisplayItemsAdapter adapter;
protected String accountID;
@@ -81,6 +84,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected HashMap<String, Account> knownAccounts=new HashMap<>();
protected HashMap<String, Relationship> relationships=new HashMap<>();
protected Rect tmpRect=new Rect();
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
private final int THRESHOLD = 800;
@@ -96,6 +100,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
UiUtils.loadMaxWidth(getContext());
if(GlobalUserPreferences.disableMarquee){
setTitleMarqueeEnabled(false);
setSubtitleMarqueeEnabled(false);
@@ -192,21 +197,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
@Override
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex){
final Status status=_status.reblog!=null ? _status.reblog : _status;
public void openPhotoViewer(String parentID, Status _status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder){
final Status status=_status.getContentStatus();
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
private ImageStatusDisplayItem.Holder<?> transitioningHolder;
private MediaAttachmentViewController transitioningHolder;
@Override
public void setPhotoViewVisibility(int index, boolean visible){
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
MediaAttachmentViewController holder=findPhotoViewHolder(index);
if(holder!=null)
holder.photo.setAlpha(visible ? 1f : 0f);
}
@Override
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
MediaAttachmentViewController holder=findPhotoViewHolder(index);
if(holder!=null){
transitioningHolder=holder;
View view=transitioningHolder.photo;
@@ -214,7 +219,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
view.getLocationOnScreen(pos);
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
list.setClipChildren(false);
transitioningHolder.itemView.setElevation(1f);
gridHolder.setClipChildren(false);
transitioningHolder.view.setElevation(1f);
return true;
}
return false;
@@ -241,15 +247,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
view.setTranslationY(0f);
view.setScaleX(1f);
view.setScaleY(1f);
transitioningHolder.itemView.setElevation(0f);
transitioningHolder.view.setElevation(0f);
if(list!=null)
list.setClipChildren(true);
gridHolder.setClipChildren(true);
transitioningHolder=null;
}
@Override
public Drawable getPhotoViewCurrentDrawable(int index){
ImageStatusDisplayItem.Holder<?> holder=findPhotoViewHolder(index);
MediaAttachmentViewController holder=findPhotoViewHolder(index);
if(holder!=null)
return holder.photo.getDrawable();
return null;
@@ -265,23 +272,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST);
}
private ImageStatusDisplayItem.Holder<?> findPhotoViewHolder(int index){
if(list==null)
return null;
int offset=0;
for(StatusDisplayItem item:displayItems){
if(item.parentID.equals(parentID)){
if(item instanceof ImageStatusDisplayItem){
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(getMainAdapterOffset()+offset+index);
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
return imgHolder;
}
return null;
}
}
offset++;
}
return null;
private MediaAttachmentViewController findPhotoViewHolder(int index){
return gridHolder.getViewController(index);
}
});
}
@@ -380,31 +372,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
}
@Override
protected RecyclerView.LayoutManager onCreateLayoutManager(){
GridLayoutManager lm=new TileGridLayoutManager(getActivity(), 1000);
lm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup(){
@Override
public int getSpanSize(int position){
position-=getMainAdapterOffset();
if(position>=0 && position<displayItems.size()){
StatusDisplayItem item=displayItems.get(position);
if(item instanceof ImageStatusDisplayItem imgItem){
PhotoLayoutHelper.TiledLayoutResult layout=imgItem.tiledLayout;
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgItem.thisTile;
int spans=0;
for(int i=0;i<tile.colSpan;i++){
spans+=layout.columnSizes[tile.startCol+i];
}
return spans;
}
}
return 1000;
}
});
return lm;
}
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
@@ -453,7 +420,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
int prevSize=pollItems.size();
pollItems.clear();
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems);
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems, status);
if(prevSize!=pollItems.size()){
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
@@ -490,12 +457,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
}
}else{
if(holder.getItem().status.reloadWhenClicked){
Status queryStatus = holder.getItem().status;
UiUtils.lookupStatus(getContext(), queryStatus, accountID, null, status -> {
submitPollVote(holder.getItemID(), status.poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
});
return;
}
submitPollVote(holder.getItemID(), poll.id, Collections.singletonList(poll.options.indexOf(option)));
}
}
public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){
Poll poll=holder.getItem().poll;
if(holder.getItem().status.reloadWhenClicked){
Status queryStatus = holder.getItem().status;
UiUtils.lookupStatus(getContext(), queryStatus, accountID, null, status -> {
submitPollVote(holder.getItemID(), status.poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
});
return;
}
submitPollVote(holder.getItemID(), poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
}
@@ -523,7 +504,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
revealSpoiler(status, holder.getItemID());
}
public void onRevealSpoilerClick(ImageStatusDisplayItem.Holder<?> holder){
public void onRevealSpoilerClick(MediaGridStatusDisplayItem.Holder holder){
Status status=holder.getItem().status;
revealSpoiler(status, holder.getItemID());
}
@@ -557,7 +538,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
holder.getItem().status.textExpandable = expandable;
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if (header != null) header.rebind();
holder.rebind();
}
}
@@ -571,13 +551,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected void updateImagesSpoilerState(Status status, String itemID){
ArrayList<Integer> updatedPositions=new ArrayList<>();
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
photo.setRevealed(status.spoilerRevealed);
updatedPositions.add(photo.getAbsoluteAdapterPosition()-getMainAdapterOffset());
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
if(mediaGrid!=null){
mediaGrid.setRevealed(status.spoilerRevealed);
updatedPositions.add(mediaGrid.getAbsoluteAdapterPosition()-getMainAdapterOffset());
}
int i=0;
for(StatusDisplayItem item:displayItems){
if(itemID.equals(item.parentID) && item instanceof ImageStatusDisplayItem && !updatedPositions.contains(i)){
if(itemID.equals(item.parentID) && item instanceof MediaGridStatusDisplayItem && !updatedPositions.contains(i)){
adapter.notifyItemChanged(i);
}
i++;
@@ -664,6 +645,11 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0) == null || list.getChildAt(0).getTop() == 0;
}
protected int getListWidthForMediaLayout(){
return list.getWidth();
}
@@ -720,6 +706,15 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return UiUtils.pickAccountForCompose(getActivity(), accountID);
}
private MediaAttachmentViewController makeNewMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){
return new MediaAttachmentViewController(getActivity(), type);
}
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> getAttachmentViewsPool(){
return attachmentViewsPool;
}
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
public DisplayItemsAdapter(){
@@ -757,16 +752,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public ImageLoaderRequest getImageRequest(int position, int image){
return displayItems.get(position).getImageRequest(image);
}
// @Override
// public void onViewDetachedFromWindow(@NonNull BindableViewHolder<StatusDisplayItem> holder){
// if(holder instanceof ImageLoaderViewHolder){
// int count=holder.getItem().getImageCount();
// for(int i=0;i<count;i++){
// ((ImageLoaderViewHolder) holder).clearImage(i);
// }
// }
// }
}
private class StatusListItemDecoration extends RecyclerView.ItemDecoration{
@@ -800,25 +785,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
for(int i=0;i<parent.getChildCount();i++){
View child=parent.getChildAt(i);
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
if(!imgHolder.getItem().status.spoilerRevealed && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
hiddenMediaPaint.setColor(0x80000000);
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
float hGap=tile.startCol>0 ? V.dp(1) : 0;
float vGap=tile.startRow>0 ? V.dp(1) : 0;
c.drawRect(child.getX()-hGap, child.getY()-vGap, child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
c.drawRect(child.getX(), child.getY(), child.getX()+child.getWidth(), child.getY()+child.getHeight(), hiddenMediaPaint);
}
}
}
for(int i=0;i<parent.getChildCount();i++){
View child=parent.getChildAt(i);
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
if(holder instanceof MediaGridStatusDisplayItem.Holder imgHolder){
if(!imgHolder.getItem().status.spoilerRevealed){
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
if(tile.startCol==0 && tile.startRow==0 && TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
if(TextUtils.isEmpty(imgHolder.getItem().status.spoilerText)){
int listWidth=getListWidthForMediaLayout();
int width=Math.min(listWidth, V.dp(ImageAttachmentFrameLayout.MAX_WIDTH));
int width=Math.min(listWidth, UiUtils.MAX_WIDTH);
if(currentMediaHiddenLayoutsWidth!=width)
rebuildMediaHiddenLayouts(width-V.dp(32));
c.save();
@@ -843,47 +824,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof ImageStatusDisplayItem.Holder){
int listWidth=getListWidthForMediaLayout();
int width=Math.min(listWidth, V.dp(ImageAttachmentFrameLayout.MAX_WIDTH));
PhotoLayoutHelper.TiledLayoutResult layout=((ImageStatusDisplayItem.Holder<?>) holder).getItem().tiledLayout;
PhotoLayoutHelper.TiledLayoutResult.Tile tile=((ImageStatusDisplayItem.Holder<?>) holder).getItem().thisTile;
if(tile.startCol+tile.colSpan<layout.columnSizes.length){
outRect.right=V.dp(1);
}
if(tile.startRow+tile.rowSpan<layout.rowSizes.length){
outRect.bottom=V.dp(1);
}
// For a view that spans rows, compensate its additional height so the row it's in stays the right height
if(tile.rowSpan>1){
outRect.bottom=-(Math.round(tile.height/1000f*width)-Math.round(layout.rowSizes[tile.startRow]/1000f*width));
}
// ...and for its siblings, offset those on rows below first to the right where they belong
if(tile.startCol>0 && layout.tiles[0].rowSpan>1 && tile.startRow>layout.tiles[0].startRow){
int xOffset=Math.round(layout.tiles[0].width/1000f*listWidth);
outRect.left=xOffset;
outRect.right=-xOffset;
}
// If the width of the media block is smaller than that of the RecyclerView, offset the views horizontally to center them
if(listWidth>width){
outRect.left+=(listWidth-V.dp(ImageAttachmentFrameLayout.MAX_WIDTH))/2;
if(tile.startCol>0){
int spanOffset=0;
for(int i=0;i<tile.startCol;i++){
spanOffset+=layout.columnSizes[i];
}
outRect.left-=Math.round(spanOffset/1000f*listWidth);
outRect.left+=Math.round(spanOffset/1000f*width);
}
}
}
}
private void rebuildMediaHiddenLayouts(int width){
currentMediaHiddenLayoutsWidth=width;
String title=getString(R.string.sensitive_content);

View File

@@ -112,7 +112,7 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.TransferSpeedTracker;
import org.joinmastodon.android.utils.TransferSpeedTracker;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ComposeEditText;
import org.joinmastodon.android.ui.views.ComposeMediaLayout;
@@ -207,6 +207,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private List<EmojiCategory> customEmojis;
private CustomEmojiPopupKeyboard emojiKeyboard;
private Status replyTo;
private Status quote;
private String initialText;
private String uuid;
private int pollDuration=24*3600;
@@ -259,6 +260,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
if(getArguments().containsKey("replyTo"))
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
if(getArguments().containsKey("quote"))
quote=Parcels.unwrap(getArguments().getParcelable("quote"));
if(instance==null){
Nav.finish(this);
return;
@@ -356,7 +359,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
scheduleTimeBtn=view.findViewById(R.id.scheduled_time_btn);
sensitiveIcon=view.findViewById(R.id.sensitive_icon);
sensitiveItem=view.findViewById(R.id.sensitive_item);
replyText=view.findViewById(R.id.reply_text);
replyText=view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text : R.id.reply_text_below);
view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text_below : R.id.reply_text)
.setVisibility(View.GONE);
if (isPhotoPickerAvailable()) {
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
@@ -628,7 +633,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
});
spoilerEdit.addTextChangedListener(new SimpleTextWatcher(e->updateCharCounter()));
if(replyTo!=null){
if(replyTo!=null || quote!=null){
Status status = quote!=null ? quote : replyTo;
View replyWrap = view.findViewById(R.id.reply_wrap);
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
int scrollHeight = scrollView.getHeight();
@@ -654,13 +660,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
originalPost.setOnClickListener(v->{
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(replyTo));
args.putParcelable("status", Parcels.wrap(status));
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
Nav.go(getActivity(), ThreadFragment.class, args);
});
ImageView avatar = view.findViewById(R.id.avatar);
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(replyTo.account.avatar));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(status.account.avatar));
ViewOutlineProvider roundCornersOutline=new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
@@ -672,15 +678,15 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
avatar.setOnClickListener(v->{
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(replyTo.account));
args.putParcelable("profileAccount", Parcels.wrap(status.account));
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
Nav.go(getActivity(), ProfileFragment.class, args);
});
((TextView) view.findViewById(R.id.name)).setText(replyTo.account.displayName);
((TextView) view.findViewById(R.id.username)).setText(replyTo.account.getDisplayUsername());
((TextView) view.findViewById(R.id.name)).setText(status.account.displayName);
((TextView) view.findViewById(R.id.username)).setText(status.account.getDisplayUsername());
view.findViewById(R.id.visibility).setVisibility(View.GONE);
Drawable visibilityIcon = getActivity().getDrawable(switch(replyTo.visibility){
Drawable visibilityIcon = getActivity().getDrawable(switch(status.visibility){
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular;
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled;
@@ -691,27 +697,28 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
moreBtn.setImageDrawable(visibilityIcon);
moreBtn.setBackground(null);
TextView timestamp = view.findViewById(R.id.timestamp);
if (replyTo.editedAt==null) timestamp.setText(UiUtils.formatRelativeTimestamp(getContext(), replyTo.createdAt));
else timestamp.setText(getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), replyTo.editedAt)));
if (replyTo.spoilerText != null && !replyTo.spoilerText.isBlank()) {
if (status.editedAt!=null) timestamp.setText(getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(getContext(), status.editedAt)));
else if (status.createdAt!=null) timestamp.setText(UiUtils.formatRelativeTimestamp(getContext(), status.createdAt));
else timestamp.setText("");
if (status.spoilerText != null && !status.spoilerText.isBlank()) {
view.findViewById(R.id.spoiler_header).setVisibility(View.VISIBLE);
((TextView) view.findViewById(R.id.spoiler_title_inline)).setText(replyTo.spoilerText);
((TextView) view.findViewById(R.id.spoiler_title_inline)).setText(status.spoilerText);
}
SpannableStringBuilder content = HtmlParser.parse(replyTo.content, replyTo.emojis, replyTo.mentions, replyTo.tags, accountID);
SpannableStringBuilder content = HtmlParser.parse(status.content, status.emojis, status.mentions, status.tags, accountID);
LinkedTextView text = view.findViewById(R.id.text);
if (content.length() > 0) text.setText(content);
else view.findViewById(R.id.display_item_text).setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName));
int visibilityNameRes = switch (replyTo.visibility) {
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.displayName));
int visibilityNameRes = switch (status.visibility) {
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
case PRIVATE -> R.string.visibility_followers_only;
case DIRECT -> R.string.visibility_private;
case LOCAL -> R.string.sk_local_only;
};
replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
replyText.setOnClickListener(v->{
scrollView.smoothScrollTo(0, 0);
});
@@ -722,9 +729,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
ArrayList<String> mentions=new ArrayList<>();
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
if(!replyTo.account.id.equals(ownID))
mentions.add('@'+replyTo.account.acct);
for(Mention mention:replyTo.mentions){
if(!status.account.id.equals(ownID))
mentions.add('@'+status.account.acct);
for(Mention mention:status.mentions){
if(mention.id.equals(ownID))
continue;
String m='@'+mention.acct;
@@ -737,17 +744,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
ignoreSelectionChanges=true;
mainEditText.setSelection(mainEditText.length());
ignoreSelectionChanges=false;
if(!TextUtils.isEmpty(replyTo.spoilerText)){
if(!TextUtils.isEmpty(status.spoilerText)){
hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE);
if(GlobalUserPreferences.prefixRepliesWithRe && !replyTo.spoilerText.startsWith("re: ")){
spoilerEdit.setText("re: " + replyTo.spoilerText);
if(GlobalUserPreferences.prefixRepliesWithRe && !status.spoilerText.startsWith("re: ")){
spoilerEdit.setText("re: " + status.spoilerText);
}else{
spoilerEdit.setText(replyTo.spoilerText);
spoilerEdit.setText(status.spoilerText);
}
spoilerBtn.setSelected(true);
}
if (replyTo.language != null && !replyTo.language.isEmpty()) updateLanguage(replyTo.language);
if (status.language != null && !status.language.isEmpty()) updateLanguage(status.language);
}
}else if (editingStatus==null || editingStatus.inReplyToId==null){
// TODO: remove workaround after https://github.com/mastodon/mastodon-android/issues/341 gets fixed
@@ -1013,9 +1020,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void onCustomEmojiClick(Emoji emoji){
int start=mainEditText.getSelectionStart();
String prefix=start>0 && !Character.isWhitespace(mainEditText.getText().charAt(start-1)) ? " :" : ":";
mainEditText.getText().replace(start, mainEditText.getSelectionEnd(), prefix+emoji.shortcode+':');
if(getActivity().getCurrentFocus() instanceof EditText edit){
int start=edit.getSelectionStart();
String prefix=start>0 && !Character.isWhitespace(edit.getText().charAt(start-1)) ? " :" : ":";
edit.getText().replace(start, edit.getSelectionEnd(), prefix+emoji.shortcode+':');
}
}
@Override
@@ -1138,6 +1147,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(hasSpoiler && spoilerEdit.length()>0){
req.spoilerText=spoilerEdit.getText().toString();
}
if(quote != null){
req.quoteId=quote.id;
}
if(uuid==null)
uuid=UUID.randomUUID().toString();
@@ -1170,7 +1182,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}else{
E.post(new StatusUpdatedEvent(result));
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
Nav.finish(ComposeFragment.this);
}
if (getArguments().getBoolean("navigateToStatus", false)) {
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -1371,6 +1385,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
}
@SuppressLint("StringFormatInvalid")
private boolean addMediaAttachment(Uri uri, String description){
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){
showMediaAttachmentError(getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS));

View File

@@ -1,6 +1,9 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status;
@@ -35,6 +38,11 @@ public class CustomLocalTimelineFragment extends StatusListFragment {
setTitle(this.domain);
}
@Override
public String getDomain() {
return domain;
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
@@ -47,6 +55,7 @@ public class CustomLocalTimelineFragment extends StatusListFragment {
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
result.stream().forEach(status -> {
status.account.acct += "@"+domain;
status.mentions.forEach(mention -> mention.id = null);
status.reloadWhenClicked = true;
});

View File

@@ -0,0 +1,15 @@
package org.joinmastodon.android.fragments;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
public interface DomainDisplay {
default String getDomain(){
AccountSession session = AccountSessionManager.getInstance().getLastActiveAccount();
if (session != null)
return session.domain;
else
return "";
}
}

View File

@@ -50,12 +50,11 @@ import java.util.Map;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition> implements ScrollableToTop {
private String accountID;
private TimelinesAdapter adapter;
private final ItemTouchHelper itemTouchHelper;
@@ -151,7 +150,7 @@ public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefiniti
FrameLayout inputWrap = new FrameLayout(getContext());
EditText input = new EditText(getContext());
input.setHint(R.string.sk_example_domain);
input.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
input.setLayoutParams(params);
@@ -237,6 +236,11 @@ public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefiniti
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
@Override
public void onDestroy() {
super.onDestroy();

View File

@@ -38,7 +38,6 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -47,7 +46,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
private String accountID;
private Map<String, Relationship> relationships=Collections.emptyMap();
private GetAccountRelationships relationshipsRequest;
@@ -149,6 +148,11 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){

View File

@@ -16,11 +16,10 @@ import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop {
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop {
private String nextMaxID;
private String accountId;
@@ -76,6 +75,11 @@ public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
@NonNull
@Override

View File

@@ -11,6 +11,7 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.Toast;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.tags.GetHashtag;
@@ -43,12 +44,19 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
return true;
}
@Override
public String getDomain() {
return super.getDomain() + "/tags/" + hashtag;
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
updateTitle(getArguments().getString("hashtag"));
following=getArguments().getBoolean("following", false);
setHasOptionsMenu(true);
DomainManager.getInstance().setCurrentDomain(getDomain());
}
private void updateTitle(String hashtagName) {

View File

@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments;
import android.app.Fragment;
import android.app.NotificationManager;
import android.content.Intent;
import android.graphics.Outline;
import android.os.Build;
import android.os.Bundle;
@@ -19,20 +20,38 @@ import android.widget.LinearLayout;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.E;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.notifications.GetNotifications;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
import org.joinmastodon.android.events.NotificationReceivedEvent;
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TabBar;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import com.squareup.otto.Subscribe;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.AppKitFragment;
import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
@@ -46,8 +65,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
private HomeTabFragment homeTabFragment;
// private HomeTimelineFragment homeTimelineFragment;
private NotificationsFragment notificationsFragment;
private DiscoverFragment searchFragment;
private ProfileFragment profileFragment;
@@ -63,6 +80,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
E.register(this);
accountID=getArguments().getString("account");
setTitle(R.string.mo_app_name);
@@ -76,9 +94,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
homeTabFragment=new HomeTabFragment();
homeTabFragment.setArguments(args);
// homeTimelineFragment=new HomeTimelineFragment();
// homeTimelineFragment.setArguments(args);
args=new Bundle(args);
args.putBoolean("noAutoLoad", true);
searchFragment=new DiscoverFragment();
@@ -121,7 +136,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
ViewImageLoader.load(tabBarAvatar, null, new UrlImageLoaderRequest(self.avatar, V.dp(28), V.dp(28)));
notificationTabIcon=content.findViewById(R.id.tab_notifications);
setNotificationBadge();
updateNotificationBadge();
if(savedInstanceState==null){
getChildFragmentManager().beginTransaction()
@@ -131,12 +146,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
.commit();
// getChildFragmentManager().beginTransaction()
// .add(R.id.fragment_wrap, homeTimelineFragment)
// .add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
// .add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
// .add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
// .commit();
String defaultTab=getArguments().getString("tab");
if("notifications".equals(defaultTab)){
@@ -161,12 +170,9 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
if(savedInstanceState==null) return;
// if(savedInstanceState==null || homeTimelineFragment!=null)
// return;
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
// homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment");
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
@@ -181,19 +187,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
.show(current)
.commit();
// getChildFragmentManager().beginTransaction()
// .hide(homeTimelineFragment)
// .hide(searchFragment)
// .hide(notificationsFragment)
// .hide(profileFragment)
// .show(current)
// .commit();
maybeTriggerLoading(current);
}
@Override
public void onHiddenChanged(boolean hidden){
super.onHiddenChanged(hidden);
if (!hidden && fragmentForTab(currentTab) instanceof DomainDisplay display)
DomainManager.getInstance().setCurrentDomain(display.getDomain());
fragmentForTab(currentTab).onHiddenChanged(hidden);
}
@@ -220,8 +221,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
// homeTimelineFragment.onApplyWindowInsets(topOnlyInsets);
searchFragment.onApplyWindowInsets(topOnlyInsets);
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
profileFragment.onApplyWindowInsets(topOnlyInsets);
@@ -230,9 +229,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
private Fragment fragmentForTab(@IdRes int tab){
if(tab==R.id.tab_home){
return homeTabFragment;
// if(tab==R.id.tab_home){
// return homeTimelineFragment;
}else if(tab==R.id.tab_search){
return searchFragment;
}else if(tab==R.id.tab_notifications){
@@ -262,10 +258,8 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
return;
}
if(tab == R.id.tab_notifications){
GlobalUserPreferences.unreadNotifications = false;
GlobalUserPreferences.save();
setNotificationBadge();
if (newFragment instanceof DomainDisplay display) {
DomainManager.getInstance().setCurrentDomain(display.getDomain());
}
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
@@ -298,7 +292,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
}
new AccountSwitcherSheet(getActivity()).show();
new AccountSwitcherSheet(getActivity(), true, true, false, accountSession -> {
getActivity().finish();
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
}).show();
return true;
}
if(tab==R.id.tab_search){
@@ -334,14 +331,50 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
if (profileFragment.isAdded()) getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
// getChildFragmentManager().putFragment(outState, "homeTimelineFragment", homeTimelineFragment);
// getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
// getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
// getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
}
private void setNotificationBadge() {
notificationTabIcon.setImageDrawable(getContext().getDrawable(GlobalUserPreferences.unreadNotifications ? R.drawable.ic_notifications_tab_badged : R.drawable.ic_fluent_alert_28_selector));
public void updateNotificationBadge() {
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
if (instance == null) return;
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.pleroma != null)
.setCallback(new Callback<>() {
@Override
public void onSuccess(List<Notification> notifications) {
if (notifications.size() > 0) {
try {
long newestId = Long.parseLong(notifications.get(0).id);
long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId);
System.out.println("NEWEST: " + newestId);
System.out.println("LAST SEEN: " + lastSeenId);
setNotificationBadge(newestId > lastSeenId);
} catch (Exception ignored) {
setNotificationBadge(false);
}
}
}
@Override
public void onError(ErrorResponse error) {
setNotificationBadge(false);
}
}).exec(accountID);
}
public void setNotificationBadge(boolean badge) {
notificationTabIcon.setImageResource(badge
? R.drawable.ic_fluent_alert_28_selector_badged
: R.drawable.ic_fluent_alert_28_selector);
}
@Subscribe
public void onNotificationReceived(NotificationReceivedEvent notificationReceivedEvent) {
if (notificationReceivedEvent.account.equals(accountID)) setNotificationBadge(true);
}
@Subscribe
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
setNotificationBadge(false);
}
}

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments;
import static org.joinmastodon.android.GlobalUserPreferences.reduceMotion;
import static org.joinmastodon.android.GlobalUserPreferences.showNewPostsButton;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -35,6 +36,7 @@ import androidx.viewpager2.widget.ViewPager2;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
@@ -70,7 +72,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener {
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay {
private static final int ANNOUNCEMENTS_RESULT = 654;
private String accountID;
@@ -196,6 +198,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (fragments[position] instanceof BaseRecyclerFragment<?> page){
if(!page.loaded && !page.isDataLoading()) page.loadData();
}
//update recent app list url
if (fragments[position] instanceof DomainDisplay page)
DomainManager.getInstance().setCurrentDomain(page.getDomain());
}
});
@@ -280,6 +286,14 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
}).exec(accountID);
}
@Override
public String getDomain() {
if (fragments[pager.getCurrentItem()] instanceof DomainDisplay page) {
return page.getDomain();
}
return DomainDisplay.super.getDomain();
}
private void addListsToOverflowMenu() {
Context ctx = getContext();
listsMenu.clear();
@@ -358,7 +372,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
addListsToOverflowMenu();
addHashtagsToOverflowMenu();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
m.setGroupDividerEnabled(true);
}
}
@@ -464,9 +478,20 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Override
public void scrollToTop(){
if (((ScrollableToTop) fragments[pager.getCurrentItem()]).isScrolledToTop() &&
!GlobalUserPreferences.disableDoubleTapToSwipe && !newPostsBtnShown) {
int nextPage = (pager.getCurrentItem() + 1) % count;
navigateTo(nextPage);
return;
}
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
}
@Override
public boolean isScrolledToTop() {
return ((ScrollableToTop) fragments[pager.getCurrentItem()]).isScrolledToTop();
}
public void hideNewPostsButton(){
if(!newPostsBtnShown)
return;
@@ -537,8 +562,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
private void onNewPostsBtnClick(View view) {
if(newPostsBtnShown){
hideNewPostsButton();
scrollToTop();
hideNewPostsButton();
}
}

View File

@@ -42,6 +42,11 @@ public class HomeTimelineFragment extends StatusListFragment {
return true;
}
@Override
public String getDomain() {
return super.getDomain() + "/home";
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
@@ -167,6 +172,10 @@ public class HomeTimelineFragment extends StatusListFragment {
}
})
.exec(accountID);
if (parent.getParentFragment() instanceof HomeFragment homeFragment) {
homeFragment.updateNotificationBadge();
}
}
@Override

View File

@@ -37,11 +37,10 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
public class ListTimelinesFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop {
private String accountId;
private String profileAccountId;
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
@@ -200,6 +199,11 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
@NonNull
@Override

View File

@@ -37,7 +37,7 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.V;
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop{
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, DomainDisplay{
private TabLayout tabLayout;
private ViewPager2 pager;
@@ -48,6 +48,11 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
private String accountID;
@Override
public String getDomain() {
return DomainDisplay.super.getDomain() + "/notifications";
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -140,17 +145,20 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("__is_tab", true);
args.putBoolean("noAutoLoad", true);
allNotificationsFragment=new NotificationsListFragment();
allNotificationsFragment.setArguments(args);
args=new Bundle(args);
args.putBoolean("onlyMentions", true);
args.putBoolean("noAutoLoad", true);
mentionsFragment=new NotificationsListFragment();
mentionsFragment.setArguments(args);
args=new Bundle(args);
args.putBoolean("onlyPosts", true);
args.putBoolean("noAutoLoad", true);
postsFragment=new NotificationsListFragment();
postsFragment.setArguments(args);
@@ -197,9 +205,19 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
@Override
public void scrollToTop(){
if (getFragmentForPage(pager.getCurrentItem()).isScrolledToTop() && !GlobalUserPreferences.disableDoubleTapToSwipe) {
int nextPage = (pager.getCurrentItem() + 1) % tabViews.length;
pager.setCurrentItem(nextPage, true);
return;
}
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
}
@Override
public boolean isScrolledToTop() {
return getFragmentForPage(pager.getCurrentItem()).isScrolledToTop();
}
public void loadData(){
refreshFollowRequestsBadge();
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
@@ -226,7 +244,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);

View File

@@ -11,17 +11,17 @@ import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.AllNotificationsSeenEvent;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
@@ -40,7 +40,6 @@ import java.util.stream.Stream;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
private boolean onlyMentions;
@@ -53,6 +52,11 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
return true;
}
@Override
public String getDomain() {
return super.getDomain() + "/notifications";
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -97,14 +101,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
};
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null;
if(n.status!=null){
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS, titleItem);
if(titleItem!=null){
for(StatusDisplayItem item:items){
if(item instanceof ImageStatusDisplayItem imgItem){
imgItem.horizontalInset=V.dp(32);
}
}
}
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS);
if(titleItem!=null)
items.add(0, titleItem);
return items;
}else if(titleItem!=null){
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this,
@@ -125,6 +124,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
knownAccounts.put(s.account.id, s.account);
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
knownAccounts.put(s.status.account.id, s.status.account);
if(s.status!=null && s.status.reblog!=null && !knownAccounts.containsKey(s.status.reblog.account.id))
knownAccounts.put(s.status.reblog.account.id, s.status.reblog.account);
}
@Override
@@ -133,7 +134,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
.getAccount(accountID).getCacheController()
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing, new SimpleCallback<>(this){
@Override
public void onSuccess(PaginatedResponse<List<Notification>> result){
public void onSuccess(CacheablePaginatedResponse<List<Notification>> result){
if (getActivity() == null) return;
if(refreshing)
relationships.clear();
@@ -145,8 +146,12 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
loadRelationships(needRelationships);
maxID=result.maxID;
if(offset==0 && !result.items.isEmpty()){
if(offset==0 && !result.items.isEmpty() && !result.isFromCache()){
E.post(new AllNotificationsSeenEvent());
new SaveMarkers(null, result.items.get(0).id).exec(accountID);
AccountSessionManager.getInstance().getAccount(accountID).markers
.notifications.lastReadId = result.items.get(0).id;
AccountSessionManager.getInstance().writeAccountsFile();
}
}
});

View File

@@ -14,7 +14,7 @@ import org.joinmastodon.android.model.TimelineDefinition;
import java.util.ArrayList;
import java.util.List;
public abstract class PinnableStatusListFragment extends StatusListFragment {
public abstract class PinnableStatusListFragment extends StatusListFragment implements DomainDisplay {
protected boolean pinnedUpdated;
protected List<TimelineDefinition> pinnedTimelines;

View File

@@ -19,13 +19,11 @@ import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
@@ -50,7 +48,9 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
@@ -92,13 +92,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -209,6 +206,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
setHasOptionsMenu(true);
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
DomainManager.getInstance().setCurrentDomain(account.url);
}
}
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View content=inflater.inflate(R.layout.fragment_profile, container, false);
@@ -520,6 +525,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
toolbarTitleView.setTranslationY(titleTransY);
toolbarSubtitleView.setTranslationY(titleTransY);
}
RecyclerFragment.setRefreshLayoutColors(refreshLayout);
}
@Override
@@ -647,10 +653,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
fields.clear();
if (account.createdAt != null) {
AccountField joined=new AccountField();
joined.parsedName=joined.name=getString(R.string.profile_joined);
joined.parsedValue=joined.value=DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(LocalDateTime.ofInstant(account.createdAt, ZoneId.systemDefault()));
fields.add(joined);
}
for(AccountField field:account.fields){
field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
@@ -857,6 +865,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
// aboutFragment.setNote(relationship.note, accountID, profileAccountID);
}
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
DomainManager.getInstance().setCurrentDomain(account.url);
}
public ImageButton getFab() {
@@ -1223,6 +1232,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
scrollView.smoothScrollTo(0, 0);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
private void onFollowersOrFollowingClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -1242,7 +1256,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);

View File

@@ -0,0 +1,50 @@
package org.joinmastodon.android.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
public abstract class RecyclerFragment<T> extends BaseRecyclerFragment<T> {
public RecyclerFragment(int perPage) {
super(perPage);
}
public RecyclerFragment(int layout, int perPage) {
super(layout, perPage);
}
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (refreshLayout != null) setRefreshLayoutColors(refreshLayout);
}
public static void setRefreshLayoutColors(SwipeRefreshLayout l) {
List<Integer> colors = new ArrayList<>(Arrays.asList(
R.color.primary_600,
R.color.red_primary_600,
R.color.green_primary_600,
R.color.blue_primary_600,
R.color.purple_600
));
int primary = UiUtils.getThemeColorRes(l.getContext(), R.attr.colorPrimary600);
if (!colors.contains(primary)) colors.add(0, primary);
int offset = colors.indexOf(primary);
int[] sorted = new int[colors.size()];
for (int i = 0; i < colors.size(); i++) {
sorted[i] = colors.get((i + offset) % colors.size());
}
l.setColorSchemeResources(sorted);
}
}

View File

@@ -6,6 +6,8 @@ import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.utils.V;
public interface ScrollableToTop{
boolean isScrolledToTop();
void scrollToTop();
/**

View File

@@ -36,6 +36,7 @@ import android.widget.Toast;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
@@ -81,7 +82,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
private ArrayList<Item> items=new ArrayList<>();
private ThemeItem themeItem;
private NotificationPolicyItem notificationPolicyItem;
private SwitchItem showNewPostsButtonItem, glitchModeItem;
private SwitchItem showNewPostsButtonItem, glitchModeItem, compactReblogReplyLineItem;
private String accountID;
private boolean needUpdateNotificationSettings;
private boolean needAppRestart;
@@ -103,6 +104,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
String instanceName = UiUtils.getInstanceName(accountID);
DomainManager.getInstance().setCurrentDomain(session.domain + "/settings");
if(GithubSelfUpdater.needSelfUpdating()){
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
GithubSelfUpdater.UpdateState state=updater.getState();
@@ -112,23 +115,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
}
items.add(new HeaderItem(R.string.settings_theme));
items.add(themeItem=new ThemeItem());
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
GlobalUserPreferences.disableMarquee=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
GlobalUserPreferences.uniformNotificationIcon=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
GlobalUserPreferences.reduceMotion=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.color_palettes);
@@ -148,39 +136,21 @@ public class SettingsFragment extends MastodonToolbarFragment{
case NORD -> R.string.mo_color_palette_nord;
});
}));
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
updatePublishText(b);
if (GlobalUserPreferences.relocatePublishButton) {
b.setOnClickListener(l -> {
Toast.makeText(getActivity(), R.string.mo_disable_relocate_publish_button_to_enable_customization,
Toast.LENGTH_LONG).show();
});
} else {
b.setOnClickListener(l -> {
FrameLayout inputWrap = new FrameLayout(getContext());
EditText input = new EditText(getContext());
input.setHint(R.string.publish);
input.setText(GlobalUserPreferences.publishButtonText.trim());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
input.setLayoutParams(params);
inputWrap.addView(input);
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap)
.setPositiveButton(R.string.save, (d, which) -> {
GlobalUserPreferences.publishButtonText = input.getText().toString().trim();
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
GlobalUserPreferences.disableMarquee=i.checked;
GlobalUserPreferences.save();
updatePublishText(b);
})
.setNeutralButton(R.string.clear, (d, which) -> {
GlobalUserPreferences.publishButtonText = "";
GlobalUserPreferences.save();
updatePublishText(b);
})
.setNegativeButton(R.string.cancel, (d, which) -> {
})
.show();
});}
}));
items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
GlobalUserPreferences.uniformNotificationIcon=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
GlobalUserPreferences.reduceMotion=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new HeaderItem(R.string.settings_behavior));
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
@@ -211,19 +181,65 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.mo_disable_double_tap_to_swipe_between_tabs, R.drawable.ic_fluent_double_tap_swipe_right_24_regular, GlobalUserPreferences.disableDoubleTapToSwipe, i->{
GlobalUserPreferences.disableDoubleTapToSwipe=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
GlobalUserPreferences.confirmBeforeReblog=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.mo_swap_bookmark_with_reblog, R.drawable.ic_boost, GlobalUserPreferences.swapBookmarkWithBoostAction, i -> {
GlobalUserPreferences.swapBookmarkWithBoostAction=i.checked;
GlobalUserPreferences.save();
}));
items.add(new HeaderItem(R.string.mo_composer_behavior));
items.add(new SwitchItem(R.string.mo_change_default_reply_visibility_to_unlisted, R.drawable.ic_fluent_lock_closed_24_regular, GlobalUserPreferences.defaultToUnlistedReplies, i->{
GlobalUserPreferences.defaultToUnlistedReplies=i.checked;
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
updatePublishText(b);
b.setOnClickListener(l -> {
if(!GlobalUserPreferences.relocatePublishButton) {
FrameLayout inputWrap = new FrameLayout(getContext());
EditText input = new EditText(getContext());
input.setHint(R.string.publish);
input.setText(GlobalUserPreferences.publishButtonText.trim());
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
input.setLayoutParams(params);
inputWrap.addView(input);
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap)
.setPositiveButton(R.string.save, (d, which) -> {
GlobalUserPreferences.publishButtonText = input.getText().toString().trim();
GlobalUserPreferences.save();
updatePublishText(b);
})
.setNeutralButton(R.string.clear, (d, which) -> {
GlobalUserPreferences.publishButtonText = "";
GlobalUserPreferences.save();
updatePublishText(b);
})
.setNegativeButton(R.string.cancel, (d, which) -> {
})
.show();
} else {
Toast.makeText(getActivity(), R.string.mo_disable_relocate_publish_button_to_enable_customization,
Toast.LENGTH_LONG).show();
}
});
}));
items.add(new SwitchItem(R.string.mo_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
GlobalUserPreferences.relocatePublishButton=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.mo_change_default_reply_visibility_to_unlisted, R.drawable.ic_fluent_lock_open_24_regular, GlobalUserPreferences.defaultToUnlistedReplies, i->{
GlobalUserPreferences.defaultToUnlistedReplies=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.mo_disable_reminder_to_add_alt_text, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, i->{
GlobalUserPreferences.showNoAltIndicator=i.checked;
GlobalUserPreferences.disableAltTextReminder=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
@@ -237,6 +253,22 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.showReplies=i.checked;
GlobalUserPreferences.save();
}));
if (instance.pleroma != null) {
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.reply_visibility);
popupMenu.setOnMenuItemClickListener(item -> this.onReplyVisibilityChanged(item, b));
b.setOnTouchListener(popupMenu.getDragToOpenListener());
b.setOnClickListener(v->popupMenu.show());
b.setText(GlobalUserPreferences.replyVisibility == null ?
R.string.sk_settings_reply_visibility_all :
switch(GlobalUserPreferences.replyVisibility){
case "following" -> R.string.sk_settings_reply_visibility_following;
case "self" -> R.string.sk_settings_reply_visibility_self;
default -> R.string.sk_settings_reply_visibility_all;
});
}));
}
items.add(new SwitchItem(R.string.sk_settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
GlobalUserPreferences.showBoosts=i.checked;
GlobalUserPreferences.save();
@@ -275,13 +307,32 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_settings_hide_fab, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.autoHideFab, i->{
GlobalUserPreferences.autoHideFab=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_reply_line_above_avatar, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.replyLineAboveHeader, i->{
GlobalUserPreferences.replyLineAboveHeader=i.checked;
GlobalUserPreferences.compactReblogReplyLine=i.checked;
compactReblogReplyLineItem.enabled=i.checked;
compactReblogReplyLineItem.checked= GlobalUserPreferences.replyLineAboveHeader;
if (list.findViewHolderForAdapterPosition(items.indexOf(compactReblogReplyLineItem)) instanceof SwitchViewHolder svh) svh.rebind();
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(compactReblogReplyLineItem=new SwitchItem(R.string.sk_compact_reblog_reply_line, R.drawable.ic_fluent_re_order_24_regular, GlobalUserPreferences.compactReblogReplyLine, i->{
GlobalUserPreferences.compactReblogReplyLine=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
compactReblogReplyLineItem.enabled=GlobalUserPreferences.replyLineAboveHeader;
// items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
// GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
// GlobalUserPreferences.save();
// needAppRestart=true;
// }));
items.add(new HeaderItem(R.string.settings_notifications));
items.add(notificationPolicyItem=new NotificationPolicyItem());
PushSubscription pushSubscription=getPushSubscription();
@@ -366,6 +417,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
items.add(new HeaderItem(R.string.sk_settings_about));
// if(BuildConfig.BUILD_TYPE.equals("nightly")){
// items.add(new TextItem(R.string.mo_download_latest_nightly_release, ()->UiUtils.launchWebBrowser(getActivity(), "https://nightly.link/LucasGGamerM/moshidon/workflows/nightly-builds/master/moshidon-nightly.apk.zip"), R.drawable.ic_fluent_open_24_regular));
// }
items.add(new TextItem(R.string.mo_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
if (GithubSelfUpdater.needSelfUpdating()) {
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
items.add(checkForUpdateItem);
@@ -375,9 +433,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
}));
}
items.add(new TextItem(R.string.mo_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
LruCache<?, ?> cache = imageCache == null ? null : imageCache.getLruCache();
clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), cache != null ? cache.size() : 0, true), this::clearImageCache, 0);
items.add(clearImageCacheItem);
@@ -520,6 +575,25 @@ public class SettingsFragment extends MastodonToolbarFragment{
}
}
private boolean onReplyVisibilityChanged(MenuItem item, Button btn){
String pref = null;
int id = item.getItemId();
if (id == R.id.reply_visibility_following) pref = "following";
else if (id == R.id.reply_visibility_self) pref = "self";
GlobalUserPreferences.replyVisibility=pref;
GlobalUserPreferences.save();
btn.setText(GlobalUserPreferences.replyVisibility == null ?
R.string.sk_settings_reply_visibility_all :
switch(GlobalUserPreferences.replyVisibility){
case "following" -> R.string.sk_settings_reply_visibility_following;
case "self" -> R.string.sk_settings_reply_visibility_self;
default -> R.string.sk_settings_reply_visibility_all;
});
return true;
}
private void restartActivityToApplyNewTheme(){
// Calling activity.recreate() causes a black screen for like half a second.
// So, let's take a screenshot and overlay it on top to create the illusion of a smoother transition.

View File

@@ -7,6 +7,7 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
@@ -29,7 +30,7 @@ import java.util.stream.Stream;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
public abstract class StatusListFragment extends BaseStatusListFragment<Status> implements DomainDisplay{
protected EventListener eventListener=new EventListener();
protected List<StatusDisplayItem> buildDisplayItems(Status s){
@@ -42,6 +43,8 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
protected void addAccountToKnown(Status s){
if(!knownAccounts.containsKey(s.account.id))
knownAccounts.put(s.account.id, s.account);
if(s.reblog!=null && !knownAccounts.containsKey(s.reblog.account.id))
knownAccounts.put(s.reblog.account.id, s.reblog.account);
}
@Override
@@ -162,7 +165,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
List<Status> toRemove=Stream.concat(data.stream(), preloadedData.stream())
.filter(s->s.account.id.equals(ev.postsByAccountID) || (s.reblog!=null && s.reblog.account.id.equals(ev.postsByAccountID)))
.filter(s->s.account.id.equals(ev.postsByAccountID) || (!ev.isUnfollow && s.reblog!=null && s.reblog.account.id.equals(ev.postsByAccountID)))
.collect(Collectors.toList());
for(Status s:toRemove){
removeStatus(s);

View File

@@ -3,11 +3,15 @@ package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.view.View;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusContext;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
@@ -19,15 +23,21 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback;
public class ThreadFragment extends StatusListFragment{
public class ThreadFragment extends StatusListFragment implements DomainDisplay{
protected Status mainStatus;
@Override
public String getDomain() {
return mainStatus.url;
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -38,6 +48,8 @@ public class ThreadFragment extends StatusListFragment{
data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus));
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
DomainManager.getInstance().setCurrentDomain(getDomain());
}
@Override
@@ -68,6 +80,30 @@ public class ThreadFragment extends StatusListFragment{
data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus));
}
AccountSession account=AccountSessionManager.getInstance().getAccount(accountID);
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if(instance.pleroma != null){
List<String> threadIds=new ArrayList<>();
threadIds.add(mainStatus.id);
for(Status s:result.descendants){
if(threadIds.contains(s.inReplyToId)){
threadIds.add(s.id);
}
}
threadIds.add(mainStatus.inReplyToId);
for(int i=result.ancestors.size()-1; i >= 0; i--){
Status s=result.ancestors.get(i);
if(s.inReplyToId != null && threadIds.contains(s.id)){
threadIds.add(s.inReplyToId);
}
}
result.ancestors=result.ancestors.stream().filter(s -> threadIds.contains(s.id)).collect(Collectors.toList());
result.descendants=getDescendantsOrdered(mainStatus.id,
result.descendants.stream()
.filter(s -> threadIds.contains(s.id))
.collect(Collectors.toList()));
}
result.descendants=filterStatuses(result.descendants);
result.ancestors=filterStatuses(result.ancestors);
if(footerProgress!=null)
@@ -90,6 +126,24 @@ public class ThreadFragment extends StatusListFragment{
.exec(accountID);
}
private List<Status> getDescendantsOrdered(String id, List<Status> statuses){
List<Status> out=new ArrayList<>();
for(Status s:getDirectDescendants(id, statuses)){
out.add(s);
getDirectDescendants(s.id, statuses).forEach(d ->{
out.add(d);
out.addAll(getDescendantsOrdered(d.id, statuses));
});
}
return out;
}
private List<Status> getDirectDescendants(String id, List<Status> statuses){
return statuses.stream()
.filter(s -> s.inReplyToId.equals(id))
.collect(Collectors.toList());
}
private List<Status> filterStatuses(List<Status> statuses){
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
return statuses.stream()

View File

@@ -25,6 +25,7 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ListTimelinesFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Relationship;
@@ -48,7 +49,6 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -57,7 +57,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseAccountListFragment.AccountItem>{
public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccountListFragment.AccountItem> {
protected HashMap<String, Relationship> relationships=new HashMap<>();
protected String accountID;
protected ArrayList<APIRequest<?>> relationshipsRequests=new ArrayList<>();
@@ -74,6 +74,8 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
@Override
protected void onDataLoaded(List<AccountItem> d, boolean more){
if (getActivity() == null)
return;
if(refreshing){
relationships.clear();
}
@@ -267,6 +269,15 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
@Override
public void onClick(){
if(item.account.reloadWhenClicked){
UiUtils.lookupAccount(getContext(), item.account, accountID, null, account -> {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(account));
Nav.go(getActivity(), ProfileFragment.class, args);
});
return;
}
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(item.account));
@@ -295,7 +306,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername())).setVisible(relationship.following);
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
@@ -309,7 +319,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
manageUserLists.setVisible(true);
}else{
hideBoosts.setVisible(false);
manageUserLists.setVisible(true);
manageUserLists.setVisible(false);
}
menu.findItem(R.id.block_domain).setVisible(false);

View File

@@ -12,6 +12,7 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
targetAccount = account;
setSubtitle(getResources().getQuantityString(R.plurals.x_followers, (int)(account.followersCount%1000), account.followersCount));
}
@@ -19,4 +20,9 @@ public class FollowerListFragment extends AccountRelatedAccountListFragment{
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetAccountFollowers(account.id, maxID, count);
}
@Override
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count){
return new GetAccountFollowers(id, maxID, count);
}
}

View File

@@ -12,6 +12,7 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
targetAccount = account;
setSubtitle(getResources().getQuantityString(R.plurals.x_following, (int)(account.followingCount%1000), account.followingCount));
}
@@ -19,4 +20,9 @@ public class FollowingListFragment extends AccountRelatedAccountListFragment{
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetAccountFollowing(account.id, maxID, count);
}
@Override
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count){
return new GetAccountFollowing(id, maxID, count);
}
}

View File

@@ -1,20 +1,52 @@
package org.joinmastodon.android.fragments.account_list;
import android.net.Uri;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
public abstract class PaginatedAccountListFragment extends BaseAccountListFragment{
private String nextMaxID;
protected Account targetAccount;
public abstract HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count);
public abstract HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count);
@Override
protected void doLoadData(int offset, int count){
if (shouldLoadRemote()) {
UiUtils.lookupRemoteAccount(getContext(), targetAccount, accountID, null, account -> {
if(account != null){
loadRemoteFollower(offset, count, account);
} else {
loadFollower(offset, count);
}
});
} else {
loadFollower(offset, count);
}
}
private boolean shouldLoadRemote() {
if (!GlobalUserPreferences.loadRemoteAccountFollowers && (this instanceof FollowingListFragment || this instanceof FollowerListFragment)) {
return false;
}
return targetAccount != null && targetAccount.getDomain() != null;
}
void loadFollower(int offset, int count) {
currentRequest=onCreateRequest(offset==0 ? null : nextMaxID, count)
.setCallback(new SimpleCallback<>(this){
@Override
@@ -23,13 +55,42 @@ public abstract class PaginatedAccountListFragment extends BaseAccountListFragme
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else
nextMaxID=null;
if (getActivity() == null) return;
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
}
})
.exec(accountID);
}
private void loadRemoteFollower(int offset, int count, Account account) {
String ownDomain = AccountSessionManager.getInstance().getLastActiveAccount().domain;
currentRequest=onCreateRemoteRequest(account.id, offset==0 ? null : nextMaxID, count)
.setCallback(new Callback<>(){
@Override
public void onSuccess(HeaderPaginationList<Account> result){
if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else
nextMaxID=null;
result.stream().forEach(remoteAccount -> {
remoteAccount.reloadWhenClicked = true;
if (remoteAccount.getDomain() == null) {
remoteAccount.acct += "@" + Uri.parse(remoteAccount.url).getHost();
} else if (remoteAccount.getDomain().equals(ownDomain)) {
remoteAccount.acct = remoteAccount.username;
}
});
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
loadFollower(offset, count);
}
})
.execNoAuth(targetAccount.getDomain());
}
@Override
public void onResume(){
super.onResume();

View File

@@ -18,4 +18,9 @@ public class StatusFavoritesListFragment extends StatusRelatedAccountListFragmen
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetStatusFavorites(status.id, maxID, count);
}
@Override
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count) {
return null;
}
}

View File

@@ -18,4 +18,9 @@ public class StatusReblogsListFragment extends StatusRelatedAccountListFragment{
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetStatusReblogs(status.id, maxID, count);
}
@Override
public HeaderPaginationRequest<Account> onCreateRemoteRequest(String id, String maxID, int count) {
return null;
}
}

View File

@@ -15,8 +15,10 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
import org.joinmastodon.android.fragments.DomainDisplay;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FollowSuggestion;
@@ -49,7 +51,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop {
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop, DomainDisplay {
private String accountID;
private Map<String, Relationship> relationships=Collections.emptyMap();
private GetAccountRelationships relationshipsRequest;
@@ -58,6 +60,11 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
super(20);
}
@Override
public String getDomain() {
return DomainDisplay.super.getDomain() + "/explore/suggestions";
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -140,6 +147,11 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
@Override
public boolean isOnTop() {
return isRecyclerViewOnTop(list);

View File

@@ -19,8 +19,10 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.DomainDisplay;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.fragments.ListTimelinesFragment;
import org.joinmastodon.android.ui.SimpleViewHolder;
@@ -38,7 +40,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.V;
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener{
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay {
private TabLayout tabLayout;
private ViewPager2 pager;
@@ -64,6 +66,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
// private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
@Override
public String getDomain() {
if (searchActive) {
return searchFragment.getDomain();
}
if (tabViews[tabLayout.getSelectedTabPosition()] instanceof DomainDisplay page) {
return page.getDomain();
}
return DomainDisplay.super.getDomain() + "/explore";
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -127,6 +140,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
if(!page.loaded && !page.isDataLoading())
page.loadData();
}
if (_page instanceof DomainDisplay display)
DomainManager.getInstance().setCurrentDomain(display.getDomain());
}
});
@@ -208,7 +224,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
tabLayoutMediator.attach();
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){}
public void onTabSelected(TabLayout.Tab tab){
DomainManager.getInstance().setCurrentDomain(getDomain());
}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@@ -284,6 +302,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
}
@Override
public boolean isScrolledToTop() {
if(!searchActive){
return ((ScrollableToTop)getFragmentForPage(pager.getCurrentItem())).isScrolledToTop();
}else{
return searchFragment.isScrolledToTop();
}
}
public void loadData(){
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
hashtagsFragment.loadData();

View File

@@ -10,7 +10,9 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
import org.joinmastodon.android.fragments.DomainDisplay;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.ui.DividerItemDecoration;
@@ -35,7 +37,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop {
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop, DomainDisplay {
private String accountID;
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
@@ -44,6 +46,11 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
super(10);
}
@Override
public String getDomain() {
return DomainDisplay.super.getDomain() + "/explore/links";
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -83,6 +90,11 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
@Override
public boolean isOnTop() {
return isRecyclerViewOnTop(list);

View File

@@ -19,6 +19,11 @@ import me.grishka.appkit.api.SimpleCallback;
public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop {
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
@Override
public String getDomain() {
return super.getDomain() + "/explore/posts";
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetTrendingStatuses(offset, count)

View File

@@ -25,6 +25,11 @@ public class FederatedTimelineFragment extends StatusListFragment {
return true;
}
@Override
public String getDomain() {
return super.getDomain() + "/public";
}
@Override
protected void doLoadData(int offset, int count){

View File

@@ -24,6 +24,10 @@ public class LocalTimelineFragment extends StatusListFragment {
return true;
}
@Override
public String getDomain() {
return super.getDomain() + "/public/local";
}
@Override
protected void doLoadData(int offset, int count){

View File

@@ -57,6 +57,11 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
setLayout(R.layout.fragment_search);
}
@Override
public String getDomain() {
return super.getDomain() + "/search";
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -248,7 +253,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}
public void setQuery(String q){
if(Objects.equals(q, currentQuery))
if(Objects.equals(q, currentQuery) || q.isBlank())
return;
if(currentRequest!=null){
currentRequest.cancel();

View File

@@ -7,7 +7,9 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
import org.joinmastodon.android.fragments.DomainDisplay;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.ui.DividerItemDecoration;
@@ -24,7 +26,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop {
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop, DomainDisplay {
private String accountID;
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
@@ -32,6 +34,11 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
super(10);
}
@Override
public String getDomain() {
return DomainDisplay.super.getDomain() + "/explore/tags";
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
@@ -68,6 +75,11 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
@Override
public boolean isOnTop() {
return isRecyclerViewOnTop(list);

View File

@@ -110,7 +110,10 @@ public class AccountActivationFragment extends ToolbarFragment{
@Override
public void onToolbarNavigationClick(){
new AccountSwitcherSheet(getActivity()).show();
new AccountSwitcherSheet(getActivity(), true, true, false, accountSession -> {
getActivity().finish();
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
}).show();
}
@Override

View File

@@ -17,6 +17,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.catalog.CatalogInstance;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
@@ -44,7 +45,6 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.V;
@@ -52,7 +52,7 @@ import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstance>{
abstract class InstanceCatalogFragment extends RecyclerFragment<CatalogInstance> {
protected RecyclerView.Adapter adapter;
protected MergeRecyclerAdapter mergeAdapter;
protected CatalogInstance chosenInstance;

View File

@@ -21,6 +21,7 @@ import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.RecyclerFragment;
import org.joinmastodon.android.model.FollowSuggestion;
import org.joinmastodon.android.model.ParsedAccount;
import org.joinmastodon.android.model.Relationship;
@@ -43,7 +44,6 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -52,7 +52,7 @@ import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import me.grishka.appkit.views.UsableRecyclerView;
public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<ParsedAccount>{
public class OnboardingFollowSuggestionsFragment extends RecyclerFragment<ParsedAccount> {
private String accountID;
private Map<String, Relationship> relationships=Collections.emptyMap();
private GetAccountRelationships relationshipsRequest;

View File

@@ -23,10 +23,8 @@ import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
@@ -133,22 +131,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
if(holder.getAbsoluteAdapterPosition()==0)
return;
outRect.left=V.dp(40);
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
PhotoLayoutHelper.TiledLayoutResult layout=imgHolder.getItem().tiledLayout;
PhotoLayoutHelper.TiledLayoutResult.Tile tile=imgHolder.getItem().thisTile;
String siblingID;
if(holder.getAbsoluteAdapterPosition()<parent.getAdapter().getItemCount()-1){
siblingID=displayItems.get(holder.getAbsoluteAdapterPosition()-getMainAdapterOffset()+1).parentID;
}else{
siblingID=null;
}
if(tile.startCol>0)
outRect.left=0;
outRect.left+=V.dp(16);
outRect.right=V.dp(16);
if(!imgHolder.getItemID().equals(siblingID) || tile.startRow+tile.rowSpan==layout.rowSizes.length)
outRect.bottom=V.dp(16);
}else if(holder instanceof AudioStatusDisplayItem.Holder){
if(holder instanceof AudioStatusDisplayItem.Holder){
outRect.bottom=V.dp(16);
}else if(holder instanceof LinkCardStatusDisplayItem.Holder){
outRect.bottom=V.dp(16);
@@ -167,10 +150,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
parent.getDecoratedBoundsWithMargins(child, tmpRect);
String id=sdiHolder.getItemID();
int height=tmpRect.height();
if(holder instanceof ImageStatusDisplayItem.Holder<?> imgHolder){
if(imgHolder.getItem().thisTile.startCol+imgHolder.getItem().thisTile.colSpan<imgHolder.getItem().tiledLayout.columnSizes.length)
height=0;
}
if(!(holder instanceof HeaderStatusDisplayItem.Holder) && !(holder instanceof ReblogOrReplyLineStatusDisplayItem.Holder))
postsWithKnownNonHeaderHeights.add(id);
knownDisplayItemHeights.put(holder.getAbsoluteAdapterPosition(), height);
@@ -237,17 +216,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
return adapter;
}
@Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, Filter.FilterContext.HOME);
for(StatusDisplayItem item:items){
if(item instanceof ImageStatusDisplayItem isdi){
isdi.horizontalInset=V.dp(40+32);
}
}
return items;
}
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
parent.getDecoratedBoundsWithMargins(child, tmpRect);
tmpRect.offset(0, Math.round(child.getTranslationY()));

View File

@@ -43,7 +43,7 @@ public class Account extends BaseModel implements Searchable{
/**
* The profile's display name.
*/
@RequiredField
// @RequiredField
public String displayName;
/**
* The profile's bio / description.
@@ -86,7 +86,7 @@ public class Account extends BaseModel implements Searchable{
/**
* When the account was created.
*/
@RequiredField
// @RequiredField
public Instant createdAt;
/**
* When the most recent status was posted.
@@ -134,6 +134,7 @@ public class Account extends BaseModel implements Searchable{
public Instant muteExpiresAt;
public List<Role> roles;
public boolean reloadWhenClicked;
@Override
public String getQuery() {

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android.model;
import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.api.AllFieldsAreRequired;
import java.time.Instant;
@@ -18,4 +20,11 @@ public class Marker extends BaseModel{
", updatedAt="+updatedAt+
'}';
}
public enum Type {
@SerializedName("home")
HOME,
@SerializedName("notifications")
NOTIFICATIONS
}
}

View File

@@ -0,0 +1,14 @@
package org.joinmastodon.android.model;
public class Markers {
public Marker notifications;
public Marker home;
@Override
public String toString() {
return "Markers{" +
"notifications=" + notifications +
", home=" + home +
'}';
}
}

View File

@@ -16,12 +16,13 @@ public class Poll extends BaseModel{
private boolean expired;
public boolean multiple;
public int votersCount;
public int votesCount;
public boolean voted;
// @RequiredField
public List<Integer> ownVotes;
@RequiredField
public List<Option> options;
@RequiredField
// @RequiredField
public List<Emoji> emojis;
public transient ArrayList<Option> selectedOptions;
@@ -29,6 +30,8 @@ public class Poll extends BaseModel{
@Override
public void postprocess() throws ObjectValidationException{
super.postprocess();
if (emojis == null) emojis = List.of();
if (ownVotes == null) ownVotes = List.of();
for(Emoji e:emojis)
e.postprocess();
}
@@ -41,10 +44,12 @@ public class Poll extends BaseModel{
", expired="+expired+
", multiple="+multiple+
", votersCount="+votersCount+
", votesCount="+votesCount+
", voted="+voted+
", ownVotes="+ownVotes+
", options="+options+
", emojis="+emojis+
", selectedOptions="+selectedOptions+
'}';
}

View File

@@ -1,5 +1,14 @@
package org.joinmastodon.android.model;
import static org.joinmastodon.android.api.MastodonAPIController.gson;
import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDeserializer;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
@@ -7,6 +16,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.lang.reflect.Type;
import java.time.Instant;
import java.util.List;
@@ -16,7 +26,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public String id;
@RequiredField
public String uri;
@RequiredField
// @RequiredField // sometimes null on calckey
public Instant createdAt;
@RequiredField
public Account account;
@@ -58,15 +68,22 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public boolean bookmarked;
public boolean pinned;
public Status quote; // can be boolean in calckey
public transient boolean filterRevealed;
public transient boolean spoilerRevealed;
public transient boolean textExpanded, textExpandable;
public transient boolean hasGapAfter;
public transient TranslatedStatus translation;
public transient boolean translationShown;
public boolean reloadWhenClicked;
private transient String strippedText;
@Override
public void postprocess() throws ObjectValidationException{
if(spoilerText!=null && !spoilerText.isEmpty() && !sensitive)
sensitive=true;
super.postprocess();
if(application!=null)
application.postprocess();
@@ -169,4 +186,28 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public String getQuery() {
return url;
}
public static class StatusDeserializer implements JsonDeserializer<Status> {
@Override
public Status deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject obj = json.getAsJsonObject();
Status quote = null;
if (obj.has("quote") && obj.get("quote").isJsonObject())
quote = gson.fromJson(obj.get("quote"), Status.class);
obj.remove("quote");
Status reblog = null;
if (obj.has("reblog"))
reblog = gson.fromJson(obj.get("reblog"), Status.class);
obj.remove("reblog");
Status status = gsonWithoutDeserializer.fromJson(json, Status.class);
status.quote = quote;
status.reblog = reblog;
return status;
}
}
}

View File

@@ -2,7 +2,6 @@ package org.joinmastodon.android.ui;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
@@ -17,21 +16,21 @@ import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -53,10 +52,14 @@ public class AccountSwitcherSheet extends BottomSheet{
private UsableRecyclerView list;
private List<WrappedAccount> accounts;
private ListImageLoaderWrapper imgLoader;
private final boolean logOutEnabled;
private final Consumer<AccountSession> onClick;
public AccountSwitcherSheet(@NonNull Activity activity){
public AccountSwitcherSheet(@NonNull Activity activity, boolean logOutEnabled, boolean addAccountEnabled, boolean showOpenURL, Consumer<AccountSession> onClick){
super(activity);
this.activity=activity;
this.logOutEnabled=logOutEnabled;
this.onClick=onClick;
accounts=AccountSessionManager.getInstance().getLoggedInAccounts().stream().map(WrappedAccount::new).collect(Collectors.toList());
@@ -70,10 +73,13 @@ public class AccountSwitcherSheet extends BottomSheet{
handle.setBackgroundResource(R.drawable.bg_bottom_sheet_handle);
adapter.addAdapter(new SingleViewRecyclerAdapter(handle));
adapter.addAdapter(new AccountsAdapter());
if(addAccountEnabled){
AccountViewHolder holder = new AccountViewHolder();
holder.more.setVisibility(View.GONE);
holder.currentIcon.setVisibility(View.GONE);
holder.name.setText(R.string.add_account);
holder.display_name.setVisibility(View.GONE);
holder.display_add_account.setVisibility(View.VISIBLE);
holder.avatar.setScaleType(ImageView.ScaleType.CENTER);
holder.avatar.setImageResource(R.drawable.ic_fluent_add_circle_24_filled);
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
@@ -81,6 +87,23 @@ public class AccountSwitcherSheet extends BottomSheet{
Nav.go(activity, CustomWelcomeFragment.class, null);
dismiss();
}));
}
if(showOpenURL) {
AccountViewHolder holder = new AccountViewHolder();
holder.more.setVisibility(View.GONE);
holder.currentIcon.setVisibility(View.GONE);
holder.display_name.setVisibility(View.VISIBLE);
holder.display_add_account.setVisibility(View.VISIBLE);
holder.display_add_account.setText(R.string.mo_share_open_url);
holder.avatar.setScaleType(ImageView.ScaleType.CENTER);
holder.avatar.setImageResource(R.drawable.ic_fluent_open_24_regular);
holder.avatar.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(activity, android.R.attr.textColorPrimary)));
adapter.addAdapter(new ClickableSingleViewRecyclerAdapter(holder.itemView, () -> {
onClick.accept(null);
dismiss();
}));
}
list.setAdapter(adapter);
DividerItemDecoration divider=new DividerItemDecoration(activity, R.attr.colorPollVoted, .5f, 72, 16, DividerItemDecoration.NOT_FIRST);
@@ -176,6 +199,8 @@ public class AccountSwitcherSheet extends BottomSheet{
private class AccountViewHolder extends BindableViewHolder<AccountSession> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
private final TextView name;
private final TextView display_name;
private final TextView display_add_account;
private final ImageView avatar;
private final ImageButton more;
private final View currentIcon;
@@ -184,6 +209,8 @@ public class AccountSwitcherSheet extends BottomSheet{
public AccountViewHolder(){
super(activity, R.layout.item_account_switcher, list);
name=findViewById(R.id.name);
display_name=findViewById(R.id.display_name);
display_add_account=findViewById(R.id.add_account);
avatar=findViewById(R.id.avatar);
more=findViewById(R.id.more);
currentIcon=findViewById(R.id.current);
@@ -203,6 +230,7 @@ public class AccountSwitcherSheet extends BottomSheet{
@SuppressLint("SetTextI18n")
@Override
public void onBind(AccountSession item){
display_name.setText(item.self.displayName);
name.setText("@"+item.self.username+"@"+item.domain);
if(AccountSessionManager.getInstance().getLastActiveAccountID().equals(item.getID())){
more.setVisibility(View.GONE);
@@ -211,6 +239,11 @@ public class AccountSwitcherSheet extends BottomSheet{
more.setVisibility(View.VISIBLE);
currentIcon.setVisibility(View.GONE);
}
if(!logOutEnabled){
more.setVisibility(View.GONE);
currentIcon.setVisibility(View.GONE);
}
menu.getMenu().findItem(R.id.log_out).setTitle(activity.getString(R.string.log_out_account, "@"+item.self.username));
UiUtils.enablePopupMenuIcons(activity, menu);
}
@@ -230,8 +263,8 @@ public class AccountSwitcherSheet extends BottomSheet{
@Override
public void onClick(){
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
activity.finish();
activity.startActivity(new Intent(activity, MainActivity.class));
dismiss();
onClick.accept(AccountSessionManager.getInstance().getAccount(item.getID()));
}
}

View File

@@ -11,8 +11,14 @@ import java.util.List;
import androidx.annotation.NonNull;
public class PhotoLayoutHelper{
public static final int MAX_WIDTH=1000;
public static final int MAX_HEIGHT=1910;
@NonNull
public static TiledLayoutResult processThumbs(int _maxW, int _maxH, List<Attachment> thumbs){
public static TiledLayoutResult processThumbs(List<Attachment> thumbs){
int _maxW=MAX_WIDTH;
int _maxH=MAX_HEIGHT;
TiledLayoutResult result=new TiledLayoutResult();
if(thumbs.size()==1){
Attachment att=thumbs.get(0);
@@ -45,13 +51,8 @@ public class PhotoLayoutHelper{
float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f;
float maxW, maxH, marginW=0, marginH=0;
if(_maxW>0){
maxW=_maxW;
maxH=_maxH;
}else{
maxW=510;
maxH=510;
}
float maxRatio=maxW/maxH;

View File

@@ -79,10 +79,10 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
}else{
editHistory.setVisibility(View.GONE);
}
String timeStr=TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault()));
String timeStr=item.status.createdAt != null ? TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault())) : null;
if (item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)) {
time.setText(item.parentFragment.getString(R.string.timestamp_via_app, timeStr, ""));
time.setText(timeStr != null ? item.parentFragment.getString(R.string.timestamp_via_app, timeStr, "") : "");
applicationName.setText(item.status.application.name);
if (item.status.application.website != null && item.status.application.website.toLowerCase().startsWith("https://")) {
applicationName.setOnClickListener(e -> UiUtils.openURL(context, null, item.status.application.website));

View File

@@ -29,6 +29,7 @@ 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.ComposeFragment;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
@@ -227,6 +228,11 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onBoostClick(View v){
if (GlobalUserPreferences.confirmBeforeReblog) {
v.startAnimation(opacityIn);
onBoostLongClick(v);
return;
}
if(item.status.reloadWhenClicked){
UiUtils.lookupStatus(v.getContext(),
item.status, item.accountID, null,
@@ -254,9 +260,22 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
Consumer<StatusPrivacy> doReblog = (visibility) -> {
v.startAnimation(opacityOut);
if(item.status.reloadWhenClicked){
UiUtils.lookupStatus(v.getContext(),
item.status, item.accountID, null,
status -> {
session.getStatusInteractionController()
.setReblogged(status, !status.reblogged, visibility, r->boostConsumer(v, r));
boost.setSelected(status.reblogged);
dialog.dismiss();
}
);
} else {
session.getStatusInteractionController()
.setReblogged(item.status, !item.status.reblogged, visibility, r->boostConsumer(v, r));
boost.setSelected(item.status.reblogged);
dialog.dismiss();
}
};
View separator = menu.findViewById(R.id.separator);
@@ -272,9 +291,9 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
reblogHeader.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
reblogAs.setVisibility(AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1 ? View.VISIBLE : View.GONE);
itemPublic.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.PUBLIC) ? View.GONE : View.VISIBLE);
itemUnlisted.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) ? View.GONE : View.VISIBLE);
itemFollowers.setVisibility(item.status.reblogged || item.status.visibility.isLessVisibleThan(StatusPrivacy.PRIVATE) ? View.GONE : View.VISIBLE);
itemPublic.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
itemUnlisted.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
itemFollowers.setVisibility(item.status.reblogged ? View.GONE : View.VISIBLE);
Drawable checkMark = ctx.getDrawable(R.drawable.ic_fluent_checkmark_circle_20_regular);
Drawable publicDrawable = ctx.getDrawable(R.drawable.ic_fluent_earth_24_regular);
@@ -282,16 +301,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_lock_closed_24_regular);
StatusPrivacy defaultVisibility = session.preferences != null ? session.preferences.postingDefaultVisibility : null;
// e.g. post visibility is unlisted, but default is public
// in this case, we want to display the check mark on the most visible visibility
if (defaultVisibility != null && item.status.visibility.isLessVisibleThan(defaultVisibility)) {
for (StatusPrivacy vis : StatusPrivacy.values()) {
if (vis.equals(item.status.visibility)) {
defaultVisibility = vis;
break;
}
}
}
itemPublic.setCompoundDrawablesWithIntrinsicBounds(publicDrawable, null, StatusPrivacy.PUBLIC.equals(defaultVisibility) ? checkMark : null, null);
itemUnlisted.setCompoundDrawablesWithIntrinsicBounds(unlistedDrawable, null, StatusPrivacy.UNLISTED.equals(defaultVisibility) ? checkMark : null, null);
itemFollowers.setCompoundDrawablesWithIntrinsicBounds(followersDrawable, null, StatusPrivacy.PRIVATE.equals(defaultVisibility) ? checkMark : null, null);
@@ -319,12 +328,18 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
v.startAnimation(opacityIn);
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){
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);
});

View File

@@ -1,42 +0,0 @@
package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.graphics.Outline;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
public class GifVStatusDisplayItem extends ImageStatusDisplayItem{
public GifVStatusDisplayItem(String parentID, Status status, Attachment attachment, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
super(parentID, parentFragment, attachment, status, index, totalPhotos, tiledLayout, thisTile);
request=new UrlImageLoaderRequest(attachment.previewUrl, 1000, 1000);
}
@Override
public Type getType(){
return Type.GIFV;
}
public static class Holder extends ImageStatusDisplayItem.Holder<GifVStatusDisplayItem>{
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_gifv, parent);
View play=findViewById(R.id.play_button);
play.setOutlineProvider(new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
outline.setOval(0, 0, view.getWidth(), view.getHeight());
outline.setAlpha(.99f); // fixes shadow rendering
}
});
}
}
}

View File

@@ -1,8 +1,12 @@
package org.joinmastodon.android.ui.displayitems;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -270,7 +274,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
progress.dismiss();
}, rel->{
relationship=rel;
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : R.string.unfollowed_user, account.getShortUsername()), Toast.LENGTH_SHORT).show();
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, ()->{});
@@ -497,6 +501,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
Account account=item.user;
String username = account.getShortUsername();
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
boolean isPostScheduled=item.scheduledStatus!=null;
menu.findItem(R.id.open_with_account).setVisible(!isPostScheduled && hasMultipleAccounts);
@@ -532,14 +537,15 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
manageUserLists.setVisible(false);
}else{
mute.setVisible(true);
block.setVisible(true);
// hiding when following to keep menu item count equal (trading it for user lists)
block.setVisible(relationship == null || !relationship.following);
report.setVisible(true);
follow.setVisible(relationship==null || relationship.following || (!relationship.blocking && !relationship.blockedBy && !relationship.domainBlocking && !relationship.muting));
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
mute.setTitle(item.parentFragment.getString(relationship!=null && relationship.muting ? R.string.unmute_user : R.string.mute_user, username));
mute.setIcon(relationship!=null && relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), mute);
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
report.setTitle(item.parentFragment.getString(R.string.report_user, account.getShortUsername()));
block.setTitle(item.parentFragment.getString(relationship!=null && relationship.blocking ? R.string.unblock_user : R.string.block_user, username));
report.setTitle(item.parentFragment.getString(R.string.report_user, username));
// disabled in megalodon. domain blocks from a post clutters the context menu and looks out of place
// if(!account.isLocal()){
// blockDomain.setVisible(true);
@@ -548,12 +554,53 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
blockDomain.setVisible(false);
// }
boolean following = relationship!=null && relationship.following;
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, account.getShortUsername()));
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, username));
follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular);
manageUserLists.setVisible(relationship != null && relationship.following);
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, username));
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow);
}
workaroundChangingMenuItemWidths(menu, username);
}
// ugliest piece of code you'll see in a while: i measure the menu items' text widths to
// determine the biggest one, because it's probably not being displayed at first
// (before the relationship loaded). i take the largest one's size and add a space to the
// last item ("open in browser") until it takes up as much space as the largest item.
// goal: no more ugly ellipsis after the relationship loads in when opening the context menu
// of a post
private void workaroundChangingMenuItemWidths(Menu menu, String username) {
String openInBrowserText = item.parentFragment.getString(R.string.open_in_browser);
if (relationship == null) {
float largestWidth = 0;
Paint paint = new Paint();
paint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
String[] otherStrings = new String[] {
item.parentFragment.getString(R.string.unfollow_user, username),
item.parentFragment.getString(R.string.unblock_user, username),
item.parentFragment.getString(R.string.unmute_user, username),
item.parentFragment.getString(R.string.sk_lists_with_user, username),
};
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
if (item.getItemId() == R.id.open_in_browser || !item.isVisible()) continue;
float width = paint.measureText(menu.getItem(i).getTitle().toString());
if (width > largestWidth) largestWidth = width;
}
for (String str : otherStrings) {
float width = paint.measureText(str);
if (width > largestWidth) largestWidth = width;
}
float textWidth = paint.measureText(openInBrowserText);
float missingWidth = Math.max(0, largestWidth - textWidth);
float singleSpaceWidth = paint.measureText("");
int howManySpaces = (int) Math.ceil(missingWidth / singleSpaceWidth);
String enlargedText = openInBrowserText + "".repeat(howManySpaces);
menu.findItem(R.id.open_in_browser).setTitle(enlargedText);
} else {
menu.findItem(R.id.open_in_browser).setTitle(openInBrowserText);
}
}
}
}

View File

@@ -1,244 +0,0 @@
package org.joinmastodon.android.ui.displayitems;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout;
import androidx.annotation.LayoutRes;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
public abstract class ImageStatusDisplayItem extends StatusDisplayItem{
public final int index;
public final int totalPhotos;
protected Attachment attachment;
protected ImageLoaderRequest request;
public final Status status;
public final PhotoLayoutHelper.TiledLayoutResult tiledLayout;
public final PhotoLayoutHelper.TiledLayoutResult.Tile thisTile;
public int horizontalInset;
public ImageStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Attachment photo, Status status, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
super(parentID, parentFragment);
this.attachment=photo;
this.status=status;
this.index=index;
this.totalPhotos=totalPhotos;
this.tiledLayout=tiledLayout;
this.thisTile=thisTile;
}
@Override
public int getImageCount(){
return 1;
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return request;
}
public static abstract class Holder<T extends ImageStatusDisplayItem> extends StatusDisplayItem.Holder<T> implements ImageLoaderViewHolder{
public final ImageView photo;
private ImageAttachmentFrameLayout layout;
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
private boolean didClear;
private AnimatorSet currentAnim;
private final FrameLayout altTextWrapper;
private final TextView altTextButton;
private final ImageView noAltTextButton;
private final View altTextScroller;
private final ImageButton altTextClose;
private final TextView altText, noAltText;
private View altOrNoAltButton;
private boolean altTextShown;
public Holder(Activity activity, @LayoutRes int layout, ViewGroup parent){
super(activity, layout, parent);
photo=findViewById(R.id.photo);
photo.setOnClickListener(this::onViewClick);
this.layout=(ImageAttachmentFrameLayout)itemView;
altTextWrapper=findViewById(R.id.alt_text_wrapper);
altTextButton=findViewById(R.id.alt_button);
noAltTextButton=findViewById(R.id.no_alt_button);
altTextScroller=findViewById(R.id.alt_text_scroller);
altTextClose=findViewById(R.id.alt_text_close);
altText=findViewById(R.id.alt_text);
noAltText=findViewById(R.id.no_alt_text);
altTextButton.setOnClickListener(this::onShowHideClick);
noAltTextButton.setOnClickListener(this::onShowHideClick);
altTextClose.setOnClickListener(this::onShowHideClick);
// altTextScroller.setNestedScrollingEnabled(true);
}
@Override
public void onBind(ImageStatusDisplayItem item){
layout.setLayout(item.tiledLayout, item.thisTile, item.horizontalInset);
crossfadeDrawable.setSize(item.attachment.getWidth(), item.attachment.getHeight());
crossfadeDrawable.setBlurhashDrawable(item.attachment.blurhashPlaceholder);
crossfadeDrawable.setCrossfadeAlpha(item.status.spoilerRevealed ? 0f : 1f);
photo.setImageDrawable(null);
photo.setImageDrawable(crossfadeDrawable);
photo.setContentDescription(TextUtils.isEmpty(item.attachment.description) ? item.parentFragment.getString(R.string.media_no_description) : item.attachment.description);
didClear=false;
if (currentAnim != null) currentAnim.cancel();
boolean altTextMissing = TextUtils.isEmpty(item.attachment.description);
altOrNoAltButton = altTextMissing ? noAltTextButton : altTextButton;
altTextShown=false;
altTextScroller.setVisibility(View.GONE);
altTextClose.setVisibility(View.GONE);
altTextButton.setVisibility(View.VISIBLE);
noAltTextButton.setVisibility(View.VISIBLE);
altTextButton.setAlpha(1f);
noAltTextButton.setAlpha(1f);
altTextWrapper.setVisibility(View.VISIBLE);
if (altTextMissing){
if (GlobalUserPreferences.showNoAltIndicator) {
noAltTextButton.setVisibility(View.VISIBLE);
noAltText.setVisibility(View.VISIBLE);
altTextWrapper.setBackgroundResource(R.drawable.bg_image_no_alt_overlay);
altTextButton.setVisibility(View.GONE);
altText.setVisibility(View.GONE);
} else {
altTextWrapper.setVisibility(View.GONE);
}
}else{
if (GlobalUserPreferences.showAltIndicator) {
noAltTextButton.setVisibility(View.GONE);
noAltText.setVisibility(View.GONE);
altTextWrapper.setBackgroundResource(R.drawable.bg_image_alt_overlay);
altTextButton.setVisibility(View.VISIBLE);
altTextButton.setText(R.string.sk_alt_button);
altText.setVisibility(View.VISIBLE);
altText.setText(item.attachment.description);
altText.setPadding(0, 0, 0, 0);
} else {
altTextWrapper.setVisibility(View.GONE);
}
}
}
private void onShowHideClick(View v){
boolean show=v.getId()==R.id.alt_button || v.getId()==R.id.no_alt_button;
if(altTextShown==show)
return;
if(currentAnim!=null)
currentAnim.cancel();
altTextShown=show;
if(show){
altTextScroller.setVisibility(View.VISIBLE);
altTextClose.setVisibility(View.VISIBLE);
}else{
altOrNoAltButton.setVisibility(View.VISIBLE);
// Hide these views temporarily so FrameLayout measures correctly
altTextScroller.setVisibility(View.GONE);
altTextClose.setVisibility(View.GONE);
}
// This is the current size...
int prevLeft=altTextWrapper.getLeft();
int prevRight=altTextWrapper.getRight();
int prevTop=altTextWrapper.getTop();
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
// ...and this is after the layout pass, right now the FrameLayout has its final size, but we animate that change
if(!show){
// Show these views again so they're visible for the duration of the animation.
// No one would notice they were missing during measure/layout.
altTextScroller.setVisibility(View.VISIBLE);
altTextClose.setVisibility(View.VISIBLE);
}
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofInt(altTextWrapper, "left", prevLeft, altTextWrapper.getLeft()),
ObjectAnimator.ofInt(altTextWrapper, "right", prevRight, altTextWrapper.getRight()),
ObjectAnimator.ofInt(altTextWrapper, "top", prevTop, altTextWrapper.getTop()),
ObjectAnimator.ofFloat(altOrNoAltButton, View.ALPHA, show ? 1f : 0f, show ? 0f : 1f),
ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f),
ObjectAnimator.ofFloat(altTextClose, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f)
);
set.setDuration(300);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
if(show){
altOrNoAltButton.setVisibility(View.GONE);
}else{
altTextScroller.setVisibility(View.GONE);
altTextClose.setVisibility(View.GONE);
}
currentAnim=null;
}
});
set.start();
currentAnim=set;
return true;
}
});
}
@Override
public void setImage(int index, Drawable drawable){
crossfadeDrawable.setImageDrawable(drawable);
if(didClear && item.status.spoilerRevealed)
crossfadeDrawable.animateAlpha(0f);
}
@Override
public void clearImage(int index){
crossfadeDrawable.setCrossfadeAlpha(1f);
crossfadeDrawable.setImageDrawable(null);
didClear=true;
}
private void onViewClick(View v){
if(!item.status.spoilerRevealed){
item.parentFragment.onRevealSpoilerClick(this);
}else if(item.parentFragment instanceof PhotoViewerHost){
Status contentStatus=item.status.reblog!=null ? item.status.reblog : item.status;
((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, contentStatus.mediaAttachments.indexOf(item.attachment));
}
}
public void setRevealed(boolean revealed){
crossfadeDrawable.animateAlpha(revealed ? 0f : 1f);
}
}
}

View File

@@ -0,0 +1,316 @@
package org.joinmastodon.android.ui.displayitems;
import static org.joinmastodon.android.ui.utils.MediaAttachmentViewController.altWrapPadding;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild;
import org.joinmastodon.android.ui.views.MediaGridLayout;
import org.joinmastodon.android.utils.TypedObjectPool;
import java.util.ArrayList;
import java.util.List;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private static final String TAG="MediaGridDisplayItem";
private final PhotoLayoutHelper.TiledLayoutResult tiledLayout;
private final TypedObjectPool<GridItemType, MediaAttachmentViewController> viewPool;
private final List<Attachment> attachments;
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
public final Status status;
public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> attachments, Status status){
super(parentID, parentFragment);
this.tiledLayout=tiledLayout;
this.viewPool=parentFragment.getAttachmentViewsPool();
this.attachments=attachments;
this.status=status;
for(Attachment att:attachments){
requests.add(new UrlImageLoaderRequest(switch(att.type){
case IMAGE -> att.url;
case VIDEO, GIFV -> att.previewUrl;
default -> throw new IllegalStateException("Unexpected value: "+att.type);
}, 1000, 1000));
}
}
@Override
public Type getType(){
return Type.MEDIA_GRID;
}
@Override
public int getImageCount(){
return requests.size();
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return requests.get(index);
}
public enum GridItemType{
PHOTO,
VIDEO,
GIFV
}
public static class Holder extends StatusDisplayItem.Holder<MediaGridStatusDisplayItem> implements ImageLoaderViewHolder{
private final FrameLayout wrapper;
private final MediaGridLayout layout;
private final View.OnClickListener clickListener=this::onViewClick, altTextClickListener=this::onAltTextClick;
private final ArrayList<MediaAttachmentViewController> controllers=new ArrayList<>();
private final FrameLayout altTextWrapper;
private final TextView altTextButton;
private final ImageView noAltTextButton;
private final View altTextScroller;
private final ImageButton altTextClose;
private final TextView altText, noAltText;
private int altTextIndex=-1;
private Animator altTextAnimator;
public Holder(Activity activity, ViewGroup parent){
super(new FrameLayoutThatOnlyMeasuresFirstChild(activity));
wrapper=(FrameLayout)itemView;
layout=new MediaGridLayout(activity);
wrapper.addView(layout);
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper);
altTextWrapper=findViewById(R.id.alt_text_wrapper);
altTextButton=findViewById(R.id.alt_button);
noAltTextButton=findViewById(R.id.no_alt_button);
altTextScroller=findViewById(R.id.alt_text_scroller);
altTextClose=findViewById(R.id.alt_text_close);
altText=findViewById(R.id.alt_text);
noAltText=findViewById(R.id.no_alt_text);
altTextClose.setOnClickListener(this::onAltTextCloseClick);
}
@Override
public void onBind(MediaGridStatusDisplayItem item){
if(altTextAnimator!=null)
altTextAnimator.cancel();
layout.setTiledLayout(item.tiledLayout);
for(MediaAttachmentViewController c:controllers){
item.viewPool.reuse(c.type, c);
}
layout.removeAllViews();
controllers.clear();
int i=0;
for(Attachment att:item.attachments){
MediaAttachmentViewController c=item.viewPool.obtain(switch(att.type){
case IMAGE -> GridItemType.PHOTO;
case VIDEO -> GridItemType.VIDEO;
case GIFV -> GridItemType.GIFV;
default -> throw new IllegalStateException("Unexpected value: "+att.type);
});
if(c.view.getLayoutParams()==null)
c.view.setLayoutParams(new MediaGridLayout.LayoutParams(item.tiledLayout.tiles[i]));
else
((MediaGridLayout.LayoutParams) c.view.getLayoutParams()).tile=item.tiledLayout.tiles[i];
layout.addView(c.view);
c.view.setOnClickListener(clickListener);
c.view.setTag(i);
if(c.btnsWrap!=null){
c.btnsWrap.setOnClickListener(altTextClickListener);
c.btnsWrap.setTag(i);
c.btnsWrap.setAlpha(1f);
}
controllers.add(c);
c.bind(att, item.status);
i++;
}
altTextButton.setVisibility(View.VISIBLE);
noAltTextButton.setVisibility(View.VISIBLE);
altTextWrapper.setVisibility(View.GONE);
altTextIndex=-1;
}
@Override
public void setImage(int index, Drawable drawable){
controllers.get(index).setImage(drawable);
}
@Override
public void clearImage(int index){
controllers.get(index).clearImage();
}
private void onViewClick(View v){
int index=(Integer)v.getTag();
if(!item.status.spoilerRevealed){
item.parentFragment.onRevealSpoilerClick(this);
}else if(item.parentFragment instanceof PhotoViewerHost){
((PhotoViewerHost) item.parentFragment).openPhotoViewer(item.parentID, item.status, index, this);
}
}
private void onAltTextClick(View v){
if(altTextAnimator!=null)
altTextAnimator.cancel();
v.setVisibility(View.INVISIBLE);
int index=(Integer)v.getTag();
altTextIndex=index;
Attachment att=item.attachments.get(index);
boolean hasAltText = !TextUtils.isEmpty(att.description);
altTextButton.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
noAltTextButton.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
altText.setVisibility(hasAltText && GlobalUserPreferences.showAltIndicator ? View.VISIBLE : View.GONE);
noAltText.setVisibility(!hasAltText && GlobalUserPreferences.showNoAltIndicator ? View.VISIBLE : View.GONE);
altText.setText(att.description);
altTextWrapper.setVisibility(View.VISIBLE);
altTextWrapper.setBackgroundResource(hasAltText ? R.drawable.bg_image_alt_overlay : R.drawable.bg_image_no_alt_overlay);
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
int[] loc={0, 0};
v.getLocationInWindow(loc);
int btnL=loc[0], btnT=loc[1];
wrapper.getLocationInWindow(loc);
btnL-=loc[0];
btnT-=loc[1];
ArrayList<Animator> anims=new ArrayList<>();
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1, 0));
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1, 0));
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0, 1));
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0, 1));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+altWrapPadding[0], altTextWrapper.getLeft()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+altWrapPadding[1], altTextWrapper.getTop()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+v.getWidth()-altWrapPadding[2], altTextWrapper.getRight()));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+v.getHeight()-altWrapPadding[3], altTextWrapper.getBottom()));
for(Animator a:anims)
a.setDuration(300);
for(MediaAttachmentViewController c:controllers){
if(c.btnsWrap!=null && c.btnsWrap!=v){
anims.add(ObjectAnimator.ofFloat(c.btnsWrap, View.ALPHA, 1, 0).setDuration(150));
}
}
AnimatorSet set=new AnimatorSet();
set.playTogether(anims);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
altTextAnimator=null;
for(MediaAttachmentViewController c:controllers){
if(c.btnsWrap!=null){
c.btnsWrap.setVisibility(View.INVISIBLE);
}
}
}
});
altTextAnimator=set;
set.start();
return true;
}
});
}
private void onAltTextCloseClick(View v){
if(altTextAnimator!=null)
altTextAnimator.cancel();
View btn=controllers.get(altTextIndex).btnsWrap;
int i=0;
for(MediaAttachmentViewController c:controllers){
boolean hasAltText = !TextUtils.isEmpty(item.attachments.get(i).description);
if(c.btnsWrap!=null
&& c.btnsWrap!=btn
&& ((hasAltText && GlobalUserPreferences.showAltIndicator)
|| (!hasAltText && GlobalUserPreferences.showNoAltIndicator))
) c.btnsWrap.setVisibility(View.VISIBLE);
i++;
}
int[] loc={0, 0};
btn.getLocationInWindow(loc);
int btnL=loc[0], btnT=loc[1];
wrapper.getLocationInWindow(loc);
btnL-=loc[0];
btnT-=loc[1];
ArrayList<Animator> anims=new ArrayList<>();
anims.add(ObjectAnimator.ofFloat(altTextButton, View.ALPHA, 1));
anims.add(ObjectAnimator.ofFloat(noAltTextButton, View.ALPHA, 1));
anims.add(ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, 0));
anims.add(ObjectAnimator.ofFloat(altTextClose, View.ALPHA, 0));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "left", btnL+altWrapPadding[0]));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "top", btnT+altWrapPadding[1]));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "right", btnL+btn.getWidth()-altWrapPadding[2]));
anims.add(ObjectAnimator.ofInt(altTextWrapper, "bottom", btnT+btn.getHeight()-altWrapPadding[3]));
for(Animator a:anims)
a.setDuration(300);
for(MediaAttachmentViewController c:controllers){
// if(c.btnsWrap!=null && c.btnsWrap!=btn){
anims.add(ObjectAnimator.ofFloat(c.btnsWrap, View.ALPHA, 1).setDuration(150));
// }
}
AnimatorSet set=new AnimatorSet();
set.playTogether(anims);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
altTextAnimator=null;
altTextWrapper.setVisibility(View.GONE);
btn.setVisibility(View.VISIBLE);
}
});
altTextAnimator=set;
set.start();
}
public void setRevealed(boolean revealed){
for(MediaAttachmentViewController c:controllers){
c.setRevealed(revealed);
}
}
public MediaAttachmentViewController getViewController(int index){
return controllers.get(index);
}
public void setClipChildren(boolean clip){
layout.setClipChildren(clip);
wrapper.setClipChildren(clip);
}
}
}

View File

@@ -1,45 +0,0 @@
package org.joinmastodon.android.ui.displayitems;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{
public PhotoStatusDisplayItem(String parentID, Status status, Attachment photo, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
super(parentID, parentFragment, photo, status, index, totalPhotos, tiledLayout, thisTile);
request=new UrlImageLoaderRequest(photo.url, 1000, 1000);
}
@Override
public Type getType(){
return Type.PHOTO;
}
public static class Holder extends ImageStatusDisplayItem.Holder<PhotoStatusDisplayItem> {
public Holder(Activity activity, ViewGroup parent) {
super(activity, R.layout.display_item_photo, parent);
}
}
}

View File

@@ -10,14 +10,17 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.UiUtils;
public class PollFooterStatusDisplayItem extends StatusDisplayItem{
public final Poll poll;
public final Status status;
public PollFooterStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Poll poll){
public PollFooterStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Poll poll, Status status){
super(parentID, parentFragment);
this.poll=poll;
this.status=status;
}
@Override

View File

@@ -11,6 +11,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
@@ -22,21 +23,24 @@ import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
public final Poll.Option option;
public final Status status;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private boolean showResults;
private float votesFraction; // 0..1
private boolean isMostVoted;
public final Poll poll;
public PollOptionStatusDisplayItem(String parentID, Poll poll, Poll.Option option, BaseStatusListFragment parentFragment){
public PollOptionStatusDisplayItem(String parentID, Poll poll, Poll.Option option, BaseStatusListFragment parentFragment, Status status){
super(parentID, parentFragment);
this.option=option;
this.poll=poll;
this.status=status;
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
emojiHelper.setText(text);
showResults=poll.isExpired() || poll.voted;
if(showResults && option.votesCount!=null && poll.votersCount>0){
votesFraction=(float)option.votesCount/(float)poll.votersCount;
int total=poll.votersCount>0 ? poll.votersCount : poll.votesCount;
if(showResults && option.votesCount!=null && total>0){
votesFraction=(float)option.votesCount/(float)total;
int mostVotedCount=0;
for(Poll.Option opt:poll.options)
mostVotedCount=Math.max(mostVotedCount, opt.votesCount);
@@ -88,7 +92,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
progressBg.setLevel(Math.round(10000f*item.votesFraction));
button.setBackground(progressBg);
itemView.setSelected(item.isMostVoted);
icon.setSelected(item.poll.ownVotes.contains(item.poll.options.indexOf(item.option)));
icon.setSelected(item.poll.ownVotes != null && item.poll.ownVotes.contains(item.poll.options.indexOf(item.option)));
icon.setVisibility(item.poll.voted && item.poll.ownVotes.isEmpty() ? View.GONE : View.VISIBLE);
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
}else{

View File

@@ -10,8 +10,10 @@ import android.text.SpannableStringBuilder;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Emoji;
@@ -27,6 +29,7 @@ import androidx.annotation.Nullable;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
private CharSequence text;
@@ -35,10 +38,17 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
private StatusPrivacy visibility;
@DrawableRes
private int iconEnd;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(), fullTextEmojiHelper;
private View.OnClickListener handleClick;
boolean belowHeader, needBottomPadding;
ReblogOrReplyLineStatusDisplayItem extra;
CharSequence fullText;
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) {
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text);
}
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText) {
super(parentID, parentFragment);
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
HtmlParser.parseCustomEmoji(ssb, emojis);
@@ -49,6 +59,15 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
updateVisibility(visibility);
if (fullText != null) {
fullTextEmojiHelper = new CustomEmojiHelper();
SpannableStringBuilder fullTextSsb = new SpannableStringBuilder(fullText);
HtmlParser.parseCustomEmoji(fullTextSsb, emojis);
this.fullText=fullTextSsb;
fullTextEmojiHelper.setText(fullTextSsb);
}
}
public void updateVisibility(StatusPrivacy visibility) {
@@ -77,30 +96,77 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text;
private final TextView text, extraText;
private final View separator;
private final ViewGroup parent;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
this.parent = parent;
text=findViewById(R.id.text);
extraText=findViewById(R.id.extra_text);
separator=findViewById(R.id.separator);
if (GlobalUserPreferences.replyLineAboveHeader && GlobalUserPreferences.compactReblogReplyLine) {
parent.addOnLayoutChangeListener((v, l, t, right, b, ol, ot, oldRight, ob) -> {
if (right != oldRight) layoutLine();
});
}
}
@Override
public void onBind(ReblogOrReplyLineStatusDisplayItem item){
private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) {
if (item.fullText != null) text.setContentDescription(item.fullText);
text.setText(item.text);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
if(item.handleClick!=null) text.setOnClickListener(item.handleClick);
text.setEnabled(!item.inset);
text.setClickable(!item.inset);
text.setOnClickListener(item.handleClick);
text.setEnabled(!item.inset && item.handleClick != null);
text.setClickable(!item.inset && item.handleClick != null);
Context ctx = itemView.getContext();
int visibilityText = item.visibility != null ? switch (item.visibility) {
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
case PRIVATE -> R.string.visibility_followers_only;
case LOCAL -> R.string.sk_local_only;
default -> 0;
} : 0;
if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")");
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
text.setTextAppearance(item.belowHeader ? R.style.m3_label_large : R.style.m3_title_small);
text.setCompoundDrawableTintList(text.getTextColors());
}
@Override
public void onBind(ReblogOrReplyLineStatusDisplayItem item){
bindLine(item, text);
if (item.extra != null) bindLine(item.extra, extraText);
extraText.setVisibility(item.extra == null ? View.GONE : View.VISIBLE);
separator.setVisibility(item.extra == null ? View.GONE : View.VISIBLE);
ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.bottomMargin = item.belowHeader ? V.dp(-6) : V.dp(-12);
params.topMargin = item.belowHeader ? V.dp(-6) : 0;
itemView.setLayoutParams(params);
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
layoutLine();
}
private void layoutLine() {
// layout line only if above header, compact and has extra
if (!GlobalUserPreferences.replyLineAboveHeader
|| !GlobalUserPreferences.compactReblogReplyLine
|| item.extra == null) return;
itemView.measure(
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.UNSPECIFIED);
boolean isVertical = ((LinearLayout) itemView).getOrientation() == LinearLayout.VERTICAL;
extraText.setPaddingRelative(extraText.getPaddingStart(), item.extra != null && isVertical ? 0 : V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom());
separator.setVisibility(item.extra != null && !isVertical ? View.VISIBLE : View.GONE);
((LinearLayout) itemView).removeView(extraText);
if (isVertical) ((LinearLayout) itemView).addView(extraText);
else ((LinearLayout) itemView).addView(extraText, 0);
text.setText(isVertical ? item.fullText : item.text);
if (item.extra != null) {
extraText.setText(isVertical ? item.extra.fullText : item.extra.text);
}
}
@Override

View File

@@ -7,13 +7,13 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTabFragment;
import org.joinmastodon.android.fragments.HomeTimelineFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
@@ -21,7 +21,6 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.ScheduledStatus;
@@ -32,10 +31,8 @@ import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -70,10 +67,7 @@ public abstract class StatusDisplayItem{
case HEADER -> new HeaderStatusDisplayItem.Holder(activity, parent);
case REBLOG_OR_REPLY_LINE -> new ReblogOrReplyLineStatusDisplayItem.Holder(activity, parent);
case TEXT -> new TextStatusDisplayItem.Holder(activity, parent);
case PHOTO -> new PhotoStatusDisplayItem.Holder(activity, parent);
case GIFV -> new GifVStatusDisplayItem.Holder(activity, parent);
case AUDIO -> new AudioStatusDisplayItem.Holder(activity, parent);
case VIDEO -> new VideoStatusDisplayItem.Holder(activity, parent);
case POLL_OPTION -> new PollOptionStatusDisplayItem.Holder(activity, parent);
case POLL_FOOTER -> new PollFooterStatusDisplayItem.Holder(activity, parent);
case CARD -> new LinkCardStatusDisplayItem.Holder(activity, parent);
@@ -83,6 +77,7 @@ public abstract class StatusDisplayItem{
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent);
case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent);
case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent);
};
}
@@ -100,10 +95,6 @@ public abstract class StatusDisplayItem{
}
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, disableTranslate, filterContext, null);
}
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext, StatusDisplayItem titleItem){
String parentID=parentObject.getID();
ArrayList<StatusDisplayItem> items=new ArrayList<>();
@@ -120,24 +111,37 @@ public abstract class StatusDisplayItem{
statusForContent.filterRevealed = filterPredicate.testWithWarning(status);
}
ReblogOrReplyLineStatusDisplayItem replyLine = null;
boolean threadReply = statusForContent.inReplyToAccountId != null &&
statusForContent.inReplyToAccountId.equals(statusForContent.account.id);
if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){
Account account = knownAccounts.get(statusForContent.inReplyToAccountId);
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
: account == null ? fragment.getString(R.string.sk_in_reply)
: GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
: fragment.getString(R.string.in_reply_to, account.displayName);
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
: account == null ? fragment.getString(R.string.sk_in_reply)
: fragment.getString(R.string.in_reply_to, account.displayName);
replyLine = new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, text, account == null ? List.of() : account.emojis,
R.drawable.ic_fluent_arrow_reply_20_filled, null, null, fullText
);
}
if(status.reblog!=null){
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{
String fullText = fragment.getString(R.string.user_boosted, status.account.displayName);
String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null ? status.account.displayName : fullText;
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{
args.putParcelable("profileAccount", Parcels.wrap(status.account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
}));
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled, null, i->{
args.putParcelable("profileAccount", Parcels.wrap(account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
}));
} else if (
!(status.tags.isEmpty() ||
}, fullText));
} else if (!(status.tags.isEmpty() ||
fragment instanceof HashtagTimelineFragment ||
fragment instanceof ListTimelineFragment
) && fragment.getParentFragment() instanceof HomeTabFragment home
) {
) && fragment.getParentFragment() instanceof HomeTabFragment home) {
home.getHashtags().stream()
.filter(followed -> status.tags.stream()
.anyMatch(hashtag -> followed.name.equalsIgnoreCase(hashtag.name)))
@@ -153,28 +157,37 @@ public abstract class StatusDisplayItem{
)));
}
if (replyLine != null && GlobalUserPreferences.replyLineAboveHeader) {
Optional<ReblogOrReplyLineStatusDisplayItem> primaryLine = items.stream()
.filter(i -> i instanceof ReblogOrReplyLineStatusDisplayItem)
.map(ReblogOrReplyLineStatusDisplayItem.class::cast)
.findFirst();
if (primaryLine.isPresent() && GlobalUserPreferences.compactReblogReplyLine) {
primaryLine.get().extra = replyLine;
} else {
items.add(replyLine);
}
}
HeaderStatusDisplayItem header;
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus));
if(!TextUtils.isEmpty(statusForContent.content)){
if (replyLine != null && !GlobalUserPreferences.replyLineAboveHeader) {
replyLine.belowHeader = true;
items.add(replyLine);
}
if(!TextUtils.isEmpty(statusForContent.content))
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate));
} else
else if (!GlobalUserPreferences.replyLineAboveHeader && replyLine != null)
replyLine.needBottomPadding=true;
else
header.needBottomPadding=true;
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
if(!imageAttachments.isEmpty()){
int photoIndex=0;
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(1000, 1910, imageAttachments);
for(Attachment attachment:imageAttachments){
if(attachment.type==Attachment.Type.IMAGE){
items.add(new PhotoStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex]));
}else if(attachment.type==Attachment.Type.GIFV){
items.add(new GifVStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex]));
}else if(attachment.type==Attachment.Type.VIDEO){
items.add(new VideoStatusDisplayItem(parentID, statusForContent, attachment, fragment, photoIndex, imageAttachments.size(), layout, layout.tiles[photoIndex]));
}else{
throw new IllegalStateException("This isn't supposed to happen, type is "+attachment.type);
}
photoIndex++;
}
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent));
}
for(Attachment att:statusForContent.mediaAttachments){
if(att.type==Attachment.Type.AUDIO){
@@ -182,7 +195,7 @@ public abstract class StatusDisplayItem{
}
}
if(statusForContent.poll!=null){
buildPollItems(parentID, fragment, statusForContent.poll, items);
buildPollItems(parentID, fragment, statusForContent.poll, items, statusForContent);
}
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty() && TextUtils.isEmpty(statusForContent.spoilerText)){
items.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
@@ -200,8 +213,6 @@ public abstract class StatusDisplayItem{
item.index=i++;
}
if (titleItem != null) items.add(0, titleItem);
if (!statusForContent.filterRevealed) {
return new ArrayList<>(List.of(
new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items)
@@ -211,20 +222,17 @@ public abstract class StatusDisplayItem{
return items;
}
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items){
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items, Status status){
for(Poll.Option opt:poll.options){
items.add(new PollOptionStatusDisplayItem(parentID, poll, opt, fragment));
items.add(new PollOptionStatusDisplayItem(parentID, poll, opt, fragment, status));
}
items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll));
items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll, status));
}
public enum Type{
HEADER,
REBLOG_OR_REPLY_LINE,
TEXT,
PHOTO,
VIDEO,
GIFV,
AUDIO,
POLL_OPTION,
POLL_FOOTER,
@@ -235,7 +243,8 @@ public abstract class StatusDisplayItem{
HASHTAG,
GAP,
WARNING,
EXTENDED_FOOTER
EXTENDED_FOOTER,
MEDIA_GRID
}
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{

View File

@@ -31,6 +31,7 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.utils.StatusTextEncoder;
import java.util.Locale;
import java.util.regex.Pattern;
import me.grishka.appkit.api.Callback;
@@ -47,9 +48,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence parsedSpoilerText;
public boolean textSelectable;
public final Status status;
public boolean disableTranslate;
public boolean translated = false;
public TranslatedStatus translation = null;
public boolean disableTranslate, translationShown;
private AccountSession session;
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
@@ -58,6 +57,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
this.text=text;
this.status=status;
this.disableTranslate=disableTranslate;
this.translationShown=status.translationShown;
emojiHelper.setText(text);
if(!TextUtils.isEmpty(status.spoilerText)){
parsedSpoilerText=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
@@ -67,6 +67,11 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
}
public void setTranslationShown(boolean translationShown) {
this.translationShown = translationShown;
status.translationShown = translationShown;
}
@Override
public Type getType(){
return Type.TEXT;
@@ -97,9 +102,11 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private final float textMaxHeight, textCollapsedHeight;
private final LinearLayout.LayoutParams collapseParams, wrapParams;
private final ViewGroup parent;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent);
this.parent=parent;
text=findViewById(R.id.text);
spoilerTitle=findViewById(R.id.spoiler_title);
spoilerTitleInline=findViewById(R.id.spoiler_title_inline);
@@ -127,8 +134,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(TextStatusDisplayItem item){
text.setText(item.translated
? HtmlParser.parse(item.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
text.setText(item.translationShown
? HtmlParser.parse(item.status.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
: item.text);
text.setTextIsSelectable(item.textSelectable);
if (item.textSelectable) {
@@ -165,26 +172,32 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
boolean translateEnabled = !item.disableTranslate && instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
String bottomText = null;
try {
bottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
} catch (TranslationError ignored) {}
boolean isBottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find();
boolean translateVisible = (isBottomText || (
boolean translateVisible = (bottomText != null || (
translateEnabled &&
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
item.status.language != null &&
(item.session.preferences == null || !item.status.language.equalsIgnoreCase(item.session.preferences.postingDefaultLanguage))));
// && (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
// todo: compare to mastodon locale instead (how do i query that?!)
!item.status.language.equalsIgnoreCase(Locale.getDefault().getLanguage())));
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translated ? itemView.getResources().getString(R.string.sk_translated_using, isBottomText ? "bottom-java" : item.translation.provider) : "");
translateButton.setText(item.translationShown ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translationShown ? itemView.getResources().getString(R.string.sk_translated_using, bottomText != null ? "bottom-java" : item.status.translation.provider) : "");
String finalBottomText = bottomText;
translateButton.setOnClickListener(v->{
if (item.translation == null) {
if (isBottomText) {
if (item.status.translation == null) {
if (finalBottomText != null) {
try {
item.translation = new TranslatedStatus();
item.translation.content = new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN);
item.translated = true;
item.status.translation = new TranslatedStatus();
item.status.translation.content = finalBottomText;
item.setTranslationShown(true);
} catch (TranslationError err) {
item.translation = null;
item.status.translation = null;
Toast.makeText(itemView.getContext(), err.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
rebind();
@@ -193,11 +206,14 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
translateProgress.setVisibility(View.VISIBLE);
translateButton.setClickable(false);
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
if(item.status.reloadWhenClicked){
UiUtils.lookupStatus(item.parentFragment.getContext(), item.status, item.parentFragment.getAccountID(), null, status1 -> {
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.translation = translatedStatus;
item.translated = true;
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
@@ -213,8 +229,31 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
});
} else {
item.translated = !item.translated;
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.status.translation = translatedStatus;
item.setTranslationShown(true);
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
rebind();
}
@Override
public void onError(ErrorResponse error) {
translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
}
} else {
item.setTranslationShown(!item.translationShown);
rebind();
}
});
@@ -227,13 +266,16 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
readMore.setVisibility(View.GONE);
}
if (GlobalUserPreferences.collapseLongPosts) text.post(() -> {
text.measure(
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
if (GlobalUserPreferences.collapseLongPosts && !item.status.textExpandable) {
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
boolean inTimeline = !item.textSelectable;
boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText);
boolean expandable = inTimeline && tooBig && !hasSpoiler;
item.parentFragment.onEnableExpandable(this, expandable);
});
boolean expandable = tooBig && !hasSpoiler;
item.parentFragment.onEnableExpandable(Holder.this, expandable);
}
readMore.setVisibility(item.status.textExpandable && !item.status.textExpanded ? View.VISIBLE : View.GONE);
textScrollView.setLayoutParams(item.status.textExpandable && !item.status.textExpanded ? collapseParams : wrapParams);

View File

@@ -1,42 +0,0 @@
package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.graphics.Outline;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
public class VideoStatusDisplayItem extends ImageStatusDisplayItem{
public VideoStatusDisplayItem(String parentID, Status status, Attachment attachment, BaseStatusListFragment parentFragment, int index, int totalPhotos, PhotoLayoutHelper.TiledLayoutResult tiledLayout, PhotoLayoutHelper.TiledLayoutResult.Tile thisTile){
super(parentID, parentFragment, attachment, status, index, totalPhotos, tiledLayout, thisTile);
request=new UrlImageLoaderRequest(attachment.previewUrl, 1000, 1000);
}
@Override
public Type getType(){
return Type.VIDEO;
}
public static class Holder extends ImageStatusDisplayItem.Holder<VideoStatusDisplayItem>{
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_video, parent);
View play=findViewById(R.id.play_button);
play.setOutlineProvider(new ViewOutlineProvider(){
@Override
public void getOutline(View view, Outline outline){
outline.setOval(0, 0, view.getWidth(), view.getHeight());
outline.setAlpha(.99f); // fixes shadow rendering
}
});
}
}
}

View File

@@ -1,7 +1,8 @@
package org.joinmastodon.android.ui.photoviewer;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
public interface PhotoViewerHost{
void openPhotoViewer(String parentID, Status status, int attachmentIndex);
void openPhotoViewer(String parentID, Status status, int attachmentIndex, MediaGridStatusDisplayItem.Holder gridHolder);
}

View File

@@ -100,7 +100,7 @@ public class HtmlParser{
}
}
Map<String, String> idsByUrl=mentions.stream().collect(Collectors.toMap(m->m.url, m->m.id));
Map<String, String> idsByUrl=mentions.stream().filter(mention -> mention.id != null).collect(Collectors.toMap(m->m.url, m->m.id));
// Hashtags in remote posts have remote URLs, these have local URLs so they don't match.
// Map<String, String> tagsByUrl=tags.stream().collect(Collectors.toMap(t->t.url, t->t.name));

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