Compare commits
276 Commits
2.3.0+fork
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1f04375f6 | ||
|
|
b56a922c65 | ||
|
|
732a0f9b4c | ||
|
|
889762f667 | ||
|
|
baf9536145 | ||
|
|
b8b8287f8f | ||
|
|
fb6b9a9bed | ||
|
|
bf288f82f4 | ||
|
|
41a7ca5487 | ||
|
|
78219038bd | ||
|
|
6671253b05 | ||
|
|
18a827708c | ||
|
|
b2b0182e04 | ||
|
|
499d425e68 | ||
|
|
026f32b03c | ||
|
|
6b4f114e06 | ||
|
|
7b64d49084 | ||
|
|
ad1bc22c6a | ||
|
|
8f571e7d74 | ||
|
|
eb10544960 | ||
|
|
7190b61b94 | ||
|
|
8f9f58a0e9 | ||
|
|
f29598374c | ||
|
|
c7f6975d4f | ||
|
|
750456820a | ||
|
|
42f29291b4 | ||
|
|
9b7a64d5aa | ||
|
|
3d0392db70 | ||
|
|
08f559e70a | ||
|
|
3d9379a4c0 | ||
|
|
0da5188900 | ||
|
|
3932b9dc11 | ||
|
|
6776f1fb55 | ||
|
|
f25d731b42 | ||
|
|
6dde25009d | ||
|
|
087c5c1993 | ||
|
|
6f1a3e02b0 | ||
|
|
9e7f3a3b2c | ||
|
|
c678a37f95 | ||
|
|
eb451736e9 | ||
|
|
77687cfb4e | ||
|
|
bdec03514a | ||
|
|
2f16e06750 | ||
|
|
ab2256f90c | ||
|
|
abe40c08db | ||
|
|
2a7e8bac2d | ||
|
|
a8da796956 | ||
|
|
0de346c1bc | ||
|
|
8598dc2608 | ||
|
|
ab72435347 | ||
|
|
d5f6852bdc | ||
|
|
36f96c1ed6 | ||
|
|
058fb62253 | ||
|
|
bc43e2180f | ||
|
|
c1e96d4ff0 | ||
|
|
42a060e8a6 | ||
|
|
065db923cc | ||
|
|
0089a10a81 | ||
|
|
c6d4436467 | ||
|
|
98d58c35dc | ||
|
|
3f7c0417c2 | ||
|
|
64dcb8b387 | ||
|
|
4145b6451b | ||
|
|
188b3fade7 | ||
|
|
87800a696a | ||
|
|
f51891f64c | ||
|
|
45c822c48d | ||
|
|
a1eae5a1a1 | ||
|
|
73926e0ac1 | ||
|
|
00333604c9 | ||
|
|
8aa9e99e91 | ||
|
|
5159e8fbda | ||
|
|
6cf3253d40 | ||
|
|
b769bf5ee4 | ||
|
|
7adb3c7b39 | ||
|
|
4f33194884 | ||
|
|
bfaa6e12a7 | ||
|
|
e48143585e | ||
|
|
3f60beb999 | ||
|
|
a952a17a18 | ||
|
|
ae966cc784 | ||
|
|
5b684bd9b3 | ||
|
|
251a518e56 | ||
|
|
0156f8a732 | ||
|
|
20464001b8 | ||
|
|
2d4f3b9a88 | ||
|
|
66afd9d091 | ||
|
|
69562fa3e4 | ||
|
|
9e2839c0ae | ||
|
|
f1b9f110d2 | ||
|
|
94cb110f99 | ||
|
|
07a29564d5 | ||
|
|
015d416773 | ||
|
|
2fea6a2934 | ||
|
|
3af59de797 | ||
|
|
543f7ab30a | ||
|
|
dacc32dcaa | ||
|
|
45568f600a | ||
|
|
df96e6af31 | ||
|
|
c7b58a0982 | ||
|
|
463933b19a | ||
|
|
53f3a42588 | ||
|
|
9338fbc246 | ||
|
|
54d074839c | ||
|
|
0424dcd6ca | ||
|
|
dedd1a7b70 | ||
|
|
7684b6705c | ||
|
|
86d01cbb97 | ||
|
|
3ca18ed38b | ||
|
|
5a5c2fbc69 | ||
|
|
4bbeb4f198 | ||
|
|
2133ca7188 | ||
|
|
33a71e1c46 | ||
|
|
0cba8f30a6 | ||
|
|
0241628cf5 | ||
|
|
a4bac9c100 | ||
|
|
915bd41b71 | ||
|
|
32c3b81ec3 | ||
|
|
fad20394ff | ||
|
|
fba279f43e | ||
|
|
a8fd78b1a8 | ||
|
|
19390221ec | ||
|
|
ab47192b2c | ||
|
|
e9c01a5452 | ||
|
|
bc45072542 | ||
|
|
e24bfb0448 | ||
|
|
673ada4782 | ||
|
|
ee02634036 | ||
|
|
1c42b9a4e7 | ||
|
|
3bd855ed1e | ||
|
|
fba7650918 | ||
|
|
c3b2e9fdc2 | ||
|
|
b957f3500b | ||
|
|
51bf3416bf | ||
|
|
8bdbde0ea6 | ||
|
|
c50aecdd05 | ||
|
|
f4ffd4718e | ||
|
|
6d83453f96 | ||
|
|
69dbf38e1e | ||
|
|
a03297313a | ||
|
|
b52f1c156d | ||
|
|
28097554a7 | ||
|
|
5984783831 | ||
|
|
5299cda1ad | ||
|
|
65c8906b2a | ||
|
|
86e7e7cdc6 | ||
|
|
fe7f9f14c3 | ||
|
|
1bbfc45bd0 | ||
|
|
0b5588515e | ||
|
|
2cdc649b7d | ||
|
|
4e1f7839b3 | ||
|
|
281e989749 | ||
|
|
7cd756f6b0 | ||
|
|
cc4558458c | ||
|
|
66824aadb9 | ||
|
|
00e90e5f21 | ||
|
|
376653cb3f | ||
|
|
082de410e0 | ||
|
|
899f48b40c | ||
|
|
dae5989d64 | ||
|
|
1d1c4f2666 | ||
|
|
0fecdf345a | ||
|
|
ed5c58b4ea | ||
|
|
cbab92ed87 | ||
|
|
82bcfe3fa8 | ||
|
|
203c43343a | ||
|
|
4c105acc30 | ||
|
|
bd6f739842 | ||
|
|
e336f15cb0 | ||
|
|
a554059cce | ||
|
|
d81eb6ad0a | ||
|
|
08542cd16f | ||
|
|
f30e12f5c6 | ||
|
|
3a14fb5912 | ||
|
|
cc64a1b6a2 | ||
|
|
7fa079e362 | ||
|
|
c2e6280a18 | ||
|
|
01225b05f2 | ||
|
|
f016b87ea0 | ||
|
|
9426a9bc59 | ||
|
|
f146067cda | ||
|
|
f28e06d2f5 | ||
|
|
87cbffcb06 | ||
|
|
7a103046b4 | ||
|
|
64c53be990 | ||
|
|
e419c504e4 | ||
|
|
1f06e4e8f3 | ||
|
|
b0f8cbb2e3 | ||
|
|
89f27984b7 | ||
|
|
e3df5ce0a8 | ||
|
|
fcf7665ab5 | ||
|
|
407844378f | ||
|
|
61b933655c | ||
|
|
d47e1939d0 | ||
|
|
c834199ee4 | ||
|
|
00b934dc69 | ||
|
|
c86ff1cce4 | ||
|
|
893b88350a | ||
|
|
96b992025b | ||
|
|
cd7c546bed | ||
|
|
6a46815809 | ||
|
|
5d411e842e | ||
|
|
309d27242d | ||
|
|
5f78cd4a8e | ||
|
|
1ad2257bb1 | ||
|
|
5427b21365 | ||
|
|
d875edbc23 | ||
|
|
4aecb17497 | ||
|
|
806db1d09f | ||
|
|
49cf100d37 | ||
|
|
259a0ae140 | ||
|
|
420233da14 | ||
|
|
78ec24ff0c | ||
|
|
a6f1d981db | ||
|
|
b07789b346 | ||
|
|
42c55d5eee | ||
|
|
13545fd5ef | ||
|
|
134513babd | ||
|
|
91cb616164 | ||
|
|
3266a490be | ||
|
|
f3d600282e | ||
|
|
c26df5762f | ||
|
|
2021c335ac | ||
|
|
d121f14d30 | ||
|
|
d1a2a70cdc | ||
|
|
89ef482e2e | ||
|
|
9918649d7c | ||
|
|
09185faf9a | ||
|
|
bed201a2f7 | ||
|
|
5e7a4c0136 | ||
|
|
bcb8717d5f | ||
|
|
ed1c1bd097 | ||
|
|
f480532fd6 | ||
|
|
cc056cef08 | ||
|
|
9e7445b8d8 | ||
|
|
e2d96d3bc7 | ||
|
|
4f5c99be21 | ||
|
|
0388f9d9be | ||
|
|
c45128ced0 | ||
|
|
f404d2f9cd | ||
|
|
2dada69eb8 | ||
|
|
b7e0596014 | ||
|
|
dbef984908 | ||
|
|
55259f103d | ||
|
|
81519fe906 | ||
|
|
07ab3c394a | ||
|
|
620cc94351 | ||
|
|
2494918171 | ||
|
|
a0bed5e739 | ||
|
|
a42bf86a1e | ||
|
|
9c7ae9653b | ||
|
|
44473705b9 | ||
|
|
f1d40f8963 | ||
|
|
fbae5d8816 | ||
|
|
43afbb7523 | ||
|
|
080815846f | ||
|
|
4b6c6cbcfe | ||
|
|
117037e7e8 | ||
|
|
05972fc702 | ||
|
|
28084b9f9e | ||
|
|
02010df408 | ||
|
|
38f77c69d1 | ||
|
|
d0a8c26b65 | ||
|
|
401602e5bc | ||
|
|
86b6adf228 | ||
|
|
2856e99569 | ||
|
|
684164903a | ||
|
|
2dfb79c828 | ||
|
|
86f54f5a02 | ||
|
|
3593d8d80f | ||
|
|
454660fe89 | ||
|
|
325eda58cb | ||
|
|
0e96e23cfa | ||
|
|
2892a31c72 | ||
|
|
c8bb0de7d4 | ||
|
|
53e8c0d2f4 |
16
.github/workflows/mirror-to-codeberg.yml
vendored
Normal file
16
.github/workflows/mirror-to-codeberg.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Mirror to Codeberg
|
||||
|
||||
on: [push]
|
||||
jobs:
|
||||
sync-git:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: yesolutions/mirror-action@master
|
||||
with:
|
||||
REMOTE: 'https://codeberg.org/LucasGGamerM/moshidon.git'
|
||||
GIT_USERNAME: LucasGGamerM
|
||||
GIT_PASSWORD: ${{ secrets.CODEBERG_GIT_PASSWORD }}
|
||||
2
.github/workflows/nightly-builds.yml
vendored
2
.github/workflows/nightly-builds.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
CURRENT_DATE: ${{ steps.date.outputs.date }}
|
||||
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: moshidon-nightly.apk
|
||||
path: ./mastodon/build/outputs/apk/nightly/moshidon-nightly.apk
|
||||
|
||||
57
CSAE-POLICY.md
Normal file
57
CSAE-POLICY.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# CSAE Policy
|
||||
## "Moshidon" CSAE Policy
|
||||
> CSAE refers to child sexual abuse and exploitation, including content or behavior that sexually exploits, abuses, or endangers children. This includes, for example, grooming a child for sexual exploitation, sextorting a child, trafficking of a child for sex, or otherwise sexually exploiting a child. – Google Child Safety Standards Policy
|
||||
|
||||
## Posting or linking to CSAE
|
||||
|
||||
“Moshidon” allows you to create posts on your account’s server.
|
||||
|
||||
Using the application to post or link to CSAE is strictly prohibited.
|
||||
|
||||
## Reporting accounts posting or linking to CSAE
|
||||
#### If you suspect a child is in immediate danger in any way, contact the police immediately.
|
||||
|
||||
If you see an account posting CSAE you can report it to your server’s moderators for further action.
|
||||
|
||||
Reporting an account is a four step process.
|
||||
|
||||
### 1. Start the report
|
||||
Tap the “…” button at the bottom of any post from the account to show the per-post menu
|
||||
|
||||

|
||||
|
||||
Choose “Report” from the per-post menu.
|
||||
|
||||
### 2. Fill in appropriate server rule breakage information
|
||||
|
||||

|
||||
|
||||
### 3. Optionally, include additional posts
|
||||
Choose one or more posts to report.
|
||||
|
||||
The post you chose at the previous step is automatically selected.
|
||||
|
||||

|
||||
|
||||
Tap the “Continue” button when you have finished selecting posts.
|
||||
|
||||
### 4. Finalise the report
|
||||
Enter any additional information in the space provided.
|
||||
|
||||

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

|
||||
#  Moshidon, the material you mastodon client!
|
||||
|
||||
# Moshidon, the material you mastodon client!
|
||||
|
||||
> A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly won’t ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
|
||||
> A fast, highly customizable, up-to-date fork of [megalodon](https://github.com/sk22/megalodon) adding important features such as a fully federated timeline, unlisted posting, drafts, scheduled posts, bookmarks, and alt text warnings.
|
||||
|
||||
|
||||
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
||||
## Download Now
|
||||
|
||||
[](https://github.com/LucasGGamerM/moshidon-nightly/releases/latest/download/moshidon-nightly.apk)
|
||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.moshinda"><img height="35" alt="Get it on Google Play" src="img/google-play-badge.png"></a> <a href="https://f-droid.org/pt_BR/packages/org.joinmastodon.android.moshinda"><img height="35" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a> <a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda"><img height="35" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
||||
|
||||
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) [](https://translate.codeberg.org/engage/moshidon/) [](https://github.com/LucasGGamerM/moshidon-nightly/releases/latest/download/moshidon-nightly.apk) [](https://github.com/LucasGGamerM/moshidon/actions/workflows/nightly-builds.yml)
|
||||
|
||||
[](https://translate.codeberg.org/engage/moshidon/)
|
||||
|
||||
[](https://github.com/LucasGGamerM/moshidon/actions/workflows/nightly-builds.yml)
|
||||
## Donate
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.moshinda"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
|
||||
|
||||
<a href="https://f-droid.org/pt_BR/packages/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a>
|
||||
|
||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
||||
<a href="https://github.com/sponsors/LucasGGamerM">Github Sponsors</a> | <a href="https://liberapay.com/LucasGGamerM/donate">Liberapay</a> | Monero Wallet Key: `4886mdarcyB6Yf8Qc6vDJBK1fz6ibHFLZUmHb4GZZz9yLGNhcG3XC64e5UZ8dVQYTLZb82W6P9WhteowW4STJEec97Gf22j`
|
||||
|
||||
## Help out the project by donating at: https://github.com/sponsors/LucasGGamerM!
|
||||
### We also support LiberaPay at: https://liberapay.com/LucasGGamerM/donate
|
||||
## Key Features
|
||||
|
||||
### You can also donate some Monero through this wallet address as well:
|
||||
4886mdarcyB6Yf8Qc6vDJBK1fz6ibHFLZUmHb4GZZz9yLGNhcG3XC64e5UZ8dVQYTLZb82W6P9WhteowW4STJEec97Gf22j
|
||||
[ screenshot of full timeline in default colour scheme ]
|
||||
[ screenshot of full timeline in an alt colour scheme ]
|
||||
[ screenshot of profile page ]
|
||||
[ screenshot of compose post window ]
|
||||
|
||||
---
|
||||
### Flexible Timelines
|
||||
|
||||
## Key features
|
||||
[ Home dropdown menu ]
|
||||
|
||||
### **The ability to add other server's local timeline to your timelines**
|
||||
Under the Home menu by default you can see your active account's timeline, your server's local timeline, and your server's federated timeline. You can also pin hashtags, lists, other servers, or make a custom view of just your posts, your bookmarks, or your favourites for quick access. Then sort these timelines to prioritize the ones you visit most often.
|
||||
|
||||
It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts!
|
||||
### Multiple Accounts & Crossposting
|
||||
|
||||
### **View remote profiles**
|
||||
Sign in to multiple accounts in the same app and easily switch between them. Press and hold on the boost or fave button to boost or fave a post to a different account than the one you are currently browsing with.
|
||||
|
||||
You can now see all of a profile follows and followers, by directly loading them from the profile's home instance. In case of a failed lookup, the app will automatically fall back to the older method.
|
||||
[ boost icon pop up select profile ]
|
||||
|
||||
### **Translate posts easily**
|
||||
### Drafts & Scheduled Posts
|
||||
|
||||
Allows you to easily translate posts in another language with a translate button! Your instance must support translation, otherwise it will not work.
|
||||
Write posts and save them, or schedule them to post later. Edit and delete your drafts.
|
||||
|
||||
### **Show posts filtered with a warning**
|
||||
### Alt Text Tag & Reminder
|
||||
|
||||
Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:
|
||||
An unobtrusive ALT tag appears on images with alt text. Clicking on the icon makes the alt text appear. By default, Moshidon will show a warning to add alt text if your post has any attachments lacking alt text. This is for better accessibility, and it can be disabled in settings. You can also hide from your feed all posts that are lacking in alt text.
|
||||
|
||||
Before | After
|
||||
:-------------------------:|:-------------------------:
|
||||
 | 
|
||||
[ image with alt text icon higlighted ]
|
||||
[ alt text expanded ]
|
||||
|
||||
### Themes & Customization
|
||||
|
||||
### **Color themes**
|
||||
Moshidon is designed according to Material Design principles. Follow your device's light or dark mode settings or change colour palette - your system's default, purple, black & white, "pitch black" (battery saving) and more. Customize your experience by moving or renaming the publish button, show or hide sensitive media by default, reduce motion, collapse long posts, add haptic feedback, or making the fave button a heart ♥ or a star ★.
|
||||
|
||||
Allows you to change theme within the app. Supports Material You, purple, pink, green, blue, red, orange, yellow and Nord!
|
||||
### Not Just For Mastodon
|
||||
|
||||
### **Unlisted posting**
|
||||
Supports features available on other types of fediverse servers such as admin announcements, showing pronouns in user names, post translation, emoji reactions, local-only posting, and markdown or html in posts.
|
||||
|
||||
**Allows you to post publicly without having your post show up in trends, hashtags or public timelines (i.e., in the tabs “Local”, “Community” and “Posts”).**
|
||||
### Fully Federated Feed & Profiles
|
||||
|
||||
When posting with Unlisted visibility, your posts will still be publicly accessible in your profile. They will also be shown in people’s Home timelines, but only if they follow you or someone they follow reposted/replied to your post.
|
||||
|
||||
The Mastodon documentation has some more information about [Unlisted posting](https://docs.joinmastodon.org/user/posting/#unlisted) and [Public timelines](https://docs.joinmastodon.org/user/network/#timelines).
|
||||
See all public posts from servers your server federates with and fetch profiles from a user's local server for accurate up to date information.
|
||||
|
||||
### **Federated timeline**
|
||||
## And more...
|
||||
- quote-posts - links to fediverse posts in other posts will be loaded inline like quote-tweets
|
||||
- manage pinned posts and bookmarks
|
||||
- manage lists, filters, and most privacy settings
|
||||
- display pronouns in timelines, threads, and user listings
|
||||
- get only specific types of notifications (no more finished polls!), limit who you get notifications from, or group all notifications into one.
|
||||
- automatically add "re:" to beginning of replies with content warnings
|
||||
- ask before boosting or deleting posts
|
||||
- when replying to a boosted post automatically mention the person who boosted it
|
||||
- overlay audio from posts, allowing your existing media to keep playing
|
||||
- auto-reveal CWs that are the same as ones you've already opened, or always reveal content warnings and sensitive media
|
||||
- hide media previews in timelines (save data)
|
||||
- show post interaction counts in timeline
|
||||
- allow custom emoji in display names
|
||||
- enable scrolling text for long display names
|
||||
- hide interaction buttons
|
||||
- show post dividers
|
||||
|
||||
**This allows you to chronologically see all Public posts from people on all other Fediverse neighborhoods your home instance is connected to.**
|
||||
|
||||
Despite being one of the main features of federated social media, the Federated timeline wasn’t included in the official Mastodon app – supposedly, because this conflicts with Google’s safety requirements for apps on the Play Store.
|
||||
|
||||
That’s one of the reasons why choosing a small, **well-moderated instance is important**. Instance admins and moderators should always make sure to ban abusive users and stop federating with instances who platform them. On well-moderated instances, the Federated timeline can be a welcoming place to meet new people!
|
||||
## Installation & Releases
|
||||
|
||||
### **Image description viewer**
|
||||
Moshidon is available on GitHub, Google Play, F-Droid, and the IzzyOnDroid repo. All sources provide the same ` moshidon.apk ` stable release. Older releases are available on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||
|
||||
**Allows you to quickly check whether an image or video has an alternative text attached to it.**
|
||||
### How to Install from GitHub
|
||||
[Download the latest stable release from Github](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) and open it. You might have to accept installing APK files from your browser. Moshidon will automatically check for new updates available on GitHub and offer to download and install them within the app. You can also manually press “Check for updates” at the bottom of the settings page.
|
||||
|
||||
This is important to **ensure the content you’re sharing is as accessible as possible** to people who can’t see the images and rely on software to read back the provided content descriptions. Thankfully, it’s quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way!
|
||||
### Nightly Version
|
||||
All ` moshidon-night.apk ` nightly builds can be downloaded on the [Nightly Releases](https://github.com/LucasGGamerM/moshidon-nightly/releases) page. This is an unstable version with an integrated updater for development and testing purposes. If you find any bugs with it, please file a bug report on our [Issues](https://github.com/LucasGGamerM/moshidon/issues) page.
|
||||
|
||||
### **Reminder to add alt text to attached media**
|
||||
|
||||
By default, Moshidon will show a warning to add alt text if your post has any attachments without any alt text. This is for better accessibility, and it can easily be bypassed and disabled in settings.
|
||||
|
||||
### **Pinning posts**
|
||||
|
||||
**This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in people’s profiles shows all the posts they pinned.**
|
||||
|
||||
On the Fediverse, it’s quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
|
||||
|
||||
### **Bookmarks**
|
||||
|
||||
**They allow for quickly saving posts and viewing them through the Bookmarks button on the top right of your profile.**
|
||||
|
||||
To bookmark a post, press the button between the Favorite and Share buttons on the bottom of the post. Bookmarks are saved privately, so the post authors won’t know you saved their post – the list of bookmarked posts is only visible to you.
|
||||
|
||||
## Installation
|
||||
|
||||
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Moshidon will automatically notify you about new updates inside the app.**
|
||||
|
||||
To install this app on your Android device, download the [latest release from GitHub](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||
|
||||
Moshidon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Moshidon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
||||
|
||||
Moshidon is also available in [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda), compatible with all F-Droid clients. The APK provided here is the same as the one included in the Releases.
|
||||
|
||||
## Release variants
|
||||
|
||||
### Stable variant
|
||||
|
||||
All stable version downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
||||
|
||||
**`moshidon.apk`**
|
||||
|
||||
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
|
||||
|
||||
### Nightly variant
|
||||
|
||||
All nightly builds can be downloaded at [Nightly Releases](https://github.com/LucasGGamerM/moshidon-nightly/releases) page.
|
||||
|
||||
**`moshidon-nightly.apk`**
|
||||
|
||||
Unstable variant with an integrated updater. It's for development and testing purposes. If you find any bugs with it, please file a bug report at our [issues](https://github.com/LucasGGamerM/moshidon/issues) page.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Detailed changes
|
||||
|
||||
### Features
|
||||
|
||||
* [Adding the ability to view other server's local timelines](https://github.com/LucasGGamerM/moshidon/tree/feature/local-timelines)
|
||||
* [Adding the ability to load followers and following from remote instance](https://github.com/LucasGGamerM/moshidon/tree/feature/remote-followers)
|
||||
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
|
||||
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
|
||||
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
|
||||
* Adding a useful private profile note box
|
||||
* Auto hiding the compose button on scroll
|
||||
* Adding the ability to remind yourself to add alt text to images
|
||||
* An indicator for if an image has alt text or not
|
||||
* Adding the ability to have drafts
|
||||
* Also adding the ability to view announcements from your instance
|
||||
* Adding the ability to post for local timeline only (Only on instances that support it!)
|
||||
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
||||
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
||||
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
||||
* [Implement a bookmark button and list](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/bookmarks) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/22))
|
||||
* [Add “Check for update” button in addition to integrated update checker](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/check-for-update-button)
|
||||
* [Add “Mark media as sensitive” option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/mark-media-as-sensitive)
|
||||
* [Add settings to hide replies and reposts from the timeline](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/filter-home-timeline) ([Pull request](https://github.com/mastodon/mastodon-android/pull/317))
|
||||
* [Follow and unfollow hashtags](https://github.com/sk22/megalodon/commit/7d38f031f197aa6cefaf53e39d929538689c1e4e) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/233))
|
||||
* [Notification bell for posts](https://github.com/sk22/megalodon/commit/b166ca705eb9169025ef32bbe6315b42491b57ea) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/81))
|
||||
* [Viewing lists and adding/removing users from lists](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:list-timeline-views) based on [@obstsalatschuessel](https://github.com/obstsalatschuessel)'s [Pull request](https://github.com/mastodon/mastodon-android/pull/286)
|
||||
* [List favorited posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/favs-list)
|
||||
* [Accept/reject follow requests](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/follow-requests)
|
||||
* [Display content warning title above text](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
|
||||
* [Add notifications tab for posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/posts-notifications-tab)
|
||||
* [Show visibility of original post when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-reply-visibility)
|
||||
* [Clickable reply/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
|
||||
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
|
||||
|
||||
|
||||
### Behavior
|
||||
|
||||
* Allow for confirmation before reblogging
|
||||
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
|
||||
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
||||
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
||||
* [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
|
||||
* [Set spoiler height independently to content height](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:spoiler-height-independent) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/166))
|
||||
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
|
||||
* [Option to always reveal content warnings](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/cw-above-text)
|
||||
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
|
||||
|
||||
|
||||
### Visual
|
||||
|
||||
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
|
||||
* [Improvements to the true black mode](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:true-black-improvements)
|
||||
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)
|
||||
|
||||
|
||||
## Building
|
||||
## Building & Contributing
|
||||
|
||||
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:
|
||||
|
||||
@@ -191,14 +97,15 @@ As this app is using Java 17 features, you need JDK 17 or newer to build it. Oth
|
||||
|
||||
This project is released under the [GPL-3 License](./LICENSE).
|
||||
|
||||
## Links
|
||||
## Contact & Support
|
||||
|
||||
[F.A.Q](FAQ.md)
|
||||
**<a rel="me" href="https://floss.social/@moshidon">@moshidon@floss.social</a>**
|
||||
|
||||
[Official matrix chatroom:](https://matrix.to/#/#moshidon:floss.social) https://matrix.to/#/#moshidon:floss.social
|
||||
[Official Matrix Chatroom](https://matrix.to/#/#moshidon:floss.social)
|
||||
|
||||
[Moshidon roadmap](https://github.com/users/LucasGGamerM/projects/1)
|
||||
[F.A.Q.](FAQ.md)
|
||||
|
||||
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
|
||||
[Moshidon's CSAE policy](CSAE-POLICY.md)
|
||||
|
||||
[Moshidon Roadmap](https://github.com/users/LucasGGamerM/projects/1)
|
||||
|
||||
---
|
||||
|
||||
24
build.gradle
24
build.gradle
@@ -1,23 +1,3 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://www.jitpack.io"
|
||||
content {
|
||||
includeModule 'com.github.UnifiedPush', 'android-connector'
|
||||
}
|
||||
}
|
||||
mavenLocal()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.0.0'
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
plugins {
|
||||
id("com.android.application") version "8.7.2" apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -17,7 +17,5 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=false
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=false
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.configuration-cache=true
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,7 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||
distributionSha256Sum=57dafb5c2622c6cc08b993c85b7c06956a2f53536432a30ead46166dbca0f1e9
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
BIN
img/CSAE-POLICY/step1.png
Normal file
BIN
img/CSAE-POLICY/step1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
BIN
img/CSAE-POLICY/step2.png
Normal file
BIN
img/CSAE-POLICY/step2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
BIN
img/CSAE-POLICY/step3.png
Normal file
BIN
img/CSAE-POLICY/step3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
BIN
img/CSAE-POLICY/step4.png
Normal file
BIN
img/CSAE-POLICY/step4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -16,12 +16,19 @@ android {
|
||||
applicationId "org.joinmastodon.android.moshinda"
|
||||
minSdk 23
|
||||
targetSdk 34
|
||||
versionCode 106
|
||||
versionName "2.3.0+fork.106.moshinda"
|
||||
versionCode 109
|
||||
versionName "2.3.0+fork.109.moshinda"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
// Disables dependency metadata when building APKs.
|
||||
includeInApk = false
|
||||
// Disables dependency metadata when building Android App Bundles.
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
nightly{
|
||||
storeFile = file("keystore/nightly_keystore.jks")
|
||||
@@ -108,7 +115,9 @@ android {
|
||||
}
|
||||
fdroidRelease {
|
||||
initWith release
|
||||
versionNameSuffix '-fdroid'
|
||||
vcsInfo.include true
|
||||
// The F-droid build system doesn't like this at all for some reason.
|
||||
// versionNameSuffix '-fdroid'
|
||||
// signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
@@ -155,7 +164,7 @@ dependencies {
|
||||
implementation 'com.github.bottom-software-foundation:bottom-java:2.1.0'
|
||||
annotationProcessor 'org.parceler:parceler:1.1.12'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
implementation 'com.github.UnifiedPush:android-connector:2.1.1'
|
||||
implementation 'org.unifiedpush.android:connector:3.0.7'
|
||||
|
||||
androidTestImplementation 'androidx.test:core:1.5.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
|
||||
3
mastodon/proguard-rules.pro
vendored
3
mastodon/proguard-rules.pro
vendored
@@ -33,6 +33,9 @@
|
||||
# i don't know how proguard works
|
||||
-keep class org.joinmastodon.android.** { *; }
|
||||
|
||||
# i still don't know how proguard works
|
||||
-dontwarn android.app.BroadcastOptions
|
||||
|
||||
# Keep all enums for debugging purposes
|
||||
-keepnames public enum * {
|
||||
*;
|
||||
|
||||
@@ -211,7 +211,13 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
||||
if(state==UpdateState.DOWNLOADING)
|
||||
throw new IllegalStateException();
|
||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), Context.RECEIVER_EXPORTED);
|
||||
}else{
|
||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
}
|
||||
|
||||
downloadID=dm.enqueue(
|
||||
new DownloadManager.Request(Uri.parse(getPrefs().getString("apkURL", null)))
|
||||
.setDestinationUri(Uri.fromFile(getUpdateApkFile()))
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||
|
||||
@@ -17,8 +17,7 @@ brainsoap.net
|
||||
breastmilk.club
|
||||
brighteon.social
|
||||
cachapa.xyz
|
||||
canary.fedinuke.example.com
|
||||
catgirl.life
|
||||
caekis.love
|
||||
cawfee.club
|
||||
childlove.su
|
||||
clew.lol
|
||||
@@ -34,6 +33,7 @@ decayable.ink
|
||||
dembased.xyz
|
||||
detroitriotcity.com
|
||||
djsumdog.com
|
||||
drinkanddrive.africa
|
||||
eientei.org
|
||||
eveningzoo.club
|
||||
fluf.club
|
||||
@@ -50,7 +50,7 @@ geofront.rocks
|
||||
gleasonator.com
|
||||
glee.li
|
||||
glindr.org
|
||||
goyim.app
|
||||
goyim.social
|
||||
h5q.net
|
||||
haeder.net
|
||||
handholding.io
|
||||
@@ -78,7 +78,6 @@ morale.ch
|
||||
mouse.services
|
||||
mugicha.club
|
||||
narrativerry.xyz
|
||||
natehiggers.online
|
||||
nationalist.social
|
||||
needs.vodka
|
||||
neenster.org
|
||||
@@ -86,14 +85,13 @@ nicecrew.digital
|
||||
nightshift.social
|
||||
nnia.space
|
||||
noagendasocial.com
|
||||
noagendasocial.nl
|
||||
noagendatube.com
|
||||
noauthority.social
|
||||
nobodyhasthe.biz
|
||||
norwoodzero.net
|
||||
nyanide.com
|
||||
onionfarms.org
|
||||
parcero.bond
|
||||
parcero.casa
|
||||
pawlicker.com
|
||||
pawoo.net
|
||||
pedo.school
|
||||
@@ -138,10 +136,10 @@ sonichu.com
|
||||
spinster.xyz
|
||||
springbo.cc
|
||||
strelizia.net
|
||||
subs4social.xyz
|
||||
taihou.website
|
||||
tastingtraffic.net
|
||||
teci.world
|
||||
theapex.social
|
||||
theblab.org
|
||||
thechimp.zone
|
||||
thenobody.club
|
||||
@@ -152,7 +150,6 @@ truthsocial.co.in
|
||||
usualsuspects.lol
|
||||
vampiremaid.cafe
|
||||
varishangout.net
|
||||
vtuberfan.social
|
||||
wolfgirl.bar
|
||||
xn--p1abe3d.xn--80asehdb
|
||||
yggdrasil.social
|
||||
|
||||
@@ -88,8 +88,13 @@ public class AudioPlayerService extends Service{
|
||||
nm=getSystemService(NotificationManager.class);
|
||||
// registerReceiver(receiver, new IntentFilter(Intent.ACTION_MEDIA_BUTTON));
|
||||
registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE));
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_STOP));
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE), RECEIVER_EXPORTED);
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_STOP), RECEIVER_EXPORTED);
|
||||
}else{
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_PLAY_PAUSE));
|
||||
registerReceiver(receiver, new IntentFilter(ACTION_STOP));
|
||||
}
|
||||
instance=this;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
}
|
||||
|
||||
private void openComposeFragment(String accountID){
|
||||
AccountSession session=AccountSessionManager.get(accountID);
|
||||
UiUtils.setUserPreferredTheme(this, session);
|
||||
getWindow().setBackgroundDrawable(null);
|
||||
|
||||
Intent intent=getIntent();
|
||||
|
||||
@@ -81,6 +81,7 @@ public class GlobalUserPreferences{
|
||||
public static boolean showPostsWithoutAlt;
|
||||
public static boolean showMediaPreview;
|
||||
public static boolean removeTrackingParams;
|
||||
public static boolean enhanceTextSize;
|
||||
|
||||
public static SharedPreferences getPrefs(){
|
||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||
@@ -162,10 +163,10 @@ public class GlobalUserPreferences{
|
||||
showPostsWithoutAlt=prefs.getBoolean("showPostsWithoutAlt", true);
|
||||
showMediaPreview=prefs.getBoolean("showMediaPreview", true);
|
||||
removeTrackingParams=prefs.getBoolean("removeTrackingParams", true);
|
||||
enhanceTextSize=prefs.getBoolean("enhanceTextSize", false);
|
||||
|
||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||
|
||||
|
||||
if (prefs.contains("prefixRepliesWithRe")) {
|
||||
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
||||
? PrefixRepliesMode.TO_OTHERS : PrefixRepliesMode.NEVER;
|
||||
@@ -237,6 +238,7 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("showPostsWithoutAlt", showPostsWithoutAlt)
|
||||
.putBoolean("showMediaPreview", showMediaPreview)
|
||||
.putBoolean("removeTrackingParams", removeTrackingParams)
|
||||
.putBoolean("enhanceTextSize", enhanceTextSize)
|
||||
|
||||
.apply();
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.Fragment;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.net.Uri;
|
||||
@@ -16,7 +18,9 @@ import android.os.BadParcelableException;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
@@ -338,4 +342,20 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
if (!GlobalUserPreferences.enhanceTextSize) {
|
||||
super.attachBaseContext(base);
|
||||
return;
|
||||
}
|
||||
|
||||
final Configuration override = new Configuration(base.getResources().getConfiguration());
|
||||
|
||||
// This is the font multiplier, which should be multiplied by, because the system settings also play a role here
|
||||
override.fontScale *= 1.15f;
|
||||
final Context newBase = base.createConfigurationContext(override);
|
||||
|
||||
super.attachBaseContext(newBase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||
import org.joinmastodon.android.utils.UnifiedPushHelper;
|
||||
|
||||
import me.grishka.appkit.imageloader.ImageCache;
|
||||
import me.grishka.appkit.utils.NetworkUtils;
|
||||
@@ -27,7 +28,11 @@ public class MastodonApp extends Application{
|
||||
ImageCache.setParams(params);
|
||||
NetworkUtils.setUserAgent("MoshidonAndroid/"+BuildConfig.VERSION_NAME);
|
||||
|
||||
PushSubscriptionManager.tryRegisterFCM();
|
||||
if (UnifiedPushHelper.isUnifiedPushEnabled(this)){
|
||||
UnifiedPushHelper.registerAllAccounts(this);
|
||||
} else {
|
||||
PushSubscriptionManager.tryRegisterFCM();
|
||||
}
|
||||
GlobalUserPreferences.load();
|
||||
if(BuildConfig.DEBUG){
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
|
||||
@@ -163,7 +163,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
PushNotificationReceiver.this.notify(context, PushNotification.fromNotification(context, account, notification), account.getID(), notification);
|
||||
}
|
||||
|
||||
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
|
||||
void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
|
||||
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
||||
AccountSession session=AccountSessionManager.get(accountID);
|
||||
Account self=session.self;
|
||||
|
||||
@@ -5,14 +5,22 @@ import android.util.Log;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joinmastodon.android.api.MastodonAPIController;
|
||||
import org.joinmastodon.android.api.requests.notifications.GetNotificationByID;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.PushNotification;
|
||||
import org.unifiedpush.android.connector.FailedReason;
|
||||
import org.unifiedpush.android.connector.MessagingReceiver;
|
||||
import org.unifiedpush.android.connector.data.PublicKeySet;
|
||||
import org.unifiedpush.android.connector.data.PushEndpoint;
|
||||
import org.unifiedpush.android.connector.data.PushMessage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import kotlin.text.Charsets;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
|
||||
@@ -24,16 +32,23 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewEndpoint(@NotNull Context context, @NotNull String endpoint, @NotNull String instance) {
|
||||
public void onNewEndpoint(@NotNull Context context, @NotNull PushEndpoint endpoint, @NotNull String instance) {
|
||||
// Called when a new endpoint be used for sending push messages
|
||||
Log.d(TAG, "onNewEndpoint: New Endpoint " + endpoint + " for "+ instance);
|
||||
Log.d(TAG, "onNewEndpoint: New Endpoint " + endpoint.getUrl() + " for "+ instance);
|
||||
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||
if (account != null)
|
||||
account.getPushSubscriptionManager().registerAccountForPush(null, endpoint);
|
||||
if (account != null) {
|
||||
PublicKeySet ks = endpoint.getPubKeySet();
|
||||
if (ks != null){
|
||||
account.getPushSubscriptionManager().registerAccountForPush(account.pushSubscription, true, endpoint.getUrl(), ks.getPubKey(), ks.getAuth());
|
||||
} else {
|
||||
// ks should never be null on new endpoint
|
||||
account.getPushSubscriptionManager().registerAccountForPush(account.pushSubscription, endpoint.getUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistrationFailed(@NotNull Context context, @NotNull String instance) {
|
||||
public void onRegistrationFailed(@NotNull Context context, @NotNull FailedReason reason, @NotNull String instance) {
|
||||
// called when the registration is not possible, eg. no network
|
||||
Log.d(TAG, "onRegistrationFailed: " + instance);
|
||||
//re-register for gcm
|
||||
@@ -53,26 +68,46 @@ public class UnifiedPushNotificationReceiver extends MessagingReceiver{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(@NotNull Context context, @NotNull byte[] message, @NotNull String instance) {
|
||||
public void onMessage(@NotNull Context context, @NotNull PushMessage message, @NotNull String instance) {
|
||||
Log.d(TAG, "New message for " + instance);
|
||||
// Called when a new message is received. The message contains the full POST body of the push message
|
||||
AccountSession account = AccountSessionManager.getInstance().tryGetAccount(instance);
|
||||
|
||||
if (account == null)
|
||||
return;
|
||||
|
||||
//this is stupid
|
||||
// Mastodon stores the info to decrypt the message in the HTTP headers, which are not accessible in UnifiedPush,
|
||||
// thus it is not possible to decrypt them. SO we need to re-request them from the server and transform them later on
|
||||
// The official uses fcm and moves the headers to extra data, see
|
||||
// https://github.com/mastodon/webpush-fcm-relay/blob/cac95b28d5364b0204f629283141ac3fb749e0c5/webpush-fcm-relay.go#L116
|
||||
// https://github.com/tuskyapp/Tusky/pull/2303#issue-1112080540
|
||||
if (message.getDecrypted()) {
|
||||
// If the mastodon server supports the standard webpush, we can directly use the content
|
||||
Log.d(TAG, "Push message correctly decrypted");
|
||||
PushNotification pn = MastodonAPIController.gson.fromJson(new String(message.getContent(), Charsets.UTF_8), PushNotification.class);
|
||||
new GetNotificationByID(pn.notificationId)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(org.joinmastodon.android.model.Notification result){
|
||||
MastodonAPIController.runInBackground(()->new PushNotificationReceiver().notify(context, pn, instance, result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
MastodonAPIController.runInBackground(()-> new PushNotificationReceiver().notify(context, pn, instance, null));
|
||||
}
|
||||
})
|
||||
.exec(instance);
|
||||
} else {
|
||||
// else, we have to sync with the server
|
||||
Log.d(TAG, "Server doesn't support standard webpush, fetching one notification");
|
||||
fetchOneNotification(context, account, (notif) -> () -> new PushNotificationReceiver().notifyUnifiedPush(context, account, notif));
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchOneNotification(@NotNull Context context, @NotNull AccountSession account, @NotNull Function<Notification, Runnable> callback) {
|
||||
account.getCacheController().getNotifications(null, 1, false, false, true, new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||
result.items
|
||||
.stream()
|
||||
.findFirst()
|
||||
.ifPresent(value->MastodonAPIController.runInBackground(()->new PushNotificationReceiver().notifyUnifiedPush(context, account, value)));
|
||||
.ifPresent(value->MastodonAPIController.runInBackground(callback.apply(value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,6 +37,8 @@ import okhttp3.Response;
|
||||
public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
private static final String TAG="MastodonAPIRequest";
|
||||
|
||||
private static MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null);
|
||||
|
||||
private String domain;
|
||||
private AccountSession account;
|
||||
private String path;
|
||||
@@ -95,14 +97,14 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
|
||||
public MastodonAPIRequest<T> execNoAuth(String domain){
|
||||
this.domain=domain;
|
||||
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
|
||||
unauthenticatedApiController.submitRequest(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> exec(String domain, Token token){
|
||||
this.domain=domain;
|
||||
this.token=token;
|
||||
AccountSessionManager.getInstance().getUnauthenticatedApiController().submitRequest(this);
|
||||
unauthenticatedApiController.submitRequest(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -166,12 +166,23 @@ public class PushSubscriptionManager{
|
||||
|
||||
//work-around for adding the randomAccountId
|
||||
String newEndpoint = endpoint;
|
||||
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
|
||||
newEndpoint += pushAccountID;
|
||||
Boolean standard = true;
|
||||
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/")){
|
||||
newEndpoint+=pushAccountID;
|
||||
standard = false;
|
||||
}
|
||||
|
||||
new RegisterForPushNotifications(newEndpoint,
|
||||
encodedPublicKey,
|
||||
encodedAuthKey,
|
||||
registerAccountForPush(subscription, standard, newEndpoint, encodedPublicKey, encodedAuthKey);
|
||||
});
|
||||
}
|
||||
|
||||
public void registerAccountForPush(PushSubscription subscription, Boolean standard, String endpoint, String p256dh, String auth){
|
||||
MastodonAPIController.runInBackground(()->{
|
||||
Log.d(TAG, "registerAccountForPush: started for "+accountID);
|
||||
new RegisterForPushNotifications(endpoint,
|
||||
standard,
|
||||
p256dh,
|
||||
auth,
|
||||
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,
|
||||
subscription==null ? PushSubscription.Policy.ALL : subscription.policy)
|
||||
.setCallback(new Callback<>(){
|
||||
|
||||
@@ -8,14 +8,23 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
|
||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||
import org.joinmastodon.android.events.ReblogDeletedEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiCategory;
|
||||
import org.joinmastodon.android.model.EmojiReaction;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -42,6 +51,9 @@ public class StatusInteractionController{
|
||||
if(!Looper.getMainLooper().isCurrentThread())
|
||||
throw new IllegalStateException("Can only be called from main thread");
|
||||
|
||||
AccountSession session=AccountSessionManager.get(accountID);
|
||||
Instance instance=session.getInstance().get();
|
||||
|
||||
SetStatusFavorited current=runningFavoriteRequests.remove(status.id);
|
||||
if(current!=null){
|
||||
current.cancel();
|
||||
@@ -54,6 +66,7 @@ public class StatusInteractionController{
|
||||
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
||||
cb.accept(result);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||
if(instance.isIceshrimpJs()) E.post(new EmojiReactionsUpdatedEvent(status.id, result.reactions, false, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,12 +76,58 @@ public class StatusInteractionController{
|
||||
status.favourited=!favorited;
|
||||
cb.accept(status);
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
if(instance.isIceshrimpJs()) E.post(new EmojiReactionsUpdatedEvent(status.id, status.reactions, false, null));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
runningFavoriteRequests.put(status.id, req);
|
||||
status.favourited=favorited;
|
||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||
|
||||
if(instance.configuration==null || instance.configuration.reactions==null)
|
||||
return;
|
||||
|
||||
String defaultReactionEmojiRaw=instance.configuration.reactions.defaultReaction;
|
||||
if(!instance.isIceshrimpJs() || defaultReactionEmojiRaw==null)
|
||||
return;
|
||||
|
||||
boolean reactionIsCustom=defaultReactionEmojiRaw.startsWith(":");
|
||||
String defaultReactionEmoji=reactionIsCustom ? defaultReactionEmojiRaw.substring(1, defaultReactionEmojiRaw.length()-1) : defaultReactionEmojiRaw;
|
||||
ArrayList<EmojiReaction> reactions=new ArrayList<>(status.reactions.size());
|
||||
for(EmojiReaction reaction:status.reactions){
|
||||
reactions.add(reaction.copy());
|
||||
}
|
||||
Optional<EmojiReaction> existingReaction=reactions.stream().filter(r->r.me).findFirst();
|
||||
Optional<EmojiReaction> existingDefaultReaction=reactions.stream().filter(r->r.name.equals(defaultReactionEmoji)).findFirst();
|
||||
if(existingReaction.isPresent() && !favorited){
|
||||
existingReaction.get().me=false;
|
||||
existingReaction.get().count--;
|
||||
existingReaction.get().pendingChange=true;
|
||||
}else if(existingDefaultReaction.isPresent() && favorited){
|
||||
existingDefaultReaction.get().count++;
|
||||
existingDefaultReaction.get().me=true;
|
||||
existingDefaultReaction.get().pendingChange=true;
|
||||
}else if(favorited){
|
||||
EmojiReaction reaction=null;
|
||||
if(reactionIsCustom){
|
||||
List<EmojiCategory> customEmojis=AccountSessionManager.getInstance().getCustomEmojis(session.domain);
|
||||
for(EmojiCategory category:customEmojis){
|
||||
for(Emoji emoji:category.emojis){
|
||||
if(emoji.shortcode.equals(defaultReactionEmoji)){
|
||||
reaction=EmojiReaction.of(emoji, session.self);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(reaction==null)
|
||||
reaction=EmojiReaction.of(defaultReactionEmoji, session.self);
|
||||
}else{
|
||||
reaction=EmojiReaction.of(defaultReactionEmoji, session.self);
|
||||
}
|
||||
reaction.pendingChange=true;
|
||||
reactions.add(reaction);
|
||||
}
|
||||
E.post(new EmojiReactionsUpdatedEvent(status.id, reactions, false, null));
|
||||
}
|
||||
|
||||
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
||||
|
||||
@@ -4,10 +4,11 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
|
||||
public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscription>{
|
||||
public RegisterForPushNotifications(String endpoint, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||
public RegisterForPushNotifications(String endpoint, Boolean standard, String encryptionKey, String authKey, PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||
super(HttpMethod.POST, "/push/subscription", PushSubscription.class);
|
||||
Request r=new Request();
|
||||
r.subscription.endpoint=endpoint;
|
||||
r.subscription.standard = standard;
|
||||
r.data.alerts=alerts;
|
||||
r.policy=policy;
|
||||
r.subscription.keys.p256dh=encryptionKey;
|
||||
@@ -27,6 +28,8 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
||||
|
||||
private static class Subscription{
|
||||
public String endpoint;
|
||||
// Use standard push notifications if available
|
||||
public Boolean standard;
|
||||
public Keys keys=new Keys();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.joinmastodon.android.model.AkkomaTranslation;
|
||||
|
||||
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
|
||||
public AkkomaTranslateStatus(String id, String lang){
|
||||
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
|
||||
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toLowerCase(), AkkomaTranslation.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.joinmastodon.android.model.ContentType;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.PushSubscription;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
@@ -23,6 +24,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class AccountLocalPreferences{
|
||||
private final SharedPreferences prefs;
|
||||
@@ -72,19 +74,20 @@ public class AccountLocalPreferences{
|
||||
// preReplySheet=prefs.getBoolean("preReplySheet", false);
|
||||
|
||||
// MEGALODON
|
||||
Optional<Instance> instance=session.getInstance();
|
||||
showReplies=prefs.getBoolean("showReplies", true);
|
||||
showBoosts=prefs.getBoolean("showBoosts", true);
|
||||
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new ArrayList<>());
|
||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", ContentType.PLAIN.name()));
|
||||
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", true);
|
||||
defaultContentType=enumValue(ContentType.class, prefs.getString("defaultContentType", instance.map(Instance::isIceshrimp).orElse(false) ? ContentType.MISSKEY_MARKDOWN.name() : ContentType.PLAIN.name()));
|
||||
contentTypesEnabled=prefs.getBoolean("contentTypesEnabled", instance.map(i->!i.isIceshrimp()).orElse(false));
|
||||
timelines=fromJson(prefs.getString("timelines", null), timelinesType, TimelineDefinition.getDefaultTimelines(session.getID()));
|
||||
localOnlySupported=prefs.getBoolean("localOnlySupported", false);
|
||||
glitchInstance=prefs.getBoolean("glitchInstance", false);
|
||||
publishButtonText=prefs.getString("publishButtonText", null);
|
||||
timelineReplyVisibility=prefs.getString("timelineReplyVisibility", null);
|
||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
|
||||
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", instance.map(i->i.isAkkoma() || i.isIceshrimp()).orElse(false));
|
||||
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
||||
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
|
||||
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.joinmastodon.android.model.EmojiCategory;
|
||||
import org.joinmastodon.android.model.LegacyFilter;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Token;
|
||||
import org.joinmastodon.android.utils.UnifiedPushHelper;
|
||||
import org.unifiedpush.android.connector.UnifiedPush;
|
||||
|
||||
import java.io.File;
|
||||
@@ -70,7 +71,6 @@ public class AccountSessionManager{
|
||||
private HashMap<String, List<EmojiCategory>> customEmojis=new HashMap<>();
|
||||
private HashMap<String, Long> instancesLastUpdated=new HashMap<>();
|
||||
private HashMap<String, Instance> instances=new HashMap<>();
|
||||
private MastodonAPIController unauthenticatedApiController=new MastodonAPIController(null);
|
||||
private Instance authenticatingInstance;
|
||||
private Application authenticatingApp;
|
||||
private String lastActiveAccountID;
|
||||
@@ -109,7 +109,7 @@ public class AccountSessionManager{
|
||||
Log.e(TAG, "Error loading accounts", x);
|
||||
}
|
||||
lastActiveAccountID=prefs.getString("lastActiveAccount", null);
|
||||
MastodonAPIController.runInBackground(()->readInstanceInfo(domains));
|
||||
readInstanceInfo(domains);
|
||||
maybeUpdateShortcuts();
|
||||
}
|
||||
|
||||
@@ -127,12 +127,12 @@ public class AccountSessionManager{
|
||||
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
|
||||
|
||||
updateMoreInstanceInfo(instance, instance.uri);
|
||||
if (!UnifiedPush.getDistributor(context).isEmpty()) {
|
||||
UnifiedPush.registerApp(
|
||||
if (UnifiedPushHelper.isUnifiedPushEnabled(context)) {
|
||||
UnifiedPush.register(
|
||||
context,
|
||||
session.getID(),
|
||||
new ArrayList<>(),
|
||||
context.getPackageName()
|
||||
null,
|
||||
session.app.vapidKey.replaceAll("=","")
|
||||
);
|
||||
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||
@@ -247,11 +247,6 @@ public class AccountSessionManager{
|
||||
maybeUpdateShortcuts();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public MastodonAPIController getUnauthenticatedApiController(){
|
||||
return unauthenticatedApiController;
|
||||
}
|
||||
|
||||
public void authenticate(Activity activity, Instance instance){
|
||||
authenticatingInstance=instance;
|
||||
new CreateOAuthApp()
|
||||
|
||||
@@ -68,14 +68,14 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
||||
instanceUser.url = "https://"+session.domain+"/about";
|
||||
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
|
||||
instanceUser.emojis = List.of();
|
||||
Status fakeStatus = a.toStatus();
|
||||
Status fakeStatus = a.toStatus(isInstanceIceshrimp());
|
||||
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
|
||||
textItem.textSelectable = true;
|
||||
|
||||
List<StatusDisplayItem> items=new ArrayList<>();
|
||||
items.add(HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead));
|
||||
items.add(textItem);
|
||||
if(!isInstanceAkkoma()) items.add(new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true));
|
||||
if(!isInstanceAkkoma() && !isInstanceIceshrimp()) items.add(new EmojiReactionsStatusDisplayItem(a.id, this, fakeStatus, accountID, false, true));
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
@@ -709,26 +710,17 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
|
||||
public void updateStatusWithQuote(DisplayItemsParent parent) {
|
||||
int startIndex=-1;
|
||||
int endIndex=-1;
|
||||
for(int i=0; i<displayItems.size(); i++){
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
if(item.parentID.equals(parent.getID())) {
|
||||
startIndex= startIndex==-1 ? i : startIndex;
|
||||
endIndex=i;
|
||||
}
|
||||
}
|
||||
|
||||
if (startIndex==-1 || endIndex==-1)
|
||||
Pair<Integer, Integer> items=findAllItemsOfParent(parent);
|
||||
if (items==null)
|
||||
return;
|
||||
|
||||
// Only StatusListFragments/NotificationsListFragments can display status with quotes
|
||||
assert (this instanceof StatusListFragment) || (this instanceof NotificationsListFragment);
|
||||
List<StatusDisplayItem> oldItems = displayItems.subList(startIndex, endIndex+1);
|
||||
List<StatusDisplayItem> oldItems = displayItems.subList(items.first, items.second+1);
|
||||
List<StatusDisplayItem> newItems=this.buildDisplayItems((T) parent);
|
||||
int prevSize=oldItems.size();
|
||||
oldItems.clear();
|
||||
displayItems.addAll(startIndex, newItems);
|
||||
displayItems.addAll(items.first, newItems);
|
||||
|
||||
// Update the cache
|
||||
final CacheController cache=AccountSessionManager.get(accountID).getCacheController();
|
||||
@@ -738,8 +730,19 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
cache.updateNotification((Notification) parent);
|
||||
}
|
||||
|
||||
adapter.notifyItemRangeRemoved(startIndex, prevSize);
|
||||
adapter.notifyItemRangeInserted(startIndex, newItems.size());
|
||||
adapter.notifyItemRangeRemoved(items.first, prevSize);
|
||||
adapter.notifyItemRangeInserted(items.first, newItems.size());
|
||||
}
|
||||
|
||||
public void removeStatus(DisplayItemsParent parent) {
|
||||
Pair<Integer, Integer> items=findAllItemsOfParent(parent);
|
||||
if (items==null)
|
||||
return;
|
||||
|
||||
List<StatusDisplayItem> statusDisplayItems = displayItems.subList(items.first, items.second+1);
|
||||
int prevSize=statusDisplayItems.size();
|
||||
statusDisplayItems.clear();
|
||||
adapter.notifyItemRangeRemoved(items.first, prevSize);
|
||||
}
|
||||
|
||||
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
|
||||
@@ -815,6 +818,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
|
||||
}
|
||||
List<HeaderStatusDisplayItem.Holder> headers=findAllHoldersOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||
if (headers.isEmpty())
|
||||
return;
|
||||
HeaderStatusDisplayItem.Holder header=headers.size() > 1 && isForQuote ? headers.get(1) : headers.get(0);
|
||||
if(header!=null) header.animateExpandToggle();
|
||||
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
|
||||
@@ -833,6 +838,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
list.invalidateItemDecorations();
|
||||
}
|
||||
|
||||
public void onFavoriteChanged(Status status, String itemID) {
|
||||
FooterStatusDisplayItem.Holder footer=findHolderOfType(itemID, FooterStatusDisplayItem.Holder.class);
|
||||
if(footer!=null){
|
||||
footer.getItem().status=status;
|
||||
footer.onFavoriteClick();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID(){
|
||||
return accountID;
|
||||
@@ -943,6 +956,23 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Pair<Integer, Integer> findAllItemsOfParent(DisplayItemsParent parent){
|
||||
int startIndex=-1;
|
||||
int endIndex=-1;
|
||||
for(int i=0; i<displayItems.size(); i++){
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
if(item.parentID.equals(parent.getID())) {
|
||||
startIndex= startIndex==-1 ? i : startIndex;
|
||||
endIndex=i;
|
||||
}
|
||||
}
|
||||
|
||||
if(startIndex==-1 || endIndex==-1)
|
||||
return null;
|
||||
return Pair.create(startIndex, endIndex);
|
||||
}
|
||||
|
||||
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> List<H> findAllHoldersOfType(String id, Class<H> type){
|
||||
ArrayList<H> holders=new ArrayList<>();
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
|
||||
@@ -787,7 +787,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
||||
if(!status.account.id.equals(ownID))
|
||||
mentions.add('@'+status.account.acct);
|
||||
if(status.rebloggedBy != null && GlobalUserPreferences.mentionRebloggerAutomatically)
|
||||
if(GlobalUserPreferences.mentionRebloggerAutomatically && status.rebloggedBy != null && !status.rebloggedBy.id.equals(ownID))
|
||||
mentions.add('@'+status.rebloggedBy.acct);
|
||||
for(Mention mention:status.mentions){
|
||||
if(mention.id.equals(ownID))
|
||||
@@ -927,6 +927,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if(instance.isIceshrimpJs())
|
||||
languageButton.setVisibility(View.GONE); // hide language selector on Iceshrimp-JS because the feature is not supported
|
||||
|
||||
if (!GlobalUserPreferences.relocatePublishButton)
|
||||
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
|
||||
|
||||
@@ -1052,12 +1055,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
public void updatePublishButtonState(){
|
||||
uuid=null;
|
||||
if(GlobalUserPreferences.relocatePublishButton && publishButtonRelocated != null){
|
||||
publishButtonRelocated.setEnabled((!isInstancePixelfed() || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
|
||||
publishButtonRelocated.setEnabled(((!isInstancePixelfed() || replyTo != null) || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
|
||||
}
|
||||
|
||||
if(publishButton==null)
|
||||
return;
|
||||
publishButton.setEnabled((!isInstancePixelfed() || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
|
||||
publishButton.setEnabled(((!isInstancePixelfed() || replyTo != null) || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
|
||||
}
|
||||
|
||||
private void onCustomEmojiClick(Emoji emoji){
|
||||
|
||||
@@ -297,8 +297,8 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
||||
cover.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-2, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
name.setText(name.getText());
|
||||
bio.setText(bio.getText());
|
||||
}
|
||||
if(image instanceof Animatable a && !a.isRunning())
|
||||
a.start();
|
||||
|
||||
@@ -22,6 +22,14 @@ public interface HasAccountID {
|
||||
return getInstance().map(Instance::isPixelfed).orElse(false);
|
||||
}
|
||||
|
||||
default boolean isInstanceIceshrimp() {
|
||||
return getInstance().map(Instance::isIceshrimp).orElse(false);
|
||||
}
|
||||
|
||||
default boolean isInstanceIceshrimpJs() {
|
||||
return getInstance().map(Instance::isIceshrimpJs).orElse(false);
|
||||
}
|
||||
|
||||
default Optional<Instance> getInstance() {
|
||||
return getSession().getInstance();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@@ -32,21 +31,21 @@ import org.joinmastodon.android.model.FilterAction;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.FilterKeyword;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.sheets.MuteHashtagConfirmationSheet;
|
||||
import org.joinmastodon.android.ui.text.SpacerSpan;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.time.Duration;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -105,15 +104,40 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
||||
muteMenuItem.setIcon(newMute ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
||||
}
|
||||
|
||||
private void showMuteDialog(boolean mute) {
|
||||
UiUtils.showConfirmationAlert(getContext(),
|
||||
mute ? R.string.mo_unmute_hashtag : R.string.mo_mute_hashtag,
|
||||
mute ? R.string.mo_confirm_to_unmute_hashtag : R.string.mo_confirm_to_mute_hashtag,
|
||||
mute ? R.string.do_unmute : R.string.do_mute,
|
||||
mute ? R.drawable.ic_fluent_speaker_2_28_regular : R.drawable.ic_fluent_speaker_off_28_regular,
|
||||
mute ? this::unmuteHashtag : this::muteHashtag
|
||||
);
|
||||
private void updateFollowState(boolean following) {
|
||||
followMenuItem.setTitle(getString(following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
|
||||
followMenuItem.setIcon(following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||
}
|
||||
|
||||
private void showMuteDialog(boolean currentlyMuted) {
|
||||
if (currentlyMuted) {
|
||||
unmuteHashtag();
|
||||
return;
|
||||
}
|
||||
|
||||
//pass a references, so they can be changed inside the confirmation sheet
|
||||
AtomicReference<Duration> muteDuration=new AtomicReference<>(Duration.ZERO);
|
||||
new MuteHashtagConfirmationSheet(getContext(), null, muteDuration, hashtag, (onSuccess, onError)->{
|
||||
FilterKeyword hashtagFilter=new FilterKeyword();
|
||||
hashtagFilter.wholeWord=true;
|
||||
hashtagFilter.keyword="#"+hashtagName;
|
||||
new CreateFilter("#"+hashtagName, EnumSet.of(FilterContext.HOME), FilterAction.HIDE, (int) muteDuration.get().getSeconds(), List.of(hashtagFilter)).setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Filter result){
|
||||
filter=Optional.of(result);
|
||||
updateMuteState(true);
|
||||
onSuccess.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getContext());
|
||||
onError.run();
|
||||
}
|
||||
}).exec(accountID);
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void unmuteHashtag() {
|
||||
//safe to get, this only called if filter is present
|
||||
new DeleteFilter(filter.get().id).setCallback(new Callback<>(){
|
||||
@@ -121,6 +145,9 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
||||
public void onSuccess(Void result){
|
||||
filter=Optional.empty();
|
||||
updateMuteState(false);
|
||||
new Snackbar.Builder(getContext())
|
||||
.setText(getContext().getString(R.string.unmuted_user_x, '#'+hashtagName))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,26 +157,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
private void muteHashtag() {
|
||||
FilterKeyword hashtagFilter=new FilterKeyword();
|
||||
hashtagFilter.wholeWord=true;
|
||||
hashtagFilter.keyword="#"+hashtagName;
|
||||
new CreateFilter("#"+hashtagName, EnumSet.of(FilterContext.HOME), FilterAction.HIDE, 0 , List.of(hashtagFilter)).setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Filter result){
|
||||
filter=Optional.of(result);
|
||||
updateMuteState(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(getContext());
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected TimelineDefinition makeTimelineDefinition() {
|
||||
return TimelineDefinition.ofHashtag(hashtagName);
|
||||
@@ -292,6 +299,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
||||
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
|
||||
pinMenuItem=optionsMenu.findItem(R.id.pin);
|
||||
followMenuItem.setVisible(toolbarContentVisible);
|
||||
updateFollowState(hashtag!=null && hashtag.following);
|
||||
// pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||
super.updatePinButton(pinMenuItem);
|
||||
|
||||
@@ -388,8 +396,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
||||
followButton.setTextVisible(true);
|
||||
followProgress.setVisibility(View.GONE);
|
||||
if(followMenuItem!=null){
|
||||
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
|
||||
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||
updateFollowState(hashtag.following);
|
||||
}
|
||||
if(muteMenuItem!=null){
|
||||
muteMenuItem.setTitle(getString(filter.isPresent() ? R.string.unmute_user : R.string.mute_user, "#" + hashtag));
|
||||
@@ -429,6 +436,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
||||
return;
|
||||
hashtag=result;
|
||||
updateHeader();
|
||||
updateFollowState(result.following);
|
||||
followRequestRunning=false;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.PaginatedResponse;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -122,7 +123,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
}
|
||||
|
||||
NotificationHeaderStatusDisplayItem titleItem;
|
||||
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
|
||||
Account self=AccountSessionManager.get(accountID).self;
|
||||
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS
|
||||
|| (n.type==Notification.Type.REBLOG && n.status != null && n.status.account != null && !n.status.account.id.equals(self.id))){ // Iceshrimp quote
|
||||
titleItem=null;
|
||||
}else{
|
||||
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
||||
@@ -316,13 +319,16 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
||||
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||
for(Notification n : data){
|
||||
if(n.status!=null && n.status.getContentStatus().id.equals(ev.id)){
|
||||
n.status.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||
for(int i=0; i<list.getChildCount(); i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==n.status.getContentStatus() && ev.viewHolder!=holder){
|
||||
reactions.rebind();
|
||||
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
|
||||
reactions.updateReactions(ev.reactions);
|
||||
}
|
||||
}
|
||||
AccountSessionManager.get(accountID).getCacheController().updateNotification(n);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(n.getID())){
|
||||
text.rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
@@ -287,11 +288,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
noteEdit.setOnFocusChangeListener((v, hasFocus)->{
|
||||
if(hasFocus){
|
||||
hideFab();
|
||||
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
|
||||
}else{
|
||||
showFab();
|
||||
savePrivateNote(noteEdit.getText().toString());
|
||||
return;
|
||||
}
|
||||
showFab();
|
||||
savePrivateNote(noteEdit.getText().toString());
|
||||
});
|
||||
|
||||
FrameLayout sizeWrapper=new FrameLayout(getActivity()){
|
||||
@@ -454,8 +454,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}
|
||||
|
||||
private void hidePrivateNote(){
|
||||
noteWrap.setVisibility(View.GONE);
|
||||
noteEdit.setText(null);
|
||||
noteWrap.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void savePrivateNote(String note){
|
||||
@@ -469,6 +469,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
public void onSuccess(Relationship result) {
|
||||
updateRelationship(result);
|
||||
invalidateOptionsMenu();
|
||||
if(!TextUtils.isEmpty(result.note))
|
||||
Toast.makeText(MastodonApp.context, R.string.mo_personal_note_saved, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -875,7 +877,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
}else if(id==R.id.open_in_browser){
|
||||
UiUtils.launchWebBrowser(getActivity(), account.url);
|
||||
}else if(id==R.id.block_domain){
|
||||
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
|
||||
UiUtils.confirmToggleBlockDomain(getActivity(), accountID, account, relationship.domainBlocking, ()->{
|
||||
relationship.domainBlocking=!relationship.domainBlocking;
|
||||
updateRelationship();
|
||||
});
|
||||
@@ -989,7 +991,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
else hidePrivateNote();
|
||||
invalidateOptionsMenu();
|
||||
actionButton.setVisibility(View.VISIBLE);
|
||||
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
|
||||
notifyButton.setVisibility(relationship.following && !isInstanceIceshrimpJs() ? View.VISIBLE : View.GONE); // always hide notify button on Iceshrimp-JS because it's unsupported on the server
|
||||
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
|
||||
actionProgress.setIndeterminateTintList(actionButton.getTextColors());
|
||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
||||
@@ -1587,8 +1589,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
public void setImage(int index, Drawable image){
|
||||
CustomEmojiSpan span=index>=item.nameEmojis.length ? item.valueEmojis[index-item.nameEmojis.length] : item.nameEmojis[index];
|
||||
span.setDrawable(image);
|
||||
title.invalidate();
|
||||
value.invalidate();
|
||||
title.setText(title.getText());
|
||||
value.setText(value.getText());
|
||||
toolbarTitleView.setText(toolbarTitleView.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
||||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, null,
|
||||
return StatusDisplayItem.buildItems(this, s.toFormattedStatus(accountID), accountID, s, knownAccounts, null,
|
||||
StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS |
|
||||
StatusDisplayItem.FLAG_NO_FOOTER |
|
||||
StatusDisplayItem.FLAG_NO_TRANSLATE);
|
||||
|
||||
@@ -327,13 +327,16 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||
public void onEmojiReactionsChanged(EmojiReactionsUpdatedEvent ev){
|
||||
for(Status s:data){
|
||||
if(s.getContentStatus().id.equals(ev.id)){
|
||||
s.getContentStatus().update(ev);
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof EmojiReactionsStatusDisplayItem.Holder reactions && reactions.getItem().status==s.getContentStatus() && ev.viewHolder!=holder){
|
||||
reactions.rebind();
|
||||
}else if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
|
||||
reactions.updateReactions(ev.reactions);
|
||||
}
|
||||
}
|
||||
AccountSessionManager.get(accountID).getCacheController().updateStatus(s);
|
||||
for(int i=0;i<list.getChildCount();i++){
|
||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||
if(holder instanceof TextStatusDisplayItem.Holder text && text.getItem().parentID.equals(s.getID())){
|
||||
text.rebind();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,8 +279,8 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
|
||||
cover.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-2, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
name.setText(name.getText());
|
||||
bio.setText(bio.getText());
|
||||
}
|
||||
if(image instanceof Animatable a && !a.isRunning())
|
||||
a.start();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.joinmastodon.android.fragments.discover;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentTransaction;
|
||||
import android.app.assist.AssistContent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -31,6 +32,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.fragments.AppKitFragment;
|
||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||
@@ -60,6 +64,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
private String currentQuery;
|
||||
|
||||
private boolean disableDiscover;
|
||||
private boolean isIceshrimp;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -78,13 +83,17 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tabLayout=view.findViewById(R.id.tabbar);
|
||||
pager=view.findViewById(R.id.pager);
|
||||
|
||||
tabViews=new FrameLayout[4];
|
||||
Optional<Instance> instance=AccountSessionManager.get(accountID).getInstance();
|
||||
disableDiscover=instance.map(Instance::isAkkoma).orElse(false);
|
||||
isIceshrimp=instance.map(Instance::isIceshrimp).orElse(false);
|
||||
|
||||
tabViews=new FrameLayout[isIceshrimp ? 3 : 4]; // reduce array size on Iceshrimp to hide news feed because it's unsupported and always returns an empty list
|
||||
for(int i=0;i<tabViews.length;i++){
|
||||
FrameLayout tabView=new FrameLayout(getActivity());
|
||||
tabView.setId(switch(i){
|
||||
case 0 -> R.id.discover_posts;
|
||||
case 1 -> R.id.discover_hashtags;
|
||||
case 2 -> R.id.discover_news;
|
||||
case 2 -> isIceshrimp ? R.id.discover_users : R.id.discover_news; // skip unsupported news discovery on Iceshrimp
|
||||
case 3 -> R.id.discover_users;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||
});
|
||||
@@ -126,12 +135,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
accountsFragment=new DiscoverAccountsFragment();
|
||||
accountsFragment.setArguments(args);
|
||||
|
||||
getChildFragmentManager().beginTransaction()
|
||||
.add(R.id.discover_posts, postsFragment)
|
||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||
.add(R.id.discover_news, newsFragment)
|
||||
.add(R.id.discover_users, accountsFragment)
|
||||
.commit();
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
transaction
|
||||
.add(R.id.discover_posts, postsFragment)
|
||||
.add(R.id.discover_hashtags, hashtagsFragment);
|
||||
if(!isIceshrimp) // skip unsupported news discovery on Iceshrimp
|
||||
transaction.add(R.id.discover_news, newsFragment);
|
||||
transaction
|
||||
.add(R.id.discover_users, accountsFragment)
|
||||
.commit();
|
||||
}
|
||||
|
||||
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||
@@ -140,7 +152,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
tab.setText(switch(position){
|
||||
case 0 -> R.string.posts;
|
||||
case 1 -> R.string.hashtags;
|
||||
case 2 -> R.string.news;
|
||||
case 2 -> isIceshrimp ? R.string.for_you : R.string.news; // skip unsupported news discovery on Iceshrimp
|
||||
case 3 -> R.string.for_you;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||
});
|
||||
@@ -160,7 +172,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
}
|
||||
});
|
||||
|
||||
disableDiscover=AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false);
|
||||
searchView=view.findViewById(R.id.search_fragment);
|
||||
if(searchFragment==null){
|
||||
searchFragment=new SearchFragment();
|
||||
@@ -260,9 +271,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
||||
|
||||
private Fragment getFragmentForPage(int page){
|
||||
return switch(page){
|
||||
case 0 -> hashtagsFragment;
|
||||
case 1 -> postsFragment;
|
||||
case 2 -> newsFragment;
|
||||
case 0 -> postsFragment;
|
||||
case 1 -> hashtagsFragment;
|
||||
case 2 -> isIceshrimp ? accountsFragment : newsFragment; // skip unsupported news discovery on Iceshrimp
|
||||
case 3 -> accountsFragment;
|
||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||
};
|
||||
|
||||
@@ -131,8 +131,7 @@ public class DiscoverTrendingLinkTimelineFragment extends StatusListFragment{
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
//TODO: add URL link once web version implements a UI
|
||||
return base.path("/explore/links").build();
|
||||
return base.path("/links").appendPath(trendingLink.url).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -94,7 +94,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> impleme
|
||||
copyCrashLogItem=new ListItem<>(getString(R.string.sk_settings_copy_crash_log), lastModified, 0, this::onCopyCrashLog)
|
||||
));
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
if(GithubSelfUpdater.needSelfUpdating() && !BuildConfig.BUILD_TYPE.equals("nightly") ){
|
||||
items.add(enablePreReleasesItem=new CheckableListItem<>(R.string.sk_updater_enable_pre_releases, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enablePreReleases, i->toggleCheckableItem(enablePreReleasesItem)));
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
languageResolver.from(s.preferences.postingDefaultLanguage).orElse(null);
|
||||
|
||||
List<ListItem<Void>> items = new ArrayList<>(List.of(
|
||||
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick),
|
||||
customTabsItem=new ListItem<>(getString(R.string.settings_custom_tabs), getString(GlobalUserPreferences.useCustomTabs ? R.string.in_app_browser : R.string.system_browser), R.drawable.ic_fluent_open_24_regular, this::onCustomTabsClick),
|
||||
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, i->toggleCheckableItem(altTextItem)),
|
||||
showPostsWithoutAltItem=new CheckableListItem<>(R.string.mo_settings_show_posts_without_alt, R.string.mo_settings_show_posts_without_alt_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showPostsWithoutAlt, R.drawable.ic_fluent_eye_tracking_on_24_regular, i->toggleCheckableItem(showPostsWithoutAltItem)),
|
||||
@@ -73,6 +72,11 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
showRepliesItem=new CheckableListItem<>(R.string.sk_settings_show_replies, 0, CheckableListItem.Style.SWITCH, lp.showReplies, R.drawable.ic_fluent_arrow_reply_24_regular, i->toggleCheckableItem(showRepliesItem))
|
||||
));
|
||||
|
||||
if(!isInstanceIceshrimpJs()) items.add(
|
||||
0,
|
||||
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick)
|
||||
);
|
||||
|
||||
if(isInstanceAkkoma()) items.add(
|
||||
replyVisibilityItem=new ListItem<>(R.string.sk_settings_reply_visibility, getReplyVisibilityString(), R.drawable.ic_fluent_chat_24_regular, this::onReplyVisibilityClick)
|
||||
);
|
||||
@@ -203,7 +207,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
GlobalUserPreferences.playGifs=playGifsItem.checked;
|
||||
GlobalUserPreferences.overlayMedia=overlayMediaItem.checked;
|
||||
GlobalUserPreferences.altTextReminders=altTextItem.checked;
|
||||
GlobalUserPreferences.confirmUnfollow=confirmUnfollowItem.checked;
|
||||
@@ -215,13 +218,14 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
||||
GlobalUserPreferences.mentionRebloggerAutomatically=mentionRebloggerAutomaticallyItem.checked;
|
||||
GlobalUserPreferences.hapticFeedback=hapticFeedbackItem.checked;
|
||||
GlobalUserPreferences.showPostsWithoutAlt=showPostsWithoutAltItem.checked;
|
||||
GlobalUserPreferences.save();
|
||||
AccountLocalPreferences lp=getLocalPrefs();
|
||||
boolean restartPlease=lp.showBoosts!=showBoostsItem.checked
|
||||
|| lp.showReplies!=showRepliesItem.checked;
|
||||
|| lp.showReplies!=showRepliesItem.checked || GlobalUserPreferences.playGifs!=playGifsItem.checked;
|
||||
lp.showBoosts=showBoostsItem.checked;
|
||||
lp.showReplies=showRepliesItem.checked;
|
||||
GlobalUserPreferences.playGifs=playGifsItem.checked;
|
||||
lp.save();
|
||||
GlobalUserPreferences.save();
|
||||
if(newPostLanguage!=null){
|
||||
AccountSession s=AccountSessionManager.get(accountID);
|
||||
if(s.preferences==null)
|
||||
|
||||
@@ -47,7 +47,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem;
|
||||
|
||||
// MOSHIDON
|
||||
private CheckableListItem<Void> enableDoubleTapToSwipeItem, relocatePublishButtonItem, showPostDividersItem, enableDoubleTapToSearchItem, showMediaPreviewItem;
|
||||
private CheckableListItem<Void> enableDoubleTapToSwipeItem, relocatePublishButtonItem, showPostDividersItem, enableDoubleTapToSearchItem, showMediaPreviewItem, enhanceTextSizeItem;
|
||||
|
||||
private AccountLocalPreferences lp;
|
||||
|
||||
@@ -63,6 +63,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
trueBlackModeItem=new CheckableListItem<>(R.string.sk_settings_true_black, R.string.mo_setting_true_black_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.trueBlackTheme, R.drawable.ic_fluent_dark_theme_24_regular, i->onTrueBlackModeClick(), true),
|
||||
publishTextItem=new ListItem<>(getString(R.string.sk_settings_publish_button_text), getPublishButtonText(), R.drawable.ic_fluent_send_24_regular, this::onPublishTextClick),
|
||||
autoRevealCWsItem=new ListItem<>(R.string.sk_settings_auto_reveal_equal_spoilers, getAutoRevealSpoilersText(), R.drawable.ic_fluent_eye_24_regular, this::onAutoRevealSpoilersClick),
|
||||
enhanceTextSizeItem=new CheckableListItem<>(R.string.mo_settings_enhance_text_size, R.string.mo_settings_enhance_text_size_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enhanceTextSize, R.drawable.ic_fluent_text_more_24_regular, i->onEnhanceTextSizeClick()),
|
||||
relocatePublishButtonItem=new CheckableListItem<>(R.string.mo_relocate_publish_button, R.string.mo_setting_relocate_publish_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.relocatePublishButton, R.drawable.ic_fluent_arrow_autofit_down_24_regular, i->toggleCheckableItem(relocatePublishButtonItem)),
|
||||
revealCWsItem=new CheckableListItem<>(R.string.sk_settings_always_reveal_content_warnings, 0, CheckableListItem.Style.SWITCH, lp.revealCWs, R.drawable.ic_fluent_chat_warning_24_regular, i->toggleCheckableItem(revealCWsItem)),
|
||||
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_fluent_flag_24_regular, i->toggleCheckableItem(hideSensitiveMediaItem)),
|
||||
@@ -141,6 +142,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
GlobalUserPreferences.displayPronounsInThreads=pronounsInThreadsItem.checked;
|
||||
GlobalUserPreferences.displayPronounsInUserListings=pronounsInUserListingsItem.checked;
|
||||
GlobalUserPreferences.showMediaPreview=showMediaPreviewItem.checked;
|
||||
GlobalUserPreferences.enhanceTextSize=enhanceTextSizeItem.checked;
|
||||
GlobalUserPreferences.save();
|
||||
if(restartPlease) restartActivityToApplyNewTheme();
|
||||
else E.post(new StatusDisplaySettingsChangedEvent(accountID));
|
||||
@@ -182,6 +184,11 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
|
||||
maybeApplyNewThemeRightNow(null, null, prev);
|
||||
}
|
||||
|
||||
private void onEnhanceTextSizeClick(){
|
||||
toggleCheckableItem(enhanceTextSizeItem);
|
||||
restartActivityToApplyNewTheme();
|
||||
}
|
||||
|
||||
private void onAppearanceClick(ListItem<?> item_){
|
||||
int selected=switch(GlobalUserPreferences.theme){
|
||||
case LIGHT -> 0;
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -35,24 +36,27 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||
setTitle(R.string.sk_settings_instance);
|
||||
AccountSession s=AccountSessionManager.get(accountID);
|
||||
lp=s.getLocalPreferences();
|
||||
onDataLoaded(List.of(
|
||||
ArrayList<ListItem<Void>> items=new ArrayList<>(List.of(
|
||||
new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_fluent_server_24_regular, this::onServerClick),
|
||||
new ListItem<>(R.string.sk_settings_profile, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/profile")),
|
||||
new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")),
|
||||
new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true),
|
||||
contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, i->onContentTypeClick()),
|
||||
defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true),
|
||||
emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, i->onEmojiReactionsClick()),
|
||||
showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true),
|
||||
localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, i->onLocalOnlyClick()),
|
||||
glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, i->toggleCheckableItem(glitchModeItem))
|
||||
));
|
||||
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
|
||||
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
|
||||
if(!isInstanceIceshrimp()){
|
||||
items.add(4, contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, i->onContentTypeClick()));
|
||||
items.add(5, defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true));
|
||||
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
|
||||
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
|
||||
}
|
||||
emojiReactionsItem.checkedChangeListener=checked->onEmojiReactionsClick();
|
||||
showEmojiReactionsItem.isEnabled=emojiReactionsItem.checked;
|
||||
localOnlyItem.checkedChangeListener=checked->onLocalOnlyClick();
|
||||
glitchModeItem.isEnabled=localOnlyItem.checked;
|
||||
onDataLoaded(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,7 +65,8 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
lp.contentTypesEnabled=contentTypesItem.checked;
|
||||
if(contentTypesItem!=null)
|
||||
lp.contentTypesEnabled=contentTypesItem.checked;
|
||||
lp.emojiReactionsEnabled=emojiReactionsItem.checked;
|
||||
lp.localOnlySupported=localOnlyItem.checked;
|
||||
lp.glitchInstance=glitchModeItem.checked;
|
||||
@@ -84,7 +89,8 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
|
||||
|
||||
private void resetDefaultContentType(){
|
||||
lp.defaultContentType=defaultContentTypeItem.isEnabled
|
||||
? ContentType.PLAIN : ContentType.UNSPECIFIED;
|
||||
? isInstanceIceshrimp() ? ContentType.MISSKEY_MARKDOWN
|
||||
: ContentType.PLAIN : ContentType.UNSPECIFIED;
|
||||
defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName();
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
|
||||
));
|
||||
|
||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
|
||||
if(!instance.isAkkoma()){
|
||||
if(!instance.isAkkoma() && !instance.isIceshrimpJs()){ // hide filter settings on Akkoma and Iceshrimp-JS because the servers don't support the feature
|
||||
data.add(3, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.utils.UnifiedPushHelper;
|
||||
import org.unifiedpush.android.connector.UnifiedPush;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -57,6 +58,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
|
||||
// MEGALODON
|
||||
private boolean useUnifiedPush = false;
|
||||
private boolean hasAnyUnifiedPushDistrib = false;
|
||||
private CheckableListItem<Void> uniformIconItem, deleteItem, onlyLatestItem, unifiedPushItem;
|
||||
private CheckableListItem<Void> postsItem, updateItem;
|
||||
|
||||
@@ -72,7 +74,8 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
lp=AccountSessionManager.get(accountID).getLocalPreferences();
|
||||
|
||||
getPushSubscription();
|
||||
useUnifiedPush=!UnifiedPush.getDistributor(getContext()).isEmpty();
|
||||
useUnifiedPush=UnifiedPushHelper.isUnifiedPushEnabled(getContext());
|
||||
hasAnyUnifiedPushDistrib=UnifiedPushHelper.hasAnyDistributorInstalled(getContext());
|
||||
|
||||
onDataLoaded(List.of(
|
||||
pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_fluent_alert_snooze_24_regular, i->onPauseNotificationsClick(false)),
|
||||
@@ -94,7 +97,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
));
|
||||
|
||||
//only enable when distributors, who can receive notifications, are available
|
||||
unifiedPushItem.isEnabled=!UnifiedPush.getDistributors(getContext(), new ArrayList<>()).isEmpty();
|
||||
unifiedPushItem.isEnabled=hasAnyUnifiedPushDistrib;
|
||||
if (!unifiedPushItem.isEnabled) {
|
||||
unifiedPushItem.subtitleRes=R.string.sk_settings_unifiedpush_no_distributor_body;
|
||||
}
|
||||
@@ -124,7 +127,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
GlobalUserPreferences.save();
|
||||
lp.keepOnlyLatestNotification=onlyLatestItem.checked;
|
||||
lp.save();
|
||||
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||
if(needUpdateNotificationSettings && (PushSubscriptionManager.arePushNotificationsAvailable() || useUnifiedPush)){
|
||||
ps.alerts.mention=mentionsItem.checked;
|
||||
ps.alerts.reblog=boostsItem.checked;
|
||||
ps.alerts.favourite=favoritesItem.checked;
|
||||
@@ -316,12 +319,12 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
bannerText.setText(R.string.notifications_disabled_in_system);
|
||||
bannerButton.setText(R.string.open_system_notification_settings);
|
||||
bannerButton.setOnClickListener(v->openSystemNotificationSettings());
|
||||
}else if(BuildConfig.BUILD_TYPE.equals("fdroidRelease") && UnifiedPush.getDistributor(getContext()).isEmpty()){
|
||||
}else if(BuildConfig.BUILD_TYPE.equals("fdroidRelease") && useUnifiedPush){
|
||||
bannerAdapter.setVisible(true);
|
||||
bannerIcon.setImageResource(R.drawable.ic_fluent_warning_24_filled);
|
||||
bannerTitle.setVisibility(View.VISIBLE);
|
||||
bannerTitle.setText(R.string.mo_settings_unifiedpush_warning);
|
||||
if(UnifiedPush.getDistributors(getContext(), new ArrayList<>()).isEmpty()) {
|
||||
if(!hasAnyUnifiedPushDistrib) {
|
||||
bannerText.setText(R.string.mo_settings_unifiedpush_warning_no_distributors);
|
||||
bannerButton.setText(R.string.info);
|
||||
bannerButton.setOnClickListener(v->UiUtils.launchWebBrowser(getContext(), "https://unifiedpush.org/"));
|
||||
@@ -342,23 +345,15 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
}
|
||||
|
||||
private void onUnifiedPushClick(){
|
||||
if(UnifiedPush.getDistributor(getContext()).isEmpty()){
|
||||
List<String> distributors = UnifiedPush.getDistributors(getContext(), new ArrayList<>());
|
||||
if(!useUnifiedPush){
|
||||
List<String> distributors = UnifiedPush.getDistributors(getContext());
|
||||
showUnifiedPushRegisterDialog(distributors);
|
||||
return;
|
||||
}
|
||||
|
||||
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()) {
|
||||
UnifiedPush.unregisterApp(
|
||||
getContext(),
|
||||
accountSession.getID()
|
||||
);
|
||||
|
||||
//re-register to fcm
|
||||
accountSession.getPushSubscriptionManager().registerAccountForPush(getPushSubscription());
|
||||
}
|
||||
UnifiedPushHelper.unregisterAllAccounts(getContext());
|
||||
unifiedPushItem.toggle();
|
||||
rebindItem(unifiedPushItem);
|
||||
useUnifiedPush = false;
|
||||
}
|
||||
|
||||
private void showUnifiedPushRegisterDialog(List<String> distributors){
|
||||
@@ -366,16 +361,10 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
|
||||
(dialog, which)->{
|
||||
String userDistrib = distributors.get(which);
|
||||
UnifiedPush.saveDistributor(getContext(), userDistrib);
|
||||
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
UnifiedPush.registerApp(
|
||||
getContext(),
|
||||
accountSession.getID(),
|
||||
new ArrayList<>(),
|
||||
getContext().getPackageName()
|
||||
);
|
||||
}
|
||||
UnifiedPushHelper.registerAllAccounts(getContext());
|
||||
unifiedPushItem.toggle();
|
||||
rebindItem(unifiedPushItem);
|
||||
useUnifiedPush = true;
|
||||
}).setOnCancelListener(d->rebindItem(unifiedPushItem)).show();
|
||||
}
|
||||
|
||||
|
||||
@@ -50,11 +50,11 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
|
||||
if(reactions==null) reactions=new ArrayList<>();
|
||||
}
|
||||
|
||||
public Status toStatus() {
|
||||
public Status toStatus(boolean isIceshrimp) {
|
||||
Status s=Status.ofFake(id, content, publishedAt);
|
||||
s.createdAt=startsAt != null ? startsAt : publishedAt;
|
||||
s.reactions=reactions;
|
||||
if(updatedAt != null) s.editedAt=updatedAt;
|
||||
if(updatedAt != null && (!isIceshrimp || !updatedAt.equals(publishedAt))) s.editedAt=updatedAt;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,6 @@ public enum ContentType {
|
||||
}
|
||||
|
||||
public boolean supportedByInstance(Instance i) {
|
||||
return i.isAkkoma() || (this!=BBCODE && this!=MISSKEY_MARKDOWN);
|
||||
return i.isAkkoma() || i.isIceshrimp() || (this!=BBCODE && this!=MISSKEY_MARKDOWN);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public class EmojiReaction {
|
||||
public String staticUrl;
|
||||
|
||||
public transient ImageLoaderRequest request;
|
||||
public transient boolean pendingChange=false;
|
||||
|
||||
public String getUrl(boolean playGifs){
|
||||
String idealUrl=playGifs ? url : staticUrl;
|
||||
@@ -38,7 +39,7 @@ public class EmojiReaction {
|
||||
reaction.staticUrl=info.staticUrl;
|
||||
reaction.accounts=new ArrayList<>(Collections.singleton(me));
|
||||
reaction.accountIds=new ArrayList<>(Collections.singleton(me.id));
|
||||
reaction.request=new UrlImageLoaderRequest(info.url, V.sp(24), V.sp(24));
|
||||
reaction.request=new UrlImageLoaderRequest(info.url, 0, V.sp(24));
|
||||
return reaction;
|
||||
}
|
||||
|
||||
@@ -60,4 +61,18 @@ public class EmojiReaction {
|
||||
accounts.add(self);
|
||||
accountIds.add(self.id);
|
||||
}
|
||||
|
||||
public EmojiReaction copy() {
|
||||
EmojiReaction r=new EmojiReaction();
|
||||
r.accounts=accounts;
|
||||
r.accountIds=accountIds;
|
||||
r.count=count;
|
||||
r.me=me;
|
||||
r.name=name;
|
||||
r.url=url;
|
||||
r.staticUrl=staticUrl;
|
||||
r.request=request;
|
||||
r.pendingChange=pendingChange;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,14 +146,28 @@ public class Instance extends BaseModel{
|
||||
return ci;
|
||||
}
|
||||
|
||||
// This method has almost exclusively been used to improve support for
|
||||
// Akkoma with no regard for Pleroma, hence its name. However, it is
|
||||
// more likely than not that most uses should also apply to Pleroma,
|
||||
// so checking for that too probably causes more good than harm.
|
||||
public boolean isAkkoma() {
|
||||
return pleroma != null;
|
||||
return version.contains("compatible; Akkoma") || version.contains("compatible; Pleroma");
|
||||
}
|
||||
|
||||
public boolean isPixelfed() {
|
||||
return version.contains("compatible; Pixelfed");
|
||||
}
|
||||
|
||||
// For both Iceshrimp-JS and Iceshrimp.NET
|
||||
public boolean isIceshrimp() {
|
||||
return version.contains("compatible; Iceshrimp");
|
||||
}
|
||||
|
||||
// Only for Iceshrimp-JS
|
||||
public boolean isIceshrimpJs() {
|
||||
return version.contains("compatible; Iceshrimp "); // Iceshrimp.NET will not have a space immediately after
|
||||
}
|
||||
|
||||
public boolean hasFeature(Feature feature) {
|
||||
Optional<List<String>> pleromaFeatures = Optional.ofNullable(pleroma)
|
||||
.map(p -> p.metadata)
|
||||
@@ -219,6 +233,7 @@ public class Instance extends BaseModel{
|
||||
public StatusesConfiguration statuses;
|
||||
public MediaAttachmentsConfiguration mediaAttachments;
|
||||
public PollsConfiguration polls;
|
||||
public ReactionsConfiguration reactions;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
@@ -242,8 +257,14 @@ public class Instance extends BaseModel{
|
||||
public static class PollsConfiguration{
|
||||
public int maxOptions;
|
||||
public int maxCharactersPerOption;
|
||||
public int minExpiration;
|
||||
public int maxExpiration;
|
||||
public long minExpiration;
|
||||
public long maxExpiration;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class ReactionsConfiguration {
|
||||
public int maxReactions;
|
||||
public String defaultReaction;
|
||||
}
|
||||
|
||||
@Parcel
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import android.util.Patterns;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.Poll.Option;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Parcel
|
||||
public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
||||
private static final Pattern HIGHLIGHT_PATTER=Pattern.compile("(?<!\\w)(?:@([a-z0-9_]+)(@[a-z0-9_\\.\\-]*)?|#([^\\s.]+)|:([a-z0-9_]+))|" +Patterns.WEB_URL, Pattern.CASE_INSENSITIVE);
|
||||
|
||||
@RequiredField
|
||||
public String id;
|
||||
@RequiredField
|
||||
@@ -87,7 +97,61 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
||||
s.visibility=params.visibility;
|
||||
s.language=params.language;
|
||||
s.sensitive=params.sensitive;
|
||||
// hide media preview only if status is marked as sensitive
|
||||
s.sensitiveRevealed=!params.sensitive;
|
||||
if(params.poll!=null) s.poll=params.poll.toPoll();
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fake status, which has (somewhat) correctly formatted mentions, hashtags and URLs.
|
||||
*
|
||||
* @param accountID the ID of the account
|
||||
* @return the formatted Status object
|
||||
*/
|
||||
public Status toFormattedStatus(String accountID){
|
||||
AccountSession self=AccountSessionManager.get(accountID);
|
||||
Status s=this.toStatus();
|
||||
// the mastodon api does not return formatted (html) content, only the raw content, so we modify it
|
||||
s.content=s.content.replace("\n", "<br>");
|
||||
if(!s.content.contains("@") && !s.content.contains("#") && !s.content.contains(":"))
|
||||
return s;
|
||||
|
||||
StringBuffer sb=new StringBuffer();
|
||||
Matcher matcher=HIGHLIGHT_PATTER.matcher(s.content);
|
||||
|
||||
// I'm sure this will cause problems at some point...
|
||||
while(matcher.find()){
|
||||
String content=matcher.group();
|
||||
String href="";
|
||||
// add relevant links, so on-click actions work
|
||||
// hashtags are done by the parser
|
||||
if(content.startsWith("@"))
|
||||
href=" href=\""+formatMention(content, self.domain)+"\" class=\"u-url mention\"";
|
||||
else if(content.startsWith("https://"))
|
||||
href=" href=\""+content+"\"";
|
||||
|
||||
matcher.appendReplacement(sb, "<a"+href+">"+content+"</a>");
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
s.content=sb.toString();
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string mention into a URL of the account.
|
||||
* @param mention Mention in the form a of user name with an optional instance URL
|
||||
* @param instanceURL URL of the home instance of the user
|
||||
* @return Formatted HTML or the mention
|
||||
*/
|
||||
@NonNull
|
||||
private static String formatMention(@NonNull String mention, @NonNull String instanceURL){
|
||||
String[] parts=mention.split("@");
|
||||
if(parts.length>1){
|
||||
String username=parts[1];
|
||||
String domain=parts.length==3 ? parts[2] : instanceURL;
|
||||
return "https://"+domain+"/@"+username;
|
||||
}
|
||||
return mention;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
public String uri;
|
||||
// @RequiredField // sometimes null on calckey
|
||||
public Instant createdAt;
|
||||
@RequiredField
|
||||
// @RequiredField // sometimes null? Gonna make sure to check everytime. TODO: make account field required again
|
||||
public Account account;
|
||||
// @RequiredField
|
||||
public String content;
|
||||
@@ -62,8 +62,8 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
public List<Mention> mentions;
|
||||
@RequiredField
|
||||
public List<Hashtag> tags;
|
||||
@RequiredField
|
||||
public List<Emoji> emojis;
|
||||
// @RequiredField // sometimes null on glitch-soc? TODO: make this field required again
|
||||
public List<Emoji> emojis = new ArrayList<>();
|
||||
public long reblogsCount;
|
||||
public long favouritesCount;
|
||||
public long repliesCount;
|
||||
|
||||
@@ -336,6 +336,7 @@ public class TimelineDefinition {
|
||||
THUNDERSTORM(R.drawable.ic_fluent_weather_thunderstorm_24_regular, R.string.sk_icon_thunderstorm),
|
||||
RAIN(R.drawable.ic_fluent_weather_rain_24_regular, R.string.sk_icon_rain),
|
||||
SNOWFLAKE(R.drawable.ic_fluent_weather_snowflake_24_regular, R.string.sk_icon_snowflake),
|
||||
GNOME(R.drawable.ic_gnome_logo, R.string.mo_icon_gnome),
|
||||
|
||||
HOME(R.drawable.ic_fluent_home_24_regular, R.string.sk_timeline_home, true),
|
||||
LOCAL(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
|
||||
/**
|
||||
* Represents an OAuth token used for authenticating with the API and performing actions.
|
||||
*/
|
||||
@AllFieldsAreRequired
|
||||
public class Token extends BaseModel{
|
||||
/**
|
||||
* An OAuth token to be used for authorization.
|
||||
*/
|
||||
@RequiredField
|
||||
public String accessToken;
|
||||
/**
|
||||
* The OAuth token type. Mastodon uses Bearer tokens.
|
||||
@@ -23,5 +23,6 @@ public class Token extends BaseModel{
|
||||
* When the token was generated.
|
||||
* (unixtime)
|
||||
*/
|
||||
@RequiredField
|
||||
public long createdAt;
|
||||
}
|
||||
|
||||
@@ -225,8 +225,8 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
|
||||
cover.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-2, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
name.setText(name.getText());
|
||||
bio.setText(bio.getText());
|
||||
}
|
||||
if(image instanceof Animatable && !((Animatable) image).isRunning())
|
||||
((Animatable) image).start();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.ui.displayitems;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Paint;
|
||||
@@ -33,16 +34,24 @@ import org.joinmastodon.android.api.requests.statuses.PleromaDeleteStatusReactio
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.account_list.StatusEmojiReactionsListFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.EmojiReaction;
|
||||
import org.joinmastodon.android.model.Instance;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.CustomEmojiPopupKeyboard;
|
||||
import org.joinmastodon.android.ui.utils.TextDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import org.joinmastodon.android.ui.views.EmojiReactionButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import me.grishka.appkit.Nav;
|
||||
import me.grishka.appkit.api.Callback;
|
||||
@@ -62,6 +71,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
private final boolean hideEmpty, forAnnouncement, playGifs;
|
||||
private final String accountID;
|
||||
private static final float ALPHA_DISABLED=0.55f;
|
||||
private boolean forceShow=false;
|
||||
|
||||
public EmojiReactionsStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status, String accountID, boolean hideEmpty, boolean forAnnouncement) {
|
||||
super(parentID, parentFragment);
|
||||
@@ -90,6 +100,10 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
}
|
||||
|
||||
public boolean isHidden(){
|
||||
if(forceShow){
|
||||
forceShow=false;
|
||||
return false;
|
||||
}
|
||||
return status.reactions.isEmpty() && hideEmpty;
|
||||
}
|
||||
|
||||
@@ -101,7 +115,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
vh.btn.setAlpha(visible ? ALPHA_DISABLED : 1);
|
||||
}
|
||||
|
||||
private MastodonAPIRequest<?> createRequest(String name, int count, boolean delete, Holder.EmojiReactionViewHolder vh, Runnable cb, Runnable err){
|
||||
private MastodonAPIRequest<?> createRequest(String name, int count, boolean delete, Holder.EmojiReactionViewHolder vh, Consumer<Status> cb, Runnable err){
|
||||
setActionProgressVisible(vh, true);
|
||||
boolean ak=parentFragment.isInstanceAkkoma();
|
||||
boolean keepSpinning=delete && count == 1;
|
||||
@@ -113,7 +127,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
if(!keepSpinning) setActionProgressVisible(vh, false);
|
||||
cb.run();
|
||||
cb.accept(null);
|
||||
}
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
@@ -130,7 +144,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
if(!keepSpinning) setActionProgressVisible(vh, false);
|
||||
cb.run();
|
||||
cb.accept(result);
|
||||
}
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
@@ -151,6 +165,8 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
private final ProgressBar progress;
|
||||
private final EmojiReactionsAdapter adapter;
|
||||
private final ListImageLoaderWrapper imgLoader;
|
||||
private int meReactionCount=0;
|
||||
private Instance instance;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent) {
|
||||
super(activity, R.layout.display_item_emoji_reactions, parent);
|
||||
@@ -171,8 +187,15 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
if(emojiKeyboard != null) root.removeView(emojiKeyboard.getView());
|
||||
addButton.setSelected(false);
|
||||
AccountSession session=item.parentFragment.getSession();
|
||||
instance=item.parentFragment.getInstance().get();
|
||||
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){
|
||||
meReactionCount=(int) item.status.reactions.stream().filter(r->r.me).count();
|
||||
boolean canReact=meReactionCount<instance.configuration.reactions.maxReactions;
|
||||
addButton.setClickable(canReact);
|
||||
addButton.setAlpha(canReact ? 1 : ALPHA_DISABLED);
|
||||
}
|
||||
item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null
|
||||
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), V.sp(24), V.sp(24))
|
||||
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), 0, V.sp(24))
|
||||
: null);
|
||||
emojiKeyboard=new CustomEmojiPopupKeyboard(
|
||||
(Activity) item.parentFragment.getContext(),
|
||||
@@ -182,18 +205,34 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
emojiKeyboard.setListener(this);
|
||||
space.setVisibility(View.GONE);
|
||||
root.addView(emojiKeyboard.getView());
|
||||
boolean hidden=item.isHidden();
|
||||
root.setVisibility(hidden ? View.GONE : View.VISIBLE);
|
||||
line.setVisibility(hidden ? View.GONE : View.VISIBLE);
|
||||
updateVisibility(item.isHidden(), true);
|
||||
imgLoader.updateImages();
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
if(!GlobalUserPreferences.showDividers || item.isHidden())
|
||||
return;
|
||||
|
||||
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
|
||||
if(next!=null && !next.parentID.equals(item.parentID)) next=null;
|
||||
if(next instanceof ExtendedFooterStatusDisplayItem)
|
||||
itemView.setPadding(0, 0, 0, V.dp(12));
|
||||
else
|
||||
itemView.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void updateVisibility(boolean hidden, boolean force){
|
||||
int visibility=hidden ? View.GONE : View.VISIBLE;
|
||||
if(!force && visibility==root.getVisibility())
|
||||
return;
|
||||
root.setVisibility(visibility);
|
||||
line.setVisibility(visibility);
|
||||
line.setPadding(
|
||||
list.getPaddingLeft(),
|
||||
hidden ? 0 : V.dp(8),
|
||||
list.getPaddingRight(),
|
||||
item.forAnnouncement ? V.dp(8) : 0
|
||||
);
|
||||
imgLoader.updateImages();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideEmojiKeyboard(){
|
||||
space.setVisibility(View.GONE);
|
||||
@@ -244,19 +283,32 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
}
|
||||
}
|
||||
EmojiReaction finalExisting=existing;
|
||||
item.createRequest(emoji, existing==null ? 1 : existing.count, false, null, ()->{
|
||||
item.createRequest(emoji, existing==null ? 1 : existing.count, false, null, (status)->{
|
||||
resetBtn.run();
|
||||
if(finalExisting==null){
|
||||
int pos=item.status.reactions.size();
|
||||
int pos=status.reactions.stream()
|
||||
.filter(r->r.name.equals(info!=null ? info.shortcode : emoji))
|
||||
.findFirst()
|
||||
.map(r->status.reactions.indexOf(r))
|
||||
.orElse(item.status.reactions.size());
|
||||
boolean previouslyEmpty=item.status.reactions.isEmpty();
|
||||
item.status.reactions.add(pos, info!=null ? EmojiReaction.of(info, me) : EmojiReaction.of(emoji, me));
|
||||
adapter.notifyItemRangeInserted(pos, 1);
|
||||
if(previouslyEmpty)
|
||||
adapter.notifyItemChanged(pos);
|
||||
else
|
||||
adapter.notifyItemInserted(pos);
|
||||
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
|
||||
scroller.setTargetPosition(pos);
|
||||
list.getLayoutManager().startSmoothScroll(scroller);
|
||||
updateMeReactionCount(false);
|
||||
}else{
|
||||
finalExisting.add(me);
|
||||
adapter.notifyItemChanged(item.status.reactions.indexOf(finalExisting));
|
||||
}
|
||||
if(instance.isIceshrimpJs() && status!=null){
|
||||
item.parentFragment.onFavoriteChanged(status, getItemID());
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
E.post(new EmojiReactionsUpdatedEvent(item.status.id, item.status.reactions, countBefore==0, adapter.parentHolder));
|
||||
}, resetBtn).exec(item.accountID);
|
||||
}
|
||||
@@ -278,6 +330,99 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAddButtonClickable() {
|
||||
if(instance==null || instance.configuration==null || instance.configuration.reactions==null || instance.configuration.reactions.maxReactions==0)
|
||||
return;
|
||||
boolean canReact=meReactionCount<instance.configuration.reactions.maxReactions;
|
||||
addButton.setClickable(canReact);
|
||||
|
||||
ObjectAnimator anim=ObjectAnimator.ofFloat(
|
||||
addButton, View.ALPHA,
|
||||
canReact ? ALPHA_DISABLED : 1,
|
||||
canReact ? 1 : ALPHA_DISABLED);
|
||||
anim.setDuration(200);
|
||||
anim.start();
|
||||
}
|
||||
|
||||
private void updateMeReactionCount(boolean deleting) {
|
||||
meReactionCount=Math.max(0, meReactionCount + (deleting ? -1 : 1));
|
||||
updateAddButtonClickable();
|
||||
}
|
||||
|
||||
public void updateReactions(List<EmojiReaction> reactions){
|
||||
item.status.reactions=new ArrayList<>(item.status.reactions); // I don't know how, but this seemingly fixes a bug
|
||||
|
||||
List<EmojiReaction> toRemove=new ArrayList<>();
|
||||
for(int i=0;i<item.status.reactions.size();i++){
|
||||
EmojiReaction reaction=item.status.reactions.get(i);
|
||||
Optional<EmojiReaction> newReactionOptional=reactions.stream().filter(r->r.name.equals(reaction.name)).findFirst();
|
||||
if(newReactionOptional.isEmpty()){ // deleted reactions
|
||||
toRemove.add(reaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
// changed reactions
|
||||
EmojiReaction newReaction=newReactionOptional.get();
|
||||
if(reaction.count!=newReaction.count || reaction.me!=newReaction.me || reaction.pendingChange!=newReaction.pendingChange){
|
||||
if(newReaction.pendingChange){
|
||||
View holderView=list.getChildAt(i);
|
||||
if(holderView!=null){
|
||||
EmojiReactionViewHolder reactionHolder=(EmojiReactionViewHolder) list.getChildViewHolder(holderView);
|
||||
item.setActionProgressVisible(reactionHolder, true);
|
||||
}
|
||||
}else{
|
||||
item.status.reactions.set(i, newReaction);
|
||||
adapter.notifyItemChanged(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(toRemove);
|
||||
for(EmojiReaction r:toRemove){
|
||||
int index=item.status.reactions.indexOf(r);
|
||||
item.status.reactions.remove(index);
|
||||
adapter.notifyItemRemoved(index);
|
||||
}
|
||||
|
||||
boolean pendingAddReaction=false;
|
||||
for(int i=0;i<reactions.size();i++){
|
||||
EmojiReaction reaction=reactions.get(i);
|
||||
if(item.status.reactions.stream().anyMatch(r->r.name.equals(reaction.name)))
|
||||
continue;
|
||||
|
||||
// new reactions
|
||||
if(reaction.pendingChange){
|
||||
pendingAddReaction=true;
|
||||
item.forceShow=true;
|
||||
continue;
|
||||
}
|
||||
boolean previouslyEmpty=item.status.reactions.isEmpty();
|
||||
item.status.reactions.add(i, reaction);
|
||||
if(previouslyEmpty)
|
||||
adapter.notifyItemChanged(i);
|
||||
else
|
||||
adapter.notifyItemInserted(i);
|
||||
RecyclerView.SmoothScroller scroller=new LinearSmoothScroller(list.getContext());
|
||||
scroller.setTargetPosition(i);
|
||||
list.getLayoutManager().startSmoothScroll(scroller);
|
||||
}
|
||||
if(pendingAddReaction){
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
addButton.setClickable(false);
|
||||
addButton.setAlpha(ALPHA_DISABLED);
|
||||
}else{
|
||||
progress.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
int newMeReactionCount=(int) reactions.stream().filter(r->r.me || r.pendingChange).count();
|
||||
if (newMeReactionCount!=meReactionCount){
|
||||
meReactionCount=newMeReactionCount;
|
||||
updateAddButtonClickable();
|
||||
}
|
||||
|
||||
updateVisibility(reactions.isEmpty() && item.hideEmpty, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
View child=list.getChildAt(index);
|
||||
@@ -330,7 +475,7 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
}
|
||||
|
||||
private static class EmojiReactionViewHolder extends BindableViewHolder<Pair<EmojiReactionsStatusDisplayItem, EmojiReaction>> implements ImageLoaderViewHolder{
|
||||
private final ProgressBarButton btn;
|
||||
private final EmojiReactionButton btn;
|
||||
private final ProgressBar progress;
|
||||
|
||||
public EmojiReactionViewHolder(Context context, RecyclerView list){
|
||||
@@ -342,7 +487,9 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
|
||||
@Override
|
||||
public void setImage(int index, Drawable drawable){
|
||||
drawable.setBounds(0, 0, V.sp(24), V.sp(24));
|
||||
int height=V.sp(24);
|
||||
int width=drawable.getIntrinsicWidth()*height/drawable.getIntrinsicHeight();
|
||||
drawable.setBounds(0, 0, width, height);
|
||||
btn.setCompoundDrawablesRelative(drawable, null, null, null);
|
||||
if(drawable instanceof Animatable) ((Animatable) drawable).start();
|
||||
}
|
||||
@@ -354,6 +501,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
|
||||
@Override
|
||||
public void onBind(Pair<EmojiReactionsStatusDisplayItem, EmojiReaction> item){
|
||||
if(item.second.pendingChange){
|
||||
itemView.setVisibility(View.GONE);
|
||||
return;
|
||||
}else{
|
||||
itemView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
item.first.setActionProgressVisible(this, false);
|
||||
EmojiReactionsStatusDisplayItem parent=item.first;
|
||||
EmojiReaction reaction=item.second;
|
||||
@@ -369,10 +522,25 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
btn.setCompoundDrawablesRelative(item.first.placeholder, null, null, null);
|
||||
}
|
||||
btn.setSelected(reaction.me);
|
||||
if(parent.parentFragment.isInstanceIceshrimpJs() && reaction.name.contains("@")){
|
||||
btn.setEnabled(false);
|
||||
btn.setClickable(false);
|
||||
btn.setLongClickable(true);
|
||||
}else{
|
||||
btn.setEnabled(true);
|
||||
btn.setClickable(true);
|
||||
}
|
||||
btn.setOnClickListener(e->{
|
||||
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
|
||||
Instance instance = adapter.parentHolder.instance;
|
||||
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0 &&
|
||||
adapter.parentHolder.meReactionCount >= instance.configuration.reactions.maxReactions &&
|
||||
!reaction.me){
|
||||
return;
|
||||
}
|
||||
|
||||
boolean deleting=reaction.me;
|
||||
parent.createRequest(reaction.name, reaction.count, deleting, this, ()->{
|
||||
EmojiReactionsAdapter adapter = (EmojiReactionsAdapter) getBindingAdapter();
|
||||
parent.createRequest(reaction.name, reaction.count, deleting, this, (status)->{
|
||||
for(int i=0; i<parent.status.reactions.size(); i++){
|
||||
EmojiReaction r=parent.status.reactions.get(i);
|
||||
if(!r.name.equals(reaction.name)) continue;
|
||||
@@ -392,6 +560,14 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
|
||||
adapter.parentHolder.root.setVisibility(View.GONE);
|
||||
adapter.parentHolder.line.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(instance.configuration!=null && instance.configuration.reactions!=null && instance.configuration.reactions.maxReactions!=0){
|
||||
adapter.parentHolder.updateMeReactionCount(deleting);
|
||||
}
|
||||
if(instance.isIceshrimpJs() && status!=null){
|
||||
parent.parentFragment.onFavoriteChanged(status, adapter.parentHolder.getItemID());
|
||||
E.post(new StatusCountersUpdatedEvent(status));
|
||||
}
|
||||
E.post(new EmojiReactionsUpdatedEvent(parent.status.id, parent.status.reactions, parent.status.reactions.isEmpty(), adapter.parentHolder));
|
||||
adapter.parentHolder.imgLoader.updateImages();
|
||||
}, null).exec(parent.parentFragment.getAccountID());
|
||||
|
||||
@@ -39,6 +39,16 @@ public class ErrorStatusDisplayItem extends StatusDisplayItem{
|
||||
findViewById(R.id.button_copy_error_details).setOnClickListener(this::copyErrorDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
// explicitly do nothing when clicked
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ErrorStatusDisplayItem item) {
|
||||
openInBrowserButton.setEnabled(item.status!=null && item.status.url!=null);
|
||||
|
||||
@@ -46,7 +46,6 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
public final Status status;
|
||||
private final String accountID;
|
||||
public boolean hideCounts;
|
||||
|
||||
@@ -316,17 +315,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
UiUtils.opacityIn(v);
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", item.accountID);
|
||||
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(item.accountID);
|
||||
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
|
||||
if(instance.pleroma == null){
|
||||
Instance instance=AccountSessionManager.get(item.accountID).getInstance().get();
|
||||
if(instance.isAkkoma() || instance.isIceshrimp()){
|
||||
args.putParcelable("quote", Parcels.wrap(item.status));
|
||||
}else{
|
||||
StringBuilder prefilledText = new StringBuilder().append("\n\n");
|
||||
String ownID = AccountSessionManager.getInstance().getAccount(item.accountID).self.id;
|
||||
if (!item.status.account.id.equals(ownID)) prefilledText.append('@').append(item.status.account.acct).append(' ');
|
||||
prefilledText.append(item.status.url);
|
||||
args.putString("prefilledText", prefilledText.toString());
|
||||
args.putInt("selectionStart", 0);
|
||||
}else{
|
||||
args.putParcelable("quote", Parcels.wrap(item.status));
|
||||
}
|
||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||
});
|
||||
@@ -335,6 +333,20 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onFavoriteClick() {
|
||||
favorite.setSelected(item.status.favourited);
|
||||
favorite.animate().scaleX(0.95f).scaleY(0.95f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
|
||||
UiUtils.opacityOut(favorite);
|
||||
favorite.postDelayed(() -> {
|
||||
favorite.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
|
||||
UiUtils.opacityIn(favorite);
|
||||
if(item.status.favourited && !GlobalUserPreferences.reduceMotion && !GlobalUserPreferences.likeIcon) {
|
||||
favorite.startAnimation(spin);
|
||||
}
|
||||
}, 300);
|
||||
bindText(favorites, item.status.favouritesCount);
|
||||
}
|
||||
|
||||
private void onFavoriteClick(View v){
|
||||
if(item.status.preview) return;
|
||||
applyInteraction(v, status -> {
|
||||
|
||||
@@ -113,7 +113,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static HeaderStatusDisplayItem fromAnnouncement(Announcement a, Status fakeStatus, Account instanceUser, BaseStatusListFragment parentFragment, String accountID, Consumer<String> consumeReadID) {
|
||||
HeaderStatusDisplayItem item = new HeaderStatusDisplayItem(a.id, instanceUser, a.startsAt, parentFragment, accountID, fakeStatus, null, null, null);
|
||||
HeaderStatusDisplayItem item = new HeaderStatusDisplayItem(a.id, instanceUser, a.startsAt!=null ? a.startsAt : fakeStatus.createdAt, parentFragment, accountID, fakeStatus, null, null, null);
|
||||
item.announcement = a;
|
||||
item.consumeReadAnnouncement = consumeReadID;
|
||||
return item;
|
||||
@@ -279,7 +279,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
Toast.makeText(activity, activity.getString(rel.following ? R.string.followed_user : rel.requested ? R.string.following_user_requested : R.string.unfollowed_user, account.getDisplayUsername()), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}else if(id==R.id.block_domain){
|
||||
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
|
||||
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account, relationship!=null && relationship.domainBlocking, ()->{});
|
||||
}else if(id==R.id.bookmark){
|
||||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
|
||||
}else if(id==R.id.manage_user_lists){
|
||||
@@ -422,7 +422,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
public void setImage(int index, Drawable drawable){
|
||||
if(index>0){
|
||||
item.emojiHelper.setImageDrawable(index-1, drawable);
|
||||
name.invalidate();
|
||||
name.setText(name.getText());
|
||||
}else{
|
||||
avatar.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
avatar.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-1, image);
|
||||
text.invalidate();
|
||||
text.setText(text.getText());
|
||||
}
|
||||
if(image instanceof Animatable)
|
||||
((Animatable) image).start();
|
||||
|
||||
@@ -136,7 +136,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
item.emojiHelper.setImageDrawable(index, image);
|
||||
text.invalidate();
|
||||
text.setText(text.getText());
|
||||
if(image instanceof Animatable){
|
||||
((Animatable) image).start();
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
item.emojiHelper.setImageDrawable(index, null);
|
||||
text.invalidate();
|
||||
text.setText(text.getText());
|
||||
}
|
||||
|
||||
private void onButtonClick(View v){
|
||||
|
||||
@@ -152,8 +152,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||
int firstHelperCount=item.emojiHelper.getImageCount();
|
||||
CustomEmojiHelper helper=index<firstHelperCount ? item.emojiHelper : item.extra.emojiHelper;
|
||||
helper.setImageDrawable(firstHelperCount>0 ? index%firstHelperCount : index, image);
|
||||
text.invalidate();
|
||||
extraText.invalidate();
|
||||
text.setText(text.getText());
|
||||
extraText.setText(extraText.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -114,7 +114,7 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
item.emojiHelper.setImageDrawable(index, image);
|
||||
title.invalidate();
|
||||
title.setText(title.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,8 +17,10 @@ import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||
@@ -35,6 +37,7 @@ import org.joinmastodon.android.model.FilterResult;
|
||||
import org.joinmastodon.android.model.LegacyFilter;
|
||||
import org.joinmastodon.android.model.Notification;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.ScheduledStatus;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
@@ -87,7 +90,7 @@ public abstract class StatusDisplayItem{
|
||||
|
||||
|
||||
private final static Pattern QUOTE_MENTION_PATTERN=Pattern.compile("(?:<p>)?\\s?(?:RE:\\s?(<br\\s?\\/?>)?)?<a href=\"https:\\/\\/[^\"]+\"[^>]*><span class=\"invisible\">https:\\/\\/<\\/span><span class=\"ellipsis\">[^<]+<\\/span><span class=\"invisible\">[^<]+<\\/span><\\/a>(?:<\\/p>)?$");
|
||||
private final static Pattern QUOTE_PATTERN=Pattern.compile("[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$");
|
||||
private final static Pattern QUOTE_PATTERN=Pattern.compile("https://[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,8}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$");
|
||||
|
||||
public void setAncestryInfo(
|
||||
boolean hasDescendantNeighbor,
|
||||
@@ -180,6 +183,11 @@ public abstract class StatusDisplayItem{
|
||||
try{
|
||||
ScheduledStatus scheduledStatus=parentObject instanceof ScheduledStatus s ? s : null;
|
||||
|
||||
// Check if account is null. This should never happen, but it seems to do in latest versions of glitch-soc
|
||||
if (scheduledStatus == null && status.account == null || (status.reblog != null && status.reblog.account == null) || (status.quote != null && status.quote.account == null)) {
|
||||
throw new Exception("status " + status.url + " has null account field");
|
||||
}
|
||||
|
||||
HeaderStatusDisplayItem header=null;
|
||||
boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts;
|
||||
|
||||
@@ -271,8 +279,10 @@ public abstract class StatusDisplayItem{
|
||||
contentItems=items;
|
||||
}
|
||||
|
||||
if(statusForContent.quote!=null){
|
||||
if(statusForContent.quote!=null) {
|
||||
int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:");
|
||||
if(quoteInlineIndex==-1)
|
||||
quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br><br>RE:");
|
||||
if(quoteInlineIndex!=-1)
|
||||
statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
|
||||
else {
|
||||
@@ -338,8 +348,8 @@ public abstract class StatusDisplayItem{
|
||||
if(!statusForContent.mediaAttachments.isEmpty() && statusForContent.poll==null) // add spacing if immediately preceded by attachment
|
||||
contentItems.add(new DummyStatusDisplayItem(parentID, fragment));
|
||||
contentItems.addAll(buildItems(fragment, statusForContent.quote, accountID, parentObject, knownAccounts, filterContext, FLAG_NO_FOOTER|FLAG_INSET|FLAG_NO_EMOJI_REACTIONS|FLAG_IS_FOR_QUOTE));
|
||||
} else if((flags & FLAG_INSET)==0){
|
||||
tryAddNonOfficialQuote(statusForContent, fragment, accountID);
|
||||
} else if((flags & FLAG_INSET)==0 && statusForContent.mediaAttachments.isEmpty() && statusForContent.account!=null){
|
||||
tryAddNonOfficialQuote(statusForContent, fragment, accountID, filterContext);
|
||||
}
|
||||
if(contentItems!=items && statusForContent.spoilerRevealed){
|
||||
items.addAll(contentItems);
|
||||
@@ -421,29 +431,55 @@ public abstract class StatusDisplayItem{
|
||||
* Tries to adds a non-official quote to a status.
|
||||
* A non-official quote is a quote on an instance that does not support quotes officially.
|
||||
*/
|
||||
private static void tryAddNonOfficialQuote(Status status, BaseStatusListFragment fragment, String accountID) {
|
||||
private static void tryAddNonOfficialQuote(Status status, BaseStatusListFragment fragment, String accountID, FilterContext filterContext) {
|
||||
Matcher matcher=QUOTE_PATTERN.matcher(status.getStrippedText());
|
||||
|
||||
if(!matcher.find())
|
||||
return;
|
||||
String quoteURL="https://"+matcher.group();
|
||||
String quoteURL=matcher.group();
|
||||
|
||||
if (UiUtils.looksLikeFediverseUrl(quoteURL)) {
|
||||
new GetSearchResults(quoteURL, GetSearchResults.Type.STATUSES, true, null, 0, 0).setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults results){
|
||||
if (!results.statuses.isEmpty()){
|
||||
status.quote=results.statuses.get(0);
|
||||
fragment.updateStatusWithQuote(status);
|
||||
}
|
||||
}
|
||||
// account may be null for scheduled posts
|
||||
if (!UiUtils.looksLikeFediverseUrl(quoteURL))
|
||||
return;
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
Log.w("StatusDisplayItem", "onError: failed to find quote status with URL: " + quoteURL + " " + error);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
new GetSearchResults(quoteURL, GetSearchResults.Type.STATUSES, true, null, 0, 0).setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SearchResults results){
|
||||
AccountSessionManager.get(accountID).filterStatuses(results.statuses, filterContext);
|
||||
if (results.statuses == null || results.statuses.isEmpty())
|
||||
return;
|
||||
|
||||
Status quote=results.statuses.get(0);
|
||||
new GetAccountRelationships(Collections.singletonList(quote.account.id))
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Relationship> relationships){
|
||||
if(relationships.isEmpty())
|
||||
return;
|
||||
|
||||
Relationship relationship=relationships.get(0);
|
||||
String selfId=AccountSessionManager.get(accountID).self.id;
|
||||
if(!status.account.id.equals(selfId) && (relationship.domainBlocking || relationship.muting || relationship.blocking)) {
|
||||
// do not show posts that are quoting a muted/blocked user
|
||||
fragment.removeStatus(status);
|
||||
return;
|
||||
}
|
||||
|
||||
status.quote=results.statuses.get(0);
|
||||
fragment.updateStatusWithQuote(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
Log.w("StatusDisplayItem", "onError: failed to find quote status with URL: " + quoteURL + " " + error);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
public enum Type{
|
||||
|
||||
@@ -173,7 +173,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
@Override
|
||||
public void setImage(int index, Drawable image){
|
||||
getEmojiHelper().setImageDrawable(index, image);
|
||||
text.invalidate();
|
||||
text.setText(text.getText());
|
||||
if(image instanceof Animatable){
|
||||
((Animatable) image).start();
|
||||
if(image instanceof MovieDrawable)
|
||||
@@ -184,7 +184,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
@Override
|
||||
public void clearImage(int index){
|
||||
getEmojiHelper().setImageDrawable(index, null);
|
||||
text.invalidate();
|
||||
text.setText(text.getText());
|
||||
}
|
||||
|
||||
private CustomEmojiHelper getEmojiHelper(){
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
@@ -15,6 +16,7 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.drawables.EmptyDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.AutoOrientationLinearLayout;
|
||||
@@ -23,6 +25,11 @@ import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
import me.grishka.appkit.views.BottomSheet;
|
||||
|
||||
@@ -108,6 +115,51 @@ public abstract class AccountRestrictionConfirmationSheet extends BottomSheet{
|
||||
addRow(icon, getContext().getString(text));
|
||||
}
|
||||
|
||||
public void addDurationRow(@NonNull Context context, AtomicReference<Duration> muteDuration) {
|
||||
//Moshidon: add row to choose a duration, e.g. for muting accounts
|
||||
Button muteDurationBtn=new Button(getContext());
|
||||
muteDurationBtn.setOnClickListener(v->getMuteDurationDialog(context, muteDuration, muteDurationBtn).show());
|
||||
muteDurationBtn.setText(R.string.sk_duration_indefinite);
|
||||
addRow(R.drawable.ic_fluent_clock_20_regular, R.string.sk_mute_label, muteDurationBtn);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private M3AlertDialogBuilder getMuteDurationDialog(@NonNull Context context, AtomicReference<Duration> muteDuration, Button button){
|
||||
M3AlertDialogBuilder builder=new M3AlertDialogBuilder(context);
|
||||
builder.setTitle(R.string.sk_mute_label);
|
||||
builder.setIcon(R.drawable.ic_fluent_clock_20_regular);
|
||||
List<Duration> durations =List.of(Duration.ZERO,
|
||||
Duration.ofMinutes(5),
|
||||
Duration.ofMinutes(30),
|
||||
Duration.ofHours(1),
|
||||
Duration.ofHours(6),
|
||||
Duration.ofDays(1),
|
||||
Duration.ofDays(3),
|
||||
Duration.ofDays(7),
|
||||
Duration.ofDays(7));
|
||||
|
||||
String[] choices = {context.getString(R.string.sk_duration_indefinite),
|
||||
context.getString(R.string.sk_duration_minutes_5),
|
||||
context.getString(R.string.sk_duration_minutes_30),
|
||||
context.getString(R.string.sk_duration_hours_1),
|
||||
context.getString(R.string.sk_duration_hours_6),
|
||||
context.getString(R.string.sk_duration_days_1),
|
||||
context.getString(R.string.sk_duration_days_3),
|
||||
context.getString(R.string.sk_duration_days_7)};
|
||||
|
||||
builder.setSingleChoiceItems(choices, durations.indexOf(muteDuration.get()), (dialog, which) -> {});
|
||||
|
||||
builder.setPositiveButton(R.string.ok, (dialog, which)->{
|
||||
int selected = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||
muteDuration.set(durations.get(selected));
|
||||
button.setText(choices[selected]);
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
public interface ConfirmCallback{
|
||||
void onConfirmed(Runnable onSuccess, Runnable onError);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class BlockDomainConfirmationSheet extends AccountRestrictionConfirmationSheet{
|
||||
public BlockDomainConfirmationSheet(@NonNull Context context, Account user, ConfirmCallback confirmCallback, ConfirmCallback blockUserConfirmCallback){
|
||||
super(context, user, confirmCallback);
|
||||
titleView.setText(R.string.block_domain_confirm_title);
|
||||
confirmBtn.setText(R.string.do_block_server);
|
||||
secondaryBtn.setText(context.getString(R.string.block_user_x_instead, user.getDisplayUsername()));
|
||||
icon.setImageResource(R.drawable.ic_fluent_shield_24_regular);
|
||||
subtitleView.setText(user.getDomain());
|
||||
addRow(R.drawable.ic_campaign_24px, R.string.users_cant_see_blocked);
|
||||
addRow(R.drawable.ic_fluent_eye_off_24_regular, R.string.you_wont_see_server_posts);
|
||||
addRow(R.drawable.ic_fluent_person_delete_24_regular, R.string.server_followers_will_be_removed);
|
||||
addRow(R.drawable.ic_fluent_arrow_reply_24_regular, R.string.server_cant_mention_or_follow_you);
|
||||
addRow(R.drawable.ic_fluent_history_24_regular, R.string.server_can_interact_with_older);
|
||||
|
||||
secondaryBtn.setOnClickListener(v->{
|
||||
if(loading)
|
||||
return;
|
||||
loading=true;
|
||||
secondaryBtn.setProgressBarVisible(true);
|
||||
blockUserConfirmCallback.onConfirmed(this::dismiss, ()->{
|
||||
secondaryBtn.setProgressBarVisible(false);
|
||||
loading=false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,10 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Typeface;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.views.M3Switch;
|
||||
|
||||
import java.time.Duration;
|
||||
@@ -44,59 +35,6 @@ public class MuteAccountConfirmationSheet extends AccountRestrictionConfirmation
|
||||
addRow(R.drawable.ic_fluent_alert_off_24_regular, R.string.mo_mute_notifications, m3Switch);
|
||||
|
||||
// add mute duration (Moshidon)
|
||||
secondaryBtn.setVisibility(View.VISIBLE);
|
||||
secondaryBtn.setOnClickListener(v->getMuteDurationDialog(context, muteDuration, secondaryBtn).show());
|
||||
secondaryBtn.setText(R.string.sk_duration_indefinite);
|
||||
secondaryBtn.setTypeface(null, Typeface.BOLD_ITALIC);
|
||||
addDurationRow(context, muteDuration);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private M3AlertDialogBuilder getMuteDurationDialog(@NonNull Context context, AtomicReference<Duration> muteDuration, Button button){
|
||||
M3AlertDialogBuilder builder=new M3AlertDialogBuilder(context);
|
||||
builder.setTitle(R.string.sk_mute_label);
|
||||
builder.setIcon(R.drawable.ic_fluent_clock_20_regular);
|
||||
|
||||
String[] choices = {context.getString(R.string.sk_duration_indefinite),
|
||||
context.getString(R.string.sk_duration_minutes_5),
|
||||
context.getString(R.string.sk_duration_minutes_30),
|
||||
context.getString(R.string.sk_duration_hours_1),
|
||||
context.getString(R.string.sk_duration_hours_6),
|
||||
context.getString(R.string.sk_duration_days_1),
|
||||
context.getString(R.string.sk_duration_days_3),
|
||||
context.getString(R.string.sk_duration_days_7)};
|
||||
|
||||
builder.setSingleChoiceItems(choices, 0, (dialog, which) -> {});
|
||||
|
||||
builder.setPositiveButton(R.string.ok, (dialog, which)->{
|
||||
int selected = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||
if(selected==0){
|
||||
muteDuration.set(Duration.ZERO);
|
||||
}else if(selected==1){
|
||||
muteDuration.set(Duration.ofMinutes(5));
|
||||
}else if(selected==2){
|
||||
muteDuration.set(Duration.ofMinutes(30));
|
||||
}else if(selected==3){
|
||||
muteDuration.set(Duration.ofHours(1));
|
||||
}else if(selected==4){
|
||||
muteDuration.set(Duration.ofHours(6));
|
||||
}else if(selected==5){
|
||||
muteDuration.set(Duration.ofDays(1));
|
||||
}else if(selected==6){
|
||||
muteDuration.set(Duration.ofDays(3));
|
||||
}else if(selected==7){
|
||||
muteDuration.set(Duration.ofDays(7));
|
||||
}
|
||||
if(selected >= 0 && selected <= 7){
|
||||
button.setText(choices[selected]);
|
||||
} else {
|
||||
Toast.makeText(context, "" + selected, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.cancel, ((dialogInterface, i) -> {}));
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.joinmastodon.android.ui.sheets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
||||
// MOSHIDON
|
||||
public class MuteHashtagConfirmationSheet extends AccountRestrictionConfirmationSheet{
|
||||
public MuteHashtagConfirmationSheet(@NonNull Context context, Account user, AtomicReference<Duration> muteDuration, Hashtag hashtag, ConfirmCallback confirmCallback){
|
||||
super(context, user, confirmCallback);
|
||||
titleView.setText(R.string.mo_mute_hashtag);
|
||||
confirmBtn.setText(R.string.do_mute);
|
||||
secondaryBtn.setVisibility(View.GONE);
|
||||
icon.setImageResource(R.drawable.ic_fluent_speaker_off_24_regular);
|
||||
subtitleView.setText("#"+hashtag.name);
|
||||
addRow(R.drawable.ic_fluent_number_symbol_24_regular, R.string.mo_mute_hashtag_explanation_muted_home);
|
||||
addRow(R.drawable.ic_fluent_eye_off_24_regular, R.string.mo_mute_hashtag_explanation_discreet);
|
||||
addRow(R.drawable.ic_fluent_search_24_regular, R.string.mo_mute_hashtag_explanation_search);
|
||||
addDurationRow(context, muteDuration);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,8 @@ public class CustomEmojiSpan extends ReplacementSpan{
|
||||
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
|
||||
return Math.round(paint.descent()-paint.ascent());
|
||||
int size = Math.round(paint.descent()-paint.ascent());
|
||||
return drawable!=null ? (int) (drawable.getIntrinsicWidth()*(size/(float) drawable.getIntrinsicHeight())) : size;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,7 +46,8 @@ public class CustomEmojiSpan extends ReplacementSpan{
|
||||
}
|
||||
canvas.save();
|
||||
canvas.translate(x, top);
|
||||
canvas.scale(size/(float)dw, size/(float)dh, 0f, 0f);
|
||||
float scale = size/(float)dh;
|
||||
canvas.scale(scale, scale, 0f, 0f);
|
||||
drawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
@@ -56,7 +58,6 @@ public class CustomEmojiSpan extends ReplacementSpan{
|
||||
}
|
||||
|
||||
public UrlImageLoaderRequest createImageLoaderRequest(){
|
||||
int size=V.dp(20);
|
||||
return new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? emoji.url : emoji.staticUrl, size, size);
|
||||
return new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? emoji.url : emoji.staticUrl, 0, V.dp(20));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public class HtmlParser{
|
||||
String href=el.attr("href");
|
||||
LinkSpan.Type linkType;
|
||||
String text=el.text();
|
||||
if(el.hasClass("hashtag") || text.startsWith("#")){
|
||||
if(!TextUtils.isEmpty(text) && (el.hasClass("hashtag") || text.startsWith("#"))){
|
||||
// MOSHIDON: we have slightly refactored this so that the hashtags properly work in akkoma
|
||||
// TODO: upstream this
|
||||
linkType=LinkSpan.Type.HASHTAG;
|
||||
|
||||
@@ -123,6 +123,7 @@ import org.joinmastodon.android.ui.Snackbar;
|
||||
import org.joinmastodon.android.ui.sheets.AccountSwitcherSheet;
|
||||
import org.joinmastodon.android.ui.sheets.BlockAccountConfirmationSheet;
|
||||
import org.joinmastodon.android.ui.sheets.MuteAccountConfirmationSheet;
|
||||
import org.joinmastodon.android.ui.sheets.BlockDomainConfirmationSheet;
|
||||
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.utils.Tracking;
|
||||
@@ -417,7 +418,6 @@ public class UiUtils {
|
||||
CustomEmojiSpan[] spans = text.getSpans(0, text.length(), CustomEmojiSpan.class);
|
||||
if (spans.length == 0)
|
||||
return;
|
||||
int emojiSize = V.dp(20);
|
||||
Map<Emoji, List<CustomEmojiSpan>> spansByEmoji = Arrays.stream(spans).collect(Collectors.groupingBy(s -> s.emoji));
|
||||
for (Map.Entry<Emoji, List<CustomEmojiSpan>> emoji : spansByEmoji.entrySet()) {
|
||||
ViewImageLoader.load(new ViewImageLoader.Target() {
|
||||
@@ -428,14 +428,14 @@ public class UiUtils {
|
||||
for (CustomEmojiSpan span : emoji.getValue()) {
|
||||
span.setDrawable(d);
|
||||
}
|
||||
view.invalidate();
|
||||
view.setText(view.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView() {
|
||||
return view;
|
||||
}
|
||||
}, null, new UrlImageLoaderRequest(emoji.getKey().url, emojiSize, emojiSize), null, false, true);
|
||||
}, null, new UrlImageLoaderRequest(emoji.getKey().url, 0, V.dp(20)), null, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,27 +575,61 @@ public class UiUtils {
|
||||
);
|
||||
}
|
||||
|
||||
public static void confirmToggleBlockDomain(Activity activity, String accountID, String domain, boolean currentlyBlocked, Runnable resultCallback) {
|
||||
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_domain_title : R.string.confirm_block_domain_title),
|
||||
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, domain),
|
||||
activity.getString(currentlyBlocked ? R.string.do_unblock : R.string.do_block),
|
||||
R.drawable.ic_fluent_shield_28_regular,
|
||||
() -> {
|
||||
new SetDomainBlocked(domain, !currentlyBlocked)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
resultCallback.run();
|
||||
}
|
||||
public static void confirmToggleBlockDomain(Activity activity, String accountID, Account account, boolean currentlyBlocked, Runnable resultCallback){
|
||||
if(!currentlyBlocked){
|
||||
new BlockDomainConfirmationSheet(activity, account, (onSuccess, onError)->{
|
||||
new SetDomainBlocked(account.getDomain(), true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
resultCallback.run();
|
||||
onSuccess.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
});
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
onError.run();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}, (onSuccess, onError)->{
|
||||
new SetAccountBlocked(account.id, true)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Relationship result){
|
||||
resultCallback.run();
|
||||
onSuccess.run();
|
||||
E.post(new RemoveAccountPostsEvent(accountID, account.id, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
onError.run();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}).show();
|
||||
}else{
|
||||
new SetDomainBlocked(account.getDomain(), false)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Object result){
|
||||
resultCallback.run();
|
||||
new Snackbar.Builder(activity)
|
||||
.setText(activity.getString(R.string.unblocked_domain_x, account.getDomain()))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
error.showToast(activity);
|
||||
}
|
||||
})
|
||||
.wrapProgress(activity, R.string.loading, false)
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
public static void confirmToggleMuteUser(Context context, String accountID, Account account, boolean currentlyMuted, Consumer<Relationship> resultCallback){
|
||||
if(!currentlyMuted){
|
||||
@@ -974,6 +1008,9 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static <T> void updateList(List<T> oldList, List<T> newList, RecyclerView list, RecyclerView.Adapter<?> adapter, BiPredicate<T, T> areItemsSame) {
|
||||
RecyclerView.ItemAnimator animator=list.getItemAnimator();
|
||||
if(animator!=null)
|
||||
animator.endAnimations();
|
||||
// Save topmost item position and offset because for some reason RecyclerView would scroll the list to weird places when you insert items at the top
|
||||
int topItem, topItemOffset;
|
||||
if (list.getChildCount() == 0) {
|
||||
@@ -1184,6 +1221,10 @@ public class UiUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Akkoma somehow makes this necessary, because youtube links look like posts. And because it may trigger too many requests.
|
||||
if (uri.getHost().toLowerCase().contains("youtube.com") || uri.getHost().toLowerCase().contains("youtu.be"))
|
||||
return false;
|
||||
|
||||
if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -185,8 +185,8 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
avatar.setImageDrawable(image);
|
||||
}else{
|
||||
item.emojiHelper.setImageDrawable(index-1, image);
|
||||
name.invalidate();
|
||||
bio.invalidate();
|
||||
name.setText(name.getText());
|
||||
bio.setText(bio.getText());
|
||||
}
|
||||
|
||||
if(image instanceof Animatable a && !a.isRunning())
|
||||
@@ -312,7 +312,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
|
||||
}else if(id==R.id.open_in_browser){
|
||||
UiUtils.launchWebBrowser(fragment.getActivity(), account.url);
|
||||
}else if(id==R.id.block_domain){
|
||||
UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account.getDomain(), relationship.domainBlocking, ()->{
|
||||
UiUtils.confirmToggleBlockDomain(fragment.getActivity(), accountID, account, relationship.domainBlocking, ()->{
|
||||
relationship.domainBlocking=!relationship.domainBlocking;
|
||||
bindRelationship();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.joinmastodon.android.ui.views;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
public class EmojiReactionButton extends ProgressBarButton {
|
||||
private final Handler handler=new Handler();
|
||||
|
||||
public EmojiReactionButton(Context context){
|
||||
super(context);
|
||||
}
|
||||
|
||||
public EmojiReactionButton(Context context, AttributeSet attrs){
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public EmojiReactionButton(Context context, AttributeSet attrs, int defStyleAttr){
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// allow long click even if button is disabled
|
||||
int action=event.getAction();
|
||||
if(action==MotionEvent.ACTION_DOWN && !isEnabled())
|
||||
handler.postDelayed(this::performLongClick, ViewConfiguration.getLongPressTimeout());
|
||||
if(action==MotionEvent.ACTION_UP)
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class Tracking{
|
||||
@NonNull
|
||||
public static String removeTrackingParameters(@NonNull String url){
|
||||
Uri uri=Uri.parse(url);
|
||||
if(uri==null)
|
||||
if(uri==null || !uri.isHierarchical())
|
||||
return url;
|
||||
Uri.Builder uriBuilder=uri.buildUpon().clearQuery();
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.joinmastodon.android.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.unifiedpush.android.connector.UnifiedPush;
|
||||
|
||||
public class UnifiedPushHelper {
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @return `true` if UnifiedPush is used
|
||||
*/
|
||||
public static boolean isUnifiedPushEnabled(@NonNull Context context) {
|
||||
return UnifiedPush.getAckDistributor(context) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If any distributor is installed on the device
|
||||
* @param context
|
||||
* @return `true` if at least one is installed
|
||||
*/
|
||||
public static boolean hasAnyDistributorInstalled(@NonNull Context context) {
|
||||
return !UnifiedPush.getDistributors(context).isEmpty();
|
||||
}
|
||||
|
||||
public static void registerAllAccounts(@NonNull Context context) {
|
||||
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
// Sometimes this is null when the account's server has died (don't ask me how I know this)
|
||||
if (accountSession.app.vapidKey == null) {
|
||||
// TODO: throw this on a translatable string and tell the user to log out and back in
|
||||
Toast.makeText(context, "Error on unified push subscription: no valid vapid key for account " + accountSession.getFullUsername(), Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
UnifiedPush.register(
|
||||
context,
|
||||
accountSession.getID(),
|
||||
null,
|
||||
accountSession.app.vapidKey.replaceAll("=","")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void unregisterAllAccounts(@NonNull Context context) {
|
||||
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||
UnifiedPush.unregister(
|
||||
context,
|
||||
accountSession.getID()
|
||||
);
|
||||
// use FCM again
|
||||
accountSession.getPushSubscriptionManager().registerAccountForPush(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M17.75,2.007a2.25,2.25 0,0 1,2.245 2.096l0.005,0.154v15.498A2.25,2.25 0,0 1,17.904 22l-0.154,0.005H6.25a2.25,2.25 0,0 1,-2.245 -2.096L4,19.755V4.257a2.25,2.25 0,0 1,2.096 -2.245l0.154,-0.005h11.5ZM7.75,7a0.75,0.75 0,1 0,0 1.5h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5ZM7,11.75c0,0.414 0.336,0.75 0.75,0.75h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5a0.75,0.75 0,0 0,-0.75 0.75ZM7.75,15a0.75,0.75 0,1 0,0 1.5h8.5a0.75,0.75 0,0 0,0 -1.5h-8.5Z"
|
||||
android:fillColor="#212121"/>
|
||||
</vector>
|
||||
3
mastodon/src/main/res/drawable/ic_gnome_logo.xml
Normal file
3
mastodon/src/main/res/drawable/ic_gnome_logo.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="103.97" android:viewportWidth="85.6" android:width="19.759546dp">
|
||||
<path android:fillColor="@color/fluent_default_icon_tint" android:pathData="m74.46,0c-11.52,0 -18.66,8.37 -20.99,16.54 -1.17,4.08 -1.28,8.19 -0.1,11.64 1.18,3.45 4.25,6.36 8.26,6.36 4,0 7.83,-2.21 11.44,-5.17 3.61,-2.95 6.93,-6.79 9.28,-10.75 2.35,-3.96 3.91,-8.07 2.96,-12.02 -0.48,-1.97 -1.78,-3.83 -3.68,-4.96 -1.91,-1.13 -4.29,-1.63 -7.17,-1.63zM40.1,4c-2.84,0.56 -5.36,2.67 -6.65,5.04 -1.48,2.71 -1.87,5.83 -1.68,8.83 0.19,3 0.98,5.9 2.28,8.28 1.29,2.38 3.22,4.58 6.17,4.76 1.58,0.1 2.97,-0.57 4.03,-1.41 1.06,-0.84 1.92,-1.9 2.67,-3.11 1.52,-2.4 2.64,-5.4 3.26,-8.47 0.62,-3.08 0.76,-6.22 -0.2,-9.03 -0.96,-2.81 -4,-5.3 -7.22,-5.3h-0c-1.04,0.07 -1.77,0.19 -2.65,0.42zM74.46,5.29c2.25,0 3.68,0.42 4.46,0.88 0.78,0.47 1.06,0.89 1.24,1.66 0.37,1.53 -0.39,4.73 -2.37,8.08 -1.98,3.35 -4.99,6.82 -8.08,9.35 -2.76,2.26 -5.64,3.65 -7.48,3.92 -0.22,0.03 -0.42,0.05 -0.6,0.05 -1.77,0 -2.55,-0.75 -3.25,-2.78 -0.7,-2.04 -0.74,-5.24 0.18,-8.46 1.84,-6.45 6.89,-12.7 15.9,-12.7zM42.74,8.87c1.55,0 1.76,0.34 2.24,1.72 0.47,1.38 0.51,3.81 0.02,6.28 -0.49,2.47 -1.48,5 -2.55,6.69 -0.53,0.84 -1.09,1.47 -1.48,1.78 -0.39,0.31 -0.52,0.27 -0.42,0.28 -0.12,-0.01 -1.01,-0.46 -1.85,-2 -0.84,-1.54 -1.5,-3.83 -1.64,-6.09 -0.15,-2.26 0.23,-4.46 1.04,-5.95 0.81,-1.49 1.85,-2.38 3.97,-2.66 0.26,-0.03 0.48,-0.05 0.68,-0.05zM22.67,11.53c-0.99,0.02 -1.98,0.25 -2.95,0.66 -3.04,1.25 -4.97,3.81 -5.62,6.49 -0.65,2.68 -0.26,5.43 0.59,7.93 0.85,2.5 2.17,4.78 3.82,6.51 1.65,1.74 3.85,3.23 6.54,2.69 2.76,-0.56 3.98,-2.91 4.74,-5.16 0.76,-2.25 1.07,-4.85 0.98,-7.46 -0.08,-2.61 -0.54,-5.22 -1.7,-7.46 -1.16,-2.24 -3.49,-4.26 -6.4,-4.2zM22.79,16.82c0.69,-0.02 0.98,0.18 1.59,1.34 0.6,1.16 1.04,3.15 1.11,5.19 0.06,2.04 -0.23,4.17 -0.71,5.61 -0.48,1.44 -1.14,1.73 -0.78,1.66h-0v0c0.09,-0.02 -0.65,-0.1 -1.64,-1.15 -1,-1.05 -2.04,-2.77 -2.65,-4.57 -0.61,-1.8 -0.78,-3.65 -0.46,-4.98 0.32,-1.33 0.89,-2.18 2.49,-2.85 0.48,-0.2 0.82,-0.25 1.05,-0.26zM8.02,23.93c-1.45,-0.05 -2.92,0.46 -4.2,1.35 -2.49,1.72 -3.76,4.44 -3.82,6.99 -0.06,2.55 0.82,4.93 2.06,7 1.24,2.07 2.86,3.84 4.68,5.08 1.82,1.24 4.13,2.19 6.52,1.14 2.34,-1.03 3.02,-3.29 3.3,-5.32 0.28,-2.03 0.12,-4.26 -0.35,-6.46 -0.47,-2.2 -1.26,-4.37 -2.49,-6.19 -1.23,-1.82 -3.14,-3.5 -5.69,-3.58zM7.85,29.22c0.28,0.01 0.79,0.24 1.48,1.26 0.69,1.02 1.34,2.66 1.7,4.34 0.36,1.69 0.45,3.44 0.29,4.63 -0.16,1.13 -0.54,1.34 -0.2,1.19 0.19,-0.09 -0.37,0.03 -1.4,-0.67 -1.04,-0.7 -2.26,-1.99 -3.12,-3.43 -0.86,-1.43 -1.34,-2.99 -1.31,-4.15 0.03,-1.16 0.31,-1.92 1.54,-2.77 0.58,-0.4 0.86,-0.42 1.02,-0.42zM50.43,33.42c-8.43,-0.14 -18.01,1.86 -26.16,6.06 -8.15,4.21 -15,10.78 -17.01,19.79 -2.21,9.88 2.23,20.9 9.71,29.56 7.47,8.66 18.2,15.14 29.59,15.14 11.79,0 24.28,-9.92 26.76,-23.02v-0c0.3,-1.6 0.09,-3.25 -0.67,-4.57 -0.76,-1.32 -1.94,-2.22 -3.18,-2.8 -2.48,-1.16 -5.34,-1.3 -8.18,-1.01 -2.84,0.29 -5.66,1.05 -7.96,2.31 -1.15,0.63 -2.2,1.38 -3.03,2.42 -0.83,1.04 -1.41,2.53 -1.21,4.06 0.36,2.73 -0.54,4.08 -1.64,4.68 -1.1,0.6 -2.9,0.67 -5.28,-1.14 -2.11,-1.61 -2.94,-2.91 -3.16,-3.72 -0.22,-0.82 -0.11,-1.43 0.61,-2.51 1.44,-2.17 5.65,-5.13 10.54,-8.16 4.88,-3.02 10.41,-6.27 14.7,-10.25 4.29,-3.98 7.5,-9.12 6.48,-15.17 -0.68,-4.07 -3.67,-7.1 -7.43,-8.92 -3.76,-1.82 -8.41,-2.66 -13.47,-2.74zM50.34,38.71c4.5,0.08 8.5,0.88 11.26,2.22 2.76,1.33 4.16,2.94 4.51,5.03 0.65,3.84 -1.21,7.02 -4.86,10.41 -3.65,3.39 -8.94,6.56 -13.88,9.63 -4.95,3.06 -9.59,5.86 -12.16,9.73 -1.29,1.93 -1.97,4.42 -1.31,6.84 0.67,2.43 2.42,4.52 5.06,6.53 3.59,2.73 7.82,3.34 11.04,1.57 3.22,-1.77 4.9,-5.68 4.34,-10.01 0.01,0.06 -0.08,0.15 0.11,-0.09 0.19,-0.24 0.68,-0.67 1.41,-1.06 1.45,-0.79 3.75,-1.47 5.96,-1.69 2.21,-0.23 4.36,0.05 5.4,0.54 0.52,0.24 0.74,0.48 0.83,0.64 0.09,0.16 0.18,0.34 0.06,0.97 -1.93,10.22 -12.95,18.71 -21.56,18.71 -9.34,0 -18.91,-5.57 -25.58,-13.3 -6.67,-7.73 -10.22,-17.45 -8.54,-24.95 1.57,-7.04 7.03,-12.5 14.27,-16.24 7.24,-3.74 16.14,-5.6 23.64,-5.47z"/>
|
||||
</vector>
|
||||
@@ -567,6 +567,7 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:textColor="?colorM3OnSurface"
|
||||
android:visibility="gone"
|
||||
tools:text="500"/>
|
||||
|
||||
<ImageButton
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
android:indeterminate="true"
|
||||
android:outlineProvider="none"
|
||||
android:visibility="gone"/>
|
||||
<org.joinmastodon.android.ui.views.ProgressBarButton
|
||||
<org.joinmastodon.android.ui.views.EmojiReactionButton
|
||||
android:id="@+id/btn"
|
||||
style="@style/Widget.Mastodon.M3.Button.Outlined.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/open_link"
|
||||
android:icon="@drawable/ic_fluent_document_one_page_24_filled"
|
||||
android:icon="@drawable/ic_fluent_open_24_regular"
|
||||
android:showAsAction="always"
|
||||
android:title="@string/mo_trending_link_read"/>
|
||||
</menu>
|
||||
@@ -94,4 +94,15 @@
|
||||
<string name="mo_swap_bookmark_with_reblog">استبدال إجراء الإضافة إلى الفواصل المرجعية بإعادة النشر</string>
|
||||
<string name="mo_setting_haptic_feedback_summary">اهتز عند التفاعل مع المنشورات</string>
|
||||
<string name="mo_notification_filter_reset">إعادة التعيين إلى الإفتراضية</string>
|
||||
<string name="mo_instance_view_info">إظهار معلومات الخادم</string>
|
||||
<string name="mo_settings_remove_tracking_params">روابط ذات خصوصية</string>
|
||||
<string name="mo_personal_note_saved">تم حفظ الملاحظة</string>
|
||||
<string name="mo_settings_unifiedpush_enable">تمكين</string>
|
||||
<string name="import_settings_failed">فشل في استيراد الإعدادات</string>
|
||||
<string name="export_settings_share">تصدير الإعدادات</string>
|
||||
<string name="export_settings_fail">فشل في تصدير الإعدادات</string>
|
||||
<string name="export_settings_title">تصدير الإعدادات</string>
|
||||
<string name="import_settings_title">استيراد الإعدادات</string>
|
||||
<string name="mo_error_display_copy_error_details">نسخ التفاصيل</string>
|
||||
<string name="mo_trending_link_read">قراءة</string>
|
||||
</resources>
|
||||
@@ -170,4 +170,23 @@
|
||||
<string name="sk_icon_microscope">مجهر</string>
|
||||
<string name="sk_delete_notification_confirm_action">احذف الإشعار</string>
|
||||
<string name="sk_notification_mention">أشار إليك %s</string>
|
||||
<string name="sk_settings_show_interaction_counts">إظهار عدد التفاعلات</string>
|
||||
<string name="sk_timeline_bubble">فقاعة</string>
|
||||
<string name="sk_do_remove_follower">حذف</string>
|
||||
<string name="sk_remove">حذف</string>
|
||||
<string name="sk_icon_bot">آلي</string>
|
||||
<string name="sk_icon_location">الموضع</string>
|
||||
<string name="sk_icon_laugh">ضحك</string>
|
||||
<string name="sk_icon_code">شفرة</string>
|
||||
<string name="sk_icon_bug">حشرة</string>
|
||||
<string name="sk_icon_pizza">بيتزا</string>
|
||||
<string name="sk_icon_gavel">مطرقة</string>
|
||||
<string name="sk_icon_feed">موجز حي</string>
|
||||
<string name="sk_icon_diamond">ألماس</string>
|
||||
<string name="sk_icon_umbrella">مطارية</string>
|
||||
<string name="sk_icon_water">ماء</string>
|
||||
<string name="sk_icon_sun">شمس</string>
|
||||
<string name="sk_icon_sunset">شروق الشمس</string>
|
||||
<string name="sk_icon_cloud">سحاب</string>
|
||||
<string name="sk_icon_rain">مطر</string>
|
||||
</resources>
|
||||
80
mastodon/src/main/res/values-ast/strings_mo.xml
Normal file
80
mastodon/src/main/res/values-ast/strings_mo.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="mo_hide_compose_button_while_scrolling_setting">Ancultar el botón d\'escritura mientres te desplaces</string>
|
||||
<string name="mo_settings_contribute">Contribuyir a Moshidon</string>
|
||||
<string name="mo_instance_status">Estáu</string>
|
||||
<string name="mo_mute_label">Duración:</string>
|
||||
<string name="mo_share_open_url">Abrir na aplicación</string>
|
||||
<string name="mo_duration_hours_6">6 hores</string>
|
||||
<string name="mo_instance_users">Usuarios</string>
|
||||
<string name="mo_setting_uniform_summary">Usar l\'iconu de la aplicación pa toles notificaciones</string>
|
||||
<string name="mo_settings_show_posts_without_alt">Amosar artículos con multimedia que-yos falta\'l testu alternativu</string>
|
||||
<string name="mo_settings_unifiedpush_warning_disabled">UnifiedPush nun ta habilitáu. Nun vas recibir nenguna notificación.</string>
|
||||
<string name="mo_trending_link_read">Leer</string>
|
||||
<string name="mo_settings_show_posts_without_alt_summary">Los artículos tarán ancultos en toles cronoloxíes, pero puen amosase nos filos y los avisos</string>
|
||||
<string name="mo_color_palette_nord">Nord</string>
|
||||
<string name="mo_color_palette_black_and_white">Blancu y Negru</string>
|
||||
<string name="mo_enable_dividers">Amosar divisores ente artículos</string>
|
||||
<string name="mo_relocate_publish_button">Mover el botón d\'espublizar</string>
|
||||
<string name="mo_welcome_text">Pa entamar, introduz el nome del dominiu de la to instancia embaxo, por favor.</string>
|
||||
<string name="mo_personal_note">Amestar una nota sobre esti perfil</string>
|
||||
<string name="mo_personal_note_saved">Nota guardada</string>
|
||||
<string name="mo_personal_note_confirm">Confirmar los cambeos a la nota</string>
|
||||
<string name="mo_update_available">Moshidon %s ta llistu pa descargar.</string>
|
||||
<string name="mo_update_ready">Moshidon %s ta descargáu y llistu pa instalar.</string>
|
||||
<string name="mo_no_image_desc_title">Ensin descripción de la semeya</string>
|
||||
<string name="mo_disable_reminder_to_add_alt_text">Desactivar el recordatoriu pa amestar el testu alternativu</string>
|
||||
<string name="mo_personal_note_update_failed">Fallu al guardar la nota</string>
|
||||
<string name="mo_emoji_recent">Recién usaos</string>
|
||||
<string name="mo_open_camera">Facer una semeya</string>
|
||||
<string name="mo_camera_not_available">Nun hai una cámara disponible!</string>
|
||||
<string name="mo_poll_option_add">Amestar una nueva opción a la encuesta</string>
|
||||
<string name="mo_fab_compose">Redactar</string>
|
||||
<string name="mo_sending_error">Erru espublizando</string>
|
||||
<string name="mo_duration_indefinite">Indefiníu</string>
|
||||
<string name="mo_duration_minutes_5">5 minutos</string>
|
||||
<string name="mo_duration_minutes_30">30 minutos</string>
|
||||
<string name="mo_duration_hours_1">1 hora</string>
|
||||
<string name="mo_duration_days_1">1 día</string>
|
||||
<string name="mo_duration_days_3">3 díes</string>
|
||||
<string name="mo_duration_days_7">7 díes</string>
|
||||
<string name="mo_instance_view_info">Ver versión del servidor</string>
|
||||
<string name="mo_instance_admin">Alministráu por</string>
|
||||
<string name="mo_instance_contact">Contautu</string>
|
||||
<string name="mo_instance_registration">Rexistru</string>
|
||||
<string name="mo_instance_registration_open">Abiertu</string>
|
||||
<string name="mo_instance_registration_approval">Requier aprobación</string>
|
||||
<string name="mo_instance_info_open_timeline">Cronoloxía llocal</string>
|
||||
<string name="mo_instance_info_moderated_servers">Servidores moderaos</string>
|
||||
<string name="mo_severity_suspend">Bloquiáu</string>
|
||||
<string name="mo_setting_true_black_summary">Podría aforrar enerxía en pantalles AMOLED</string>
|
||||
<string name="mo_setting_remote_follower_summary">Amosar siguidores dende otres instancies</string>
|
||||
<string name="mo_setting_relocate_publish_summary">Mover el botón d\'espublizar a la barra inferior</string>
|
||||
<string name="mo_setting_interaction_count_summary">Amosar cuánta xente interactuó con un artículu na cronoloxía</string>
|
||||
<string name="mo_setting_disable_swipe_summary">Eslizar pa camudar de cronoloxía</string>
|
||||
<string name="mo_swap_bookmark_with_reblog_summary">Meter en marcadores o impulsar artículos dende l\'avisu</string>
|
||||
<string name="mo_setting_haptic_feedback_summary">Vibrar cuando interactues colos artículos</string>
|
||||
<string name="mo_double_tap_to_swipe_between_tabs">Doble toque pa camudar ente pestañes</string>
|
||||
<string name="mo_haptic_feedback">Respuesta táctil</string>
|
||||
<string name="mo_show_media_preview">Amosar previsualización multimedia nes cronoloxíes</string>
|
||||
<string name="mo_settings_unifiedpush_warning">UnifiedPush nun ta habilitáu</string>
|
||||
<string name="mo_confirm_unfollow_title">Dexar de siguir</string>
|
||||
<string name="mo_confirm_unfollow">Confirma pa dexar de siguir a %s</string>
|
||||
<string name="mo_settings_unifiedpush_enable">Habilitar</string>
|
||||
<string name="mo_blocked_accounts">Cuentes bloquiaes</string>
|
||||
<string name="mo_settings_remove_tracking_params">Enllaces privaos</string>
|
||||
<string name="mo_add_custom_server_local_timeline">Amestar la cronoloxía llocal d\'otru sirvidor</string>
|
||||
<string name="mo_notification_action_replied">Respondisti al artículu de %s</string>
|
||||
<string name="mo_change_default_reply_visibility_to_unlisted">Responder como \'Non llistáu\' por defeutu</string>
|
||||
<string name="mo_notification_management_settings">Xestionar avisos</string>
|
||||
<string name="mo_composer_behavior">Comportamientu del editor</string>
|
||||
<string name="mo_load_remote_followers">Cargar los siguíos y siguidores del perfil remotu</string>
|
||||
<string name="export_settings_title">Esportar axustes</string>
|
||||
<string name="import_settings_title">Importar axustes</string>
|
||||
<string name="mo_error_display_title">Nun se pudo amosar l\'artículu</string>
|
||||
<string name="mo_error_display_copy_error_details">Copiar detalles</string>
|
||||
<string name="import_settings_confirm">¿Confirmes la importación de los axustes?</string>
|
||||
<string name="export_settings_share">Esportar Axustes</string>
|
||||
<string name="export_settings_fail">Nun se pudieron esportar los axustes</string>
|
||||
<string name="import_settings_failed">Nun se pudieron importar los axustes</string>
|
||||
</resources>
|
||||
336
mastodon/src/main/res/values-ast/strings_sk.xml
Normal file
336
mastodon/src/main/res/values-ast/strings_sk.xml
Normal file
@@ -0,0 +1,336 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sk_icon_weather">Tiempu</string>
|
||||
<string name="sk_settings_allow_remote_loading">Cargando información dende instancies remotes</string>
|
||||
<string name="sk_exclusive_list">Llista escluyente</string>
|
||||
<string name="sk_icon_bicycle">Bicicleta</string>
|
||||
<string name="sk_save_draft">¿Guardar el borrador?</string>
|
||||
<string name="sk_settings_default_content_type">Tipu de conteníu per defeutu</string>
|
||||
<string name="sk_search_fediverse">Buscar nel Fediversu</string>
|
||||
<string name="sk_settings_display_pronouns_in_user_listings">Amosar los pronomes nos llistaos d\'usuarios</string>
|
||||
<string name="sk_edit_alt_text">Editar el testu alternativu</string>
|
||||
<string name="sk_hashtag_timeline_local_only_switch">¿Amosar namás los artículos llocales?</string>
|
||||
<string name="sk_follow_as">Siguir dende otra cuenta</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<plurals name="sk_posts_count_label">
|
||||
<item quantity="one">artículu</item>
|
||||
<item quantity="other">artículos</item>
|
||||
</plurals>
|
||||
<string name="sk_timeline_home">Aniciu</string>
|
||||
<string name="sk_settings_show_labels_in_navigation_bar">Amosar etiquetes de pestañes na barra de navegación</string>
|
||||
<plurals name="sk_time_minutes">
|
||||
<item quantity="one">%d minutu</item>
|
||||
<item quantity="other">%d minutos</item>
|
||||
</plurals>
|
||||
<string name="sk_poll_show_results">Amosar resultaos</string>
|
||||
<string name="sk_announcements">Anuncios</string>
|
||||
<string name="sk_your_lists">Les tos llistes</string>
|
||||
<string name="sk_confirm_unpin_post">¿De xuru que quies desapegar esti artículu?</string>
|
||||
<string name="sk_create">Crear</string>
|
||||
<string name="sk_pinned_posts">Fixáu</string>
|
||||
<string name="sk_delete_and_redraft">Desaniciar y reeditar</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">Desaniciar y reeditar artículu</string>
|
||||
<string name="sk_pinning">Fixando l\'artículu…</string>
|
||||
<string name="sk_image_description">Descripción de la semeya</string>
|
||||
<string name="sk_settings_show_replies">Amosar respuestes</string>
|
||||
<string name="sk_quoting_user">Mentando %s</string>
|
||||
<string name="sk_settings_reply_visibility">Visibilidá de la respuesta</string>
|
||||
<string name="sk_settings_reply_visibility_all">Toles respuestes</string>
|
||||
<string name="sk_settings_reply_visibility_self">Respuestes pa mi</string>
|
||||
<string name="sk_settings_load_new_posts">Cargar artículos nuevos automáticamente</string>
|
||||
<string name="sk_settings_show_interaction_counts">Amosar la cuenta d\'interaiciones</string>
|
||||
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
|
||||
<string name="sk_visibility_unlisted">Non llistáu</string>
|
||||
<string name="sk_settings_show_boosts">Amosar impulsos</string>
|
||||
<string name="sk_unpin_post">Desapegar del perfil</string>
|
||||
<string name="sk_confirm_unpin_post_title">Desapegar artículu del perfil</string>
|
||||
<string name="sk_unpinning">Desapegando artículu…</string>
|
||||
<string name="sk_settings_continues_playback">Superposición d\'audiu</string>
|
||||
<string name="sk_user_post_notifications_on">Activaes les notificaciones d\'artículos pa %s</string>
|
||||
<string name="sk_user_post_notifications_off">Desactivaes les notificaciones d\'artículos pa %s</string>
|
||||
<string name="sk_federated_timeline">Federación</string>
|
||||
<string name="sk_trending_posts_info_banner">Estos artículos tan ganando tracción nel Fediversu.</string>
|
||||
<string name="sk_update_available">Megalodon %s ta llistu pa descargar.</string>
|
||||
<string name="sk_update_ready">Megalodon %s ta descargáu y llistu pa instalar.</string>
|
||||
<string name="sk_check_for_update">Buscar actualizaciones</string>
|
||||
<string name="sk_no_update_available">Nun hai actualizaciones disponibles</string>
|
||||
<string name="sk_accept_follow_request">Aceptar solicitúes de siguimientu</string>
|
||||
<string name="sk_reject_follow_request">Refugar solicitúes de siguimientu</string>
|
||||
<string name="sk_lists_with_user">Llistes con %s</string>
|
||||
<string name="sk_list_timelines">Llistes</string>
|
||||
<string name="sk_follow_requests">Solicitúes de siguimientu</string>
|
||||
<string name="sk_notification_type_status">Artículos</string>
|
||||
<string name="sk_notification_type_posts">Avisos d\'artículos</string>
|
||||
<string name="sk_settings_color_palette">Paleta de color</string>
|
||||
<string name="sk_settings_show_federated_timeline">Amosar la llínea del tiempu federada</string>
|
||||
<string name="sk_color_palette_material3">Sistema</string>
|
||||
<string name="sk_color_palette_pink">Rosa</string>
|
||||
<string name="sk_color_palette_purple">Morao</string>
|
||||
<string name="sk_color_palette_brown">Marrón</string>
|
||||
<string name="sk_color_palette_red">Coloráu</string>
|
||||
<string name="sk_color_palette_yellow">Mariellu</string>
|
||||
<string name="sk_translate_post">Traducir</string>
|
||||
<string name="sk_translate_show_original">Amosar l\'orixinal</string>
|
||||
<string name="sk_post_language">Idioma: %s</string>
|
||||
<string name="sk_language_name">%1$s (%2$s)</string>
|
||||
<string name="sk_clear_recent_languages">Desaniciar llingües usaes apocayá</string>
|
||||
<string name="sk_welcome_title">¡Bienveníu!</string>
|
||||
<string name="sk_welcome_text">¡El tiburón te saluda! Pa entamar, introduz el nome del dominiu de la to instancia embaxo.</string>
|
||||
<string name="sk_example_domain">exemplu.social</string>
|
||||
<string name="sk_settings_posting">Preferencies d\'espublizamientu</string>
|
||||
<string name="sk_settings_filters">Configurar filtros</string>
|
||||
<string name="sk_settings_auth">Configuraciones de seguridá</string>
|
||||
<string name="sk_settings_rules">Regles</string>
|
||||
<string name="sk_settings_profile">Configurar el perfil</string>
|
||||
<string name="sk_delete_notification_confirm_action">Desaniciar l\'avisu</string>
|
||||
<string name="sk_settings_donate">Donar</string>
|
||||
<string name="sk_settings_translation_availability_note_available">¡%s almite traducción!</string>
|
||||
<string name="sk_settings_translation_availability_note_unavailable">%s paez que nun almite traducción.</string>
|
||||
<string name="sk_undo_reblog">Desfaer impulsu</string>
|
||||
<string name="sk_clear_all_notifications">Desaniciar tolos avisos</string>
|
||||
<string name="sk_clear_all_notifications_confirm">¿De xuru que quies desaniciar tolos avisos?</string>
|
||||
<string name="sk_loading_fediverse_resource_title">Buscando nel Fediversu</string>
|
||||
<string name="sk_loading_resource_on_instance_title">Buscando en %s</string>
|
||||
<string name="sk_settings_publish_button_text_title">Personalizar el testu del botón d\'Espublizar</string>
|
||||
<string name="sk_reblog_with_visibility">Impulsa con visibilidá</string>
|
||||
<string name="sk_hashtags_you_follow">Etiquetes que sigues</string>
|
||||
<string name="sk_copy_link_to_post">Copiar l\'enllaz al artículu</string>
|
||||
<string name="sk_open_with_account">Abrir con otra cuenta</string>
|
||||
<string name="sk_resource_not_found">Nun pudo atopase el recursu</string>
|
||||
<string name="sk_quote_post">Espublizar sobre esto</string>
|
||||
<string name="sk_bookmark_as">Meter en marcadores con otra cuenta</string>
|
||||
<string name="sk_reblogged_as">Impulsáu como %s</string>
|
||||
<string name="sk_already_reblogged">Yá impulsáu</string>
|
||||
<string name="sk_reply_as">Responder con otra cuenta</string>
|
||||
<string name="sk_unsent_posts">Artículos non espublizáos</string>
|
||||
<string name="sk_draft">Borrador</string>
|
||||
<string name="sk_schedule">Programar</string>
|
||||
<string name="sk_confirm_delete_draft_title">Desaniciar borrador</string>
|
||||
<string name="sk_confirm_delete_draft">¿De xuru que quies desaniciar esti borrador?</string>
|
||||
<string name="sk_confirm_delete_scheduled_post">¿De xuru que quies desaniciar esti artículu programáu?</string>
|
||||
<string name="sk_draft_or_schedule">Borrador o programar</string>
|
||||
<string name="sk_compose_scheduled">Programáu para</string>
|
||||
<string name="sk_draft_saved">Borrador guardáu</string>
|
||||
<string name="sk_post_scheduled">Artículu programáu</string>
|
||||
<string name="sk_confirm_save_draft">¿Guardar el borrador?</string>
|
||||
<string name="sk_confirm_save_changes">¿Guardar cambeos?</string>
|
||||
<string name="sk_mark_as_draft">Marcar como borrador</string>
|
||||
<string name="sk_mark_as_read">Marcar como lleíu</string>
|
||||
<string name="sk_settings_single_notification">Amosar namás un avisu</string>
|
||||
<string name="sk_settings_unifiedpush">Usar UnifiedPush</string>
|
||||
<string name="sk_create_list_title">Crear llista</string>
|
||||
<string name="sk_list_name_hint">Nome de la llista</string>
|
||||
<string name="sk_list_replies_policy_list">miembros de la llista</string>
|
||||
<string name="sk_list_replies_policy_followed">usuarios siguíos</string>
|
||||
<string name="sk_list_replies_policy_none">nengunu</string>
|
||||
<string name="sk_delete_list">Desaniciar llista</string>
|
||||
<string name="sk_delete_list_confirm">¿De xuru que quies desaniciar la llista “%s”?</string>
|
||||
<string name="sk_edit_list_title">Editar llista</string>
|
||||
<string name="sk_timeline_local">Llocal</string>
|
||||
<string name="sk_timeline_federated">Federada</string>
|
||||
<string name="sk_timeline_bubble">Burbuya</string>
|
||||
<string name="sk_recent_searches_placeholder">Escribe pa entamar a buscar</string>
|
||||
<string name="sk_do_remove_follower">Desaniciar</string>
|
||||
<string name="sk_remove_follower_success">Siguidor desaniciáu con ésitu</string>
|
||||
<string name="sk_changelog">Llista de cambeos</string>
|
||||
<string name="sk_alt_text_missing_title">Falta\'l testu alternativu</string>
|
||||
<string name="sk_alt_text_missing">A lo menos falta-y la descripción a un archivu axuntu.</string>
|
||||
<string name="sk_publish_anyway">Espublizar de toes formes</string>
|
||||
<string name="sk_settings_disable_alt_text_reminder">Desactivar el recordatoriu pa amestar el testu alternativu</string>
|
||||
<string name="sk_timelines">Cronoloxíes</string>
|
||||
<string name="sk_timeline_posts">Artículos</string>
|
||||
<string name="sk_list">Llista</string>
|
||||
<string name="sk_pin_timeline">Fixar cronoloxía</string>
|
||||
<string name="sk_remove">Desaniciar</string>
|
||||
<string name="sk_timeline_icon">Iconu</string>
|
||||
<string name="sk_icon_heart">Corazón</string>
|
||||
<string name="sk_icon_star">Estrella</string>
|
||||
<string name="sk_icon_city">Ciudá</string>
|
||||
<string name="sk_icon_cat">Gatu</string>
|
||||
<string name="sk_icon_dog">Perru</string>
|
||||
<string name="sk_icon_rabbit">Conexu</string>
|
||||
<string name="sk_icon_turtle">Tortuga</string>
|
||||
<string name="sk_icon_balloon">Globu</string>
|
||||
<string name="sk_icon_bot">Robó</string>
|
||||
<string name="sk_icon_language">Llingüa</string>
|
||||
<string name="sk_icon_megaphone">Megáfonu</string>
|
||||
<string name="sk_icon_microphone">Micrófonu</string>
|
||||
<string name="sk_icon_microscope">Microscopiu</string>
|
||||
<string name="sk_icon_keyboard">Tecláu</string>
|
||||
<string name="sk_icon_location">Llocalización</string>
|
||||
<string name="sk_icon_news">Noticies</string>
|
||||
<string name="sk_icon_tag">Etiqueta</string>
|
||||
<string name="sk_icon_games">Xuegos</string>
|
||||
<string name="sk_icon_light_bulb">Bombilla</string>
|
||||
<string name="sk_icon_train">Tren</string>
|
||||
<string name="sk_icon_leaves">Fueyes</string>
|
||||
<string name="sk_icon_sport">Deporte</string>
|
||||
<string name="sk_icon_music">Música</string>
|
||||
<string name="sk_icon_people">Xente</string>
|
||||
<string name="sk_icon_health">Salú</string>
|
||||
<string name="sk_icon_important">Importante</string>
|
||||
<string name="sk_icon_shield">Escudu</string>
|
||||
<string name="sk_icon_book">Llibru</string>
|
||||
<string name="sk_icon_code">Códigu</string>
|
||||
<string name="sk_icon_map">Mapa</string>
|
||||
<string name="sk_icon_backpack">Mochila</string>
|
||||
<string name="sk_icon_fire">Fueu</string>
|
||||
<string name="sk_icon_bug">Bichu</string>
|
||||
<string name="sk_icon_pizza">Pizza</string>
|
||||
<string name="sk_icon_gavel">Mazu</string>
|
||||
<string name="sk_icon_verified">Verificáu</string>
|
||||
<string name="sk_icon_water">Agua</string>
|
||||
<string name="sk_icon_sun">Sol</string>
|
||||
<string name="sk_icon_cloud">Nube</string>
|
||||
<string name="sk_icon_thunderstorm">Tormenta</string>
|
||||
<string name="sk_icon_rain">Lluvia</string>
|
||||
<string name="sk_add_timeline_tag_error_empty">L\'etiqueta nun pue tar vacía</string>
|
||||
<string name="sk_icon_human">Persona</string>
|
||||
<string name="sk_icon_globe">Planeta</string>
|
||||
<string name="sk_icon_bed">Cama</string>
|
||||
<string name="sk_icon_diamond">Diamante</string>
|
||||
<string name="sk_icon_umbrella">Paragües</string>
|
||||
<string name="sk_edit_timeline">Editar cronoloxía</string>
|
||||
<string name="sk_add_timeline">Amestar cronoloxía</string>
|
||||
<string name="sk_edit_timelines">Editar cronoloxíes</string>
|
||||
<string name="sk_edit_timeline_tag_any">...o dalguna d\'estes</string>
|
||||
<string name="sk_edit_timeline_tag_all">...y toes estes</string>
|
||||
<string name="sk_edit_timeline_tag_none">...pero nenguna d\'estes</string>
|
||||
<string name="sk_edit_timeline_tag_hint">Introduz l\'etiqueta…</string>
|
||||
<string name="sk_edit_timeline_tags_hint">Introduz les etiquetes…</string>
|
||||
<string name="sk_alt_button">ALT</string>
|
||||
<string name="sk_gif_badge">GIF</string>
|
||||
<string name="sk_posted">%s espublizáu</string>
|
||||
<string name="sk_post_edited">editáu</string>
|
||||
<string name="sk_notification_type_update">Artículos editáos</string>
|
||||
<string name="sk_notify_update">Editáu un artículu impulsáu</string>
|
||||
<string name="sk_attach_file">Axuntar ficheru</string>
|
||||
<string name="sk_searching">Buscando…</string>
|
||||
<string name="sk_no_results">Ensin resultaos</string>
|
||||
<string name="sk_no_alt_text">Nun hai testu alternativu disponible</string>
|
||||
<string name="sk_settings_see_new_posts_button">Botón “Ver artículos nuevos”</string>
|
||||
<string name="sk_separator">·</string>
|
||||
<string name="sk_local_only">Namás l\'instancia llocal</string>
|
||||
<string name="sk_settings_support_local_only">El servidor namás almite espublizar llocalmente</string>
|
||||
<string name="sk_settings_show_alt_indicator">Indicador pa los testos alternativos</string>
|
||||
<string name="sk_inline_local_only">Namás llocal</string>
|
||||
<string name="sk_instance_features">Carauterístiques de l\'instancia</string>
|
||||
<string name="sk_signed_up">rexistráu</string>
|
||||
<string name="sk_reported">denunciáu</string>
|
||||
<string name="sk_reacted_with">%1$s reaccionó con %2$s</string>
|
||||
<string name="sk_reacted">%s reaccionó</string>
|
||||
<string name="sk_settings_server_version">Versión de servidor: %s</string>
|
||||
<string name="sk_notify_poll_results">Resultáos de la encuesta</string>
|
||||
<string name="sk_sign_ups">Rexistru d\'usuarios</string>
|
||||
<string name="sk_new_reports">Denuncies nueves</string>
|
||||
<string name="sk_expand">Espandir</string>
|
||||
<string name="sk_collapse">Encoyer</string>
|
||||
<string name="sk_settings_collapse_long_posts">Encoyer artículos mui llargos</string>
|
||||
<string name="sk_unfinished_attachments">Subiendo los archivos axuntos</string>
|
||||
<string name="sk_unfinished_attachments_message">Dalgunos archivos axuntos nun acabaron de xubir.</string>
|
||||
<string name="sk_settings_hide_interaction">Ancultar botones d\'interaición</string>
|
||||
<string name="sk_followed_as">Siguíu dende %s</string>
|
||||
<string name="sk_show_thread">Amosar filu</string>
|
||||
<string name="sk_content_type">Tipu de conteníu</string>
|
||||
<string name="sk_content_type_unspecified">Ensin especificar</string>
|
||||
<string name="sk_content_type_plain">Testu planu</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_notification_action_replied">Respuesta unviada a %s</string>
|
||||
<string name="sk_in_reply">En respuesta</string>
|
||||
<string name="sk_settings_confirm_before_reblog">Confirma antes d\'impulsar</string>
|
||||
<string name="sk_instance_info_unavailable">L\'información de la instancia nun ta disponible temporalmente</string>
|
||||
<string name="sk_open_in_app">Abrir na aplicación</string>
|
||||
<string name="sk_open_in_app_failed">Nun se pudo abrir na aplicación</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">Nunca</string>
|
||||
<string name="sk_settings_auto_reveal_author">Respuestes del mesmu autor</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">Respuestes de toos</string>
|
||||
<string name="sk_settings_prefix_replies_never">Nunca</string>
|
||||
<string name="sk_advanced_options_show">Amosar opciones avanzaes</string>
|
||||
<string name="sk_advanced_options_hide">Ancultar opciones avanzaes</string>
|
||||
<string name="sk_spoiler_show">Amosar el conteníu</string>
|
||||
<string name="sk_pronouns_label">Pronomes</string>
|
||||
<string name="sk_settings_instance">Instancia</string>
|
||||
<string name="sk_settings_display_pronouns_in_timelines">Amosar los pronomes nes cronoloxíes</string>
|
||||
<string name="sk_settings_display_pronouns_in_threads">Amosar los pronomes nos filos</string>
|
||||
<string name="sk_list_exclusive_switch">Facer que la llista seya esclusiva</string>
|
||||
<string name="sk_switch_timeline">Camudar cronoloxía</string>
|
||||
<string name="sk_tab_home">Aniciu</string>
|
||||
<string name="sk_tab_notifications">Avisos</string>
|
||||
<string name="sk_tab_profile">Perfil</string>
|
||||
<string name="sk_settings_show_emoji_reactions_only_opened">Namás cuando s\'abre l\'artículu</string>
|
||||
<plurals name="sk_users_reacted_with">
|
||||
<item quantity="one">Un usuariu reaccionó con %2$s</item>
|
||||
<item quantity="other">%1$,d usuarios reaccionaron con %2$s</item>
|
||||
</plurals>
|
||||
<string name="sk_duration_indefinite">Indefiníu</string>
|
||||
<string name="sk_duration_minutes_5">5 minutos</string>
|
||||
<string name="sk_duration_minutes_30">30 minutos</string>
|
||||
<string name="sk_duration_hours_1">1 hora</string>
|
||||
<string name="sk_duration_hours_6">6 hores</string>
|
||||
<string name="sk_duration_days_1">1 día</string>
|
||||
<string name="sk_duration_days_3">3 díes</string>
|
||||
<string name="sk_duration_days_7">7 díes</string>
|
||||
<string name="sk_suicide_search_terms">Suicidiu</string>
|
||||
<string name="sk_tab_search">Busca</string>
|
||||
<string name="sk_mute_label">Duración</string>
|
||||
<string name="sk_load_missing_posts_above">Cargar artículos nuevos</string>
|
||||
<string name="sk_load_missing_posts_below">Cargar artículos vieyos</string>
|
||||
<plurals name="sk_time_seconds">
|
||||
<item quantity="one">%d segundu</item>
|
||||
<item quantity="other">%d segundos</item>
|
||||
</plurals>
|
||||
<plurals name="sk_time_hours">
|
||||
<item quantity="one">%d hora</item>
|
||||
<item quantity="other">%d hores</item>
|
||||
</plurals>
|
||||
<plurals name="sk_time_days">
|
||||
<item quantity="one">%d día</item>
|
||||
<item quantity="other">%d díes</item>
|
||||
</plurals>
|
||||
<string name="sk_blocked_accounts">Cuentes bloquiaes</string>
|
||||
<string name="sk_post_contains_media">L\'artículu contien conteníu multimedia</string>
|
||||
<string name="sk_poll_hide_results">Ancultar resultaos</string>
|
||||
<string name="sk_open_post_preview">Vista previa del artículu</string>
|
||||
<string name="sk_post_preview">Vista previa</string>
|
||||
<string name="sk_app_name">Megalodon</string>
|
||||
<string name="sk_confirm_delete_and_redraft">¿De xuru que quies desaniciar y reeditar esti artículu?</string>
|
||||
<string name="sk_confirm_pin_post_title">Fixar l\'artículu nel perfil</string>
|
||||
<string name="sk_color_palette_blue">Azul</string>
|
||||
<string name="sk_translated_using">Traducíu usando %s</string>
|
||||
<string name="sk_pin_post">Fixar nel perfil</string>
|
||||
<string name="sk_delete_notification">Desaniciar avisu</string>
|
||||
<string name="sk_confirm_pin_post">¿Quies fixar esti artículu al to perfil?</string>
|
||||
<string name="sk_settings_reply_visibility_following">Respuestes a los mios siguidores</string>
|
||||
<string name="sk_federated_timeline_info_banner">Estos son los artículos más recientes de la xente de la to federación.</string>
|
||||
<string name="sk_settings_color_palette_default">Por defeutu (%s)</string>
|
||||
<string name="sk_color_palette_green">Verde</string>
|
||||
<string name="sk_settings_publish_button_text">Testu del botón d\'espublizamientu</string>
|
||||
<string name="sk_confirm_delete_scheduled_post_title">Desaniciar artículu programáu</string>
|
||||
<string name="sk_hashtag">Etiqueta</string>
|
||||
<string name="sk_icon_image">Semeya</string>
|
||||
<string name="sk_available_languages">Llingües disponibles</string>
|
||||
<string name="sk_confirm_clear_recent_languages">¿De xuru que quies desaniciar les llingües usaes apocayá?</string>
|
||||
<string name="sk_icon_coffee">Café</string>
|
||||
<string name="sk_icon_color_palette">Paleta de colores</string>
|
||||
<string name="sk_clear_all_notifications_confirm_action">Desaniciar tou</string>
|
||||
<string name="sk_settings_uniform_icon_for_notifications">Mesmu icono pa tolos avisos</string>
|
||||
<string name="sk_compose_draft">L\'artículu va guardase como borrador.</string>
|
||||
<string name="sk_timelines_add">Amestar</string>
|
||||
<string name="sk_icon_briefcase">Maletu</string>
|
||||
<string name="sk_reblog_as">Impulsar con otra cuenta</string>
|
||||
<string name="sk_pinned_timeline">Fixáu a aniciu</string>
|
||||
<string name="sk_icon_headphones">Auriculares</string>
|
||||
<string name="sk_save_draft_message">¿Quies guardar los tos cambios nesti borrador o espublizalo agora?</string>
|
||||
<string name="sk_delete_notification_confirm">¿De xuru que quies desaniciar esti avisu?</string>
|
||||
<string name="sk_already_bookmarked">Yá ta metíu en marcadores</string>
|
||||
<string name="sk_compose_no_draft">Nun guardar borrador</string>
|
||||
<string name="sk_remove_follower">Desaniciar como siguidor</string>
|
||||
<string name="sk_bookmarked_as">Metíu en marcadores como %s</string>
|
||||
<string name="sk_notification_mention">Fuisti mentáu por %s</string>
|
||||
<string name="sk_settings_translate_only_opened">Namás traducir artículos abiertos</string>
|
||||
<string name="sk_list_replies_policy">Amosar respuestes a</string>
|
||||
<string name="sk_timeline">Cronoloxía</string>
|
||||
<string name="sk_edit_timeline_tag_main">Artículos que contienen l\'etiqueta…</string>
|
||||
</resources>
|
||||
@@ -375,4 +375,55 @@
|
||||
<item quantity="one">publicació</item>
|
||||
<item quantity="other">publicacions</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
<string name="sk_trending_links_info_banner">Aquestes notícies s\'estan comentant per tot el Fedivers.</string>
|
||||
<string name="sk_icon_sunset">Foscant</string>
|
||||
<string name="sk_icon_cloud">Núvol</string>
|
||||
<string name="sk_icon_water">Aigua</string>
|
||||
<string name="sk_icon_sun">Sol</string>
|
||||
<string name="sk_icon_thunderstorm">Tempesta</string>
|
||||
<string name="sk_icon_rain">Pluja</string>
|
||||
<string name="sk_icon_snowflake">Floc de neu</string>
|
||||
<string name="sk_posted">%s publicat/s</string>
|
||||
<string name="sk_settings_show_emoji_reactions">Mostrar reaccions d\'emoji a la línia de temps</string>
|
||||
<string name="sk_settings_show_emoji_reactions_hide_empty">Ocultar reaccions d\'emoji buides</string>
|
||||
<string name="sk_settings_show_emoji_reactions_only_opened">Només quan s\'obri la publicació</string>
|
||||
<string name="sk_suicide_search_terms">Suïcidi</string>
|
||||
<string name="sk_search_suicide_title">Si estàs sofrint…</string>
|
||||
<string name="sk_search_suicide_hotlines">Trobar una línia d\'ajuda</string>
|
||||
<string name="sk_do_not_show_again">No tornar a mostrar</string>
|
||||
<string name="sk_suicide_helplines_url">https://findahelpline.com</string>
|
||||
<string name="sk_post_contains_media">La publicació conté contingut multimedia</string>
|
||||
<string name="sk_load_missing_posts_below">Carregar publicacions més antigues</string>
|
||||
<plurals name="sk_time_seconds">
|
||||
<item quantity="one">%d segon</item>
|
||||
<item quantity="many">%d segons</item>
|
||||
<item quantity="other">%d segons</item>
|
||||
</plurals>
|
||||
<plurals name="sk_time_minutes">
|
||||
<item quantity="one">%d minut</item>
|
||||
<item quantity="many">%d minuts</item>
|
||||
<item quantity="other">%d minuts</item>
|
||||
</plurals>
|
||||
<plurals name="sk_time_hours">
|
||||
<item quantity="one">%d hora</item>
|
||||
<item quantity="many">%d hores</item>
|
||||
<item quantity="other">%d hores</item>
|
||||
</plurals>
|
||||
<plurals name="sk_time_days">
|
||||
<item quantity="one">%d dia</item>
|
||||
<item quantity="many">%d díes</item>
|
||||
<item quantity="other">%d díes</item>
|
||||
</plurals>
|
||||
<string name="sk_muted_accounts">Comptes silenciats</string>
|
||||
<string name="sk_blocked_accounts">Comptes bloquejats</string>
|
||||
<string name="sk_settings_like_icon">Utilitzar un cor com a icona de favorit</string>
|
||||
<string name="sk_recently_used">Utilitzat recentment</string>
|
||||
<string name="sk_settings_underlined_links">Enllaços subratllats</string>
|
||||
<string name="sk_set_as_default">Posar per defecte</string>
|
||||
<string name="sk_edit_alt_text">Editar text alternatiu</string>
|
||||
<string name="sk_settings_color_palette_default">Per defecte (%s)</string>
|
||||
<string name="sk_search_suicide_message">Si estàs buscant una senyal per a no suicidar-te, ací està. Per favor, planteja\'t cridar a una línia d\'ajuda de prevenció del suïcidi si estàs sofrint.</string>
|
||||
<string name="sk_load_missing_posts_above">Carregar publicacions més recents</string>
|
||||
<string name="sk_trending_posts_info_banner">Aquestes publicacions estan guanyant popularitat en el Fedivers.</string>
|
||||
<string name="sk_settings_show_emoji_reactions_always">Mostrar sempre el botó d\'afegir</string>
|
||||
</resources>
|
||||
|
||||
@@ -606,6 +606,7 @@
|
||||
<string name="manage_list_members">Listenmitglieder verwalten</string>
|
||||
<string name="list_no_members">Noch keine Mitglieder</string>
|
||||
<string name="dont_remind_again">Nicht erneut erinnern</string>
|
||||
<string name="reply_to_user">Antwort an %s</string>
|
||||
<!-- %s is a time interval ("5 months") -->
|
||||
<string name="old_post_sheet_title">Dieser Beitrag ist %s alt</string>
|
||||
</resources>
|
||||
|
||||
@@ -121,4 +121,7 @@
|
||||
<string name="export_settings_share">Einstellungen exportieren</string>
|
||||
<string name="export_settings_fail">Exportieren der Einstellungen fehlgeschlagen</string>
|
||||
<string name="export_settings_title">Einstellungen exportieren</string>
|
||||
</resources>
|
||||
<string name="mo_personal_note_saved">Notiz gespeichert</string>
|
||||
<string name="mo_settings_enhance_text_size_summary">Macht den Text der App größer</string>
|
||||
<string name="mo_settings_enhance_text_size">Textgröße verändern</string>
|
||||
</resources>
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
<string name="sk_icon_location">Standort</string>
|
||||
<string name="sk_icon_microphone">Mikrofon</string>
|
||||
<string name="sk_icon_microscope">Mikroskop</string>
|
||||
<string name="sk_icon_keyboard">Keyboard</string>
|
||||
<string name="sk_icon_keyboard">Tastatur</string>
|
||||
<string name="sk_icon_coffee">Kaffee</string>
|
||||
<string name="sk_icon_laugh">Lachen</string>
|
||||
<string name="sk_icon_news">Nachrichten</string>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<string name="mo_swap_bookmark_with_reblog">Εναλλαγή σελιδοδείκτη με ενέργεια αναδημοσίευσης</string>
|
||||
<string name="mo_show_media_preview">Εμφάνιση προεπισκόπησης πολυμέσων στις ροές</string>
|
||||
<string name="mo_settings_unifiedpush_warning">UnifiedPush μη ενεργό</string>
|
||||
<string name="mo_mention_reblogger_automatically">Αυτόματη αναφορά λογαριασμού που αναδημοσίευσε την ανάρτηση, στις απαντήσεις</string>
|
||||
<string name="mo_mention_reblogger_automatically">Αυτόματη επισήμανση λογαριασμού που αναδημοσίευσε την ανάρτηση, στις απαντήσεις</string>
|
||||
<string name="mo_confirm_unfollow_title">Άρση ακολούθησης Λογαριασμού</string>
|
||||
<string name="mo_settings_unifiedpush_warning_no_distributors">Δεν έχουν εγκατασταθεί διανομείς UnifiedPush. Δεν θα λάβεις ειδοποιήσεις.</string>
|
||||
<string name="mo_mute_notifications">Απόκρυψη ειδοποιήσεων από αυτόν τον χρήστη;</string>
|
||||
@@ -113,4 +113,5 @@
|
||||
<string name="mo_trending_link_read">Ανάγνωση</string>
|
||||
<string name="mo_settings_remove_tracking_params_summary">Απομάκρυνση πληροφοριών ανίχνευσης από συνδέσμους</string>
|
||||
<string name="mo_settings_remove_tracking_params">Ιδιωτικοί Σύνδεσμοι</string>
|
||||
<string name="mo_personal_note_saved">Η σημείωση αποθηκεύτηκε</string>
|
||||
</resources>
|
||||
@@ -249,7 +249,7 @@
|
||||
<string name="sk_add_timeline_tag_error_empty">Η ετικέτα δε μπορεί να είναι κενή</string>
|
||||
<string name="sk_updater_enable_pre_releases">Ενεργοποίηση προ-κυκολοφορίας</string>
|
||||
<string name="sk_inline_local_only">Μόνο τοπικά</string>
|
||||
<string name="sk_inline_direct">Μόνο με αναφορά</string>
|
||||
<string name="sk_inline_direct">Μόνο με επισήμανση</string>
|
||||
<string name="sk_separator">·</string>
|
||||
<string name="sk_local_only">Μόνο τοπική οντότητα</string>
|
||||
<string name="sk_instance_features">Δυνατότητες οντότητας</string>
|
||||
@@ -374,7 +374,7 @@
|
||||
<string name="sk_duration_hours_6">6 ώρες</string>
|
||||
<string name="sk_duration_days_1">1 ημέρα</string>
|
||||
<string name="sk_duration_days_3">3 ημέρες</string>
|
||||
<string name="sk_notification_mention">Αναφέρθηκες από τον χρήστη %s</string>
|
||||
<string name="sk_notification_mention">Επισημάνθηκες από τον χρήστη %s</string>
|
||||
<string name="sk_suicide_search_terms">Αυτοκτονία</string>
|
||||
<string name="sk_search_suicide_title">Σε περίπτωση που είσαι σε κρίση…</string>
|
||||
<string name="sk_search_suicide_hotlines">Βρες γραμμή βοήθειας</string>
|
||||
|
||||
@@ -96,4 +96,22 @@
|
||||
<string name="mo_trending_link_read">Legi</string>
|
||||
<string name="mo_error_display_copy_error_details">Kopii informojn</string>
|
||||
<string name="mo_settings_unifiedpush_enable">Ebligi</string>
|
||||
</resources>
|
||||
<string name="mo_settings_remove_tracking_params">Malpublikaj Ligiloj</string>
|
||||
<string name="import_settings_failed">Malsukcesis importi agordojn</string>
|
||||
<string name="export_settings_share">Eksporti agordojn</string>
|
||||
<string name="export_settings_fail">Malsukcesis eksporti agordojn</string>
|
||||
<string name="export_settings_title">Eksportu agordojn</string>
|
||||
<string name="export_settings_summary">Eksporti la agordojn kaj templinioj de ĉiuj ensalutitaj kontoj</string>
|
||||
<string name="mo_settings_remove_tracking_params_summary">Foru gvitidintan informacion el ligiloj</string>
|
||||
<string name="mo_personal_note_saved">Noto savita</string>
|
||||
<string name="import_settings_confirm_body">Ĉiu nunaj agordoj kaj templinioj estos anstataŭitaj! Ĉi tiu ago ne povas malfarita.</string>
|
||||
<string name="mo_settings_unifiedpush_warning">UnifiedPush ne estas ŝaltita</string>
|
||||
<string name="mo_settings_unifiedpush_warning_no_distributors">Neniom da instalitaj distribuantoj de UnifiedPush. Vi ne recivos iajn sciigojn.</string>
|
||||
<string name="mo_mute_notifications">Ĉu malmontru sciigojn de ĉi tiu uzanto?</string>
|
||||
<string name="import_settings_confirm">Ĉu akceptu importi agordojn?</string>
|
||||
<string name="mo_settings_unifiedpush_warning_disabled">UnifiedPush ne estas ŝaltita. Vi ne recivos iajn sciigojn.</string>
|
||||
<string name="import_settings_title">Importi agordojn</string>
|
||||
<string name="import_settings_summary">Importi antaŭitajn eksportitajn agordojn kaj templiniojn</string>
|
||||
<string name="mo_error_display_title">Malsukcesis montri afiŝon</string>
|
||||
<string name="mo_error_display_text">Io malsukcesis dum ŝarĝas ĉi tiun afiŝon. Se la problemo daŭras, bonvole raportu ĝin en nia paĝo de Issues kune kun la detalojn.</string>
|
||||
</resources>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<string name="mo_emoji_recent">Usados recientemente</string>
|
||||
<string name="mo_clear_recent_emoji">Borrar emojis usados recientemente</string>
|
||||
<string name="mo_disable_relocate_publish_button_to_enable_customization">Desactiva \"Recolocar botón de publicación\" para poder personalizar</string>
|
||||
<string name="mo_poll_option_add">Añadir nueva opción en la encuesta</string>
|
||||
<string name="mo_poll_option_add">Añadir opción en la encuesta</string>
|
||||
<string name="mo_fab_compose">Redactar</string>
|
||||
<string name="mo_sending_error">Error al publicar</string>
|
||||
<string name="mo_settings_contribute">Contribuir en Moshidon</string>
|
||||
@@ -70,7 +70,7 @@
|
||||
<string name="mo_setting_relocate_publish_summary">Mueve el botón de publicación a la barra inferior</string>
|
||||
<string name="mo_swap_bookmark_with_reblog_summary">Añade un marcador o impulsa publicaciones desde la notificación</string>
|
||||
<string name="mo_open_camera">Hacer foto</string>
|
||||
<string name="mo_camera_not_available">¡No hay cámara disponible!</string>
|
||||
<string name="mo_camera_not_available">¡No detecto la cámara!</string>
|
||||
<string name="mo_setting_haptic_feedback_summary">Vibra cuando interactúas con publicaciones</string>
|
||||
<string name="mo_haptic_feedback">Vibración</string>
|
||||
<string name="mo_color_palette_black_and_white">Blanco y Negro</string>
|
||||
@@ -81,7 +81,7 @@
|
||||
<string name="mo_show_media_preview">Mostrar previsualización de multimedia en la cronología</string>
|
||||
<string name="mo_recent_emoji_cleared">Emojis recientes eliminados</string>
|
||||
<string name="mo_double_tap_to_search">Doble toque para abrir la búsqueda</string>
|
||||
<string name="mo_unmuted_conversation_successfully">Conversación sin silencio</string>
|
||||
<string name="mo_unmuted_conversation_successfully">Conversación no silenciada</string>
|
||||
<string name="mo_confirm_to_unmute_conversation">¿Quieres dejar de silenciar esta conversación\?</string>
|
||||
<string name="mo_confirm_to_mute_conversation">¿Quieres silenciar esta conversación\?</string>
|
||||
<string name="mo_muted_conversation_successfully">Conversación silenciada correctamente</string>
|
||||
@@ -101,16 +101,16 @@
|
||||
<string name="mo_mute_notifications">¿Ocultar las notificaciones de este usuario?</string>
|
||||
<string name="mo_settings_unifiedpush_warning">UnifiedPush no habilitado</string>
|
||||
<string name="mo_settings_unifiedpush_warning_disabled">UnifiedPush no está habilitado. No recibirás ninguna notificación.</string>
|
||||
<string name="export_settings_share">Exportar configuración</string>
|
||||
<string name="mo_error_display_text">Algo salió mal al cargar esta publicación. Si el problema persiste, infórmelo en nuestra página de Problemas junto con los detalles del error.</string>
|
||||
<string name="export_settings_share">Exportar ajustes</string>
|
||||
<string name="mo_error_display_text">Algo salió mal al cargar esta publicación. Si el problema continúa, infórmanos en nuestra página de soporte junto con los detalles del error.</string>
|
||||
<string name="import_settings_summary">Importar configuraciones y líneas de tiempo previamente exportadas</string>
|
||||
<string name="mo_settings_unifiedpush_warning_no_distributors">No hay distribuidores de UnifiedPush instalados. No recibirás ninguna notificación.</string>
|
||||
<string name="mo_settings_unifiedpush_enable">Permitir</string>
|
||||
<string name="import_settings_confirm">¿Confirmar para importar la configuración?</string>
|
||||
<string name="import_settings_confirm_body">¡Se sobrescribirán todas las configuraciones y líneas de tiempo actuales! Esta acción no se puede deshacer.</string>
|
||||
<string name="import_settings_confirm_body">¡Se sobrescribirán todas las configuraciones y cronologías actuales! Esta acción no se puede deshacer.</string>
|
||||
<string name="import_settings_failed">No se pudo importar la configuración</string>
|
||||
<string name="export_settings_fail">No se pudo exportar la configuración</string>
|
||||
<string name="export_settings_title">Exportar configuración</string>
|
||||
<string name="export_settings_title">Exportar ajustes</string>
|
||||
<string name="export_settings_summary">Exportar configuraciones y cronogramas para todas las cuentas</string>
|
||||
<string name="import_settings_title">Importar ajustes</string>
|
||||
<string name="mo_error_display_title">No se pudo mostrar la publicación</string>
|
||||
@@ -118,4 +118,10 @@
|
||||
<string name="mo_trending_link_read">Leer</string>
|
||||
<string name="mo_settings_remove_tracking_params">Enlaces Privados</string>
|
||||
<string name="mo_settings_remove_tracking_params_summary">Eliminar información de rastreo de los enlaces</string>
|
||||
</resources>
|
||||
<string name="mo_personal_note_saved">Nota guardada</string>
|
||||
<string name="mo_settings_enhance_text_size">Aumentar tamaño del texto</string>
|
||||
<string name="mo_settings_enhance_text_size_summary">Agranda el texto de la app</string>
|
||||
<string name="mo_mute_hashtag_explanation_discreet">Los demás no sabrán que has silenciado esta etiqueta.</string>
|
||||
<string name="mo_mute_hashtag_explanation_muted_home">No verás publicaciones con esta etiqueta en tu Inicio.</string>
|
||||
<string name="mo_mute_hashtag_explanation_search">Todavía verás publicaciones con esta etiqueta buscándolas o en otras cronologías.</string>
|
||||
</resources>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="sk_image_description">Descripción de la imagen</string>
|
||||
<string name="sk_visibility_unlisted">No listada</string>
|
||||
<string name="sk_settings_show_replies">Mostrar respuestas</string>
|
||||
<string name="sk_settings_show_boosts">Mostrar boosts</string>
|
||||
<string name="sk_settings_show_boosts">Mostrar mejoras</string>
|
||||
<string name="sk_settings_load_new_posts">Cargar publicaciones nuevas automáticamente</string>
|
||||
<string name="sk_settings_show_interaction_counts">Mostrar recuento de interacciones</string>
|
||||
<string name="sk_mark_media_as_sensitive">Marcar contenido como delicado</string>
|
||||
@@ -224,7 +224,7 @@
|
||||
<string name="sk_timeline_posts">Publicaciones</string>
|
||||
<string name="sk_timelines_add">Añadir</string>
|
||||
<string name="sk_timeline">Línea de tiempo</string>
|
||||
<string name="sk_hashtag">Hashtag</string>
|
||||
<string name="sk_hashtag">Etiqueta</string>
|
||||
<string name="sk_pin_timeline">Anclar cronología</string>
|
||||
<string name="sk_unpin_timeline">Desanclar cronología</string>
|
||||
<string name="sk_pinned_timeline">Anclado a inicio</string>
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
<string name="sk_publish_anyway">Argitaratu hala ere</string>
|
||||
<string name="sk_settings_disable_alt_text_reminder">Desgaitu gogorarazlea testu alternatiboa gehitzeko</string>
|
||||
<string name="sk_timelines">Denbora-lerroak</string>
|
||||
<string name="sk_timeline_posts">Bidalketak</string>
|
||||
<string name="sk_timeline_posts">Argitalpenak</string>
|
||||
<string name="sk_timelines_add">Gehitu</string>
|
||||
<string name="sk_timeline">Denbora-lerroa</string>
|
||||
<string name="sk_list">Zerrenda</string>
|
||||
@@ -218,7 +218,7 @@
|
||||
<string name="sk_alt_button">ALT</string>
|
||||
<string name="sk_post_edited">Editatua</string>
|
||||
<string name="sk_notification_type_update">Editatutako argitalpenak</string>
|
||||
<string name="sk_notify_update">Bultzatutako bidalketa editatu da</string>
|
||||
<string name="sk_notify_update">Bultzatutako argitalpena editatu da</string>
|
||||
<string name="sk_no_results">Emaitzarik ez</string>
|
||||
<string name="sk_save_draft">Zirriborroa gorde\?</string>
|
||||
<string name="sk_no_alt_text">Ez dago testu alternatiborik eskuragarri</string>
|
||||
@@ -270,7 +270,7 @@
|
||||
<string name="sk_quoting_user">%s aipatzen</string>
|
||||
<string name="sk_settings_reply_visibility_self">Niri eginiko erantzunak</string>
|
||||
<string name="sk_notification_action_replied">%s-(r)i erantzun</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Hauexek dira zure instantziako administratzaileek saretik aukeratutako azken bidalketak.</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Hauexek dira zure instantziako administratzaileek saretik aukeratutako azken argitalpenak.</string>
|
||||
<string name="sk_content_type">Edukiaren mota</string>
|
||||
<string name="sk_content_type_unspecified">Zehaztu gabea</string>
|
||||
<string name="sk_content_type_plain">Testu arrunta</string>
|
||||
@@ -278,8 +278,8 @@
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Gaitu bidalketaren formateatzea</string>
|
||||
<string name="sk_settings_content_types_explanation">Markdown bezalako eduki-mota ezartzea ahalbidetzen du bidalketa bat sortzerakoan. Gogoan izan instantzia guztiek ez dutela hau baimentzen.</string>
|
||||
<string name="sk_settings_content_types">Gaitu argitalpenaren formateatzea</string>
|
||||
<string name="sk_settings_content_types_explanation">Markdown bezalako eduki-mota ezartzea ahalbidetzen du argitalpen bat sortzerakoan. Gogoan izan instantzia guztiek ez dutela hau baimentzen.</string>
|
||||
<string name="sk_settings_default_content_type">Eduki-mota lehenetsia</string>
|
||||
<string name="sk_settings_confirm_before_reblog">Berretsi bultzatu aurretik</string>
|
||||
<string name="sk_reacted_with">%1$s-(e)k honela erreakzionatu du: %2$s</string>
|
||||
@@ -290,9 +290,9 @@
|
||||
<string name="sk_edit_timeline_tags_explanation">Jakin eragiketa hauek zerbitzariak kudeatzen dituela. Litekeena da konbinazioetarako euskarririk ez izatea.</string>
|
||||
<string name="sk_settings_unifiedpush_no_distributor_body">Banatzaile bat instalatu behar duzu UnifiedPush jakinarazpenek funtzionatzeko. Informazio gehiago: https://unifiedpush.org/</string>
|
||||
<string name="sk_icon_feed">Jarioa</string>
|
||||
<string name="sk_edit_timeline_tag_main">Traola hau duten tootak…</string>
|
||||
<string name="sk_edit_timeline_tag_main">Traola hau duten argitalpenak…</string>
|
||||
<string name="sk_icon_verified">Egiaztatua</string>
|
||||
<string name="sk_hashtag_timeline_local_only_switch">Erakutsi toot lokalak soilik\?</string>
|
||||
<string name="sk_hashtag_timeline_local_only_switch">Erakutsi argitalpen lokalak soilik?</string>
|
||||
<string name="sk_icon_umbrella">Euritakoa</string>
|
||||
<string name="sk_settings_unifiedpush_no_distributor">Ez da banatzailerik aurkitu</string>
|
||||
<string name="sk_edit_timeline_tag_any">...edo hauetako edozein</string>
|
||||
@@ -304,4 +304,23 @@
|
||||
<string name="sk_icon_recycle_bin">Zakarrontzia</string>
|
||||
<string name="sk_trending_links_info_banner">Hauek dira zure zerbitzarian komentatzen ari diren albisteak</string>
|
||||
<string name="sk_add_timeline_tag_error_empty">Traola ezin da hutsik egon</string>
|
||||
<string name="sk_icon_sun">Eguzkia</string>
|
||||
<string name="sk_icon_sunset">Ilunabarra</string>
|
||||
<string name="sk_icon_cloud">Lainoa</string>
|
||||
<string name="sk_icon_rain">Euria</string>
|
||||
<string name="sk_icon_snowflake">Elur maluta</string>
|
||||
<string name="sk_gif_badge">GIF</string>
|
||||
<string name="sk_posted">%s(e)k argitaratua</string>
|
||||
<string name="sk_icon_thunderstorm">Ekaitza</string>
|
||||
<string name="sk_trending_posts_info_banner">Argitalpen hauek garrantzia hartzen ari dira Fedibertsoan.</string>
|
||||
<plurals name="sk_posts_count_label">
|
||||
<item quantity="one">Argitalpena</item>
|
||||
<item quantity="other">Argitalpenak</item>
|
||||
</plurals>
|
||||
<string name="sk_post_contains_media">Multimediadun argitalpena</string>
|
||||
<string name="sk_load_missing_posts_below">Argitalpen zaharragoak kargatu</string>
|
||||
<string name="sk_load_missing_posts_above">Argitalpen berriagoak kargatu</string>
|
||||
<string name="sk_settings_default_visibility">Lehenetsitako argitalpenen ikusgarritasuna</string>
|
||||
<string name="sk_open_post_preview">Aurreikusi argitalpena</string>
|
||||
<string name="sk_post_preview">Aurrebista</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
</resources>
|
||||
<string name="mo_relocate_publish_button">Siirrä julkaisupainike uudelleen</string>
|
||||
<string name="mo_color_palette_nord">Nord</string>
|
||||
<string name="mo_color_palette_black_and_white">Mustavalkoinen</string>
|
||||
<string name="mo_enable_dividers">Näytä postin jakajat</string>
|
||||
</resources>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user