Compare commits
42 Commits
v1.1.5+for
...
1.1.4+fork
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e15dd6024f | ||
|
|
e52dffeece | ||
|
|
5b85bb427d | ||
|
|
4d62388617 | ||
|
|
04b8055474 | ||
|
|
3c34b6a7d2 | ||
|
|
de4964c2cd | ||
|
|
fbcaa05c03 | ||
|
|
883f28696e | ||
|
|
df52230837 | ||
|
|
a90f26a37a | ||
|
|
8c1f76d7fa | ||
|
|
f384d44f8f | ||
|
|
4ab6ed55f5 | ||
|
|
cf99bf5152 | ||
|
|
10779717cf | ||
|
|
db4c1bfe47 | ||
|
|
27afba1cf2 | ||
|
|
4895425b40 | ||
|
|
004c414fba | ||
|
|
c8e38b134c | ||
|
|
de5a911286 | ||
|
|
606cd7442e | ||
|
|
3ebc972268 | ||
|
|
4e39bb381c | ||
|
|
b6178681b0 | ||
|
|
29abf70cec | ||
|
|
8d63be513d | ||
|
|
e63b9d0dd6 | ||
|
|
b1fda17ac7 | ||
|
|
bad44b145c | ||
|
|
77669cedf6 | ||
|
|
19238c389f | ||
|
|
1747ff98b5 | ||
|
|
8fa5824e3e | ||
|
|
6a674d7a7e | ||
|
|
dad3b8cd6b | ||
|
|
9179d2198d | ||
|
|
d096bef234 | ||
|
|
f2c47a1b84 | ||
|
|
bc2ac4e915 | ||
|
|
ff215412c8 |
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
42
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To reproduce**
|
|
||||||
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Does this happen in the official app?**
|
|
||||||
|
|
||||||
Does this issue also occur with the respective upstream release?
|
|
||||||
(Please test using the respective `upstream-xxxxxx.apk` provided in [Releases](https://github.com/sk22/megalodon/releases) or at least using the current Mastodon version from the Play Store)
|
|
||||||
|
|
||||||
> No / Yes
|
|
||||||
|
|
||||||
> In case it does, please consider filing an [upstream bug report](https://github.com/mastodon/mastodon-android/issues) instead.
|
|
||||||
> If this bug is seriously impacting your usage or you think I might want to try to fix it for Megalodon, feel free to still create this issue!
|
|
||||||
|
|
||||||
**Screenshots and screen recordings**
|
|
||||||
|
|
||||||
If applicable, add screenshots (and screen recordings, if possible) to help explain your problem.
|
|
||||||
|
|
||||||
**Version**
|
|
||||||
|
|
||||||
Megalodon version: [e.g. v1.1.4+fork.#]
|
|
||||||
|
|
||||||
**Crash log**
|
|
||||||
|
|
||||||
If you know your way around Android development tools, please consider attaching a crash log, if possible.
|
|
||||||
20
.github/ISSUE_TEMPLATE/feature-ui-request.md
vendored
20
.github/ISSUE_TEMPLATE/feature-ui-request.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature/UI request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: feature
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
If applicable: a clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
10
.github/ISSUE_TEMPLATE/something-else.md
vendored
10
.github/ISSUE_TEMPLATE/something-else.md
vendored
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
name: It's something else…
|
|
||||||
about: Issues that can't be categorized as feature requests or bug reports
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
111
README.md
111
README.md
@@ -1,25 +1,23 @@
|
|||||||

|

|
||||||
|
|
||||||
# Megalodon
|
# Moshidon
|
||||||
|
|
||||||
[](https://translate.codeberg.org/engage/megalodon/)
|
> 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.
|
||||||
|
|
||||||
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
|
**Warning! [The last version's integrated updater was broken](https://github.com/sk22/megalodon/issues/106) – I already published a fixed version! If you're not updating through Izzy's F-Droid repository (more sources to come, hopefully!), you'll have to download the current release manually. Sorry about that!**
|
||||||
|
|
||||||
<a href="#installation"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
|
||||||
|
|
||||||
> A fork of the [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 and an image description viewer.
|
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
## Key features
|
## Key features
|
||||||
|
|
||||||
### **Unlisted posting**
|
### **Unlisted posting**
|
||||||
|
|
||||||
**Allows you to post publicly without having your post show up in trends, hashtags or public timelines (i.e., in the tabs “Community”, “Federated” and “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”).**
|
||||||
|
|
||||||
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 reblogged/replied to your post.
|
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).
|
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).
|
||||||
|
|
||||||
@@ -31,12 +29,6 @@ Despite being one of the main features of federated social media, the Federated
|
|||||||
|
|
||||||
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!
|
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!
|
||||||
|
|
||||||
### **Draft and schedule posts**
|
|
||||||
|
|
||||||
**Allows for preparing a post and scheduling it to send it automatically at a specific time.**
|
|
||||||
|
|
||||||
You can create drafts, edit them, send them manually later or set a scheduled date. Drafts are technically saved as scheduled posts, so you can view and edit them from other apps that support scheduled posts. Scheduled posts are handled by your home instance, so they'll work even if you uninstall Megalodon.
|
|
||||||
|
|
||||||
### **Image description viewer**
|
### **Image description viewer**
|
||||||
|
|
||||||
**Allows you to quickly check whether an image or video has an alternative text attached to it.**
|
**Allows you to quickly check whether an image or video has an alternative text attached to it.**
|
||||||
@@ -49,40 +41,24 @@ This is important to **ensure the content you’re sharing is as accessible as p
|
|||||||
|
|
||||||
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.
|
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
|
## Installation
|
||||||
|
|
||||||
### IzzyOnDroid
|
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Megalodon will automatically notify you about new updates inside the app.**
|
||||||
|
|
||||||
[apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
|
To install this app on your Android device, download the [latest release from GitHub](https://github.com/sk22/megalodon/releases/latest/download/megalodon.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/sk22/megalodon/releases) page.
|
||||||
|
|
||||||
<a href="#installation"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
|
||||||
|
|
||||||
Note that you'll need to add Izzy's F-Droid repository to your F-Droid app first:
|
|
||||||
|
|
||||||
[`https://apt.izzysoft.de/fdroid/repo`](https://apt.izzysoft.de/fdroid/repo)
|
|
||||||
|
|
||||||
### Google Play Store
|
|
||||||
|
|
||||||
[play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
|
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
|
|
||||||
|
|
||||||
### F-Droid
|
|
||||||
|
|
||||||
**[F-Droid.org?](https://f-droid.org)** Not yet, sorry!
|
|
||||||
|
|
||||||
If you want, you can help me figure out if something's missing in the [Issue #47: F-Droid.org](https://github.com/sk22/megalodon/issues/47)
|
|
||||||
|
|
||||||
### Direct
|
|
||||||
|
|
||||||
Press the download button to download the APK. Open the downloaded file on your Android device to install it. Megalodon will automatically notify you about new updates inside the app.
|
|
||||||
|
|
||||||
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
|
||||||
|
|
||||||
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/sk22/megalodon/releases) page.
|
|
||||||
|
|
||||||
Megalodon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Megalodon 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!
|
Megalodon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Megalodon 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!
|
||||||
|
|
||||||
|
### Other sources
|
||||||
|
|
||||||
|
* **[Izzy's F-Droid repository](https://apt.izzysoft.de/fdroid/repo)**: https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
@@ -102,19 +78,6 @@ This is an **unmodified version** of the official [Mastodon for Android](https:/
|
|||||||
|
|
||||||
Variant without the integrated updater. This is the variant to be published to F-Droid.org where an integrated updater is not necessary. -->
|
Variant without the integrated updater. This is the variant to be published to F-Droid.org where an integrated updater is not necessary. -->
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contribution
|
|
||||||
|
|
||||||
### Translation
|
|
||||||
|
|
||||||
As with the source code, the translation is sourced from the official project, which you can contribute to on the official “**Mastodon for Android**” Crowdin project: https://crowdin.com/project/mastodon-for-android
|
|
||||||
|
|
||||||
There's also a handful of custom strings exclusive to this projects that would need to be translated. You can help translate **Megalodon** on Weblate: https://translate.codeberg.org/projects/megalodon/
|
|
||||||
|
|
||||||
[](https://translate.codeberg.org/engage/megalodon/)
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
@@ -131,7 +94,7 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [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))
|
* [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 “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 “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 reblogs 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))
|
* [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))
|
* [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))
|
* [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)
|
* [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)
|
||||||
@@ -141,17 +104,7 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Add notifications tab for posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/posts-notifications-tab)
|
* [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)
|
* [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/boost line above posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:clickable-boost-reply-line)
|
||||||
* [Add push notification setting for post notifications](https://github.com/sk22/megalodon/commit/b190480d7739be47f23543d9e7644660f9b4b4ee)
|
* [Clickable reply line while replying to open original post](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/clickable-reply-line-compose)
|
||||||
* [Add option to allow voting for multiple options on polls](https://github.com/sk22/megalodon/commit/5b28468efd49387b4f8b83f142f3adf3104ca60c)
|
|
||||||
* [Add translate function](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/translate-button)
|
|
||||||
* [Add language selector](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/language-selector)
|
|
||||||
* [Implement deleting notifications](https://github.com/sk22/megalodon/commit/b0f9ce081f69f29ad59658fc00ca41372cd2677d) (disabled by default)
|
|
||||||
* [Long-click boost button to "quote" a post](https://github.com/sk22/megalodon/commit/b25a237c20c6a924ed4d9b357999867c3a32b32b)
|
|
||||||
* [Draft and schedule posts](https://github.com/sk22/megalodon/pull/217)
|
|
||||||
* [Display original post when replying](https://github.com/sk22/megalodon/commit/375f8ceb2747705fedf43686681cc0e0b812f899)
|
|
||||||
* [Display server announcements](https://github.com/sk22/megalodon/commit/84179bc207d6b69cc2a770a3c28fa0a39b0b54e8)
|
|
||||||
* [Create](https://github.com/sk22/megalodon/commit/294595513a45037359b31377aafc25ae5b58d8e7), [edit](https://github.com/sk22/megalodon/commit/d47797bf7ac8cff3f9ba1cfee219a1bb2af21da6) and [delete](https://github.com/sk22/megalodon/commit/54c29fd787fc2cd0dfd2787ad796b8190f795973) lists
|
|
||||||
* [Soft-blocking (by blocking and immediately unblocking)](https://github.com/sk22/megalodon/commit/e75d350b7a2709259e9fc5138e0e1f361bdb0972)
|
|
||||||
|
|
||||||
|
|
||||||
### Behavior
|
### Behavior
|
||||||
@@ -163,18 +116,6 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Option to hide interaction numbers](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/hide-interaction-numbers)
|
* [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 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)
|
* [Option to disable scrolling title bars](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:settings/disable-marquee)
|
||||||
* [No ellipsis for long poll answers](https://github.com/mastodon/mastodon-android/commit/c9aae828e2518adccdc092e41f8d1f0489636271)
|
|
||||||
* [Show poll vote button for multiple and single answer polls](https://github.com/mastodon/mastodon-android/commit/e14dfda2fdf32f0fa3043504ac5831683a87559a)
|
|
||||||
* [Show own vote after voting](https://github.com/mastodon/mastodon-android/commit/4ab9e25fec4fd9c10b7a8ddd1be522b3cc12cf28) ([Closes issue](https://github.com/mastodon/mastodon-android/commit/4ab9e25fec4fd9c10b7a8ddd1be522b3cc12cf28))
|
|
||||||
* [Make inline emoji search case-insensitive and don't only search from start of emoji names](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:better-inline-emoji-search) ([Pull request](https://github.com/mastodon/mastodon-android/pull/445))
|
|
||||||
* [Include subject line when sharing e.g. a website to Megalodon](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:external-share-include-subject)
|
|
||||||
* [Improve semantics for voting on polls (radio buttons and checkboxes)](https://github.com/sk22/megalodon/commit/6fd58c96827cb1d2da329cebdc170a1425dd18d7)
|
|
||||||
* [Copy post URL when long-pressing share button](https://github.com/sk22/megalodon/commit/ba36347f03278763ecec617b1ce57ba89db7be72)
|
|
||||||
* [Add option to disable swiping between tabs](https://github.com/sk22/megalodon/commit/1f20b21fc84bf006c1ec14bd2229cbfad5215ec8)
|
|
||||||
* [Resolve Fediverse links in the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/open-urls-in-app)
|
|
||||||
* [Preserve whitespaces in HTML](https://github.com/sk22/megalodon/commit/7d876bddc7a07d98f0fecbf62b13bdb9fcce3412)
|
|
||||||
* [Long-click to copy links](https://github.com/sk22/megalodon/commit/b32e32274923a94742a9926ef38785f746d41405)
|
|
||||||
* Improved filtering using Mastodon 4.0 API: [#202](https://github.com/sk22/megalodon/pull/202), [#212](https://github.com/sk22/megalodon/pull/212), [#255](https://github.com/sk22/megalodon/pull/255) by [@thiagojedi](https://github.com/thiagojedi)
|
|
||||||
|
|
||||||
|
|
||||||
### Visual
|
### Visual
|
||||||
@@ -182,14 +123,6 @@ There's also a handful of custom strings exclusive to this projects that would n
|
|||||||
* [Custom extended footer redesign](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:compact-extended-footer)
|
* [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)
|
* [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)
|
* [Profile header tweaks](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:ui/profile-header-tweaks)
|
||||||
* [Custom color themes](https://github.com/sk22/megalodon/pull/124) by [@LucasGGamerM](https://github.com/LucasGGamerM)
|
|
||||||
* [Custom "megalodon" text logo](https://github.com/sk22/megalodon/commit/563afd487ca5c608cfbb00fa3909d3c27384acc0) by [@LucasGGamerM](https://github.com/LucasGGamerM)
|
|
||||||
* [Custom login screen](https://github.com/sk22/megalodon/commit/9bbf8c4618dbe13accaeb3b5482bf3fe88cac4c0)
|
|
||||||
* [More distinct filled boost icon](https://github.com/sk22/megalodon/commits/more-distinct-filled-boost-icon)
|
|
||||||
* Material You color theme by [@LucasGGamerM](https://github.com/LucasGGamerM)
|
|
||||||
* [Animations for interaction buttons](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/animate-buttons)
|
|
||||||
* [Dedicated icons for different notification types](https://github.com/sk22/megalodon/pull/178) by [@florian-obernberger](https://github.com/florian-obernberger)
|
|
||||||
* Scale text according to system settings
|
|
||||||
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
find metadata -name '*.txt' -exec sed -Ei 's/^[–—─•·*]\s+/- /' {} \;
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
@@ -5,14 +5,17 @@ plugins {
|
|||||||
android {
|
android {
|
||||||
compileSdk 33
|
compileSdk 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
archivesBaseName = "megalodon"
|
archivesBaseName = "moshidon"
|
||||||
applicationId "org.joinmastodon.android.sk"
|
applicationId "org.joinmastodon.android.moshinda"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 70
|
versionCode 56
|
||||||
versionName "1.1.5+fork.70"
|
versionName "1.1.4+fork.56.moshinda"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
resConfigs "en", "ar-rSA", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES",
|
||||||
|
"eu-rES", "fi-rFI", "fr-rFR", "gl-rES", "hr-rHR", "hy-rAM", "it-rIT", "iw-rIL",
|
||||||
|
"ja-rJP", "kab", "ko-rKR", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ru-rRU",
|
||||||
|
"sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -29,11 +32,9 @@ android {
|
|||||||
githubRelease{
|
githubRelease{
|
||||||
initWith release
|
initWith release
|
||||||
}
|
}
|
||||||
playRelease{
|
noFederatedRelease{
|
||||||
initWith release
|
initWith release
|
||||||
minifyEnabled true
|
versionNameSuffix '-nofederated'
|
||||||
shrinkResources true
|
|
||||||
versionNameSuffix '-play'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -77,4 +78,4 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.4-alpha05'
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
|
androidTestImplementation 'androidx.test:runner:1.5.0-alpha02'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
|
||||||
}
|
}
|
||||||
BIN
mastodon/src/github/ic_launcher-playstore.png
Normal file
BIN
mastodon/src/github/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -14,14 +14,12 @@ import android.os.Build;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
@@ -63,7 +61,6 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
info=new UpdateInfo();
|
info=new UpdateInfo();
|
||||||
info.version=prefs.getString("version", null);
|
info.version=prefs.getString("version", null);
|
||||||
info.size=prefs.getLong("apkSize", 0);
|
info.size=prefs.getLong("apkSize", 0);
|
||||||
info.changelog=prefs.getString("changelog", null);
|
|
||||||
downloadID=prefs.getLong("downloadID", 0);
|
downloadID=prefs.getLong("downloadID", 0);
|
||||||
if(downloadID==0 || !getUpdateApkFile().exists()){
|
if(downloadID==0 || !getUpdateApkFile().exists()){
|
||||||
state=UpdateState.UPDATE_AVAILABLE;
|
state=UpdateState.UPDATE_AVAILABLE;
|
||||||
@@ -87,7 +84,6 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
.remove("apkURL")
|
.remove("apkURL")
|
||||||
.remove("checkedByBuild")
|
.remove("checkedByBuild")
|
||||||
.remove("downloadID")
|
.remove("downloadID")
|
||||||
.remove("changelog")
|
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,70 +111,61 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
|
|
||||||
private void actuallyCheckForUpdates(){
|
private void actuallyCheckForUpdates(){
|
||||||
Request req=new Request.Builder()
|
Request req=new Request.Builder()
|
||||||
.url("https://api.github.com/repos/sk22/megalodon/releases")
|
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases/latest")
|
||||||
.build();
|
.build();
|
||||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||||
try(Response resp=call.execute()){
|
try(Response resp=call.execute()){
|
||||||
JsonArray arr=JsonParser.parseReader(resp.body().charStream()).getAsJsonArray();
|
JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
|
||||||
for (JsonElement jsonElement : arr) {
|
String tag=obj.get("tag_name").getAsString();
|
||||||
JsonObject obj = jsonElement.getAsJsonObject();
|
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
||||||
if (obj.get("prerelease").getAsBoolean() && !GlobalUserPreferences.enablePreReleases) continue;
|
Matcher matcher=pattern.matcher(tag);
|
||||||
|
if(!matcher.find()){
|
||||||
|
Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int newMajor=Integer.parseInt(matcher.group(1)),
|
||||||
|
newMinor=Integer.parseInt(matcher.group(2)),
|
||||||
|
newRevision=Integer.parseInt(matcher.group(3)),
|
||||||
|
newForkNumber=Integer.parseInt(matcher.group(4));
|
||||||
|
matcher=pattern.matcher(BuildConfig.VERSION_NAME);
|
||||||
|
String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
|
||||||
|
if(!matcher.find()){
|
||||||
|
Log.w(TAG, "actuallyCheckForUpdates: current version has wrong format: "+BuildConfig.VERSION_NAME);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int curMajor=Integer.parseInt(matcher.group(1)),
|
||||||
|
curMinor=Integer.parseInt(matcher.group(2)),
|
||||||
|
curRevision=Integer.parseInt(matcher.group(3)),
|
||||||
|
curForkNumber=Integer.parseInt(matcher.group(4));
|
||||||
|
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
||||||
|
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
||||||
|
if(newVersion>curVersion || newForkNumber>curForkNumber){
|
||||||
|
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
||||||
|
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
||||||
|
for(JsonElement el:obj.getAsJsonArray("assets")){
|
||||||
|
JsonObject asset=el.getAsJsonObject();
|
||||||
|
if("moshidon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
||||||
|
long size=asset.get("size").getAsLong();
|
||||||
|
String url=asset.get("browser_download_url").getAsString();
|
||||||
|
|
||||||
String tag=obj.get("tag_name").getAsString();
|
UpdateInfo info=new UpdateInfo();
|
||||||
String changelog=obj.get("body").getAsString();
|
info.size=size;
|
||||||
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
info.version=version;
|
||||||
Matcher matcher=pattern.matcher(tag);
|
this.info=info;
|
||||||
if(!matcher.find()){
|
|
||||||
Log.w(TAG, "actuallyCheckForUpdates: release tag has wrong format: "+tag);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int newMajor=Integer.parseInt(matcher.group(1)),
|
|
||||||
newMinor=Integer.parseInt(matcher.group(2)),
|
|
||||||
newRevision=Integer.parseInt(matcher.group(3)),
|
|
||||||
newForkNumber=Integer.parseInt(matcher.group(4));
|
|
||||||
matcher=pattern.matcher(BuildConfig.VERSION_NAME);
|
|
||||||
String[] currentParts=BuildConfig.VERSION_NAME.split("[.+]");
|
|
||||||
if(!matcher.find()){
|
|
||||||
Log.w(TAG, "actuallyCheckForUpdates: current version has wrong format: "+BuildConfig.VERSION_NAME);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int curMajor=Integer.parseInt(matcher.group(1)),
|
|
||||||
curMinor=Integer.parseInt(matcher.group(2)),
|
|
||||||
curRevision=Integer.parseInt(matcher.group(3)),
|
|
||||||
curForkNumber=Integer.parseInt(matcher.group(4));
|
|
||||||
long newVersion=((long)newMajor << 32) | ((long)newMinor << 16) | newRevision;
|
|
||||||
long curVersion=((long)curMajor << 32) | ((long)curMinor << 16) | curRevision;
|
|
||||||
if(newVersion>curVersion || newForkNumber>curForkNumber){
|
|
||||||
String version=newMajor+"."+newMinor+"."+newRevision+"+fork."+newForkNumber;
|
|
||||||
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
|
||||||
for(JsonElement el:obj.getAsJsonArray("assets")){
|
|
||||||
JsonObject asset=el.getAsJsonObject();
|
|
||||||
if("megalodon.apk".equals(asset.get("name").getAsString()) && "application/vnd.android.package-archive".equals(asset.get("content_type").getAsString()) && "uploaded".equals(asset.get("state").getAsString())){
|
|
||||||
long size=asset.get("size").getAsLong();
|
|
||||||
String url=asset.get("browser_download_url").getAsString();
|
|
||||||
|
|
||||||
UpdateInfo info=new UpdateInfo();
|
getPrefs().edit()
|
||||||
info.size=size;
|
.putLong("apkSize", size)
|
||||||
info.version=version;
|
.putString("version", version)
|
||||||
info.changelog=changelog;
|
.putString("apkURL", url)
|
||||||
this.info=info;
|
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
||||||
|
.remove("downloadID")
|
||||||
|
.apply();
|
||||||
|
|
||||||
getPrefs().edit()
|
break;
|
||||||
.putLong("apkSize", size)
|
|
||||||
.putString("version", version)
|
|
||||||
.putString("apkURL", url)
|
|
||||||
.putString("changelog", changelog)
|
|
||||||
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
|
|
||||||
.remove("downloadID")
|
|
||||||
.apply();
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
|
||||||
}catch(Exception x){
|
}catch(Exception x){
|
||||||
Log.w(TAG, "actuallyCheckForUpdates", x);
|
Log.w(TAG, "actuallyCheckForUpdates", x);
|
||||||
}finally{
|
}finally{
|
||||||
|
|||||||
@@ -4,30 +4,22 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
|
|
||||||
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
<uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>
|
||||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
|
||||||
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
|
||||||
|
|
||||||
<queries>
|
|
||||||
<intent>
|
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
|
||||||
<data android:mimeType="text/plain" />
|
|
||||||
</intent>
|
|
||||||
</queries>
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MastodonApp"
|
android:name=".MastodonApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:label="@string/sk_app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
android:theme="@style/Theme.Mastodon.AutoLightDark.Original"
|
||||||
android:largeHeap="true">
|
android:largeHeap="true">
|
||||||
|
|
||||||
<activity android:name=".MainActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize" android:launchMode="singleTask">
|
<activity android:name=".MainActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize" android:launchMode="singleTask">
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
# lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
|
|
||||||
# https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
|
|
||||||
# This list contains domains of toxic mastodon instances
|
|
||||||
# Last-Modified: 1672044500
|
|
||||||
|
|
||||||
# gab - a neonazi social network
|
|
||||||
gab.ai
|
|
||||||
gab.com
|
|
||||||
gab.protohype.net
|
|
||||||
|
|
||||||
# consequence-free speech
|
|
||||||
social.unzensiert.to
|
|
||||||
freeatlantis.com
|
|
||||||
|
|
||||||
# reactionary bigotry and hatespeech against magrinalized groups
|
|
||||||
poa.st
|
|
||||||
freespeechextremist.com
|
|
||||||
rdrama.cc
|
|
||||||
outpoa.st
|
|
||||||
anime.website
|
|
||||||
gameliberty.club
|
|
||||||
social.byoblu.com
|
|
||||||
yggdrasil.social
|
|
||||||
smuglo.li
|
|
||||||
dogeposting.social
|
|
||||||
unsafe.space
|
|
||||||
freezepeach.xyz
|
|
||||||
|
|
||||||
# + CSAM
|
|
||||||
rojogato.com
|
|
||||||
|
|
||||||
# antivaxxer shitposting & fearmongering
|
|
||||||
shadowsocial.org
|
|
||||||
|
|
||||||
# Kiwifarms
|
|
||||||
kiwifarms.net
|
|
||||||
kiwifarms.cc
|
|
||||||
kiwifarms.is
|
|
||||||
kiwifarms.pleroma.net
|
|
||||||
|
|
||||||
|
|
||||||
# https://mastodon.art/@Curator/109649354849593592
|
|
||||||
|
|
||||||
poa.st antisemitic racist homophobic
|
|
||||||
nicecrew.digital antisemitic
|
|
||||||
beefyboys.win antisemitic racist homophobic harassment
|
|
||||||
cawfee.club antisemitic racist homophobic
|
|
||||||
comfyboy.club antisemitic racist homophobic
|
|
||||||
freespeechextremist.com racist homophobic
|
|
||||||
cum.salon racist misogynist
|
|
||||||
bae.st racist
|
|
||||||
natehiggers.online racist
|
|
||||||
rapemeat.solutions misogynist
|
|
||||||
rapist.town misogynist
|
|
||||||
rapefeminists.network misogynist
|
|
||||||
kiwifarms.cc harassment
|
|
||||||
noagendasocial.com noagenda
|
|
||||||
posting.lolicon.rocks underage
|
|
||||||
urchan.org harassment homophobic racist
|
|
||||||
ryona.agency harassment
|
|
||||||
yggdrasil.social antisemitic homophobic racist
|
|
||||||
genderheretics.xyz transphobic
|
|
||||||
baraag.net underage
|
|
||||||
lolison.top underage
|
|
||||||
shota.house underage
|
|
||||||
shota.social underage
|
|
||||||
aethy.com underage
|
|
||||||
taullo.social underage
|
|
||||||
childpawn.shop underage
|
|
||||||
posting.lolicon.rocks underage
|
|
||||||
loli.best underage
|
|
||||||
gothloli.club underage
|
|
||||||
smuglo.li underage
|
|
||||||
youjo.love underage
|
|
||||||
pedo.school underage
|
|
||||||
lolison.network underage
|
|
||||||
freak.university underage
|
|
||||||
mirr0r.city underage
|
|
||||||
xhais.love underage
|
|
||||||
refusal.biz underage
|
|
||||||
refusal.llc underage
|
|
||||||
mirr0r.city underage
|
|
||||||
nnia.space underage
|
|
||||||
ignorelist.com malicious
|
|
||||||
repl.co malicious
|
|
||||||
|
|
||||||
# custom
|
|
||||||
|
|
||||||
pawoo.net csam
|
|
||||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 358 KiB After Width: | Height: | Size: 15 KiB |
@@ -12,6 +12,7 @@ import android.widget.Toast;
|
|||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -35,10 +36,13 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
openComposeFragment(sessions.get(0).getID());
|
openComposeFragment(sessions.get(0).getID());
|
||||||
}else{
|
}else{
|
||||||
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
|
||||||
UiUtils.pickAccount(this, null, R.string.choose_account, 0,
|
new M3AlertDialogBuilder(this)
|
||||||
session -> openComposeFragment(session.getID()),
|
.setItems(sessions.stream().map(as->"@"+as.self.username+"@"+as.domain).toArray(String[]::new), (dialog, which)->{
|
||||||
b -> b.setOnCancelListener(d -> finish())
|
openComposeFragment(sessions.get(which).getID());
|
||||||
);
|
})
|
||||||
|
.setTitle(R.string.choose_account)
|
||||||
|
.setOnCancelListener(dialog -> finish())
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,14 +51,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
getWindow().setBackgroundDrawable(null);
|
getWindow().setBackgroundDrawable(null);
|
||||||
|
|
||||||
Intent intent=getIntent();
|
Intent intent=getIntent();
|
||||||
StringBuilder builder=new StringBuilder();
|
String text=intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||||
String subject = "";
|
|
||||||
if (intent.hasExtra(Intent.EXTRA_SUBJECT)) {
|
|
||||||
subject = intent.getStringExtra(Intent.EXTRA_SUBJECT);
|
|
||||||
if (!subject.isBlank()) builder.append(subject).append("\n\n");
|
|
||||||
}
|
|
||||||
if (intent.hasExtra(Intent.EXTRA_TEXT)) builder.append(intent.getStringExtra(Intent.EXTRA_TEXT)).append("\n");
|
|
||||||
String text=builder.toString();
|
|
||||||
List<Uri> mediaUris;
|
List<Uri> mediaUris;
|
||||||
if(Intent.ACTION_SEND.equals(intent.getAction())){
|
if(Intent.ACTION_SEND.equals(intent.getAction())){
|
||||||
Uri singleUri=intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
Uri singleUri=intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||||
@@ -80,8 +77,6 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
if(!TextUtils.isEmpty(text))
|
if(!TextUtils.isEmpty(text))
|
||||||
args.putString("prefilledText", text);
|
args.putString("prefilledText", text);
|
||||||
if(!subject.isBlank())
|
|
||||||
args.putInt("selectionEnd", subject.length());
|
|
||||||
if(mediaUris!=null && !mediaUris.isEmpty())
|
if(mediaUris!=null && !mediaUris.isEmpty())
|
||||||
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
|
args.putParcelableArrayList("mediaAttachments", toArrayList(mediaUris));
|
||||||
Fragment fragment=new ComposeFragment();
|
Fragment fragment=new ComposeFragment();
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
package org.joinmastodon.android;
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class GlobalUserPreferences{
|
public class GlobalUserPreferences{
|
||||||
public static boolean playGifs;
|
public static boolean playGifs;
|
||||||
public static boolean useCustomTabs;
|
public static boolean useCustomTabs;
|
||||||
@@ -25,36 +13,13 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean showInteractionCounts;
|
public static boolean showInteractionCounts;
|
||||||
public static boolean alwaysExpandContentWarnings;
|
public static boolean alwaysExpandContentWarnings;
|
||||||
public static boolean disableMarquee;
|
public static boolean disableMarquee;
|
||||||
public static boolean disableSwipe;
|
|
||||||
public static boolean voteButtonForSingleChoice;
|
|
||||||
public static boolean enableDeleteNotifications;
|
|
||||||
public static boolean translateButtonOpenedOnly;
|
|
||||||
public static boolean uniformNotificationIcon;
|
|
||||||
public static boolean reduceMotion;
|
|
||||||
public static boolean keepOnlyLatestNotification;
|
|
||||||
public static boolean disableAltTextReminder;
|
|
||||||
public static boolean showAltIndicator;
|
|
||||||
public static boolean showNoAltIndicator;
|
|
||||||
public static boolean enablePreReleases;
|
|
||||||
public static String publishButtonText;
|
|
||||||
public static ThemePreference theme;
|
public static ThemePreference theme;
|
||||||
public static ColorPreference color;
|
public static ColorPreference color;
|
||||||
|
|
||||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
|
||||||
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
|
||||||
public static Map<String, List<String>> recentLanguages;
|
|
||||||
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
|
||||||
|
|
||||||
private static SharedPreferences getPrefs(){
|
private static SharedPreferences getPrefs(){
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T fromJson(String json, Type type, T orElse) {
|
|
||||||
if (json == null) return orElse;
|
|
||||||
try { return gson.fromJson(json, type); }
|
|
||||||
catch (JsonSyntaxException ignored) { return orElse; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void load(){
|
public static void load(){
|
||||||
SharedPreferences prefs=getPrefs();
|
SharedPreferences prefs=getPrefs();
|
||||||
playGifs=prefs.getBoolean("playGifs", true);
|
playGifs=prefs.getBoolean("playGifs", true);
|
||||||
@@ -66,28 +31,8 @@ public class GlobalUserPreferences{
|
|||||||
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
|
||||||
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
|
||||||
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
disableMarquee=prefs.getBoolean("disableMarquee", false);
|
||||||
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
|
||||||
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
|
||||||
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
|
||||||
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
|
|
||||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
|
||||||
reduceMotion=prefs.getBoolean("reduceMotion", false);
|
|
||||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
|
||||||
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
|
|
||||||
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
|
||||||
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
|
||||||
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
|
|
||||||
publishButtonText=prefs.getString("publishButtonText", "");
|
|
||||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
||||||
recentLanguages=fromJson(prefs.getString("recentLanguages", null), recentLanguagesType, new HashMap<>());
|
color=ColorPreference.values()[prefs.getInt("color", 1)];
|
||||||
pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
|
|
||||||
|
|
||||||
try {
|
|
||||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
|
||||||
} catch (IllegalArgumentException|ClassCastException ignored) {
|
|
||||||
// invalid color name or color was previously saved as integer
|
|
||||||
color=ColorPreference.PINK;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void save(){
|
public static void save(){
|
||||||
@@ -101,32 +46,17 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("showInteractionCounts", showInteractionCounts)
|
.putBoolean("showInteractionCounts", showInteractionCounts)
|
||||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||||
.putBoolean("disableMarquee", disableMarquee)
|
.putBoolean("disableMarquee", disableMarquee)
|
||||||
.putBoolean("disableSwipe", disableSwipe)
|
|
||||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
|
||||||
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
|
|
||||||
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
|
||||||
.putBoolean("reduceMotion", reduceMotion)
|
|
||||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
|
||||||
.putBoolean("disableAltTextReminder", disableAltTextReminder)
|
|
||||||
.putBoolean("showAltIndicator", showAltIndicator)
|
|
||||||
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
|
||||||
.putBoolean("enablePreReleases", enablePreReleases)
|
|
||||||
.putString("publishButtonText", publishButtonText)
|
|
||||||
.putInt("theme", theme.ordinal())
|
.putInt("theme", theme.ordinal())
|
||||||
.putString("color", color.name())
|
.putInt("color", color.ordinal())
|
||||||
.putString("recentLanguages", gson.toJson(recentLanguages))
|
|
||||||
.putString("pinnedTimelines", gson.toJson(pinnedTimelines))
|
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ColorPreference{
|
public enum ColorPreference{
|
||||||
MATERIAL3,
|
|
||||||
PINK,
|
PINK,
|
||||||
PURPLE,
|
PURPLE,
|
||||||
GREEN,
|
GREEN,
|
||||||
BLUE,
|
BLUE,
|
||||||
BROWN,
|
ORANGE,
|
||||||
RED,
|
|
||||||
YELLOW
|
YELLOW
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
|||||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||||
import org.joinmastodon.android.fragments.HomeFragment;
|
import org.joinmastodon.android.fragments.HomeFragment;
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
|
import org.joinmastodon.android.fragments.SplashFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
|
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
@@ -33,19 +33,18 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
showFragmentClearingBackStack(new SplashFragment());
|
||||||
}else{
|
}else{
|
||||||
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
|
||||||
AccountSession session;
|
AccountSession session;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
Intent intent=getIntent();
|
Intent intent=getIntent();
|
||||||
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
if(intent.getBooleanExtra("fromNotification", false)){
|
||||||
boolean hasNotification = intent.hasExtra("notification");
|
|
||||||
if(fromNotification){
|
|
||||||
String accountID=intent.getStringExtra("accountID");
|
String accountID=intent.getStringExtra("accountID");
|
||||||
try{
|
try{
|
||||||
session=AccountSessionManager.getInstance().getAccount(accountID);
|
session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
if(!hasNotification) args.putString("tab", "notifications");
|
if(!intent.hasExtra("notification"))
|
||||||
|
args.putString("tab", "notifications");
|
||||||
}catch(IllegalStateException x){
|
}catch(IllegalStateException x){
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
||||||
}
|
}
|
||||||
@@ -55,13 +54,13 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
args.putString("account", session.getID());
|
args.putString("account", session.getID());
|
||||||
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
if(fromNotification && hasNotification){
|
showFragmentClearingBackStack(fragment);
|
||||||
|
if(intent.getBooleanExtra("fromNotification", false) && intent.hasExtra("notification")){
|
||||||
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
|
||||||
showFragmentForNotification(notification, session.getID());
|
showFragmentForNotification(notification, session.getID());
|
||||||
} else if (intent.getBooleanExtra("compose", false)){
|
}else if(intent.getBooleanExtra("compose", false)){
|
||||||
showCompose();
|
showCompose();
|
||||||
} else {
|
}else{
|
||||||
showFragmentClearingBackStack(fragment);
|
|
||||||
maybeRequestNotificationsPermission();
|
maybeRequestNotificationsPermission();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,30 +139,4 @@ public class MainActivity extends FragmentStackActivity{
|
|||||||
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* when opening app through a notification: if (thread) fragment "can go back", clear back stack
|
|
||||||
* and show home fragment. upstream's implementation doesn't require this as it opens home first
|
|
||||||
* and then immediately switches to the notification's ThreadFragment. this causes a black
|
|
||||||
* screen in megalodon, for some reason, so i'm working around this that way.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
Fragment currentFragment = getFragmentManager().findFragmentById(
|
|
||||||
(fragmentContainers.get(fragmentContainers.size() - 1)).getId()
|
|
||||||
);
|
|
||||||
Bundle currentArgs = currentFragment.getArguments();
|
|
||||||
if (this.fragmentContainers.size() == 1
|
|
||||||
&& currentArgs.getBoolean("_can_go_back", false)
|
|
||||||
&& currentArgs.containsKey("account")) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString("account", currentArgs.getString("account"));
|
|
||||||
args.putString("tab", "notifications");
|
|
||||||
Fragment fragment=new HomeFragment();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
showFragmentClearingBackStack(fragment);
|
|
||||||
} else {
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class OAuthActivity extends Activity{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account account){
|
public void onSuccess(Account account){
|
||||||
AccountSessionManager.getInstance().addAccount(instance, token, account, app, null);
|
AccountSessionManager.getInstance().addAccount(instance, token, account, app, true);
|
||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
finish();
|
finish();
|
||||||
// not calling restartMainActivity() here on purpose to have it recreated (notice different flags)
|
// not calling restartMainActivity() here on purpose to have it recreated (notice different flags)
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import android.app.PendingIntent;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -37,7 +39,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
private static final String TAG="PushNotificationReceive";
|
private static final String TAG="PushNotificationReceive";
|
||||||
|
|
||||||
public static final int NOTIFICATION_ID=178;
|
public static final int NOTIFICATION_ID=178;
|
||||||
private static int notificationId = 0;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent){
|
public void onReceive(Context context, Intent intent){
|
||||||
@@ -137,31 +138,18 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
.setContentText(pn.body)
|
.setContentText(pn.body)
|
||||||
.setStyle(new Notification.BigTextStyle().bigText(pn.body))
|
.setStyle(new Notification.BigTextStyle().bigText(pn.body))
|
||||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||||
.setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
.setContentIntent(PendingIntent.getActivity(context, accountID.hashCode() & 0xFFFF, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
|
||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setColor(context.getColor(R.color.primary_700));
|
.setColor(context.getColor(R.color.primary_700));
|
||||||
|
|
||||||
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
|
||||||
builder.setSmallIcon(switch (pn.notificationType) {
|
|
||||||
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
|
|
||||||
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
|
|
||||||
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
|
|
||||||
case MENTION -> R.drawable.ic_fluent_mention_24_filled;
|
|
||||||
case POLL -> R.drawable.ic_fluent_poll_24_filled;
|
|
||||||
case STATUS -> R.drawable.ic_fluent_chat_24_filled;
|
|
||||||
case UPDATE -> R.drawable.ic_fluent_history_24_filled;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(avatar!=null){
|
if(avatar!=null){
|
||||||
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
builder.setLargeIcon(UiUtils.getBitmapFromDrawable(avatar));
|
||||||
}
|
}
|
||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().size()>1){
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().size()>1){
|
||||||
builder.setSubText(accountName);
|
builder.setSubText(accountName);
|
||||||
}
|
}
|
||||||
nm.notify(accountID, GlobalUserPreferences.keepOnlyLatestNotification ? NOTIFICATION_ID : notificationId++, builder.build());
|
nm.notify(accountID, NOTIFICATION_ID, builder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,11 @@ import com.google.gson.JsonParser;
|
|||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
|
||||||
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@@ -50,26 +47,9 @@ public class MastodonAPIController{
|
|||||||
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
|
||||||
|
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
private static List<String> badDomains = new ArrayList<>();
|
|
||||||
|
|
||||||
static{
|
static{
|
||||||
thread.start();
|
thread.start();
|
||||||
try {
|
|
||||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
|
||||||
MastodonApp.context.getAssets().open("blocks.tsv")
|
|
||||||
));
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
if (line.isBlank() || line.startsWith("#")) continue;
|
|
||||||
String[] parts = line.replaceAll("\"", "").split("[\s,;]");
|
|
||||||
if (parts.length == 0) continue;
|
|
||||||
String domain = parts[0].toLowerCase().trim();
|
|
||||||
if (domain.isBlank()) continue;
|
|
||||||
badDomains.add(domain);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPIController(@Nullable AccountSession session){
|
public MastodonAPIController(@Nullable AccountSession session){
|
||||||
@@ -77,11 +57,8 @@ public class MastodonAPIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <T> void submitRequest(final MastodonAPIRequest<T> req){
|
public <T> void submitRequest(final MastodonAPIRequest<T> req){
|
||||||
final String host = req.getURL().getHost();
|
|
||||||
final boolean isBad = host == null || badDomains.stream().anyMatch(h -> h.equalsIgnoreCase(host) || host.toLowerCase().endsWith("." + h));
|
|
||||||
thread.postRunnable(()->{
|
thread.postRunnable(()->{
|
||||||
try{
|
try{
|
||||||
if (isBad) throw new IllegalArgumentException();
|
|
||||||
if(req.canceled)
|
if(req.canceled)
|
||||||
return;
|
return;
|
||||||
Request.Builder builder=new Request.Builder()
|
Request.Builder builder=new Request.Builder()
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
@@ -102,14 +101,9 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
|
||||||
return wrapProgress(activity, message, cancelable, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable, Consumer<ProgressDialog> transform){
|
|
||||||
progressDialog=new ProgressDialog(activity);
|
progressDialog=new ProgressDialog(activity);
|
||||||
progressDialog.setMessage(activity.getString(message));
|
progressDialog.setMessage(activity.getString(message));
|
||||||
progressDialog.setCancelable(cancelable);
|
progressDialog.setCancelable(cancelable);
|
||||||
if (transform != null) transform.accept(progressDialog);
|
|
||||||
if(cancelable){
|
if(cancelable){
|
||||||
progressDialog.setOnCancelListener(dialog->cancel());
|
progressDialog.setOnCancelListener(dialog->cancel());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,8 +162,6 @@ public class PushSubscriptionManager{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(PushSubscription result){
|
public void onSuccess(PushSubscription result){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
result.serverKey=result.serverKey.replace('/','_');
|
|
||||||
result.serverKey=result.serverKey.replace('+','-');
|
|
||||||
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
serverKey=deserializeRawPublicKey(Base64.decode(result.serverKey, Base64.URL_SAFE));
|
||||||
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().tryGetAccount(accountID);
|
||||||
@@ -372,7 +370,7 @@ public class PushSubscriptionManager{
|
|||||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||||
if(session.pushSubscription==null || forceReRegister)
|
if(session.pushSubscription==null || forceReRegister)
|
||||||
session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription);
|
session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription);
|
||||||
else
|
else if(session.needUpdatePushSettings)
|
||||||
session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription);
|
session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,31 +9,23 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
|||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
public class StatusInteractionController{
|
public class StatusInteractionController{
|
||||||
private final String accountID;
|
private final String accountID;
|
||||||
private final boolean updateCounters;
|
|
||||||
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
private final HashMap<String, SetStatusFavorited> runningFavoriteRequests=new HashMap<>();
|
||||||
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
private final HashMap<String, SetStatusReblogged> runningReblogRequests=new HashMap<>();
|
||||||
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
private final HashMap<String, SetStatusBookmarked> runningBookmarkRequests=new HashMap<>();
|
||||||
|
|
||||||
public StatusInteractionController(String accountID, boolean updateCounters) {
|
|
||||||
this.accountID=accountID;
|
|
||||||
this.updateCounters=updateCounters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StatusInteractionController(String accountID){
|
public StatusInteractionController(String accountID){
|
||||||
this(accountID, true);
|
this.accountID=accountID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFavorited(Status status, boolean favorited, Consumer<Status> cb){
|
public void setFavorited(Status status, boolean favorited){
|
||||||
if(!Looper.getMainLooper().isCurrentThread())
|
if(!Looper.getMainLooper().isCurrentThread())
|
||||||
throw new IllegalStateException("Can only be called from main thread");
|
throw new IllegalStateException("Can only be called from main thread");
|
||||||
|
|
||||||
@@ -46,9 +38,7 @@ public class StatusInteractionController{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
runningFavoriteRequests.remove(status.id);
|
runningFavoriteRequests.remove(status.id);
|
||||||
result.favouritesCount = Math.max(0, status.favouritesCount) + (favorited ? 1 : -1);
|
E.post(new StatusCountersUpdatedEvent(result));
|
||||||
cb.accept(result);
|
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,17 +46,24 @@ public class StatusInteractionController{
|
|||||||
runningFavoriteRequests.remove(status.id);
|
runningFavoriteRequests.remove(status.id);
|
||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.favourited=!favorited;
|
status.favourited=!favorited;
|
||||||
cb.accept(status);
|
if(favorited)
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
status.favouritesCount--;
|
||||||
|
else
|
||||||
|
status.favouritesCount++;
|
||||||
|
E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningFavoriteRequests.put(status.id, req);
|
runningFavoriteRequests.put(status.id, req);
|
||||||
status.favourited=favorited;
|
status.favourited=favorited;
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(favorited)
|
||||||
|
status.favouritesCount++;
|
||||||
|
else
|
||||||
|
status.favouritesCount--;
|
||||||
|
E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
public void setReblogged(Status status, boolean reblogged){
|
||||||
if(!Looper.getMainLooper().isCurrentThread())
|
if(!Looper.getMainLooper().isCurrentThread())
|
||||||
throw new IllegalStateException("Can only be called from main thread");
|
throw new IllegalStateException("Can only be called from main thread");
|
||||||
|
|
||||||
@@ -74,15 +71,12 @@ public class StatusInteractionController{
|
|||||||
if(current!=null){
|
if(current!=null){
|
||||||
current.cancel();
|
current.cancel();
|
||||||
}
|
}
|
||||||
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged, visibility)
|
SetStatusReblogged req=(SetStatusReblogged) new SetStatusReblogged(status.id, reblogged)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status reblog){
|
public void onSuccess(Status result){
|
||||||
Status result = reblog.getContentStatus();
|
|
||||||
runningReblogRequests.remove(status.id);
|
runningReblogRequests.remove(status.id);
|
||||||
result.reblogsCount = Math.max(0, status.reblogsCount) + (reblogged ? 1 : -1);
|
E.post(new StatusCountersUpdatedEvent(result));
|
||||||
cb.accept(result);
|
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,21 +84,24 @@ public class StatusInteractionController{
|
|||||||
runningReblogRequests.remove(status.id);
|
runningReblogRequests.remove(status.id);
|
||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.reblogged=!reblogged;
|
status.reblogged=!reblogged;
|
||||||
cb.accept(status);
|
if(reblogged)
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
status.reblogsCount--;
|
||||||
|
else
|
||||||
|
status.reblogsCount++;
|
||||||
|
E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningReblogRequests.put(status.id, req);
|
runningReblogRequests.put(status.id, req);
|
||||||
status.reblogged=reblogged;
|
status.reblogged=reblogged;
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if(reblogged)
|
||||||
|
status.reblogsCount++;
|
||||||
|
else
|
||||||
|
status.reblogsCount--;
|
||||||
|
E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBookmarked(Status status, boolean bookmarked){
|
public void setBookmarked(Status status, boolean bookmarked){
|
||||||
setBookmarked(status, bookmarked, r->{});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBookmarked(Status status, boolean bookmarked, Consumer<Status> cb){
|
|
||||||
if(!Looper.getMainLooper().isCurrentThread())
|
if(!Looper.getMainLooper().isCurrentThread())
|
||||||
throw new IllegalStateException("Can only be called from main thread");
|
throw new IllegalStateException("Can only be called from main thread");
|
||||||
|
|
||||||
@@ -117,8 +114,7 @@ public class StatusInteractionController{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
runningBookmarkRequests.remove(status.id);
|
runningBookmarkRequests.remove(status.id);
|
||||||
cb.accept(result);
|
E.post(new StatusCountersUpdatedEvent(result));
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -126,13 +122,12 @@ public class StatusInteractionController{
|
|||||||
runningBookmarkRequests.remove(status.id);
|
runningBookmarkRequests.remove(status.id);
|
||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.bookmarked=!bookmarked;
|
status.bookmarked=!bookmarked;
|
||||||
cb.accept(status);
|
E.post(new StatusCountersUpdatedEvent(status));
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
runningBookmarkRequests.put(status.id, req);
|
runningBookmarkRequests.put(status.id, req);
|
||||||
status.bookmarked=bookmarked;
|
status.bookmarked=bookmarked;
|
||||||
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.announcements;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
|
|
||||||
public class DismissAnnouncement extends MastodonAPIRequest<Object>{
|
|
||||||
public DismissAnnouncement(String id){
|
|
||||||
super(HttpMethod.POST, "/announcements/" + id + "/dismiss", Object.class);
|
|
||||||
setRequestBody(new Object());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.announcements;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.Announcement;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GetAnnouncements extends MastodonAPIRequest<List<Announcement>> {
|
|
||||||
public GetAnnouncements(boolean withDismissed) {
|
|
||||||
super(MastodonAPIRequest.HttpMethod.GET, "/announcements", new TypeToken<>(){});
|
|
||||||
addQueryParameter("with_dismissed", withDismissed ? "true" : "false");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,15 +7,4 @@ public class GetInstance extends MastodonAPIRequest<Instance>{
|
|||||||
public GetInstance(){
|
public GetInstance(){
|
||||||
super(HttpMethod.GET, "/instance", Instance.class);
|
super(HttpMethod.GET, "/instance", Instance.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class V2 extends MastodonAPIRequest<Instance.V2>{
|
|
||||||
public V2(){
|
|
||||||
super(HttpMethod.GET, "/instance", Instance.V2.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getPathPrefix() {
|
|
||||||
return "/api/v2";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
|
|
||||||
public class CreateList extends MastodonAPIRequest<ListTimeline> {
|
|
||||||
public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
|
||||||
super(HttpMethod.POST, "/lists", ListTimeline.class);
|
|
||||||
Request req = new Request();
|
|
||||||
req.title = title;
|
|
||||||
req.repliesPolicy = repliesPolicy;
|
|
||||||
setRequestBody(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request {
|
|
||||||
public String title;
|
|
||||||
public ListTimeline.RepliesPolicy repliesPolicy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
|
|
||||||
public class DeleteList extends MastodonAPIRequest<Object> {
|
|
||||||
public DeleteList(String id) {
|
|
||||||
super(HttpMethod.DELETE, "/lists/" + id, Object.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
|
|
||||||
public class GetList extends MastodonAPIRequest<ListTimeline> {
|
|
||||||
public GetList(String id) {
|
|
||||||
super(HttpMethod.GET, "/lists/" + id, ListTimeline.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
|
|
||||||
public class UpdateList extends MastodonAPIRequest<ListTimeline> {
|
|
||||||
public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
|
||||||
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
|
|
||||||
CreateList.Request req = new CreateList.Request();
|
|
||||||
req.title = title;
|
|
||||||
req.repliesPolicy = repliesPolicy;
|
|
||||||
setRequestBody(req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.notifications;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.ApiUtils;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.Notification;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DismissNotification extends MastodonAPIRequest<Object>{
|
|
||||||
public DismissNotification(String id){
|
|
||||||
super(HttpMethod.POST, "/notifications/" + (id != null ? id + "/dismiss" : "clear"), Object.class);
|
|
||||||
setRequestBody(new Object());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
Request r=new Request();
|
Request r=new Request();
|
||||||
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
|
||||||
r.data.alerts=alerts;
|
r.data.alerts=alerts;
|
||||||
r.policy=policy;
|
r.data.policy=policy;
|
||||||
r.subscription.keys.p256dh=encryptionKey;
|
r.subscription.keys.p256dh=encryptionKey;
|
||||||
r.subscription.keys.auth=authKey;
|
r.subscription.keys.auth=authKey;
|
||||||
setRequestBody(r);
|
setRequestBody(r);
|
||||||
@@ -18,7 +18,6 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
private static class Request{
|
private static class Request{
|
||||||
public Subscription subscription=new Subscription();
|
public Subscription subscription=new Subscription();
|
||||||
public Data data=new Data();
|
public Data data=new Data();
|
||||||
public PushSubscription.Policy policy;
|
|
||||||
|
|
||||||
private static class Keys{
|
private static class Keys{
|
||||||
public String p256dh;
|
public String p256dh;
|
||||||
@@ -32,6 +31,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
|
|||||||
|
|
||||||
private static class Data{
|
private static class Data{
|
||||||
public PushSubscription.Alerts alerts;
|
public PushSubscription.Alerts alerts;
|
||||||
|
public PushSubscription.Policy policy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,36 +3,23 @@ package org.joinmastodon.android.api.requests.notifications;
|
|||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
|
||||||
private final PushSubscription.Policy policy;
|
|
||||||
|
|
||||||
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
|
||||||
setRequestBody(new Request(alerts, policy));
|
setRequestBody(new Request(alerts, policy));
|
||||||
this.policy=policy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void validateAndPostprocessResponse(PushSubscription respObj, Response httpResponse) throws IOException{
|
|
||||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
|
||||||
respObj.policy=policy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Request{
|
private static class Request{
|
||||||
public Data data=new Data();
|
public Data data=new Data();
|
||||||
public PushSubscription.Policy policy;
|
|
||||||
|
|
||||||
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
|
||||||
this.data.alerts=alerts;
|
this.data.alerts=alerts;
|
||||||
this.policy=policy;
|
this.data.policy=policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Data{
|
private static class Data{
|
||||||
public PushSubscription.Alerts alerts;
|
public PushSubscription.Alerts alerts;
|
||||||
|
public PushSubscription.Policy policy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
package org.joinmastodon.android.api.requests.statuses;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
@@ -10,29 +9,12 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class CreateStatus extends MastodonAPIRequest<Status>{
|
public class CreateStatus extends MastodonAPIRequest<Status>{
|
||||||
public static final Instant DRAFTS_AFTER_INSTANT = Instant.ofEpochMilli(253370764799999L) /* end of 9998 */;
|
|
||||||
private static final float draftFactor = 31536000000f /* one year */ / 253370764799999f /* end of 9998 */;
|
|
||||||
|
|
||||||
public static Instant getDraftInstant() {
|
|
||||||
// returns an instant between 9999-01-01 00:00:00 and 9999-12-31 23:59:59
|
|
||||||
// yes, this is a weird implementation for something that hardly matters
|
|
||||||
return DRAFTS_AFTER_INSTANT.plusMillis(1 + (long) (System.currentTimeMillis() * draftFactor));
|
|
||||||
}
|
|
||||||
|
|
||||||
public CreateStatus(CreateStatus.Request req, String uuid){
|
public CreateStatus(CreateStatus.Request req, String uuid){
|
||||||
super(HttpMethod.POST, "/statuses", Status.class);
|
super(HttpMethod.POST, "/statuses", Status.class);
|
||||||
setRequestBody(req);
|
setRequestBody(req);
|
||||||
addHeader("Idempotency-Key", uuid);
|
addHeader("Idempotency-Key", uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Scheduled extends MastodonAPIRequest<ScheduledStatus>{
|
|
||||||
public Scheduled(CreateStatus.Request req, String uuid){
|
|
||||||
super(HttpMethod.POST, "/statuses", ScheduledStatus.class);
|
|
||||||
setRequestBody(req);
|
|
||||||
addHeader("Idempotency-Key", uuid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request{
|
public static class Request{
|
||||||
public String status;
|
public String status;
|
||||||
public List<String> mediaIds;
|
public List<String> mediaIds;
|
||||||
|
|||||||
@@ -7,10 +7,4 @@ public class DeleteStatus extends MastodonAPIRequest<Status>{
|
|||||||
public DeleteStatus(String id){
|
public DeleteStatus(String id){
|
||||||
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
|
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Scheduled extends MastodonAPIRequest<Object> {
|
|
||||||
public Scheduled(String id) {
|
|
||||||
super(HttpMethod.DELETE, "/scheduled_statuses/"+id, Object.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
|
||||||
|
|
||||||
public class GetScheduledStatuses extends HeaderPaginationRequest<ScheduledStatus>{
|
|
||||||
public GetScheduledStatuses(String maxID, int limit){
|
|
||||||
super(HttpMethod.GET, "/scheduled_statuses", new TypeToken<>(){});
|
|
||||||
if(maxID!=null)
|
|
||||||
addQueryParameter("max_id", maxID);
|
|
||||||
if(limit>0)
|
|
||||||
addQueryParameter("limit", limit+"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,10 @@ package org.joinmastodon.android.api.requests.statuses;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
|
||||||
|
|
||||||
public class SetStatusReblogged extends MastodonAPIRequest<Status>{
|
public class SetStatusReblogged extends MastodonAPIRequest<Status>{
|
||||||
public SetStatusReblogged(String id, boolean reblogged, StatusPrivacy visibility){
|
public SetStatusReblogged(String id, boolean reblogged){
|
||||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class);
|
super(HttpMethod.POST, "/statuses/"+id+"/"+(reblogged ? "reblog" : "unreblog"), Status.class);
|
||||||
Request req = new Request();
|
setRequestBody(new Object());
|
||||||
req.visibility = visibility;
|
|
||||||
setRequestBody(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request {
|
|
||||||
public StatusPrivacy visibility;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.TranslatedStatus;
|
|
||||||
|
|
||||||
public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> {
|
|
||||||
public TranslateStatus(String id) {
|
|
||||||
super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class);
|
|
||||||
setRequestBody(new Object());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.tags;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GetFollowedHashtags extends HeaderPaginationRequest<Hashtag> {
|
|
||||||
public GetFollowedHashtags() {
|
|
||||||
this(null, null, -1, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetFollowedHashtags(String maxID, String minID, int limit, String sinceID){
|
|
||||||
super(HttpMethod.GET, "/followed_tags", new TypeToken<>(){});
|
|
||||||
if(maxID!=null)
|
|
||||||
addQueryParameter("max_id", maxID);
|
|
||||||
if(minID!=null)
|
|
||||||
addQueryParameter("min_id", minID);
|
|
||||||
if(sinceID!=null)
|
|
||||||
addQueryParameter("since_id", sinceID);
|
|
||||||
if(limit>0)
|
|
||||||
addQueryParameter("limit", ""+limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -8,11 +8,9 @@ import org.joinmastodon.android.model.Status;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
public class GetTrendingStatuses extends MastodonAPIRequest<List<Status>>{
|
||||||
public GetTrendingStatuses(int offset, int limit){
|
public GetTrendingStatuses(int limit){
|
||||||
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
super(HttpMethod.GET, "/trends/statuses", new TypeToken<>(){});
|
||||||
if(limit>0)
|
if(limit>0)
|
||||||
addQueryParameter("limit", ""+limit);
|
addQueryParameter("limit", ""+limit);
|
||||||
if(offset>0)
|
|
||||||
addQueryParameter("offset", ""+offset);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.session;
|
|
||||||
|
|
||||||
public class AccountActivationInfo{
|
|
||||||
public String email;
|
|
||||||
public long lastEmailConfirmationResend;
|
|
||||||
|
|
||||||
public AccountActivationInfo(String email, long lastEmailConfirmationResend){
|
|
||||||
this.email=email;
|
|
||||||
this.lastEmailConfirmationResend=lastEmailConfirmationResend;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ import org.joinmastodon.android.api.StatusInteractionController;
|
|||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
|
||||||
@@ -29,20 +28,17 @@ public class AccountSession{
|
|||||||
public long filtersLastUpdated;
|
public long filtersLastUpdated;
|
||||||
public List<Filter> wordFilters=new ArrayList<>();
|
public List<Filter> wordFilters=new ArrayList<>();
|
||||||
public String pushAccountID;
|
public String pushAccountID;
|
||||||
public Preferences preferences;
|
|
||||||
public AccountActivationInfo activationInfo;
|
|
||||||
private transient MastodonAPIController apiController;
|
private transient MastodonAPIController apiController;
|
||||||
private transient StatusInteractionController statusInteractionController, remoteStatusInteractionController;
|
private transient StatusInteractionController statusInteractionController;
|
||||||
private transient CacheController cacheController;
|
private transient CacheController cacheController;
|
||||||
private transient PushSubscriptionManager pushSubscriptionManager;
|
private transient PushSubscriptionManager pushSubscriptionManager;
|
||||||
|
|
||||||
AccountSession(Token token, Account self, Application app, String domain, boolean activated, AccountActivationInfo activationInfo){
|
AccountSession(Token token, Account self, Application app, String domain, boolean activated){
|
||||||
this.token=token;
|
this.token=token;
|
||||||
this.self=self;
|
this.self=self;
|
||||||
this.domain=domain;
|
this.domain=domain;
|
||||||
this.app=app;
|
this.app=app;
|
||||||
this.activated=activated;
|
this.activated=activated;
|
||||||
this.activationInfo=activationInfo;
|
|
||||||
infoLastUpdated=System.currentTimeMillis();
|
infoLastUpdated=System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +48,6 @@ public class AccountSession{
|
|||||||
return domain+"_"+self.id;
|
return domain+"_"+self.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFullUsername() {
|
|
||||||
return "@"+self.username+"@"+domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MastodonAPIController getApiController(){
|
public MastodonAPIController getApiController(){
|
||||||
if(apiController==null)
|
if(apiController==null)
|
||||||
apiController=new MastodonAPIController(this);
|
apiController=new MastodonAPIController(this);
|
||||||
@@ -68,12 +60,6 @@ public class AccountSession{
|
|||||||
return statusInteractionController;
|
return statusInteractionController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StatusInteractionController getRemoteStatusInteractionController(){
|
|
||||||
if(remoteStatusInteractionController==null)
|
|
||||||
remoteStatusInteractionController=new StatusInteractionController(getID(), false);
|
|
||||||
return remoteStatusInteractionController;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheController getCacheController(){
|
public CacheController getCacheController(){
|
||||||
if(cacheController==null)
|
if(cacheController==null)
|
||||||
cacheController=new CacheController(getID());
|
cacheController=new CacheController(getID());
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
@@ -20,7 +22,6 @@ import org.joinmastodon.android.MastodonApp;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.PushSubscriptionManager;
|
import org.joinmastodon.android.api.PushSubscriptionManager;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
|
import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||||
@@ -33,7 +34,6 @@ import org.joinmastodon.android.model.Emoji;
|
|||||||
import org.joinmastodon.android.model.EmojiCategory;
|
import org.joinmastodon.android.model.EmojiCategory;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Preferences;
|
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -100,13 +100,13 @@ public class AccountSessionManager{
|
|||||||
maybeUpdateShortcuts();
|
maybeUpdateShortcuts();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
public void addAccount(Instance instance, Token token, Account self, Application app, boolean active){
|
||||||
instances.put(instance.uri, instance);
|
instances.put(instance.uri, instance);
|
||||||
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
|
AccountSession session=new AccountSession(token, self, app, instance.uri, active);
|
||||||
sessions.put(session.getID(), session);
|
sessions.put(session.getID(), session);
|
||||||
lastActiveAccountID=session.getID();
|
lastActiveAccountID=session.getID();
|
||||||
writeAccountsFile();
|
writeAccountsFile();
|
||||||
updateMoreInstanceInfo(instance, instance.uri);
|
updateInstanceEmojis(instance, instance.uri);
|
||||||
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||||
}
|
}
|
||||||
@@ -248,13 +248,12 @@ public class AccountSessionManager{
|
|||||||
HashSet<String> domains=new HashSet<>();
|
HashSet<String> domains=new HashSet<>();
|
||||||
for(AccountSession session:sessions.values()){
|
for(AccountSession session:sessions.values()){
|
||||||
domains.add(session.domain.toLowerCase());
|
domains.add(session.domain.toLowerCase());
|
||||||
// if(now-session.infoLastUpdated>24L*3600_000L){
|
if(now-session.infoLastUpdated>24L*3600_000L){
|
||||||
updateSessionPreferences(session);
|
updateSessionLocalInfo(session);
|
||||||
updateSessionLocalInfo(session);
|
}
|
||||||
// }
|
if(now-session.filtersLastUpdated>3600_000L){
|
||||||
// if(now-session.filtersLastUpdated>3600_000L){
|
updateSessionWordFilters(session);
|
||||||
updateSessionWordFilters(session);
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
if(loadedInstances){
|
if(loadedInstances){
|
||||||
maybeUpdateCustomEmojis(domains);
|
maybeUpdateCustomEmojis(domains);
|
||||||
@@ -264,10 +263,10 @@ public class AccountSessionManager{
|
|||||||
private void maybeUpdateCustomEmojis(Set<String> domains){
|
private void maybeUpdateCustomEmojis(Set<String> domains){
|
||||||
long now=System.currentTimeMillis();
|
long now=System.currentTimeMillis();
|
||||||
for(String domain:domains){
|
for(String domain:domains){
|
||||||
// Long lastUpdated=instancesLastUpdated.get(domain);
|
Long lastUpdated=instancesLastUpdated.get(domain);
|
||||||
// if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
|
||||||
updateInstanceInfo(domain);
|
updateInstanceInfo(domain);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,18 +288,6 @@ public class AccountSessionManager{
|
|||||||
.exec(session.getID());
|
.exec(session.getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSessionPreferences(AccountSession session){
|
|
||||||
new GetPreferences().setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Preferences preferences) {
|
|
||||||
session.preferences=preferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {}
|
|
||||||
}).exec(session.getID());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSessionWordFilters(AccountSession session){
|
private void updateSessionWordFilters(AccountSession session){
|
||||||
new GetWordFilters()
|
new GetWordFilters()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@@ -325,7 +312,7 @@ public class AccountSessionManager{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(Instance instance){
|
public void onSuccess(Instance instance){
|
||||||
instances.put(domain, instance);
|
instances.put(domain, instance);
|
||||||
updateMoreInstanceInfo(instance, domain);
|
updateInstanceEmojis(instance, domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -336,21 +323,6 @@ public class AccountSessionManager{
|
|||||||
.execNoAuth(domain);
|
.execNoAuth(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMoreInstanceInfo(Instance instance, String domain) {
|
|
||||||
new GetInstance.V2().setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Instance.V2 v2) {
|
|
||||||
if (instance != null) instance.v2 = v2;
|
|
||||||
updateInstanceEmojis(instance, domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse errorResponse) {
|
|
||||||
updateInstanceEmojis(instance, domain);
|
|
||||||
}
|
|
||||||
}).execNoAuth(instance.uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateInstanceEmojis(Instance instance, String domain){
|
private void updateInstanceEmojis(Instance instance, String domain){
|
||||||
new GetCustomEmojis()
|
new GetCustomEmojis()
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@@ -426,10 +398,6 @@ public class AccountSessionManager{
|
|||||||
return instances.get(domain);
|
return instances.get(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instance getInstanceInfoForAccount(String account) {
|
|
||||||
return AccountSessionManager.getInstance().getInstanceInfo(instance.getAccount(account).domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateAccountInfo(String id, Account account){
|
public void updateAccountInfo(String id, Account account){
|
||||||
AccountSession session=getAccount(id);
|
AccountSession session=getAccount(id);
|
||||||
session.self=account;
|
session.self=account;
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class HashtagUpdatedEvent {
|
|
||||||
public final String name;
|
|
||||||
public final boolean following;
|
|
||||||
|
|
||||||
public HashtagUpdatedEvent(String name, boolean following) {
|
|
||||||
this.name = name;
|
|
||||||
this.following = following;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class ListDeletedEvent {
|
|
||||||
public final String id;
|
|
||||||
|
|
||||||
public ListDeletedEvent(String id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
|
|
||||||
public class ListUpdatedCreatedEvent {
|
|
||||||
public final String id;
|
|
||||||
public final String title;
|
|
||||||
public final ListTimeline.RepliesPolicy repliesPolicy;
|
|
||||||
|
|
||||||
public ListUpdatedCreatedEvent(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
|
|
||||||
this.id = id;
|
|
||||||
this.title = title;
|
|
||||||
this.repliesPolicy = repliesPolicy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
|
||||||
|
|
||||||
public class ScheduledStatusCreatedEvent {
|
|
||||||
public final ScheduledStatus scheduledStatus;
|
|
||||||
public final String accountID;
|
|
||||||
|
|
||||||
public ScheduledStatusCreatedEvent(ScheduledStatus scheduledStatus, String accountID){
|
|
||||||
this.scheduledStatus = scheduledStatus;
|
|
||||||
this.accountID=accountID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
|
||||||
|
|
||||||
public class ScheduledStatusDeletedEvent{
|
|
||||||
public final String id;
|
|
||||||
public final String accountID;
|
|
||||||
|
|
||||||
public ScheduledStatusDeletedEvent(String id, String accountID){
|
|
||||||
this.id=id;
|
|
||||||
this.accountID=accountID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toList;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
|
||||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
|
||||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
|
||||||
import org.joinmastodon.android.model.Announcement;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.PaginatedList;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
|
|
||||||
public class AnnouncementsFragment extends BaseStatusListFragment<Announcement> {
|
|
||||||
private Instance instance;
|
|
||||||
private AccountSession session;
|
|
||||||
private List<String> unreadIDs = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity){
|
|
||||||
super.onAttach(activity);
|
|
||||||
setTitle(R.string.sk_announcements);
|
|
||||||
session = AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Announcement a) {
|
|
||||||
if(TextUtils.isEmpty(a.content)) return List.of();
|
|
||||||
Account instanceUser = new Account();
|
|
||||||
instanceUser.id = instanceUser.acct = instanceUser.username = session.domain;
|
|
||||||
instanceUser.displayName = instance.title;
|
|
||||||
instanceUser.url = "https://"+session.domain+"/about";
|
|
||||||
instanceUser.avatar = instanceUser.avatarStatic = instance.thumbnail;
|
|
||||||
instanceUser.emojis = List.of();
|
|
||||||
Status fakeStatus = a.toStatus();
|
|
||||||
TextStatusDisplayItem textItem = new TextStatusDisplayItem(a.id, HtmlParser.parse(a.content, a.emojis, a.mentions, a.tags, accountID), this, fakeStatus, true);
|
|
||||||
textItem.textSelectable = true;
|
|
||||||
return List.of(
|
|
||||||
HeaderStatusDisplayItem.fromAnnouncement(a, fakeStatus, instanceUser, this, accountID, this::onMarkAsRead),
|
|
||||||
textItem
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMarkAsRead(String id) {
|
|
||||||
if (unreadIDs == null) return;
|
|
||||||
unreadIDs.remove(id);
|
|
||||||
if (unreadIDs.isEmpty()) setResult(true, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void addAccountToKnown(Announcement s) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(String id) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
currentRequest=new GetAnnouncements(true)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Announcement> result){
|
|
||||||
List<Announcement> unread = result.stream().filter(a -> !a.read).collect(toList());
|
|
||||||
List<Announcement> read = result.stream().filter(a -> a.read).collect(toList());
|
|
||||||
onDataLoaded(unread, true);
|
|
||||||
onDataLoaded(read, false);
|
|
||||||
if (unread.isEmpty()) setResult(true, null);
|
|
||||||
else unreadIDs = unread.stream().map(a -> a.id).collect(toList());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -400,12 +400,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
public void onPollOptionClick(PollOptionStatusDisplayItem.Holder holder){
|
public void onPollOptionClick(PollOptionStatusDisplayItem.Holder holder){
|
||||||
Poll poll=holder.getItem().poll;
|
Poll poll=holder.getItem().poll;
|
||||||
Poll.Option option=holder.getItem().option;
|
Poll.Option option=holder.getItem().option;
|
||||||
if(poll.multiple || GlobalUserPreferences.voteButtonForSingleChoice){
|
if(poll.multiple){
|
||||||
if(poll.selectedOptions==null)
|
if(poll.selectedOptions==null)
|
||||||
poll.selectedOptions=new ArrayList<>();
|
poll.selectedOptions=new ArrayList<>();
|
||||||
boolean optionContained=poll.selectedOptions.contains(option);
|
if(poll.selectedOptions.contains(option)){
|
||||||
if(!poll.multiple) poll.selectedOptions.clear();
|
|
||||||
if(optionContained){
|
|
||||||
poll.selectedOptions.remove(option);
|
poll.selectedOptions.remove(option);
|
||||||
holder.itemView.setSelected(false);
|
holder.itemView.setSelected(false);
|
||||||
}else{
|
}else{
|
||||||
@@ -414,9 +412,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
RecyclerView.ViewHolder vh=list.getChildViewHolder(list.getChildAt(i));
|
RecyclerView.ViewHolder vh=list.getChildViewHolder(list.getChildAt(i));
|
||||||
if(!poll.multiple && vh instanceof PollOptionStatusDisplayItem.Holder item){
|
|
||||||
if (item != holder) item.itemView.setSelected(false);
|
|
||||||
}
|
|
||||||
if(vh instanceof PollFooterStatusDisplayItem.Holder footer){
|
if(vh instanceof PollFooterStatusDisplayItem.Holder footer){
|
||||||
if(footer.getItemID().equals(holder.getItemID())){
|
if(footer.getItemID().equals(holder.getItemID())){
|
||||||
footer.rebind();
|
footer.rebind();
|
||||||
@@ -787,7 +782,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
currentMediaHiddenLayoutsWidth=width;
|
currentMediaHiddenLayoutsWidth=width;
|
||||||
String title=getString(R.string.sensitive_content);
|
String title=getString(R.string.sensitive_content);
|
||||||
TextPaint titlePaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
TextPaint titlePaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||||
titlePaint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorGray50));
|
titlePaint.setColor(getResources().getColor(R.color.gray_50));
|
||||||
titlePaint.setTextSize(V.dp(22));
|
titlePaint.setTextSize(V.dp(22));
|
||||||
titlePaint.setTypeface(mediumTypeface);
|
titlePaint.setTypeface(mediumTypeface);
|
||||||
mediaHiddenTitleLayout=StaticLayout.Builder.obtain(title, 0, title.length(), titlePaint, width)
|
mediaHiddenTitleLayout=StaticLayout.Builder.obtain(title, 0, title.length(), titlePaint, width)
|
||||||
@@ -798,7 +793,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||||
.build();
|
.build();
|
||||||
TextPaint textPaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
TextPaint textPaint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||||
textPaint.setColor(UiUtils.getThemeColor(getContext(), R.attr.colorGray200));
|
textPaint.setColor(getResources().getColor(R.color.gray_200));
|
||||||
textPaint.setTextSize(V.dp(16));
|
textPaint.setTextSize(V.dp(16));
|
||||||
String text=getString(R.string.sensitive_content_explain);
|
String text=getString(R.string.sensitive_content_explain);
|
||||||
mediaHiddenTextLayout=StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, width)
|
mediaHiddenTextLayout=StaticLayout.Builder.obtain(text, 0, text.length(), textPaint, width)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,351 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import static android.view.Menu.NONE;
|
|
||||||
|
|
||||||
import static org.joinmastodon.android.ui.utils.UiUtils.makeBackItem;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.SubMenu;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
|
||||||
|
|
||||||
public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
|
||||||
private String accountID;
|
|
||||||
private TimelinesAdapter adapter;
|
|
||||||
private final ItemTouchHelper itemTouchHelper;
|
|
||||||
private Menu optionsMenu;
|
|
||||||
private boolean updated;
|
|
||||||
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>();
|
|
||||||
private final List<ListTimeline> listTimelines = new ArrayList<>();
|
|
||||||
private final List<Hashtag> hashtags = new ArrayList<>();
|
|
||||||
|
|
||||||
public EditTimelinesFragment() {
|
|
||||||
super(10);
|
|
||||||
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ;
|
|
||||||
itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
setTitle(R.string.sk_timelines);
|
|
||||||
accountID = getArguments().getString("account");
|
|
||||||
|
|
||||||
new GetLists().setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<ListTimeline> result) {
|
|
||||||
listTimelines.addAll(result);
|
|
||||||
updateOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
|
|
||||||
new GetFollowedHashtags().setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> result) {
|
|
||||||
hashtags.addAll(result);
|
|
||||||
updateOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onShown(){
|
|
||||||
super.onShown();
|
|
||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
itemTouchHelper.attachToRecyclerView(list);
|
|
||||||
refreshLayout.setEnabled(false);
|
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
this.optionsMenu = menu;
|
|
||||||
updateOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.menu_back) {
|
|
||||||
updateOptionsMenu();
|
|
||||||
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
TimelineDefinition tl = timelineByMenuItem.get(item);
|
|
||||||
if (tl != null) {
|
|
||||||
data.add(tl.copy());
|
|
||||||
adapter.notifyItemInserted(data.size());
|
|
||||||
saveTimelines();
|
|
||||||
updateOptionsMenu();
|
|
||||||
};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
|
|
||||||
if (data.contains(tl)) return;
|
|
||||||
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, tl.getTitle(getContext()));
|
|
||||||
item.setIcon(tl.getIcon().iconRes);
|
|
||||||
timelineByMenuItem.put(item, tl);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateOptionsMenu() {
|
|
||||||
optionsMenu.clear();
|
|
||||||
timelineByMenuItem.clear();
|
|
||||||
|
|
||||||
SubMenu menu = optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
|
|
||||||
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
|
||||||
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
|
|
||||||
|
|
||||||
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline);
|
|
||||||
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
|
|
||||||
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list);
|
|
||||||
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_list_24_regular);
|
|
||||||
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag);
|
|
||||||
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
|
||||||
|
|
||||||
makeBackItem(timelinesMenu);
|
|
||||||
makeBackItem(listsMenu);
|
|
||||||
makeBackItem(hashtagsMenu);
|
|
||||||
|
|
||||||
TimelineDefinition.ALL_TIMELINES.forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
|
||||||
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
|
||||||
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
|
||||||
|
|
||||||
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0);
|
|
||||||
listsMenu.getItem().setVisible(listsMenu.size() > 0);
|
|
||||||
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0);
|
|
||||||
|
|
||||||
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveTimelines() {
|
|
||||||
updated = true;
|
|
||||||
GlobalUserPreferences.pinnedTimelines.put(accountID, data.size() > 0 ? data : List.of(TimelineDefinition.HOME_TIMELINE));
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeTimeline(int position) {
|
|
||||||
data.remove(position);
|
|
||||||
adapter.notifyItemRemoved(position);
|
|
||||||
saveTimelines();
|
|
||||||
updateOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES), false);
|
|
||||||
updateOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() {
|
|
||||||
return adapter = new TimelinesAdapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scrollToTop() {
|
|
||||||
smoothScrollRecyclerViewToTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (updated) UiUtils.restartApp();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new TimelineViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) {
|
|
||||||
holder.bind(data.get(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return data.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
|
|
||||||
private final TextView title;
|
|
||||||
private final ImageView dragger;
|
|
||||||
|
|
||||||
public TimelineViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_text, list);
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
dragger=findViewById(R.id.dragger_thingy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
@Override
|
|
||||||
public void onBind(TimelineDefinition item) {
|
|
||||||
title.setText(item.getTitle(getContext()));
|
|
||||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
|
|
||||||
dragger.setVisibility(View.VISIBLE);
|
|
||||||
dragger.setOnTouchListener((View v, MotionEvent event) -> {
|
|
||||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
|
||||||
itemTouchHelper.startDrag(this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
Context ctx = getContext();
|
|
||||||
LinearLayout view = (LinearLayout) getActivity().getLayoutInflater()
|
|
||||||
.inflate(R.layout.edit_timeline, (ViewGroup) itemView, false);
|
|
||||||
|
|
||||||
TextInputFrameLayout inputLayout = view.findViewById(R.id.input);
|
|
||||||
EditText editText = inputLayout.getEditText();
|
|
||||||
editText.setText(item.getCustomTitle());
|
|
||||||
editText.setHint(item.getDefaultTitle(ctx));
|
|
||||||
|
|
||||||
ImageButton btn = view.findViewById(R.id.button);
|
|
||||||
PopupMenu popup = new PopupMenu(ctx, btn);
|
|
||||||
TimelineDefinition.Icon currentIcon = item.getIcon();
|
|
||||||
btn.setImageResource(currentIcon.iconRes);
|
|
||||||
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
|
|
||||||
btn.setOnTouchListener(popup.getDragToOpenListener());
|
|
||||||
btn.setOnClickListener(l -> popup.show());
|
|
||||||
|
|
||||||
Menu menu = popup.getMenu();
|
|
||||||
TimelineDefinition.Icon defaultIcon = item.getDefaultIcon();
|
|
||||||
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
|
|
||||||
if (!currentIcon.equals(defaultIcon)) {
|
|
||||||
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
|
|
||||||
}
|
|
||||||
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
|
|
||||||
if (icon.hidden || icon.equals(item.getIcon())) continue;
|
|
||||||
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
|
|
||||||
}
|
|
||||||
UiUtils.enablePopupMenuIcons(ctx, popup);
|
|
||||||
|
|
||||||
popup.setOnMenuItemClickListener(menuItem -> {
|
|
||||||
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
|
|
||||||
btn.setImageResource(icon.iconRes);
|
|
||||||
btn.setContentDescription(ctx.getString(icon.nameRes));
|
|
||||||
item.setIcon(icon);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
new M3AlertDialogBuilder(ctx)
|
|
||||||
.setTitle(R.string.sk_edit_timeline)
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton(R.string.save, (d, which) -> {
|
|
||||||
item.setTitle(editText.getText().toString().trim());
|
|
||||||
rebind();
|
|
||||||
saveTimelines();
|
|
||||||
})
|
|
||||||
.setNeutralButton(R.string.sk_remove, (d, which) ->
|
|
||||||
removeTimeline(getAbsoluteAdapterPosition()))
|
|
||||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
|
||||||
.show();
|
|
||||||
|
|
||||||
btn.requestFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
|
|
||||||
public ItemTouchHelperCallback() {
|
|
||||||
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
|
||||||
int fromPosition = viewHolder.getAbsoluteAdapterPosition();
|
|
||||||
int toPosition = target.getAbsoluteAdapterPosition();
|
|
||||||
if (Math.max(fromPosition, toPosition) >= data.size() || Math.min(fromPosition, toPosition) < 0) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
Collections.swap(data, fromPosition, toPosition);
|
|
||||||
adapter.notifyItemMoved(fromPosition, toPosition);
|
|
||||||
saveTimelines();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
|
|
||||||
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
|
|
||||||
viewHolder.itemView.animate().alpha(0.65f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
|
||||||
super.clearView(recyclerView, viewHolder);
|
|
||||||
viewHolder.itemView.animate().alpha(1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
|
||||||
int position = viewHolder.getAbsoluteAdapterPosition();
|
|
||||||
removeTimeline(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
|
|
||||||
public abstract class FabStatusListFragment extends StatusListFragment {
|
|
||||||
protected ImageButton fab;
|
|
||||||
|
|
||||||
public FabStatusListFragment() {
|
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
fab = view.findViewById(R.id.fab);
|
|
||||||
fab.setOnClickListener(this::onFabClick);
|
|
||||||
fab.setOnLongClickListener(this::onFabLongClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onFabClick(View v){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean onFabLongClick(View v) {
|
|
||||||
return UiUtils.pickAccountForCompose(getActivity(), accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -67,7 +67,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
|||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
setTitle(R.string.sk_follow_requests);
|
setTitle(R.string.follow_requests);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -302,7 +302,7 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
|
|||||||
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter = getBindingAdapter();
|
||||||
if (!rel.requested && !rel.followedBy && adapter != null) {
|
if (!rel.requested && !rel.followedBy && adapter != null) {
|
||||||
data.remove(item);
|
data.remove(item);
|
||||||
adapter.notifyItemRemoved(getLayoutPosition());
|
adapter.notifyItemRemoved(getBindingAdapterPosition());
|
||||||
} else {
|
} else {
|
||||||
rebind();
|
rebind();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
|
||||||
|
|
||||||
public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop {
|
|
||||||
private String nextMaxID;
|
|
||||||
private String accountId;
|
|
||||||
|
|
||||||
public FollowedHashtagsFragment() {
|
|
||||||
super(20);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
Bundle args=getArguments();
|
|
||||||
accountId=args.getString("account");
|
|
||||||
setTitle(R.string.sk_hashtags_you_follow);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onShown(){
|
|
||||||
super.onShown();
|
|
||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
currentRequest=new GetFollowedHashtags(offset==0 ? null : nextMaxID, null, count, null)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
|
||||||
if(result.nextPageUri!=null)
|
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
|
||||||
else
|
|
||||||
nextMaxID=null;
|
|
||||||
onDataLoaded(result, nextMaxID!=null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter getAdapter() {
|
|
||||||
return new HashtagsAdapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scrollToTop() {
|
|
||||||
smoothScrollRecyclerViewToTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public HashtagViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new HashtagViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull HashtagViewHolder holder, int position) {
|
|
||||||
holder.bind(data.get(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return data.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HashtagViewHolder extends BindableViewHolder<Hashtag> implements UsableRecyclerView.Clickable{
|
|
||||||
private final TextView title;
|
|
||||||
|
|
||||||
public HashtagViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_text, list);
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(Hashtag item) {
|
|
||||||
title.setText(item.name);
|
|
||||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_number_symbol_24_regular), null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
UiUtils.openHashtagTimeline(getActivity(), accountId, item.name, item.following);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.HapticFeedbackConstants;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -11,16 +10,12 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.tags.GetHashtag;
|
import org.joinmastodon.android.api.requests.tags.GetHashtag;
|
||||||
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
||||||
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -30,7 +25,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
public class HashtagTimelineFragment extends StatusListFragment{
|
||||||
private String hashtag;
|
private String hashtag;
|
||||||
private boolean following;
|
private boolean following;
|
||||||
private ImageButton fab;
|
private ImageButton fab;
|
||||||
@@ -45,6 +40,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
updateTitle(getArguments().getString("hashtag"));
|
updateTitle(getArguments().getString("hashtag"));
|
||||||
following=getArguments().getBoolean("following", false);
|
following=getArguments().getBoolean("following", false);
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,37 +53,16 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
this.following = newFollowing;
|
this.following = newFollowing;
|
||||||
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
|
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
|
||||||
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||||
E.post(new HashtagUpdatedEvent(hashtag, following));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.hashtag_timeline, menu);
|
inflater.inflate(R.menu.hashtag_timeline, menu);
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
followButton = menu.findItem(R.id.follow_hashtag);
|
followButton = menu.findItem(R.id.follow_hashtag);
|
||||||
updateFollowingState(following);
|
updateFollowingState(following);
|
||||||
|
|
||||||
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
followButton.setOnMenuItemClickListener(i -> {
|
||||||
@Override
|
|
||||||
public void onSuccess(Hashtag hashtag) {
|
|
||||||
updateTitle(hashtag.name);
|
|
||||||
updateFollowingState(hashtag.following);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (super.onOptionsItemSelected(item)) return true;
|
|
||||||
if (item.getItemId() == R.id.follow_hashtag) {
|
|
||||||
updateFollowingState(!following);
|
updateFollowingState(!following);
|
||||||
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
|
||||||
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
|
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Hashtag i) {
|
public void onSuccess(Hashtag i) {
|
||||||
@@ -102,13 +77,20 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
return true;
|
return true;
|
||||||
}
|
});
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
new GetHashtag(hashtag).setCallback(new Callback<>() {
|
||||||
protected TimelineDefinition makeTimelineDefinition() {
|
@Override
|
||||||
return TimelineDefinition.ofHashtag(hashtag);
|
public void onSuccess(Hashtag hashtag) {
|
||||||
|
updateTitle(hashtag.name);
|
||||||
|
updateFollowingState(hashtag.following);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error) {
|
||||||
|
error.showToast(getActivity());
|
||||||
|
}
|
||||||
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -135,7 +117,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
fab=view.findViewById(R.id.fab);
|
fab=view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.app.NotificationManager;
|
|||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.service.notification.StatusBarNotification;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -21,7 +20,6 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||||
import org.joinmastodon.android.fragments.discover.SearchFragment;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
@@ -43,7 +41,7 @@ import me.grishka.appkit.views.FragmentRootLinearLayout;
|
|||||||
|
|
||||||
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
|
||||||
private FragmentRootLinearLayout content;
|
private FragmentRootLinearLayout content;
|
||||||
private HomeTabFragment homeTabFragment;
|
private HomeTimelineFragment homeTimelineFragment;
|
||||||
private NotificationsFragment notificationsFragment;
|
private NotificationsFragment notificationsFragment;
|
||||||
private DiscoverFragment searchFragment;
|
private DiscoverFragment searchFragment;
|
||||||
private ProfileFragment profileFragment;
|
private ProfileFragment profileFragment;
|
||||||
@@ -59,7 +57,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
setTitle(R.string.sk_app_name);
|
setTitle(R.string.app_name);
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
@@ -67,8 +65,8 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
homeTabFragment=new HomeTabFragment();
|
homeTimelineFragment=new HomeTimelineFragment();
|
||||||
homeTabFragment.setArguments(args);
|
homeTimelineFragment.setArguments(args);
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
args.putBoolean("noAutoLoad", true);
|
args.putBoolean("noAutoLoad", true);
|
||||||
searchFragment=new DiscoverFragment();
|
searchFragment=new DiscoverFragment();
|
||||||
@@ -112,7 +110,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.fragment_wrap, homeTabFragment)
|
.add(R.id.fragment_wrap, homeTimelineFragment)
|
||||||
.add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
.add(R.id.fragment_wrap, searchFragment).hide(searchFragment)
|
||||||
.add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
.add(R.id.fragment_wrap, notificationsFragment).hide(notificationsFragment)
|
||||||
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
.add(R.id.fragment_wrap, profileFragment).hide(profileFragment)
|
||||||
@@ -138,15 +136,16 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
@Override
|
@Override
|
||||||
public void onViewStateRestored(Bundle savedInstanceState){
|
public void onViewStateRestored(Bundle savedInstanceState){
|
||||||
super.onViewStateRestored(savedInstanceState);
|
super.onViewStateRestored(savedInstanceState);
|
||||||
if(savedInstanceState==null) return;
|
if(savedInstanceState==null || homeTimelineFragment!=null)
|
||||||
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
|
return;
|
||||||
|
homeTimelineFragment=(HomeTimelineFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTimelineFragment");
|
||||||
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
|
||||||
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
|
||||||
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
|
||||||
currentTab=savedInstanceState.getInt("selectedTab");
|
currentTab=savedInstanceState.getInt("selectedTab");
|
||||||
Fragment current=fragmentForTab(currentTab);
|
Fragment current=fragmentForTab(currentTab);
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.hide(homeTabFragment)
|
.hide(homeTimelineFragment)
|
||||||
.hide(searchFragment)
|
.hide(searchFragment)
|
||||||
.hide(notificationsFragment)
|
.hide(notificationsFragment)
|
||||||
.hide(profileFragment)
|
.hide(profileFragment)
|
||||||
@@ -181,7 +180,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
}
|
}
|
||||||
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
|
||||||
homeTabFragment.onApplyWindowInsets(topOnlyInsets);
|
homeTimelineFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
searchFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
notificationsFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
profileFragment.onApplyWindowInsets(topOnlyInsets);
|
||||||
@@ -189,7 +188,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
|
|
||||||
private Fragment fragmentForTab(@IdRes int tab){
|
private Fragment fragmentForTab(@IdRes int tab){
|
||||||
if(tab==R.id.tab_home){
|
if(tab==R.id.tab_home){
|
||||||
return homeTabFragment;
|
return homeTimelineFragment;
|
||||||
}else if(tab==R.id.tab_search){
|
}else if(tab==R.id.tab_search){
|
||||||
return searchFragment;
|
return searchFragment;
|
||||||
}else if(tab==R.id.tab_notifications){
|
}else if(tab==R.id.tab_notifications){
|
||||||
@@ -203,9 +202,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
private void onTabSelected(@IdRes int tab){
|
private void onTabSelected(@IdRes int tab){
|
||||||
Fragment newFragment=fragmentForTab(tab);
|
Fragment newFragment=fragmentForTab(tab);
|
||||||
if(tab==currentTab){
|
if(tab==currentTab){
|
||||||
if (tab == R.id.tab_search)
|
if(newFragment instanceof ScrollableToTop scrollable)
|
||||||
searchFragment.onSelect();
|
|
||||||
else if(newFragment instanceof ScrollableToTop scrollable)
|
|
||||||
scrollable.scrollToTop();
|
scrollable.scrollToTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -225,11 +222,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
((NotificationsFragment) newFragment).loadData();
|
((NotificationsFragment) newFragment).loadData();
|
||||||
// TODO make an interface?
|
// TODO make an interface?
|
||||||
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
NotificationManager nm=getActivity().getSystemService(NotificationManager.class);
|
||||||
for (StatusBarNotification notification : nm.getActiveNotifications()) {
|
nm.cancel(accountID, PushNotificationReceiver.NOTIFICATION_ID);
|
||||||
if (accountID.equals(notification.getTag())) {
|
|
||||||
nm.cancel(accountID, notification.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,18 +248,17 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
tabBar.selectTab(R.id.tab_home);
|
tabBar.selectTab(R.id.tab_home);
|
||||||
onTabSelected(R.id.tab_home);
|
onTabSelected(R.id.tab_home);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return homeTabFragment.onBackPressed();
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState){
|
public void onSaveInstanceState(Bundle outState){
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt("selectedTab", currentTab);
|
outState.putInt("selectedTab", currentTab);
|
||||||
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
|
getChildFragmentManager().putFragment(outState, "homeTimelineFragment", homeTimelineFragment);
|
||||||
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
|
||||||
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
|
||||||
if (profileFragment.isAdded()) getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
getChildFragmentManager().putFragment(outState, "profileFragment", profileFragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,662 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.reduceMotion;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Fragment;
|
|
||||||
import android.app.FragmentTransaction;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.SubMenu;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewParent;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
|
||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
|
||||||
import org.joinmastodon.android.events.HashtagUpdatedEvent;
|
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
|
||||||
import org.joinmastodon.android.model.Announcement;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
|
|
||||||
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener {
|
|
||||||
private static final int ANNOUNCEMENTS_RESULT = 654;
|
|
||||||
|
|
||||||
private String accountID;
|
|
||||||
private MenuItem announcements, announcementsAction, settings, settingsAction;
|
|
||||||
// private ImageView toolbarLogo;
|
|
||||||
private Button toolbarShowNewPostsBtn;
|
|
||||||
private boolean newPostsBtnShown;
|
|
||||||
private AnimatorSet currentNewPostsAnim;
|
|
||||||
private ViewPager2 pager;
|
|
||||||
private View switcher;
|
|
||||||
private FrameLayout toolbarFrame;
|
|
||||||
private ImageView timelineIcon;
|
|
||||||
private ImageView collapsedChevron;
|
|
||||||
private TextView timelineTitle;
|
|
||||||
private PopupMenu switcherPopup;
|
|
||||||
private final Map<Integer, ListTimeline> listItems = new HashMap<>();
|
|
||||||
private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>();
|
|
||||||
private List<TimelineDefinition> timelineDefinitions;
|
|
||||||
private int count;
|
|
||||||
private Fragment[] fragments;
|
|
||||||
private FrameLayout[] tabViews;
|
|
||||||
private TimelineDefinition[] timelines;
|
|
||||||
private final Map<Integer, TimelineDefinition> timelinesByMenuItem = new HashMap<>();
|
|
||||||
private SubMenu hashtagsMenu, listsMenu;
|
|
||||||
private Menu optionsMenu;
|
|
||||||
private MenuInflater optionsMenuInflater;
|
|
||||||
private boolean announcementsBadged, settingsBadged;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
E.register(this);
|
|
||||||
accountID = getArguments().getString("account");
|
|
||||||
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES);
|
|
||||||
assert timelineDefinitions != null;
|
|
||||||
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
|
|
||||||
count = timelineDefinitions.size();
|
|
||||||
fragments = new Fragment[count];
|
|
||||||
tabViews = new FrameLayout[count];
|
|
||||||
timelines = new TimelineDefinition[count];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity) {
|
|
||||||
super.onAttach(activity);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
|
||||||
FrameLayout view = new FrameLayout(getContext());
|
|
||||||
pager = new ViewPager2(getContext());
|
|
||||||
toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false);
|
|
||||||
|
|
||||||
if (fragments[0] == null) {
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putBoolean("__is_tab", true);
|
|
||||||
args.putBoolean("onlyPosts", true);
|
|
||||||
|
|
||||||
for (int i = 0; i < timelineDefinitions.size(); i++) {
|
|
||||||
TimelineDefinition tl = timelineDefinitions.get(i);
|
|
||||||
fragments[i] = tl.getFragment();
|
|
||||||
timelines[i] = tl;
|
|
||||||
}
|
|
||||||
|
|
||||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
fragments[i].setArguments(timelines[i].populateArguments(new Bundle(args)));
|
|
||||||
FrameLayout tabView = new FrameLayout(getActivity());
|
|
||||||
tabView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
||||||
tabView.setVisibility(View.GONE);
|
|
||||||
tabView.setId(i + 1);
|
|
||||||
transaction.add(i + 1, fragments[i]);
|
|
||||||
view.addView(tabView);
|
|
||||||
tabViews[i] = tabView;
|
|
||||||
}
|
|
||||||
transaction.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
view.addView(pager, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
|
|
||||||
timelineIcon = toolbarFrame.findViewById(R.id.timeline_icon);
|
|
||||||
timelineTitle = toolbarFrame.findViewById(R.id.timeline_title);
|
|
||||||
collapsedChevron = toolbarFrame.findViewById(R.id.collapsed_chevron);
|
|
||||||
switcher = toolbarFrame.findViewById(R.id.switcher_btn);
|
|
||||||
switcherPopup = new PopupMenu(getContext(), switcher);
|
|
||||||
switcherPopup.setOnMenuItemClickListener(this::onSwitcherItemSelected);
|
|
||||||
UiUtils.enablePopupMenuIcons(getContext(), switcherPopup);
|
|
||||||
switcher.setOnClickListener(v->switcherPopup.show());
|
|
||||||
switcher.setOnTouchListener(switcherPopup.getDragToOpenListener());
|
|
||||||
updateSwitcherMenu();
|
|
||||||
|
|
||||||
UiUtils.reduceSwipeSensitivity(pager);
|
|
||||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
|
||||||
pager.setAdapter(new HomePagerAdapter());
|
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
|
||||||
@Override
|
|
||||||
public void onPageSelected(int position){
|
|
||||||
if (!reduceMotion) {
|
|
||||||
// setting this here because page transformer appears to fire too late so the
|
|
||||||
// animation can appear bumpy, especially when navigating to a further-away tab
|
|
||||||
switcher.setScaleY(0.85f);
|
|
||||||
switcher.setScaleX(0.85f);
|
|
||||||
switcher.setAlpha(0.65f);
|
|
||||||
}
|
|
||||||
updateSwitcherIcon(position);
|
|
||||||
if (!timelines[position].equals(TimelineDefinition.HOME_TIMELINE)) hideNewPostsButton();
|
|
||||||
if (fragments[position] instanceof BaseRecyclerFragment<?> page){
|
|
||||||
if(!page.loaded && !page.isDataLoading()) page.loadData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!reduceMotion) {
|
|
||||||
pager.setPageTransformer((v, pos) -> {
|
|
||||||
if (reduceMotion || tabViews[pager.getCurrentItem()] != v) return;
|
|
||||||
float scaleFactor = Math.max(0.85f, 1 - Math.abs(pos) * 0.06f);
|
|
||||||
switcher.setScaleY(scaleFactor);
|
|
||||||
switcher.setScaleX(scaleFactor);
|
|
||||||
switcher.setAlpha(Math.max(0.65f, 1 - Math.abs(pos)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateToolbarLogo();
|
|
||||||
|
|
||||||
ViewTreeObserver vto = getToolbar().getViewTreeObserver();
|
|
||||||
if (vto.isAlive()) {
|
|
||||||
vto.addOnGlobalLayoutListener(() -> {
|
|
||||||
Toolbar t = getToolbar();
|
|
||||||
if (t == null) return;
|
|
||||||
int toolbarWidth = t.getWidth();
|
|
||||||
if (toolbarWidth == 0) return;
|
|
||||||
|
|
||||||
int toolbarFrameWidth = toolbarFrame.getWidth();
|
|
||||||
int padding = toolbarWidth - toolbarFrameWidth;
|
|
||||||
FrameLayout parent = ((FrameLayout) toolbarShowNewPostsBtn.getParent());
|
|
||||||
if (padding == parent.getPaddingStart()) return;
|
|
||||||
|
|
||||||
// toolbar frame goes from screen edge to beginning of right-aligned option buttons.
|
|
||||||
// centering button by applying the same space on the left
|
|
||||||
parent.setPaddingRelative(padding, 0, 0, 0);
|
|
||||||
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth - padding * 2);
|
|
||||||
|
|
||||||
switcher.setPivotX(V.dp(28)); // padding + half of icon
|
|
||||||
switcher.setPivotY(switcher.getHeight() / 2f);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
|
||||||
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addListsToOptionsMenu() {
|
|
||||||
Context ctx = getContext();
|
|
||||||
listsMenu.clear();
|
|
||||||
listsMenu.getItem().setVisible(listItems.size() > 0);
|
|
||||||
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(listsMenu));
|
|
||||||
listItems.forEach((id, list) -> {
|
|
||||||
MenuItem item = listsMenu.add(Menu.NONE, id, Menu.NONE, list.title);
|
|
||||||
item.setIcon(R.drawable.ic_fluent_people_list_24_regular);
|
|
||||||
UiUtils.insetPopupMenuIcon(ctx, item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addHashtagsToOptionsMenu() {
|
|
||||||
Context ctx = getContext();
|
|
||||||
hashtagsMenu.clear();
|
|
||||||
hashtagsMenu.getItem().setVisible(hashtagsItems.size() > 0);
|
|
||||||
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(hashtagsMenu));
|
|
||||||
hashtagsItems.forEach((id, hashtag) -> {
|
|
||||||
MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name);
|
|
||||||
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
|
||||||
UiUtils.insetPopupMenuIcon(ctx, item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateToolbarLogo(){
|
|
||||||
Toolbar toolbar = getToolbar();
|
|
||||||
ViewParent parentView = toolbarFrame.getParent();
|
|
||||||
if (parentView == toolbar) return;
|
|
||||||
if (parentView instanceof Toolbar parentToolbar) parentToolbar.removeView(toolbarFrame);
|
|
||||||
toolbar.addView(toolbarFrame, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
|
||||||
toolbar.setOnClickListener(v->scrollToTop());
|
|
||||||
toolbar.setNavigationContentDescription(R.string.back);
|
|
||||||
toolbar.setContentInsetsAbsolute(0, toolbar.getContentInsetRight());
|
|
||||||
|
|
||||||
updateSwitcherIcon(pager.getCurrentItem());
|
|
||||||
|
|
||||||
toolbarShowNewPostsBtn=toolbarFrame.findViewById(R.id.show_new_posts_btn);
|
|
||||||
toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors());
|
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N) UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn);
|
|
||||||
toolbarShowNewPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
|
|
||||||
|
|
||||||
if(newPostsBtnShown){
|
|
||||||
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
|
||||||
collapsedChevron.setVisibility(View.VISIBLE);
|
|
||||||
collapsedChevron.setAlpha(1f);
|
|
||||||
timelineTitle.setVisibility(View.GONE);
|
|
||||||
timelineTitle.setAlpha(0f);
|
|
||||||
}else{
|
|
||||||
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
|
||||||
toolbarShowNewPostsBtn.setAlpha(0f);
|
|
||||||
collapsedChevron.setVisibility(View.GONE);
|
|
||||||
collapsedChevron.setAlpha(0f);
|
|
||||||
toolbarShowNewPostsBtn.setScaleX(.8f);
|
|
||||||
toolbarShowNewPostsBtn.setScaleY(.8f);
|
|
||||||
timelineTitle.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createOptionsMenu() {
|
|
||||||
optionsMenu.clear();
|
|
||||||
optionsMenuInflater.inflate(R.menu.home, optionsMenu);
|
|
||||||
announcements = optionsMenu.findItem(R.id.announcements);
|
|
||||||
announcementsAction = optionsMenu.findItem(R.id.announcements_action);
|
|
||||||
settings = optionsMenu.findItem(R.id.settings);
|
|
||||||
settingsAction = optionsMenu.findItem(R.id.settings_action);
|
|
||||||
hashtagsMenu = optionsMenu.findItem(R.id.hashtags).getSubMenu();
|
|
||||||
listsMenu = optionsMenu.findItem(R.id.lists).getSubMenu();
|
|
||||||
|
|
||||||
announcements.setVisible(!announcementsBadged);
|
|
||||||
announcementsAction.setVisible(announcementsBadged);
|
|
||||||
settings.setVisible(!settingsBadged);
|
|
||||||
settingsAction.setVisible(settingsBadged);
|
|
||||||
|
|
||||||
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu,
|
|
||||||
R.id.overflow, R.id.announcements_action, R.id.settings_action);
|
|
||||||
|
|
||||||
addListsToOptionsMenu();
|
|
||||||
addHashtagsToOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
|
||||||
this.optionsMenu = menu;
|
|
||||||
this.optionsMenuInflater = inflater;
|
|
||||||
createOptionsMenu();
|
|
||||||
|
|
||||||
new GetLists().setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<ListTimeline> lists) {
|
|
||||||
updateList(lists, listItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
|
|
||||||
new GetFollowedHashtags().setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> hashtags) {
|
|
||||||
updateList(hashtags, hashtagsItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
|
|
||||||
new GetAnnouncements(false).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Announcement> result) {
|
|
||||||
if (result.stream().anyMatch(a -> !a.read)) {
|
|
||||||
announcementsBadged = true;
|
|
||||||
announcements.setVisible(false);
|
|
||||||
announcementsAction.setVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> void updateList(List<T> addItems, Map<Integer, T> items) {
|
|
||||||
if (addItems.size() == 0) return;
|
|
||||||
for (int i = 0; i < addItems.size(); i++) items.put(View.generateViewId(), addItems.get(i));
|
|
||||||
createOptionsMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSwitcherMenu() {
|
|
||||||
Menu switcherMenu = switcherPopup.getMenu();
|
|
||||||
switcherMenu.clear();
|
|
||||||
timelinesByMenuItem.clear();
|
|
||||||
|
|
||||||
for (TimelineDefinition tl : timelines) {
|
|
||||||
int menuItemId = View.generateViewId();
|
|
||||||
timelinesByMenuItem.put(menuItemId, tl);
|
|
||||||
MenuItem item = switcherMenu.add(0, menuItemId, 0, tl.getTitle(getContext()));
|
|
||||||
item.setIcon(tl.getIcon().iconRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
UiUtils.enablePopupMenuIcons(getContext(), switcherPopup);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean onSwitcherItemSelected(MenuItem item) {
|
|
||||||
int id = item.getItemId();
|
|
||||||
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
|
|
||||||
if (id == R.id.menu_back) {
|
|
||||||
switcher.post(() -> switcherPopup.show());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
TimelineDefinition tl = timelinesByMenuItem.get(id);
|
|
||||||
if (tl != null) {
|
|
||||||
for (int i = 0; i < timelines.length; i++) {
|
|
||||||
if (timelines[i] == tl) {
|
|
||||||
navigateTo(i);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
private void navigateTo(int i) {
|
|
||||||
navigateTo(i, !reduceMotion);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void navigateTo(int i, boolean smooth) {
|
|
||||||
pager.setCurrentItem(i, smooth);
|
|
||||||
updateSwitcherIcon(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSwitcherIcon(int i) {
|
|
||||||
timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
|
|
||||||
timelineTitle.setText(timelines[i].getTitle(getContext()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
int id = item.getItemId();
|
|
||||||
ListTimeline list;
|
|
||||||
Hashtag hashtag;
|
|
||||||
|
|
||||||
if (item.getItemId() == R.id.menu_back) {
|
|
||||||
createOptionsMenu();
|
|
||||||
optionsMenu.performIdentifierAction(R.id.overflow, 0);
|
|
||||||
return true;
|
|
||||||
} else if (id == R.id.settings || id == R.id.settings_action) {
|
|
||||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
|
||||||
} else if (id == R.id.announcements || id == R.id.announcements_action) {
|
|
||||||
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
|
|
||||||
} else if (id == R.id.edit_timelines) {
|
|
||||||
Nav.go(getActivity(), EditTimelinesFragment.class, args);
|
|
||||||
} else if ((list = listItems.get(id)) != null) {
|
|
||||||
args.putString("listID", list.id);
|
|
||||||
args.putString("listTitle", list.title);
|
|
||||||
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
|
|
||||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
|
||||||
} else if ((hashtag = hashtagsItems.get(id)) != null) {
|
|
||||||
args.putString("hashtag", hashtag.name);
|
|
||||||
args.putBoolean("following", hashtag.following);
|
|
||||||
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scrollToTop(){
|
|
||||||
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hideNewPostsButton(){
|
|
||||||
if(!newPostsBtnShown)
|
|
||||||
return;
|
|
||||||
newPostsBtnShown=false;
|
|
||||||
if(currentNewPostsAnim!=null){
|
|
||||||
currentNewPostsAnim.cancel();
|
|
||||||
}
|
|
||||||
timelineTitle.setVisibility(View.VISIBLE);
|
|
||||||
AnimatorSet set=new AnimatorSet();
|
|
||||||
set.playTogether(
|
|
||||||
ObjectAnimator.ofFloat(timelineTitle, View.ALPHA, 1f),
|
|
||||||
ObjectAnimator.ofFloat(timelineTitle, View.SCALE_X, 1f),
|
|
||||||
ObjectAnimator.ofFloat(timelineTitle, View.SCALE_Y, 1f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 0f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, .8f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f),
|
|
||||||
ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 0f)
|
|
||||||
);
|
|
||||||
set.setDuration(reduceMotion ? 0 : 300);
|
|
||||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
set.addListener(new AnimatorListenerAdapter(){
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation){
|
|
||||||
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
|
||||||
collapsedChevron.setVisibility(View.GONE);
|
|
||||||
currentNewPostsAnim=null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
currentNewPostsAnim=set;
|
|
||||||
set.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showNewPostsButton(){
|
|
||||||
if(newPostsBtnShown || pager == null || !timelines[pager.getCurrentItem()].equals(TimelineDefinition.HOME_TIMELINE))
|
|
||||||
return;
|
|
||||||
newPostsBtnShown=true;
|
|
||||||
if(currentNewPostsAnim!=null){
|
|
||||||
currentNewPostsAnim.cancel();
|
|
||||||
}
|
|
||||||
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
|
||||||
collapsedChevron.setVisibility(View.VISIBLE);
|
|
||||||
AnimatorSet set=new AnimatorSet();
|
|
||||||
set.playTogether(
|
|
||||||
ObjectAnimator.ofFloat(timelineTitle, View.ALPHA, 0f),
|
|
||||||
ObjectAnimator.ofFloat(timelineTitle, View.SCALE_X, .8f),
|
|
||||||
ObjectAnimator.ofFloat(timelineTitle, View.SCALE_Y, .8f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 1f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, 1f),
|
|
||||||
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f),
|
|
||||||
ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 1f)
|
|
||||||
);
|
|
||||||
set.setDuration(reduceMotion ? 0 : 300);
|
|
||||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
set.addListener(new AnimatorListenerAdapter(){
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation){
|
|
||||||
timelineTitle.setVisibility(View.GONE);
|
|
||||||
currentNewPostsAnim=null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
currentNewPostsAnim=set;
|
|
||||||
set.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNewPostsBtnShown() {
|
|
||||||
return newPostsBtnShown;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onNewPostsBtnClick(View view) {
|
|
||||||
if(newPostsBtnShown){
|
|
||||||
hideNewPostsButton();
|
|
||||||
scrollToTop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFragmentResult(int reqCode, boolean success, Bundle result){
|
|
||||||
if (reqCode == ANNOUNCEMENTS_RESULT && success) {
|
|
||||||
announcementsBadged = false;
|
|
||||||
announcements.setVisible(true);
|
|
||||||
announcementsAction.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
|
||||||
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING) {
|
|
||||||
settingsBadged = true;
|
|
||||||
settingsAction.setVisible(true);
|
|
||||||
settings.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
|
||||||
updateUpdateState(ev.state);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onBackPressed(){
|
|
||||||
if(pager.getCurrentItem() > 0){
|
|
||||||
navigateTo(0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView(){
|
|
||||||
super.onDestroyView();
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
|
||||||
E.unregister(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onShown() {
|
|
||||||
super.onShown();
|
|
||||||
Object pinnedTimelines = GlobalUserPreferences.pinnedTimelines.get(accountID);
|
|
||||||
if (pinnedTimelines != null && timelineDefinitions != pinnedTimelines) UiUtils.restartApp();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewStateRestored(Bundle savedInstanceState) {
|
|
||||||
super.onViewStateRestored(savedInstanceState);
|
|
||||||
if (savedInstanceState == null) return;
|
|
||||||
navigateTo(savedInstanceState.getInt("selectedTab"), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putInt("selectedTab", pager.getCurrentItem());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onHashtagUpdatedEvent(HashtagUpdatedEvent event) {
|
|
||||||
handleListEvent(hashtagsItems, h -> h.name.equalsIgnoreCase(event.name), event.following, () -> {
|
|
||||||
Hashtag hashtag = new Hashtag();
|
|
||||||
hashtag.name = event.name;
|
|
||||||
hashtag.following = true;
|
|
||||||
return hashtag;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onListDeletedEvent(ListDeletedEvent event) {
|
|
||||||
handleListEvent(listItems, l -> l.id.equals(event.id), false, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
|
||||||
handleListEvent(listItems, l -> l.id.equals(event.id), true, () -> {
|
|
||||||
ListTimeline list = new ListTimeline();
|
|
||||||
list.id = event.id;
|
|
||||||
list.title = event.title;
|
|
||||||
list.repliesPolicy = event.repliesPolicy;
|
|
||||||
return list;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> void handleListEvent(
|
|
||||||
Map<Integer, T> existingThings,
|
|
||||||
Predicate<T> matchExisting,
|
|
||||||
boolean shouldBeInList,
|
|
||||||
Supplier<T> makeNewThing
|
|
||||||
) {
|
|
||||||
Optional<Map.Entry<Integer, T>> existingThing = existingThings.entrySet().stream()
|
|
||||||
.filter(e -> matchExisting.test(e.getValue())).findFirst();
|
|
||||||
if (shouldBeInList) {
|
|
||||||
existingThings.put(existingThing.isPresent()
|
|
||||||
? existingThing.get().getKey() : View.generateViewId(), makeNewThing.get());
|
|
||||||
createOptionsMenu();
|
|
||||||
} else if (existingThing.isPresent() && !shouldBeInList) {
|
|
||||||
existingThings.remove(existingThing.get().getKey());
|
|
||||||
createOptionsMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<Hashtag> getHashtags() {
|
|
||||||
return hashtagsItems.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
FrameLayout tabView = tabViews[viewType % getItemCount()];
|
|
||||||
((ViewGroup)tabView.getParent()).removeView(tabView);
|
|
||||||
tabView.setVisibility(View.VISIBLE);
|
|
||||||
return new SimpleViewHolder(tabView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount(){
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position){
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,78 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import com.squareup.otto.Subscribe;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HomeTimelineFragment extends FabStatusListFragment {
|
public class HomeTimelineFragment extends StatusListFragment{
|
||||||
private HomeTabFragment parent;
|
private ImageButton fab;
|
||||||
|
private ImageView toolbarLogo;
|
||||||
|
private Button toolbarShowNewPostsBtn;
|
||||||
|
private boolean newPostsBtnShown;
|
||||||
|
private AnimatorSet currentNewPostsAnim;
|
||||||
|
|
||||||
private String maxID;
|
private String maxID;
|
||||||
private String lastSavedMarkerID;
|
|
||||||
|
public HomeTimelineFragment(){
|
||||||
|
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
if (getParentFragment() instanceof HomeTabFragment home) parent = home;
|
setHasOptionsMenu(true);
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,15 +104,41 @@ public class HomeTimelineFragment extends FabStatusListFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
fab=view.findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(this::onFabClick);
|
||||||
|
updateToolbarLogo();
|
||||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
||||||
if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
if(newPostsBtnShown && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
||||||
parent.hideNewPostsButton();
|
hideNewPostsButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
|
E.register(this);
|
||||||
|
updateUpdateState(GithubSelfUpdater.getInstance().getState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
|
inflater.inflate(R.menu.home, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration newConfig){
|
||||||
|
super.onConfigurationChanged(newConfig);
|
||||||
|
updateToolbarLogo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,33 +153,16 @@ public class HomeTimelineFragment extends FabStatusListFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onHidden(){
|
|
||||||
super.onHidden();
|
|
||||||
if(!data.isEmpty()){
|
|
||||||
String topPostID=displayItems.get(Math.max(0, list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset())).parentID;
|
|
||||||
if(!topPostID.equals(lastSavedMarkerID)){
|
|
||||||
lastSavedMarkerID=topPostID;
|
|
||||||
new SaveMarkers(topPostID, null)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(SaveMarkers.Response result){
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
lastSavedMarkerID=null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onStatusCreated(StatusCreatedEvent ev){
|
public void onStatusCreated(StatusCreatedEvent ev){
|
||||||
prependItems(Collections.singletonList(ev.status), true);
|
prependItems(Collections.singletonList(ev.status), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onFabClick(View v){
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putString("account", accountID);
|
||||||
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
private void loadNewPosts(){
|
private void loadNewPosts(){
|
||||||
if (!GlobalUserPreferences.loadNewPosts) return;
|
if (!GlobalUserPreferences.loadNewPosts) return;
|
||||||
dataLoading=true;
|
dataLoading=true;
|
||||||
@@ -146,11 +187,13 @@ public class HomeTimelineFragment extends FabStatusListFragment {
|
|||||||
result.get(result.size()-1).hasGapAfter=true;
|
result.get(result.size()-1).hasGapAfter=true;
|
||||||
toAdd=result;
|
toAdd=result;
|
||||||
}
|
}
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||||
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
if(!filters.isEmpty()){
|
||||||
|
toAdd=toAdd.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
if(!toAdd.isEmpty()){
|
if(!toAdd.isEmpty()){
|
||||||
prependItems(toAdd, true);
|
prependItems(toAdd, true);
|
||||||
if (parent != null) parent.showNewPostsButton();
|
showNewPostsButton();
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,14 +264,18 @@ public class HomeTimelineFragment extends FabStatusListFragment {
|
|||||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||||
targetList.clear();
|
targetList.clear();
|
||||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||||
|
outer:
|
||||||
for(Status s:result){
|
for(Status s:result){
|
||||||
if(idsBelowGap.contains(s.id))
|
if(idsBelowGap.contains(s.id))
|
||||||
break;
|
break;
|
||||||
if(filterPredicate.test(s)){
|
for(Filter filter:filters){
|
||||||
targetList.addAll(buildDisplayItems(s));
|
if(filter.matches(s)){
|
||||||
insertedPosts.add(s);
|
continue outer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
targetList.addAll(buildDisplayItems(s));
|
||||||
|
insertedPosts.add(s);
|
||||||
}
|
}
|
||||||
if(targetList.isEmpty()){
|
if(targetList.isEmpty()){
|
||||||
// oops. We didn't add new posts, but at least we know there are none.
|
// oops. We didn't add new posts, but at least we know there are none.
|
||||||
@@ -266,10 +313,132 @@ public class HomeTimelineFragment extends FabStatusListFragment {
|
|||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
}
|
}
|
||||||
if (parent != null) parent.hideNewPostsButton();
|
|
||||||
super.onRefresh();
|
super.onRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateToolbarLogo(){
|
||||||
|
toolbarLogo=new ImageView(getActivity());
|
||||||
|
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
|
||||||
|
toolbarLogo.setImageResource(R.drawable.logo);
|
||||||
|
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
||||||
|
// toolbarLogo =new TextView(getActivity());
|
||||||
|
// toolbarLogo.setText(getString(R.string.app_name).toLowerCase(Locale.getDefault()));
|
||||||
|
// toolbarLogo.setTextAppearance(R.style.app_title);
|
||||||
|
|
||||||
|
toolbarShowNewPostsBtn=new Button(getActivity());
|
||||||
|
toolbarShowNewPostsBtn.setTextAppearance(R.style.m3_title_medium);
|
||||||
|
toolbarShowNewPostsBtn.setTextColor(0xffffffff);
|
||||||
|
toolbarShowNewPostsBtn.setStateListAnimator(null);
|
||||||
|
toolbarShowNewPostsBtn.setBackgroundResource(R.drawable.bg_button_new_posts);
|
||||||
|
toolbarShowNewPostsBtn.setText(R.string.see_new_posts);
|
||||||
|
toolbarShowNewPostsBtn.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_fluent_arrow_up_16_filled, 0, 0, 0);
|
||||||
|
toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors());
|
||||||
|
toolbarShowNewPostsBtn.setCompoundDrawablePadding(V.dp(8));
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
|
||||||
|
UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn);
|
||||||
|
toolbarShowNewPostsBtn.setOnClickListener(this::onNewPostsBtnClick);
|
||||||
|
|
||||||
|
if(newPostsBtnShown){
|
||||||
|
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
||||||
|
toolbarLogo.setVisibility(View.INVISIBLE);
|
||||||
|
toolbarLogo.setAlpha(0f);
|
||||||
|
}else{
|
||||||
|
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
||||||
|
toolbarShowNewPostsBtn.setAlpha(0f);
|
||||||
|
toolbarShowNewPostsBtn.setScaleX(.8f);
|
||||||
|
toolbarShowNewPostsBtn.setScaleY(.8f);
|
||||||
|
toolbarLogo.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameLayout logoWrap=new FrameLayout(getActivity());
|
||||||
|
logoWrap.addView(toolbarLogo, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||||
|
logoWrap.addView(toolbarShowNewPostsBtn, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, V.dp(32), Gravity.CENTER));
|
||||||
|
|
||||||
|
Toolbar toolbar=getToolbar();
|
||||||
|
toolbar.addView(logoWrap, new Toolbar.LayoutParams(Gravity.CENTER));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showNewPostsButton(){
|
||||||
|
if(newPostsBtnShown)
|
||||||
|
return;
|
||||||
|
newPostsBtnShown=true;
|
||||||
|
if(currentNewPostsAnim!=null){
|
||||||
|
currentNewPostsAnim.cancel();
|
||||||
|
}
|
||||||
|
toolbarShowNewPostsBtn.setVisibility(View.VISIBLE);
|
||||||
|
AnimatorSet set=new AnimatorSet();
|
||||||
|
set.playTogether(
|
||||||
|
ObjectAnimator.ofFloat(toolbarLogo, View.ALPHA, 0f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 1f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, 1f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f)
|
||||||
|
);
|
||||||
|
set.setDuration(300);
|
||||||
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
toolbarLogo.setVisibility(View.INVISIBLE);
|
||||||
|
currentNewPostsAnim=null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentNewPostsAnim=set;
|
||||||
|
set.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideNewPostsButton(){
|
||||||
|
if(!newPostsBtnShown)
|
||||||
|
return;
|
||||||
|
newPostsBtnShown=false;
|
||||||
|
if(currentNewPostsAnim!=null){
|
||||||
|
currentNewPostsAnim.cancel();
|
||||||
|
}
|
||||||
|
toolbarLogo.setVisibility(View.VISIBLE);
|
||||||
|
AnimatorSet set=new AnimatorSet();
|
||||||
|
set.playTogether(
|
||||||
|
ObjectAnimator.ofFloat(toolbarLogo, View.ALPHA, 1f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.ALPHA, 0f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_X, .8f),
|
||||||
|
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f)
|
||||||
|
);
|
||||||
|
set.setDuration(300);
|
||||||
|
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||||
|
set.addListener(new AnimatorListenerAdapter(){
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation){
|
||||||
|
toolbarShowNewPostsBtn.setVisibility(View.INVISIBLE);
|
||||||
|
currentNewPostsAnim=null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentNewPostsAnim=set;
|
||||||
|
set.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNewPostsBtnClick(View v){
|
||||||
|
if(newPostsBtnShown){
|
||||||
|
hideNewPostsButton();
|
||||||
|
scrollToTop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView(){
|
||||||
|
super.onDestroyView();
|
||||||
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
|
E.unregister(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUpdateState(GithubSelfUpdater.UpdateState state){
|
||||||
|
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
|
||||||
|
getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_24_badged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onSelfUpdateStateChanged(SelfUpdateStateChangedEvent ev){
|
||||||
|
updateUpdateState(ev.state);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
public interface IsOnTop {
|
|
||||||
boolean isOnTop();
|
|
||||||
|
|
||||||
default boolean isRecyclerViewOnTop(@Nullable RecyclerView list) {
|
|
||||||
if (list == null) return true;
|
|
||||||
return !list.canScrollVertically(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +1,28 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.media.MediaRouter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.lists.GetList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
|
|
||||||
public class ListTimelineFragment extends PinnableStatusListFragment {
|
public class ListTimelineFragment extends StatusListFragment {
|
||||||
private String listID;
|
private String listID;
|
||||||
private String listTitle;
|
private String listTitle;
|
||||||
@Nullable
|
|
||||||
private ListTimeline.RepliesPolicy repliesPolicy;
|
|
||||||
private ImageButton fab;
|
private ImageButton fab;
|
||||||
|
|
||||||
public ListTimelineFragment() {
|
public ListTimelineFragment() {
|
||||||
@@ -48,81 +32,17 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
Bundle args = getArguments();
|
listID=getArguments().getString("listID");
|
||||||
listID = args.getString("listID");
|
listTitle=getArguments().getString("listTitle");
|
||||||
listTitle = args.getString("listTitle");
|
|
||||||
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
|
|
||||||
|
|
||||||
setTitle(listTitle);
|
setTitle(listTitle);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
new GetList(listID).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(ListTimeline listTimeline) {
|
|
||||||
// TODO: save updated info
|
|
||||||
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
|
|
||||||
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
|
|
||||||
repliesPolicy = listTimeline.repliesPolicy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.list, menu);
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
UiUtils.enableOptionsMenuIcons(getContext(), menu, R.id.pin);
|
// TODO: implement edit, delete
|
||||||
}
|
// inflater.inflate(R.menu.list, menu);
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (super.onOptionsItemSelected(item)) return true;
|
|
||||||
if (item.getItemId() == R.id.edit) {
|
|
||||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
|
||||||
editor.applyList(listTitle, repliesPolicy);
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.sk_edit_list_title)
|
|
||||||
.setIcon(R.drawable.ic_fluent_people_list_28_regular)
|
|
||||||
.setView(editor)
|
|
||||||
.setPositiveButton(R.string.save, (d, which) -> {
|
|
||||||
String newTitle = editor.getTitle().trim();
|
|
||||||
setTitle(newTitle);
|
|
||||||
new UpdateList(listID, newTitle, editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(ListTimeline list) {
|
|
||||||
setTitle(list.title);
|
|
||||||
listTitle = list.title;
|
|
||||||
repliesPolicy = list.repliesPolicy;
|
|
||||||
E.post(new ListUpdatedCreatedEvent(listID, listTitle, repliesPolicy));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
setTitle(listTitle);
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
|
||||||
.show();
|
|
||||||
} else if (item.getItemId() == R.id.delete) {
|
|
||||||
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
|
|
||||||
E.post(new ListDeletedEvent(listID));
|
|
||||||
Nav.finish(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TimelineDefinition makeTimelineDefinition() {
|
|
||||||
return TimelineDefinition.ofList(listID, listTitle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -149,7 +69,6 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
fab=view.findViewById(R.id.fab);
|
fab=view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
|
|||||||
@@ -6,253 +6,183 @@ import android.view.MenuInflater;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
|
||||||
import org.joinmastodon.android.api.requests.lists.CreateList;
|
|
||||||
import org.joinmastodon.android.api.requests.lists.GetLists;
|
import org.joinmastodon.android.api.requests.lists.GetLists;
|
||||||
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.views.ListTimelineEditor;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
|
||||||
private String accountId;
|
private String accountId;
|
||||||
private String profileAccountId;
|
private String profileAccountId;
|
||||||
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
private String profileDisplayUsername;
|
||||||
private final HashMap<String, Boolean> userInList = new HashMap<>();
|
private HashMap<String, Boolean> userInListBefore = new HashMap<>();
|
||||||
private ListsAdapter adapter;
|
private HashMap<String, Boolean> userInList = new HashMap<>();
|
||||||
|
private int inProgress = 0;
|
||||||
|
|
||||||
public ListTimelinesFragment() {
|
public ListTimelinesFragment() {
|
||||||
super(10);
|
super(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Bundle args=getArguments();
|
Bundle args=getArguments();
|
||||||
accountId=args.getString("account");
|
accountId=args.getString("account");
|
||||||
setHasOptionsMenu(true);
|
|
||||||
E.register(this);
|
|
||||||
|
|
||||||
if(args.containsKey("profileAccount")){
|
if(args.containsKey("profileAccount")){
|
||||||
profileAccountId=args.getString("profileAccount");
|
profileAccountId=args.getString("profileAccount");
|
||||||
String profileDisplayUsername = args.getString("profileDisplayUsername");
|
profileDisplayUsername=args.getString("profileDisplayUsername");
|
||||||
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
|
setTitle(getString(R.string.lists_with_user, profileDisplayUsername));
|
||||||
} else {
|
// setHasOptionsMenu(true);
|
||||||
setTitle(R.string.sk_your_lists);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onShown(){
|
protected void onShown(){
|
||||||
super.onShown();
|
super.onShown();
|
||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// @Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
// public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
// Button saveButton=new Button(getActivity());
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
|
// saveButton.setText(R.string.save);
|
||||||
}
|
// saveButton.setOnClickListener(this::onSaveClick);
|
||||||
|
// LinearLayout wrap=new LinearLayout(getActivity());
|
||||||
|
// wrap.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
// wrap.addView(saveButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
// wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8));
|
||||||
|
// wrap.setClipToPadding(false);
|
||||||
|
// MenuItem item=menu.add(R.string.save);
|
||||||
|
// item.setActionView(wrap);
|
||||||
|
// item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
// }
|
||||||
|
|
||||||
@Override
|
private void saveListMembership(String listId, boolean isMember) {
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
userInList.put(listId, isMember);
|
||||||
inflater.inflate(R.menu.menu_list, menu);
|
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
||||||
}
|
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
|
||||||
|
req.setCallback(new SimpleCallback<>(this) {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Object o) {}
|
||||||
|
}).exec(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
protected void doLoadData(int offset, int count){
|
||||||
if (item.getItemId() == R.id.create) {
|
userInListBefore.clear();
|
||||||
ListTimelineEditor editor = new ListTimelineEditor(getContext());
|
userInList.clear();
|
||||||
new M3AlertDialogBuilder(getActivity())
|
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
||||||
.setTitle(R.string.sk_create_list_title)
|
.setCallback(new SimpleCallback<>(this) {
|
||||||
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
|
@Override
|
||||||
.setView(editor)
|
public void onSuccess(List<ListTimeline> lists) {
|
||||||
.setPositiveButton(R.string.sk_create, (d, which) ->
|
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
||||||
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
userInList.putAll(userInListBefore);
|
||||||
@Override
|
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
||||||
public void onSuccess(ListTimeline list) {
|
if (profileAccountId == null) return;
|
||||||
saveListMembership(list.id, true);
|
|
||||||
data.add(0, list);
|
|
||||||
adapter.notifyItemRangeInserted(0, 1);
|
|
||||||
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
|
||||||
public void onError(ErrorResponse error) {
|
@Override
|
||||||
error.showToast(getContext());
|
public void onSuccess(List<ListTimeline> allLists) {
|
||||||
}
|
List<ListTimeline> newLists = new ArrayList<>();
|
||||||
}).exec(accountId)
|
for (ListTimeline l : allLists) {
|
||||||
)
|
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
||||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
if (!userInListBefore.containsKey(l.id)) {
|
||||||
.show();
|
userInListBefore.put(l.id, false);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
}
|
userInList.putAll(userInListBefore);
|
||||||
|
onDataLoaded(newLists, false);
|
||||||
|
}
|
||||||
|
}).exec(accountId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
private void saveListMembership(String listId, boolean isMember) {
|
@Override
|
||||||
userInList.put(listId, isMember);
|
protected RecyclerView.Adapter getAdapter() {
|
||||||
List<String> accountIdList = Collections.singletonList(profileAccountId);
|
return new ListsAdapter();
|
||||||
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
|
}
|
||||||
req.setCallback(new Callback<>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Object o) {}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error) {
|
public void scrollToTop() {
|
||||||
error.showToast(getContext());
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
}).exec(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
||||||
protected void doLoadData(int offset, int count){
|
@NonNull
|
||||||
userInListBefore.clear();
|
@Override
|
||||||
userInList.clear();
|
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
|
return new ListViewHolder();
|
||||||
.setCallback(new SimpleCallback<>(this) {
|
}
|
||||||
@Override
|
|
||||||
public void onSuccess(List<ListTimeline> lists) {
|
|
||||||
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
|
||||||
userInList.putAll(userInListBefore);
|
|
||||||
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
|
||||||
if (profileAccountId == null) return;
|
|
||||||
|
|
||||||
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
|
@Override
|
||||||
@Override
|
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
||||||
public void onSuccess(List<ListTimeline> allLists) {
|
holder.bind(data.get(position));
|
||||||
List<ListTimeline> newLists = new ArrayList<>();
|
}
|
||||||
for (ListTimeline l : allLists) {
|
|
||||||
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
|
||||||
if (!userInListBefore.containsKey(l.id)) {
|
|
||||||
userInListBefore.put(l.id, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userInList.putAll(userInListBefore);
|
|
||||||
onDataLoaded(newLists, false);
|
|
||||||
}
|
|
||||||
}).exec(accountId);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
@Override
|
||||||
public void onListDeletedEvent(ListDeletedEvent event) {
|
public int getItemCount() {
|
||||||
for (int i = 0; i < data.size(); i++) {
|
return data.size();
|
||||||
ListTimeline item = data.get(i);
|
}
|
||||||
if (item.id.equals(event.id)) {
|
}
|
||||||
data.remove(i);
|
|
||||||
adapter.notifyItemRemoved(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
|
||||||
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
|
private final TextView title;
|
||||||
for (int i = 0; i < data.size(); i++) {
|
private final CheckBox listToggle;
|
||||||
ListTimeline item = data.get(i);
|
|
||||||
if (item.id.equals(event.id)) {
|
|
||||||
item.title = event.title;
|
|
||||||
item.repliesPolicy = event.repliesPolicy;
|
|
||||||
adapter.notifyItemChanged(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public ListViewHolder(){
|
||||||
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
|
super(getActivity(), R.layout.item_list_timeline, list);
|
||||||
return adapter = new ListsAdapter();
|
title=findViewById(R.id.title);
|
||||||
}
|
listToggle=findViewById(R.id.list_toggle);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop() {
|
public void onBind(ListTimeline item) {
|
||||||
smoothScrollRecyclerViewToTop(list);
|
title.setText(item.title);
|
||||||
}
|
if (profileAccountId != null) {
|
||||||
|
Boolean checked = userInList.get(item.id);
|
||||||
|
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
||||||
|
listToggle.setOnClickListener(this::onClickToggle);
|
||||||
|
} else {
|
||||||
|
listToggle.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
|
private void onClickToggle(View view) {
|
||||||
@NonNull
|
saveListMembership(item.id, listToggle.isChecked());
|
||||||
@Override
|
}
|
||||||
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new ListViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
|
public void onClick() {
|
||||||
holder.bind(data.get(position));
|
UiUtils.openListTimeline(getActivity(), accountId, item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return data.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
|
|
||||||
private final TextView title;
|
|
||||||
private final CheckBox listToggle;
|
|
||||||
|
|
||||||
public ListViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_text, list);
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
listToggle=findViewById(R.id.list_toggle);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(ListTimeline item) {
|
|
||||||
title.setText(item.title);
|
|
||||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_list_24_regular), null, null, null);
|
|
||||||
if (profileAccountId != null) {
|
|
||||||
Boolean checked = userInList.get(item.id);
|
|
||||||
listToggle.setVisibility(View.VISIBLE);
|
|
||||||
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
|
|
||||||
listToggle.setOnClickListener(this::onClickToggle);
|
|
||||||
} else {
|
|
||||||
listToggle.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onClickToggle(View view) {
|
|
||||||
saveListMembership(item.id, listToggle.isChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountId);
|
|
||||||
args.putString("listID", item.id);
|
|
||||||
args.putString("listTitle", item.title);
|
|
||||||
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
|
|
||||||
Nav.go(getActivity(), ListTimelineFragment.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
||||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||||
@@ -44,7 +43,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
private FrameLayout[] tabViews;
|
private FrameLayout[] tabViews;
|
||||||
private TabLayoutMediator tabLayoutMediator;
|
private TabLayoutMediator tabLayoutMediator;
|
||||||
|
|
||||||
private NotificationsListFragment allNotificationsFragment, mentionsFragment;
|
private NotificationsListFragment allNotificationsFragment, mentionsFragment, postsFragment;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
|
||||||
@@ -74,26 +73,15 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(R.menu.notifications, menu);
|
inflater.inflate(R.menu.notifications, menu);
|
||||||
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
|
|
||||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (item.getItemId() == R.id.follow_requests) {
|
if (item.getItemId() != R.id.follow_requests) return false;
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), FollowRequestsListFragment.class, args);
|
Nav.go(getActivity(), FollowRequestsListFragment.class, args);
|
||||||
return true;
|
return true;
|
||||||
} else if (item.getItemId() == R.id.clear_notifications) {
|
|
||||||
UiUtils.confirmDeleteNotification(getActivity(), accountID, null, ()->{
|
|
||||||
for (int i = 0; i < tabViews.length; i++) {
|
|
||||||
getFragmentForPage(i).reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -102,14 +90,14 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
|
|
||||||
tabLayout=view.findViewById(R.id.tabbar);
|
tabLayout=view.findViewById(R.id.tabbar);
|
||||||
pager=view.findViewById(R.id.pager);
|
pager=view.findViewById(R.id.pager);
|
||||||
UiUtils.reduceSwipeSensitivity(pager);
|
|
||||||
|
|
||||||
tabViews=new FrameLayout[2];
|
tabViews=new FrameLayout[3];
|
||||||
for(int i=0;i<tabViews.length;i++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
FrameLayout tabView=new FrameLayout(getActivity());
|
FrameLayout tabView=new FrameLayout(getActivity());
|
||||||
tabView.setId(switch(i){
|
tabView.setId(switch(i){
|
||||||
case 0 -> R.id.notifications_all;
|
case 0 -> R.id.notifications_all;
|
||||||
case 1 -> R.id.notifications_mentions;
|
case 1 -> R.id.notifications_mentions;
|
||||||
|
case 2 -> R.id.notifications_posts;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
default -> throw new IllegalStateException("Unexpected value: "+i);
|
||||||
});
|
});
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
@@ -119,21 +107,8 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
|
|
||||||
tabLayout.setTabTextSize(V.dp(16));
|
tabLayout.setTabTextSize(V.dp(16));
|
||||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
|
||||||
@Override
|
|
||||||
public void onTabSelected(TabLayout.Tab tab) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTabReselected(TabLayout.Tab tab) {
|
|
||||||
scrollToTop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pager.setOffscreenPageLimit(4);
|
pager.setOffscreenPageLimit(4);
|
||||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
|
||||||
pager.setAdapter(new DiscoverPagerAdapter());
|
pager.setAdapter(new DiscoverPagerAdapter());
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||||
@Override
|
@Override
|
||||||
@@ -161,9 +136,15 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
mentionsFragment=new NotificationsListFragment();
|
mentionsFragment=new NotificationsListFragment();
|
||||||
mentionsFragment.setArguments(args);
|
mentionsFragment.setArguments(args);
|
||||||
|
|
||||||
|
args=new Bundle(args);
|
||||||
|
args.putBoolean("onlyPosts", true);
|
||||||
|
postsFragment=new NotificationsListFragment();
|
||||||
|
postsFragment.setArguments(args);
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.notifications_all, allNotificationsFragment)
|
.add(R.id.notifications_all, allNotificationsFragment)
|
||||||
.add(R.id.notifications_mentions, mentionsFragment)
|
.add(R.id.notifications_mentions, mentionsFragment)
|
||||||
|
.add(R.id.notifications_posts, postsFragment)
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +154,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.all_notifications;
|
case 0 -> R.string.all_notifications;
|
||||||
case 1 -> R.string.mentions;
|
case 1 -> R.string.mentions;
|
||||||
|
case 2 -> R.string.posts;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
tab.view.textView.setAllCaps(true);
|
||||||
@@ -215,13 +197,13 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
protected void updateToolbar(){
|
protected void updateToolbar(){
|
||||||
super.updateToolbar();
|
super.updateToolbar();
|
||||||
getToolbar().setOutlineProvider(null);
|
getToolbar().setOutlineProvider(null);
|
||||||
getToolbar().setOnClickListener(v->scrollToTop());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationsListFragment getFragmentForPage(int page){
|
private NotificationsListFragment getFragmentForPage(int page){
|
||||||
return switch(page){
|
return switch(page){
|
||||||
case 0 -> allNotificationsFragment;
|
case 0 -> allNotificationsFragment;
|
||||||
case 1 -> mentionsFragment;
|
case 1 -> mentionsFragment;
|
||||||
|
case 2 -> postsFragment;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -242,7 +224,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount(){
|
||||||
return 2;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.joinmastodon.android.E;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.events.NotificationDeletedEvent;
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
@@ -19,7 +20,6 @@ import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
|
|||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
|
||||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
@@ -40,7 +40,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
private boolean onlyMentions;
|
private boolean onlyMentions;
|
||||||
private boolean onlyPosts;
|
private boolean onlyPosts;
|
||||||
private String maxID;
|
private String maxID;
|
||||||
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -78,11 +77,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
case REBLOG -> getString(R.string.notification_boosted);
|
case REBLOG -> getString(R.string.notification_boosted);
|
||||||
case FAVORITE -> getString(R.string.user_favorited);
|
case FAVORITE -> getString(R.string.user_favorited);
|
||||||
case POLL -> getString(R.string.poll_ended);
|
case POLL -> getString(R.string.poll_ended);
|
||||||
case UPDATE -> getString(R.string.sk_post_edited);
|
|
||||||
};
|
};
|
||||||
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText, n, null) : null;
|
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText) : null;
|
||||||
if(n.status!=null){
|
if(n.status!=null){
|
||||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n);
|
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null);
|
||||||
if(titleItem!=null){
|
if(titleItem!=null){
|
||||||
for(StatusDisplayItem item:items){
|
for(StatusDisplayItem item:items){
|
||||||
if(item instanceof ImageStatusDisplayItem imgItem){
|
if(item instanceof ImageStatusDisplayItem imgItem){
|
||||||
@@ -176,7 +174,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
||||||
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Notification getNotificationByID(String id){
|
private Notification getNotificationByID(String id){
|
||||||
@@ -213,7 +210,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeNotification(Notification n){
|
private void removeNotification(Notification n){
|
||||||
data.remove(n);
|
data.remove(n);
|
||||||
preloadedData.remove(n);
|
preloadedData.remove(n);
|
||||||
int index=-1;
|
int index=-1;
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.HapticFeedbackConstants;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class PinnableStatusListFragment extends StatusListFragment {
|
|
||||||
protected List<TimelineDefinition> pinnedTimelines;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
updatePinButton(menu.findItem(R.id.pin));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isPinned() {
|
|
||||||
return pinnedTimelines.contains(makeTimelineDefinition());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updatePinButton(MenuItem pin) {
|
|
||||||
boolean pinned = isPinned();
|
|
||||||
pin.setIcon(pinned ?
|
|
||||||
R.drawable.ic_fluent_pin_24_filled :
|
|
||||||
R.drawable.ic_fluent_pin_24_regular);
|
|
||||||
pin.setTitle(pinned ? R.string.sk_unpin_timeline : R.string.sk_pin_timeline);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract TimelineDefinition makeTimelineDefinition();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.pin) {
|
|
||||||
togglePin(item);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void togglePin(MenuItem pin) {
|
|
||||||
onPinnedUpdated(true);
|
|
||||||
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
|
|
||||||
TimelineDefinition def = makeTimelineDefinition();
|
|
||||||
boolean pinned = isPinned();
|
|
||||||
if (pinned) pinnedTimelines.remove(def);
|
|
||||||
else pinnedTimelines.add(def);
|
|
||||||
Toast.makeText(getContext(), pinned ? R.string.sk_unpinned_timeline : R.string.sk_pinned_timeline, Toast.LENGTH_SHORT).show();
|
|
||||||
GlobalUserPreferences.pinnedTimelines.put(accountID, pinnedTimelines);
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
updatePinButton(pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPinnedUpdated(boolean pinned) {}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ import android.app.Activity;
|
|||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
@@ -19,8 +18,6 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.VibrationEffect;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ImageSpan;
|
import android.text.style.ImageSpan;
|
||||||
@@ -164,11 +161,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getPrefilledText() {
|
|
||||||
return account == null || AccountSessionManager.getInstance().isSelf(accountID, account)
|
|
||||||
? null : '@'+account.acct+' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
@@ -241,9 +233,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
tabViews[i]=tabView;
|
tabViews[i]=tabView;
|
||||||
}
|
}
|
||||||
|
|
||||||
UiUtils.reduceSwipeSensitivity(pager);
|
|
||||||
pager.setOffscreenPageLimit(5);
|
pager.setOffscreenPageLimit(5);
|
||||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
|
||||||
pager.setAdapter(new ProfilePagerAdapter());
|
pager.setAdapter(new ProfilePagerAdapter());
|
||||||
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
|
||||||
|
|
||||||
@@ -259,7 +249,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.posts;
|
case 0 -> R.string.posts;
|
||||||
case 1 -> R.string.posts_and_replies;
|
case 1 -> R.string.posts_and_replies;
|
||||||
case 2 -> R.string.sk_pinned_posts;
|
case 2 -> R.string.pinned_posts;
|
||||||
case 3 -> R.string.media;
|
case 3 -> R.string.media;
|
||||||
case 4 -> R.string.profile_about;
|
case 4 -> R.string.profile_about;
|
||||||
default -> throw new IllegalStateException();
|
default -> throw new IllegalStateException();
|
||||||
@@ -281,7 +271,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
cover.setOnClickListener(this::onCoverClick);
|
cover.setOnClickListener(this::onCoverClick);
|
||||||
refreshLayout.setOnRefreshListener(this);
|
refreshLayout.setOnRefreshListener(this);
|
||||||
fab.setOnClickListener(this::onFabClick);
|
fab.setOnClickListener(this::onFabClick);
|
||||||
fab.setOnLongClickListener(v->UiUtils.pickAccountForCompose(getActivity(), accountID, getPrefilledText()));
|
|
||||||
|
|
||||||
if(loaded){
|
if(loaded){
|
||||||
bindHeaderView();
|
bindHeaderView();
|
||||||
@@ -295,11 +284,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
|
||||||
|
|
||||||
username.setOnLongClickListener(v->{
|
username.setOnLongClickListener(v->{
|
||||||
String usernameString=account.acct;
|
String username=account.acct;
|
||||||
if(!usernameString.contains("@")){
|
if(!username.contains("@")){
|
||||||
usernameString+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
username+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
|
||||||
|
}
|
||||||
|
getActivity().getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText(null, "@"+username));
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.TIRAMISU){ // Android 13+ SystemUI shows its own thing when you put things into the clipboard
|
||||||
|
Toast.makeText(getActivity(), R.string.text_copied, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
UiUtils.copyText(username, '@'+usernameString);
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -556,28 +548,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
if(relationship==null && !isOwnProfile)
|
if(relationship==null && !isOwnProfile)
|
||||||
return;
|
return;
|
||||||
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
|
||||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags);
|
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
||||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
|
||||||
if(isOwnProfile)
|
if(isOwnProfile)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MenuItem mute = menu.findItem(R.id.mute);
|
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
||||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
||||||
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
||||||
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
|
||||||
|
|
||||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
|
||||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
|
||||||
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
|
|
||||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
|
||||||
if(relationship.following) {
|
if(relationship.following) {
|
||||||
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
|
menu.findItem(R.id.hide_boosts).setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
|
||||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
|
||||||
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
|
|
||||||
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
|
||||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
|
||||||
}else {
|
}else {
|
||||||
menu.findItem(R.id.hide_boosts).setVisible(false);
|
menu.findItem(R.id.hide_boosts).setVisible(false);
|
||||||
|
menu.findItem(R.id.manage_user_lists).setVisible(false);
|
||||||
}
|
}
|
||||||
if(!account.isLocal())
|
if(!account.isLocal())
|
||||||
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||||
@@ -597,8 +579,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
confirmToggleMuted();
|
confirmToggleMuted();
|
||||||
}else if(id==R.id.block){
|
}else if(id==R.id.block){
|
||||||
confirmToggleBlocked();
|
confirmToggleBlocked();
|
||||||
}else if(id==R.id.soft_block){
|
|
||||||
confirmSoftBlockUser();
|
|
||||||
}else if(id==R.id.report){
|
}else if(id==R.id.report){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -637,19 +617,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
}else if(id==R.id.manage_user_lists){
|
}else if(id==R.id.manage_user_lists){
|
||||||
final Bundle args=new Bundle();
|
final Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
if (!isOwnProfile) {
|
args.putString("profileAccount", profileAccountID);
|
||||||
args.putString("profileAccount", profileAccountID);
|
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
|
||||||
}
|
|
||||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
||||||
}else if(id==R.id.followed_hashtags){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
Nav.go(getActivity(), FollowedHashtagsFragment.class, args);
|
|
||||||
}else if(id==R.id.scheduled){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
Nav.go(getActivity(), ScheduledStatusListFragment.class, args);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -688,7 +658,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
|
||||||
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
|
||||||
notifyButton.setSelected(relationship.notifying);
|
notifyButton.setSelected(relationship.notifying);
|
||||||
if (getActivity() != null) notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
|
if (getActivity() != null) notifyButton.setContentDescription(getString(relationship.notifying ? R.string.user_post_notifications_on : R.string.user_post_notifications_off, '@'+account.username));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
|
||||||
@@ -905,10 +875,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmSoftBlockUser(){
|
|
||||||
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRelationship(Relationship r){
|
private void updateRelationship(Relationship r){
|
||||||
relationship=r;
|
relationship=r;
|
||||||
updateRelationship();
|
updateRelationship();
|
||||||
@@ -965,7 +931,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||||||
private void onFabClick(View v){
|
private void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
if(getPrefilledText() != null) args.putString("prefilledText", getPrefilledText());
|
if(!AccountSessionManager.getInstance().isSelf(accountID, account)){
|
||||||
|
args.putString("prefilledText", '@'+account.acct+' ');
|
||||||
|
}
|
||||||
Nav.go(getActivity(), ComposeFragment.class, args);
|
Nav.go(getActivity(), ComposeFragment.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses;
|
|
||||||
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
|
||||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
|
||||||
import org.joinmastodon.android.model.ScheduledStatus;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
|
|
||||||
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
|
|
||||||
private String nextMaxID;
|
|
||||||
private ImageButton fab;
|
|
||||||
private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
|
|
||||||
|
|
||||||
public ScheduledStatusListFragment() {
|
|
||||||
setListLayoutId(R.layout.recycler_fragment_with_fab);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
E.register(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy(){
|
|
||||||
super.onDestroy();
|
|
||||||
E.unregister(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity){
|
|
||||||
super.onAttach(activity);
|
|
||||||
setTitle(R.string.sk_unsent_posts);
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
fab=view.findViewById(R.id.fab);
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
|
|
||||||
fab.setOnClickListener(v -> Nav.go(getActivity(), ComposeFragment.class, args));
|
|
||||||
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, args));
|
|
||||||
if (getArguments().getBoolean("hide_fab", false)) fab.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
|
||||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void addAccountToKnown(ScheduledStatus s) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(String id) {
|
|
||||||
final Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
ScheduledStatus scheduledStatus = getStatusByID(id);
|
|
||||||
Status status = scheduledStatus.toStatus();
|
|
||||||
args.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
|
|
||||||
args.putParcelable("editStatus", Parcels.wrap(status));
|
|
||||||
args.putString("sourceText", status.text);
|
|
||||||
args.putString("sourceSpoiler", status.spoilerText);
|
|
||||||
args.putBoolean("redraftStatus", true);
|
|
||||||
setResult(true, null);
|
|
||||||
|
|
||||||
// closing this scheduled status list if another status list is opened from compose fragment
|
|
||||||
Nav.goForResult(getActivity(), ComposeFragment.class, args, SCHEDULED_STATUS_LIST_OPENED, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFragmentResult(int reqCode, boolean success, Bundle result) {
|
|
||||||
if (reqCode == SCHEDULED_STATUS_LIST_OPENED && success && getActivity() != null) {
|
|
||||||
Nav.finish(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(HeaderPaginationList<ScheduledStatus> result){
|
|
||||||
if(result.nextPageUri!=null)
|
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
|
||||||
else
|
|
||||||
nextMaxID=null;
|
|
||||||
onDataLoaded(result, nextMaxID!=null);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// copied from StatusListFragment.java
|
|
||||||
@Subscribe
|
|
||||||
public void onScheduledStatusDeleted(ScheduledStatusDeletedEvent ev){
|
|
||||||
if(!ev.accountID.equals(accountID)) return;
|
|
||||||
ScheduledStatus status=getStatusByID(ev.id);
|
|
||||||
if(status==null) return;
|
|
||||||
removeStatus(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
// copied from StatusListFragment.java
|
|
||||||
@Subscribe
|
|
||||||
public void onScheduledStatusCreated(ScheduledStatusCreatedEvent ev){
|
|
||||||
if(!ev.accountID.equals(accountID)) return;
|
|
||||||
prependItems(Collections.singletonList(ev.scheduledStatus), true);
|
|
||||||
scrollToTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// copied from StatusListFragment.java
|
|
||||||
protected void removeStatus(ScheduledStatus status){
|
|
||||||
data.remove(status);
|
|
||||||
preloadedData.remove(status);
|
|
||||||
int index=-1;
|
|
||||||
for(int i=0;i<displayItems.size();i++){
|
|
||||||
if(status.id.equals(displayItems.get(i).parentID)){
|
|
||||||
index=i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(index==-1)
|
|
||||||
return;
|
|
||||||
int lastIndex;
|
|
||||||
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
|
|
||||||
if(!displayItems.get(lastIndex).parentID.equals(status.id))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
displayItems.subList(index, lastIndex).clear();
|
|
||||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// copied from StatusListFragment.java
|
|
||||||
protected ScheduledStatus getStatusByID(String id){
|
|
||||||
for(ScheduledStatus s:data){
|
|
||||||
if(s.id.equals(id)){
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(ScheduledStatus s:preloadedData){
|
|
||||||
if(s.id.equals(id)){
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,19 +9,18 @@ import android.graphics.Canvas;
|
|||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.TypedValue;
|
import android.provider.Settings;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.AlphaAnimation;
|
||||||
import android.view.animation.LinearInterpolator;
|
import android.view.animation.LinearInterpolator;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
@@ -34,7 +33,6 @@ import com.squareup.otto.Subscribe;
|
|||||||
import org.joinmastodon.android.BuildConfig;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
@@ -44,16 +42,12 @@ import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
|
|||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
import org.joinmastodon.android.model.PushSubscription;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
|
|
||||||
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
import org.joinmastodon.android.updater.GithubSelfUpdater;
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -63,8 +57,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.imageloader.ImageCache;
|
import me.grishka.appkit.imageloader.ImageCache;
|
||||||
@@ -79,12 +71,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
private NotificationPolicyItem notificationPolicyItem;
|
private NotificationPolicyItem notificationPolicyItem;
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private boolean needUpdateNotificationSettings;
|
private boolean needUpdateNotificationSettings;
|
||||||
private boolean needAppRestart;
|
|
||||||
private PushSubscription pushSubscription;
|
private PushSubscription pushSubscription;
|
||||||
|
|
||||||
private ImageView themeTransitionWindowView;
|
private ImageView themeTransitionWindowView;
|
||||||
private TextItem checkForUpdateItem, clearImageCacheItem;
|
private TextItem checkForUpdateItem;
|
||||||
private ImageCache imageCache;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -92,11 +82,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
setTitle(R.string.settings);
|
setTitle(R.string.settings);
|
||||||
imageCache = ImageCache.getInstance(getActivity());
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
|
||||||
String instanceName = UiUtils.getInstanceName(accountID);
|
|
||||||
|
|
||||||
if(GithubSelfUpdater.needSelfUpdating()){
|
if(GithubSelfUpdater.needSelfUpdating()){
|
||||||
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
GithubSelfUpdater updater=GithubSelfUpdater.getInstance();
|
||||||
@@ -109,66 +96,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
items.add(new HeaderItem(R.string.settings_theme));
|
items.add(new HeaderItem(R.string.settings_theme));
|
||||||
items.add(themeItem=new ThemeItem());
|
items.add(themeItem=new ThemeItem());
|
||||||
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
items.add(new SwitchItem(R.string.theme_true_black, R.drawable.ic_fluent_dark_theme_24_regular, GlobalUserPreferences.trueBlackTheme, this::onTrueBlackThemeChanged));
|
||||||
items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
|
items.add(new SwitchItem(R.string.disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
|
||||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
|
||||||
popupMenu.inflate(R.menu.color_palettes);
|
|
||||||
popupMenu.getMenu().findItem(R.id.m3_color).setVisible(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S);
|
|
||||||
popupMenu.setOnMenuItemClickListener(SettingsFragment.this::onColorPreferenceClick);
|
|
||||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
|
||||||
b.setOnClickListener(v->popupMenu.show());
|
|
||||||
b.setText(switch(GlobalUserPreferences.color){
|
|
||||||
case MATERIAL3 -> R.string.sk_color_palette_material3;
|
|
||||||
case PINK -> R.string.sk_color_palette_pink;
|
|
||||||
case PURPLE -> R.string.sk_color_palette_purple;
|
|
||||||
case GREEN -> R.string.sk_color_palette_green;
|
|
||||||
case BLUE -> R.string.sk_color_palette_blue;
|
|
||||||
case BROWN -> R.string.sk_color_palette_brown;
|
|
||||||
case RED -> R.string.sk_color_palette_red;
|
|
||||||
case YELLOW -> R.string.sk_color_palette_yellow;
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b->{
|
|
||||||
updatePublishText(b);
|
|
||||||
|
|
||||||
b.setOnClickListener(l->{
|
|
||||||
TextInputFrameLayout input = new TextInputFrameLayout(
|
|
||||||
getContext(),
|
|
||||||
getString(R.string.publish),
|
|
||||||
GlobalUserPreferences.publishButtonText.trim()
|
|
||||||
);
|
|
||||||
new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(input)
|
|
||||||
.setPositiveButton(R.string.save, (d, which) -> {
|
|
||||||
GlobalUserPreferences.publishButtonText = input.getEditText().getText().toString().trim();
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
updatePublishText(b);
|
|
||||||
})
|
|
||||||
.setNeutralButton(R.string.clear, (d, which) -> {
|
|
||||||
GlobalUserPreferences.publishButtonText = "";
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
updatePublishText(b);
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel, (d, which) -> {})
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.sk_settings_uniform_icon_for_notifications, R.drawable.ic_ntf_logo, GlobalUserPreferences.uniformNotificationIcon, i->{
|
|
||||||
GlobalUserPreferences.uniformNotificationIcon=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.sk_disable_marquee, R.drawable.ic_fluent_text_more_24_regular, GlobalUserPreferences.disableMarquee, i->{
|
|
||||||
GlobalUserPreferences.disableMarquee=i.checked;
|
GlobalUserPreferences.disableMarquee=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_reduce_motion, R.drawable.ic_fluent_star_emphasis_24_regular, GlobalUserPreferences.reduceMotion, i->{
|
items.add(new ColorPicker());
|
||||||
GlobalUserPreferences.reduceMotion=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_alt_indicator, R.drawable.ic_fluent_scan_text_24_regular, GlobalUserPreferences.showAltIndicator, i->{
|
|
||||||
GlobalUserPreferences.showAltIndicator=i.checked;
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_no_alt_indicator, R.drawable.ic_fluent_important_24_regular, GlobalUserPreferences.showNoAltIndicator, i->{
|
|
||||||
GlobalUserPreferences.showNoAltIndicator=i.checked;
|
|
||||||
}));
|
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_behavior));
|
items.add(new HeaderItem(R.string.settings_behavior));
|
||||||
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
|
||||||
@@ -179,52 +111,25 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
GlobalUserPreferences.useCustomTabs=i.checked;
|
GlobalUserPreferences.useCustomTabs=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
|
items.add(new SwitchItem(R.string.settings_show_interaction_counts, R.drawable.ic_fluent_number_row_24_regular, GlobalUserPreferences.showInteractionCounts, i->{
|
||||||
GlobalUserPreferences.showInteractionCounts=i.checked;
|
GlobalUserPreferences.showInteractionCounts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
items.add(new SwitchItem(R.string.settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
||||||
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_tabs_disable_swipe, R.drawable.ic_fluent_swipe_right_24_regular, GlobalUserPreferences.disableSwipe, i->{
|
|
||||||
GlobalUserPreferences.disableSwipe=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
needAppRestart=true;
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
|
|
||||||
GlobalUserPreferences.enableDeleteNotifications=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
needAppRestart=true;
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.sk_settings_disable_alt_text_reminder, R.drawable.ic_fluent_image_alt_text_24_regular, GlobalUserPreferences.disableAltTextReminder, i->{
|
|
||||||
GlobalUserPreferences.disableAltTextReminder=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
|
|
||||||
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
}));
|
|
||||||
items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
|
|
||||||
GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
needAppRestart=true;
|
|
||||||
}));
|
|
||||||
boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled;
|
|
||||||
items.add(new SmallTextItem(getString(translationAvailable ?
|
|
||||||
R.string.sk_settings_translation_availability_note_available :
|
|
||||||
R.string.sk_settings_translation_availability_note_unavailable, instanceName)));
|
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.home_timeline));
|
items.add(new HeaderItem(R.string.home_timeline));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
items.add(new SwitchItem(R.string.settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
|
||||||
GlobalUserPreferences.showReplies=i.checked;
|
GlobalUserPreferences.showReplies=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
|
items.add(new SwitchItem(R.string.settings_show_boosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, GlobalUserPreferences.showBoosts, i->{
|
||||||
GlobalUserPreferences.showBoosts=i.checked;
|
GlobalUserPreferences.showBoosts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
items.add(new SwitchItem(R.string.settings_load_new_posts, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.loadNewPosts, i->{
|
||||||
GlobalUserPreferences.loadNewPosts=i.checked;
|
GlobalUserPreferences.loadNewPosts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
@@ -235,51 +140,23 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i->onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked)));
|
items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i->onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked)));
|
||||||
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i->onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked)));
|
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i->onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked)));
|
||||||
items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i->onNotificationsChanged(PushNotification.Type.REBLOG, i.checked)));
|
items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i->onNotificationsChanged(PushNotification.Type.REBLOG, i.checked)));
|
||||||
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked)));
|
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_at_symbol, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked)));
|
||||||
items.add(new SwitchItem(R.string.sk_notify_update, R.drawable.ic_fluent_history_24_regular, pushSubscription.alerts.update, i->onNotificationsChanged(PushNotification.Type.UPDATE, i.checked)));
|
|
||||||
items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_alert_24_regular, pushSubscription.alerts.status, i->onNotificationsChanged(PushNotification.Type.STATUS, i.checked)));
|
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.settings_account));
|
items.add(new HeaderItem(R.string.settings_boring));
|
||||||
items.add(new TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.settings_account, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit")));
|
||||||
items.add(new TextItem(R.string.sk_settings_posting, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/preferences/other"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
||||||
items.add(new TextItem(R.string.sk_settings_filters, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/filters"), R.drawable.ic_fluent_open_24_regular));
|
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")));
|
||||||
items.add(new TextItem(R.string.sk_settings_auth, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/auth/edit"), R.drawable.ic_fluent_open_24_regular));
|
|
||||||
|
|
||||||
items.add(new HeaderItem(instanceName));
|
items.add(new RedHeaderItem(R.string.settings_spicy));
|
||||||
items.add(new TextItem(R.string.sk_settings_rules, ()->{
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putParcelable("instance", Parcels.wrap(instance));
|
|
||||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
|
||||||
}, R.drawable.ic_fluent_task_list_ltr_24_regular));
|
|
||||||
items.add(new TextItem(R.string.sk_settings_about_instance , ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/about"), R.drawable.ic_fluent_info_24_regular));
|
|
||||||
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
|
||||||
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
|
|
||||||
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
|
|
||||||
|
|
||||||
items.add(new HeaderItem(R.string.sk_settings_about));
|
|
||||||
items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon"), R.drawable.ic_fluent_open_24_regular));
|
|
||||||
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://ko-fi.com/xsk22"), R.drawable.ic_fluent_heart_24_regular));
|
|
||||||
clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), imageCache.getDiskCache().size(), true), this::clearImageCache, 0);
|
|
||||||
items.add(clearImageCacheItem);
|
|
||||||
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
|
|
||||||
GlobalUserPreferences.recentLanguages.remove(accountID);
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
})));
|
|
||||||
if (GithubSelfUpdater.needSelfUpdating()) {
|
if (GithubSelfUpdater.needSelfUpdating()) {
|
||||||
items.add(new SwitchItem(R.string.sk_updater_enable_pre_releases, 0, GlobalUserPreferences.enablePreReleases, i->{
|
checkForUpdateItem = new TextItem(R.string.check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
||||||
GlobalUserPreferences.enablePreReleases=i.checked;
|
|
||||||
GlobalUserPreferences.save();
|
|
||||||
}));
|
|
||||||
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
|
|
||||||
items.add(checkForUpdateItem);
|
items.add(checkForUpdateItem);
|
||||||
}
|
}
|
||||||
|
items.add(new TextItem(R.string.settings_contribute_fork, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon")));
|
||||||
|
items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
|
||||||
|
items.add(new TextItem(R.string.log_out, this::confirmLogOut));
|
||||||
|
|
||||||
items.add(new FooterItem(getString(R.string.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
items.add(new FooterItem(getString(R.string.settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
|
||||||
}
|
|
||||||
|
|
||||||
private void updatePublishText(Button btn) {
|
|
||||||
if (GlobalUserPreferences.publishButtonText.isBlank()) btn.setText(R.string.publish);
|
|
||||||
else btn.setText(GlobalUserPreferences.publishButtonText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -327,7 +204,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
|
||||||
}
|
}
|
||||||
if(needAppRestart) UiUtils.restartApp();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -350,25 +226,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
restartActivityToApplyNewTheme();
|
restartActivityToApplyNewTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onColorPreferenceClick(MenuItem item){
|
private void onColorPreferenceClick(GlobalUserPreferences.ColorPreference color){
|
||||||
ColorPreference pref = null;
|
GlobalUserPreferences.color=color;
|
||||||
int id = item.getItemId();
|
|
||||||
|
|
||||||
if (id == R.id.m3_color) pref = ColorPreference.MATERIAL3;
|
|
||||||
else if (id == R.id.pink_color) pref = ColorPreference.PINK;
|
|
||||||
else if (id == R.id.purple_color) pref = ColorPreference.PURPLE;
|
|
||||||
else if (id == R.id.green_color) pref = ColorPreference.GREEN;
|
|
||||||
else if (id == R.id.blue_color) pref = ColorPreference.BLUE;
|
|
||||||
else if (id == R.id.brown_color) pref = ColorPreference.BROWN;
|
|
||||||
else if (id == R.id.red_color) pref = ColorPreference.RED;
|
|
||||||
else if (id == R.id.yellow_color) pref = ColorPreference.YELLOW;
|
|
||||||
|
|
||||||
if (pref == null) return false;
|
|
||||||
|
|
||||||
GlobalUserPreferences.color=pref;
|
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
restartActivityToApplyNewTheme();
|
restartActivityToApplyNewTheme();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onTrueBlackThemeChanged(SwitchItem item){
|
private void onTrueBlackThemeChanged(SwitchItem item){
|
||||||
@@ -430,8 +291,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
case FOLLOW -> subscription.alerts.follow=enabled;
|
case FOLLOW -> subscription.alerts.follow=enabled;
|
||||||
case REBLOG -> subscription.alerts.reblog=enabled;
|
case REBLOG -> subscription.alerts.reblog=enabled;
|
||||||
case MENTION -> subscription.alerts.mention=subscription.alerts.poll=enabled;
|
case MENTION -> subscription.alerts.mention=subscription.alerts.poll=enabled;
|
||||||
case STATUS -> subscription.alerts.status=enabled;
|
|
||||||
case UPDATE -> subscription.alerts.update=enabled;
|
|
||||||
}
|
}
|
||||||
needUpdateNotificationSettings=true;
|
needUpdateNotificationSettings=true;
|
||||||
}
|
}
|
||||||
@@ -501,13 +360,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
private void clearImageCache(){
|
private void clearImageCache(){
|
||||||
MastodonAPIController.runInBackground(()->{
|
MastodonAPIController.runInBackground(()->{
|
||||||
Activity activity=getActivity();
|
Activity activity=getActivity();
|
||||||
imageCache.clear();
|
ImageCache.getInstance(getActivity()).clear();
|
||||||
Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
|
Toast.makeText(activity, R.string.media_cache_cleared, Toast.LENGTH_SHORT).show();
|
||||||
});
|
});
|
||||||
if (list.findViewHolderForAdapterPosition(items.indexOf(clearImageCacheItem)) instanceof TextViewHolder tvh) {
|
|
||||||
clearImageCacheItem.secondaryText = UiUtils.formatFileSize(getContext(), 0, true);
|
|
||||||
tvh.rebind();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
@@ -530,7 +385,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ev.state == GithubSelfUpdater.UpdateState.NO_UPDATE) {
|
if (ev.state == GithubSelfUpdater.UpdateState.NO_UPDATE) {
|
||||||
Toast.makeText(getActivity(), R.string.sk_no_update_available, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), R.string.no_update_available, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,10 +400,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
this.text=getString(text);
|
this.text=getString(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HeaderItem(String text) {
|
|
||||||
this.text=text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
return 0;
|
return 0;
|
||||||
@@ -583,17 +434,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ButtonItem extends Item{
|
public class ColorPicker extends Item{
|
||||||
private int text;
|
|
||||||
private int icon;
|
|
||||||
private Consumer<Button> buttonConsumer;
|
|
||||||
|
|
||||||
public ButtonItem(@StringRes int text, @DrawableRes int icon, Consumer<Button> buttonConsumer) {
|
|
||||||
this.text = text;
|
|
||||||
this.icon = icon;
|
|
||||||
this.buttonConsumer = buttonConsumer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getViewType(){
|
public int getViewType(){
|
||||||
return 8;
|
return 8;
|
||||||
@@ -616,44 +457,19 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SmallTextItem extends Item {
|
|
||||||
private String text;
|
|
||||||
|
|
||||||
public SmallTextItem(String text) {
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getViewType() {
|
|
||||||
return 9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TextItem extends Item{
|
private class TextItem extends Item{
|
||||||
private String text;
|
private String text;
|
||||||
private String secondaryText;
|
|
||||||
private Runnable onClick;
|
private Runnable onClick;
|
||||||
private boolean loading;
|
private boolean loading;
|
||||||
private int icon;
|
|
||||||
|
|
||||||
public TextItem(@StringRes int text, Runnable onClick) {
|
public TextItem(@StringRes int text, Runnable onClick) {
|
||||||
this(text, null, onClick, false, 0);
|
this(text, onClick, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextItem(@StringRes int text, Runnable onClick, @DrawableRes int icon) {
|
public TextItem(@StringRes int text, Runnable onClick, boolean loading){
|
||||||
this(text, null, onClick, false, icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, @DrawableRes int icon) {
|
|
||||||
this(text, secondaryText, onClick, false, icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextItem(@StringRes int text, String secondaryText, Runnable onClick, boolean loading, @DrawableRes int icon){
|
|
||||||
this.text=getString(text);
|
this.text=getString(text);
|
||||||
this.onClick=onClick;
|
this.onClick=onClick;
|
||||||
this.loading=loading;
|
this.loading=loading;
|
||||||
this.icon=icon;
|
|
||||||
this.secondaryText = secondaryText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -709,8 +525,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
case 5 -> new HeaderViewHolder(true);
|
case 5 -> new HeaderViewHolder(true);
|
||||||
case 6 -> new FooterViewHolder();
|
case 6 -> new FooterViewHolder();
|
||||||
case 7 -> new UpdateViewHolder();
|
case 7 -> new UpdateViewHolder();
|
||||||
case 8 -> new ButtonViewHolder();
|
case 8 -> new ColorPickerViewHolder();
|
||||||
case 9 -> new SmallTextViewHolder();
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
default -> throw new IllegalStateException("Unexpected value: "+viewType);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -761,12 +576,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onBind(SwitchItem item){
|
public void onBind(SwitchItem item){
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
if (item.icon == 0) {
|
icon.setImageResource(item.icon);
|
||||||
icon.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
icon.setVisibility(View.VISIBLE);
|
|
||||||
icon.setImageResource(item.icon);
|
|
||||||
}
|
|
||||||
checkbox.setChecked(item.checked && item.enabled);
|
checkbox.setChecked(item.checked && item.enabled);
|
||||||
checkbox.setEnabled(item.enabled);
|
checkbox.setEnabled(item.enabled);
|
||||||
}
|
}
|
||||||
@@ -844,24 +654,65 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private class ButtonViewHolder extends BindableViewHolder<ButtonItem>{
|
private class ColorPickerViewHolder extends BindableViewHolder<ColorPicker>{
|
||||||
private final Button button;
|
private final Button button;
|
||||||
|
private final PopupMenu popupMenu;
|
||||||
private final ImageView icon;
|
private final ImageView icon;
|
||||||
private final TextView text;
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
public ButtonViewHolder(){
|
public ColorPickerViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_button, list);
|
super(getActivity(), R.layout.item_settings_color_picker, list);
|
||||||
text=findViewById(R.id.text);
|
|
||||||
icon=findViewById(R.id.icon);
|
icon=findViewById(R.id.icon);
|
||||||
button=findViewById(R.id.button);
|
button=findViewById(R.id.color_picker_button);
|
||||||
|
popupMenu=new PopupMenu(getActivity(), button, Gravity.CENTER_HORIZONTAL);
|
||||||
|
popupMenu.inflate(R.menu.color_picker);
|
||||||
|
popupMenu.setOnMenuItemClickListener(item->{
|
||||||
|
GlobalUserPreferences.ColorPreference pref;
|
||||||
|
int id=item.getItemId();
|
||||||
|
if(id==R.id.pink_color) {
|
||||||
|
pref = GlobalUserPreferences.ColorPreference.PINK;
|
||||||
|
onColorPreferenceClick(pref);
|
||||||
|
}
|
||||||
|
else if(id==R.id.purple_color) {
|
||||||
|
pref = GlobalUserPreferences.ColorPreference.PURPLE;
|
||||||
|
onColorPreferenceClick(pref);
|
||||||
|
}
|
||||||
|
else if(id==R.id.green_color) {
|
||||||
|
pref = GlobalUserPreferences.ColorPreference.GREEN;
|
||||||
|
onColorPreferenceClick(pref);
|
||||||
|
}
|
||||||
|
else if(id==R.id.blue_color) {
|
||||||
|
pref = GlobalUserPreferences.ColorPreference.BLUE;
|
||||||
|
onColorPreferenceClick(pref);
|
||||||
|
}
|
||||||
|
else if(id==R.id.orange_color) {
|
||||||
|
pref = GlobalUserPreferences.ColorPreference.ORANGE;
|
||||||
|
onColorPreferenceClick(pref);
|
||||||
|
}
|
||||||
|
else if(id==R.id.yellow_color) {
|
||||||
|
pref = GlobalUserPreferences.ColorPreference.YELLOW;
|
||||||
|
onColorPreferenceClick(pref);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
// UiUtils.enablePopupMenuIcons(getActivity(), popupMenu);
|
||||||
|
button.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||||
|
button.setOnClickListener(v->popupMenu.show());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(ButtonItem item){
|
public void onBind(ColorPicker item){
|
||||||
text.setText(item.text);
|
icon.setImageResource(R.drawable.ic_color_theme_preference);
|
||||||
icon.setImageResource(item.icon);
|
button.setText(switch(GlobalUserPreferences.color){
|
||||||
item.buttonConsumer.accept(button);
|
case PINK -> R.string.pink_color;
|
||||||
|
case PURPLE -> R.string.purple_color;
|
||||||
|
case GREEN -> R.string.green_color;
|
||||||
|
case BLUE -> R.string.blue_color;
|
||||||
|
case ORANGE -> R.string.orange_color;
|
||||||
|
case YELLOW -> R.string.yellow_color;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -909,27 +760,19 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
private class TextViewHolder extends BindableViewHolder<TextItem> implements UsableRecyclerView.Clickable{
|
||||||
private final TextView text, secondaryText;
|
private final TextView text;
|
||||||
private final ProgressBar progress;
|
private final ProgressBar progress;
|
||||||
private final ImageView icon;
|
|
||||||
|
|
||||||
public TextViewHolder(){
|
public TextViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_text, list);
|
super(getActivity(), R.layout.item_settings_text, list);
|
||||||
text = itemView.findViewById(R.id.text);
|
text = itemView.findViewById(R.id.text);
|
||||||
secondaryText = itemView.findViewById(R.id.secondary_text);
|
|
||||||
progress = itemView.findViewById(R.id.progress);
|
progress = itemView.findViewById(R.id.progress);
|
||||||
icon = itemView.findViewById(R.id.icon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(TextItem item){
|
public void onBind(TextItem item){
|
||||||
icon.setVisibility(item.icon != 0 ? View.VISIBLE : View.GONE);
|
|
||||||
secondaryText.setVisibility(item.secondaryText != null ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
text.setText(item.text);
|
text.setText(item.text);
|
||||||
progress.animate().alpha(item.loading ? 1 : 0);
|
progress.animate().alpha(item.loading ? 1 : 0);
|
||||||
icon.setImageResource(item.icon);
|
|
||||||
secondaryText.setText(item.secondaryText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -938,24 +781,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> {
|
|
||||||
private final TextView text;
|
|
||||||
;
|
|
||||||
|
|
||||||
public SmallTextViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_settings_text, list);
|
|
||||||
text = itemView.findViewById(R.id.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(SmallTextItem item){
|
|
||||||
text.setText(item.text);
|
|
||||||
text.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary));
|
|
||||||
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
|
||||||
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FooterViewHolder extends BindableViewHolder<FooterItem>{
|
private class FooterViewHolder extends BindableViewHolder<FooterItem>{
|
||||||
private final TextView text;
|
private final TextView text;
|
||||||
public FooterViewHolder(){
|
public FooterViewHolder(){
|
||||||
@@ -971,7 +796,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
|
|
||||||
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
private class UpdateViewHolder extends BindableViewHolder<UpdateItem>{
|
||||||
|
|
||||||
private final TextView text, changelog;
|
private final TextView text;
|
||||||
private final Button button;
|
private final Button button;
|
||||||
private final ImageButton cancelBtn;
|
private final ImageButton cancelBtn;
|
||||||
private final ProgressBar progress;
|
private final ProgressBar progress;
|
||||||
@@ -982,7 +807,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
public UpdateViewHolder(){
|
public UpdateViewHolder(){
|
||||||
super(getActivity(), R.layout.item_settings_update, list);
|
super(getActivity(), R.layout.item_settings_update, list);
|
||||||
text=findViewById(R.id.text);
|
text=findViewById(R.id.text);
|
||||||
changelog=findViewById(R.id.changelog);
|
|
||||||
button=findViewById(R.id.button);
|
button=findViewById(R.id.button);
|
||||||
cancelBtn=findViewById(R.id.cancel_btn);
|
cancelBtn=findViewById(R.id.cancel_btn);
|
||||||
progress=findViewById(R.id.progress);
|
progress=findViewById(R.id.progress);
|
||||||
@@ -1007,10 +831,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
|
||||||
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
|
||||||
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
|
||||||
text.setText(getString(R.string.sk_update_available, info.version));
|
text.setText(getString(R.string.update_available, info.version));
|
||||||
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
|
||||||
}else{
|
}else{
|
||||||
text.setText(getString(R.string.sk_update_ready, info.version));
|
text.setText(getString(R.string.update_ready, info.version));
|
||||||
button.setText(R.string.install_update);
|
button.setText(R.string.install_update);
|
||||||
}
|
}
|
||||||
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){
|
||||||
@@ -1026,7 +850,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||||||
progress.setVisibility(View.GONE);
|
progress.setVisibility(View.GONE);
|
||||||
progress.removeCallbacks(progressUpdater);
|
progress.removeCallbacks(progressUpdater);
|
||||||
}
|
}
|
||||||
changelog.setText(info.changelog);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateProgress(){
|
private void updateProgress(){
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.style.ReplacementSpan;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogSignupFragment;
|
import org.joinmastodon.android.fragments.onboarding.InstanceCatalogFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragment;
|
import org.joinmastodon.android.ui.InterpolatingMotionEffect;
|
||||||
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
import org.joinmastodon.android.ui.views.SizeListenerFrameLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
@@ -31,13 +23,12 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
|
|
||||||
private SizeListenerFrameLayout contentView;
|
private SizeListenerFrameLayout contentView;
|
||||||
private View artContainer, blueFill, greenFill;
|
private View artContainer, blueFill, greenFill;
|
||||||
private ViewPager2 pager;
|
private InterpolatingMotionEffect motionEffect;
|
||||||
private ViewGroup pagerDots;
|
|
||||||
private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -46,44 +37,15 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false);
|
||||||
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick);
|
||||||
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick);
|
||||||
artClouds=contentView.findViewById(R.id.art_clouds);
|
|
||||||
artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant);
|
|
||||||
artRightHill=contentView.findViewById(R.id.art_right_hill);
|
|
||||||
artLeftHill=contentView.findViewById(R.id.art_left_hill);
|
|
||||||
artCenterHill=contentView.findViewById(R.id.art_center_hill);
|
|
||||||
pager=contentView.findViewById(R.id.pager);
|
|
||||||
pagerDots=contentView.findViewById(R.id.pager_dots);
|
|
||||||
pager.setAdapter(new PagerAdapter());
|
|
||||||
pager.setOffscreenPageLimit(3);
|
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
|
||||||
@Override
|
|
||||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
|
|
||||||
for(int i=0;i<pagerDots.getChildCount();i++){
|
|
||||||
float alpha;
|
|
||||||
if(i==position){
|
|
||||||
alpha=0.3f+0.7f*(1f-positionOffset);
|
|
||||||
}else if(i==position+1){
|
|
||||||
alpha=0.3f+0.7f*positionOffset;
|
|
||||||
}else{
|
|
||||||
alpha=0.3f;
|
|
||||||
}
|
|
||||||
pagerDots.getChildAt(i).setAlpha(alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
float parallaxProgress=(position+positionOffset)/2f;
|
|
||||||
artClouds.setTranslationX(V.dp(-27)*(position>=1 ? 1f : positionOffset));
|
|
||||||
artPlaneElephant.setTranslationX(V.dp(101.55f)*parallaxProgress);
|
|
||||||
artLeftHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
|
||||||
artLeftHill.setTranslationY(V.dp(24)*parallaxProgress);
|
|
||||||
artRightHill.setTranslationX(V.dp(-88)*parallaxProgress);
|
|
||||||
artRightHill.setTranslationY(V.dp(-24)*parallaxProgress);
|
|
||||||
artCenterHill.setTranslationX(V.dp(-40)*parallaxProgress);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
artContainer=contentView.findViewById(R.id.art_container);
|
artContainer=contentView.findViewById(R.id.art_container);
|
||||||
blueFill=contentView.findViewById(R.id.blue_fill);
|
blueFill=contentView.findViewById(R.id.blue_fill);
|
||||||
greenFill=contentView.findViewById(R.id.green_fill);
|
greenFill=contentView.findViewById(R.id.green_fill);
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_clouds), V.dp(-5), V.dp(5), V.dp(-5), V.dp(5)));
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_right_hill), V.dp(-15), V.dp(25), V.dp(-10), V.dp(10)));
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_left_hill), V.dp(-25), V.dp(15), V.dp(-15), V.dp(15)));
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_center_hill), V.dp(-14), V.dp(14), V.dp(-5), V.dp(25)));
|
||||||
|
motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_plane_elephant), V.dp(-20), V.dp(12), V.dp(-20), V.dp(12)));
|
||||||
|
|
||||||
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
|
contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){
|
||||||
@Override
|
@Override
|
||||||
@@ -104,16 +66,15 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
|
|
||||||
private void onButtonClick(View v){
|
private void onButtonClick(View v){
|
||||||
Bundle extras=new Bundle();
|
Bundle extras=new Bundle();
|
||||||
boolean isSignup=v.getId()==R.id.btn_get_started;
|
extras.putBoolean("signup", v.getId()==R.id.btn_get_started);
|
||||||
extras.putBoolean("signup", isSignup);
|
Nav.go(getActivity(), InstanceCatalogFragment.class, extras);
|
||||||
Nav.go(getActivity(), isSignup ? InstanceCatalogSignupFragment.class : InstanceChooserLoginFragment.class, extras);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateArtSize(int w, int h){
|
private void updateArtSize(int w, int h){
|
||||||
float scale=w/(float)V.dp(360);
|
float scale=w/(float)V.dp(412);
|
||||||
artContainer.setScaleX(scale);
|
artContainer.setScaleX(scale);
|
||||||
artContainer.setScaleY(scale);
|
artContainer.setScaleY(scale);
|
||||||
blueFill.setScaleY(artContainer.getBottom()-V.dp(90));
|
blueFill.setScaleY(h/2f);
|
||||||
greenFill.setScaleY(h-artContainer.getBottom()+V.dp(90));
|
greenFill.setScaleY(h-artContainer.getBottom()+V.dp(90));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,91 +100,15 @@ public class SplashFragment extends AppKitFragment{
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PagerAdapter extends RecyclerView.Adapter<PagerViewHolder>{
|
@Override
|
||||||
|
protected void onShown(){
|
||||||
@NonNull
|
super.onShown();
|
||||||
@Override
|
motionEffect.activate();
|
||||||
public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new PagerViewHolder(viewType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull PagerViewHolder holder, int position){}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount(){
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position){
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PagerViewHolder extends RecyclerView.ViewHolder{
|
@Override
|
||||||
public PagerViewHolder(int page){
|
protected void onHidden(){
|
||||||
super(new LinearLayout(getActivity()));
|
super.onHidden();
|
||||||
LinearLayout ll=(LinearLayout) itemView;
|
motionEffect.deactivate();
|
||||||
ll.setOrientation(LinearLayout.VERTICAL);
|
|
||||||
int pad=V.dp(16);
|
|
||||||
ll.setPadding(pad, pad, pad, pad);
|
|
||||||
ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
||||||
|
|
||||||
TextView title=new TextView(getActivity());
|
|
||||||
title.setTextAppearance(R.style.m3_headline_medium);
|
|
||||||
title.setText(switch(page){
|
|
||||||
case 0 -> {
|
|
||||||
String src=getString(R.string.welcome_page1_title);
|
|
||||||
SpannableString ss=new SpannableString(src);
|
|
||||||
int start=src.indexOf("{logo}");
|
|
||||||
if(start!=-1){
|
|
||||||
LogoSpan span=new LogoSpan(getResources().getDrawable(R.drawable.splash_logo, getActivity().getTheme()));
|
|
||||||
ss.setSpan(span, start, start+6, 0);
|
|
||||||
}
|
|
||||||
yield ss;
|
|
||||||
}
|
|
||||||
case 1 -> getString(R.string.welcome_page2_title);
|
|
||||||
case 2 -> getString(R.string.welcome_page3_title);
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
|
||||||
});
|
|
||||||
title.setTextColor(0xFF17063B);
|
|
||||||
LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(page==0 ? 46 : 36));
|
|
||||||
lp.bottomMargin=V.dp(page==0 ? 4 : 14);
|
|
||||||
ll.addView(title, lp);
|
|
||||||
|
|
||||||
TextView text=new TextView(getActivity());
|
|
||||||
text.setTextAppearance(R.style.m3_body_medium);
|
|
||||||
text.setText(switch(page){
|
|
||||||
case 0 -> R.string.welcome_page1_text;
|
|
||||||
case 1 -> R.string.welcome_page2_text;
|
|
||||||
case 2 -> R.string.welcome_page3_text;
|
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
|
||||||
});
|
|
||||||
text.setTextColor(0xFF17063B);
|
|
||||||
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LogoSpan extends ReplacementSpan{
|
|
||||||
private final Drawable drawable;
|
|
||||||
|
|
||||||
private LogoSpan(Drawable drawable){
|
|
||||||
this.drawable=drawable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
|
|
||||||
return drawable.getIntrinsicWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){
|
|
||||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
|
||||||
canvas.save();
|
|
||||||
canvas.translate(x, y-V.dp(20));
|
|
||||||
drawable.draw(canvas);
|
|
||||||
canvas.restore();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false);
|
||||||
int idx=data.indexOf(s);
|
int idx=data.indexOf(s);
|
||||||
if(idx>=0){
|
if(idx>=0){
|
||||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||||
@@ -139,7 +139,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||||||
action=getString(R.string.edit_multiple_changed);
|
action=getString(R.string.edit_multiple_changed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null, null));
|
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null));
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
package org.joinmastodon.android.fragments;
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
@@ -30,7 +29,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
protected EventListener eventListener=new EventListener();
|
protected EventListener eventListener=new EventListener();
|
||||||
|
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true, null);
|
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -172,19 +171,13 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
adapter.notifyItemRangeRemoved(index, lastIndex-index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(Configuration newConfig){
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
if (getParentFragment() instanceof HomeTabFragment home) home.updateToolbarLogo();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class EventListener{
|
public class EventListener{
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
|
||||||
for(Status s:data){
|
for(Status s:data){
|
||||||
if(s.getContentStatus().id.equals(ev.id)){
|
if(s.getContentStatus().id.equals(ev.id)){
|
||||||
s.getContentStatus().update(ev);
|
s.update(ev);
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
for(int i=0;i<list.getChildCount();i++){
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
||||||
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==s.getContentStatus()){
|
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==s.getContentStatus()){
|
||||||
@@ -196,8 +189,8 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(Status s:preloadedData){
|
for(Status s:preloadedData){
|
||||||
if(s.getContentStatus().id.equals(ev.id)){
|
if(s.id.equals(ev.id)){
|
||||||
s.getContentStatus().update(ev);
|
s.update(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.view.View;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
@@ -16,7 +17,6 @@ import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
|||||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -92,10 +92,16 @@ public class ThreadFragment extends StatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<Status> filterStatuses(List<Status> statuses){
|
private List<Status> filterStatuses(List<Status> statuses){
|
||||||
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
|
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.THREAD)).collect(Collectors.toList());
|
||||||
return statuses.stream()
|
if(filters.isEmpty())
|
||||||
.filter(statusFilterPredicate)
|
return statuses;
|
||||||
.collect(Collectors.toList());
|
return statuses.stream().filter(status->{
|
||||||
|
for(Filter filter:filters){
|
||||||
|
if(filter.matches(status))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -126,14 +132,4 @@ public class ThreadFragment extends StatusListFragment{
|
|||||||
public boolean isItemEnabled(String id){
|
public boolean isItemEnabled(String id){
|
||||||
return !id.equals(mainStatus.id);
|
return !id.equals(mainStatus.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean wantsLightStatusBar(){
|
|
||||||
return !UiUtils.isDarkTheme();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean wantsLightNavigationBar(){
|
|
||||||
return !UiUtils.isDarkTheme();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -225,7 +224,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
contextMenu=new PopupMenu(getActivity(), menuAnchor);
|
contextMenu=new PopupMenu(getActivity(), menuAnchor);
|
||||||
contextMenu.inflate(R.menu.profile);
|
contextMenu.inflate(R.menu.profile);
|
||||||
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
|
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
|
||||||
UiUtils.enablePopupMenuIcons(getActivity(), contextMenu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -284,32 +282,25 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
Menu menu=contextMenu.getMenu();
|
Menu menu=contextMenu.getMenu();
|
||||||
Account account=item.account;
|
Account account=item.account;
|
||||||
|
|
||||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
|
||||||
|
menu.findItem(R.id.mute).setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
|
||||||
MenuItem mute = menu.findItem(R.id.mute);
|
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
|
||||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getDisplayUsername()));
|
||||||
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.lists_with_user, account.getDisplayUsername()));
|
||||||
UiUtils.insetPopupMenuIcon(getContext(), mute);
|
|
||||||
|
|
||||||
menu.findItem(R.id.block).setTitle(getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
|
|
||||||
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
|
|
||||||
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername())).setVisible(relationship.following);
|
|
||||||
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
|
|
||||||
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
|
||||||
MenuItem manageUserLists=menu.findItem(R.id.manage_user_lists);
|
|
||||||
if(relationship.following){
|
if(relationship.following){
|
||||||
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
|
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
|
||||||
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
|
|
||||||
hideBoosts.setVisible(true);
|
hideBoosts.setVisible(true);
|
||||||
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
|
|
||||||
|
|
||||||
manageUserLists.setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
|
|
||||||
manageUserLists.setVisible(true);
|
|
||||||
}else{
|
}else{
|
||||||
hideBoosts.setVisible(false);
|
hideBoosts.setVisible(false);
|
||||||
manageUserLists.setVisible(true);
|
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.block_domain).setVisible(false);
|
MenuItem blockDomain=menu.findItem(R.id.block_domain);
|
||||||
|
if(!account.isLocal()){
|
||||||
|
blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
|
||||||
|
blockDomain.setVisible(true);
|
||||||
|
}else{
|
||||||
|
blockDomain.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
menuAnchor.setTranslationX(x);
|
menuAnchor.setTranslationX(x);
|
||||||
menuAnchor.setTranslationY(y);
|
menuAnchor.setTranslationY(y);
|
||||||
@@ -350,8 +341,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
|
||||||
}else if(id==R.id.block){
|
}else if(id==R.id.block){
|
||||||
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
|
||||||
}else if(id==R.id.soft_block){
|
|
||||||
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
|
|
||||||
}else if(id==R.id.report){
|
}else if(id==R.id.report){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
@@ -380,12 +369,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
|
|||||||
})
|
})
|
||||||
.wrapProgress(getActivity(), R.string.loading, false)
|
.wrapProgress(getActivity(), R.string.loading, false)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}else if(id==R.id.manage_user_lists){
|
|
||||||
final Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putString("profileAccount", account.id);
|
|
||||||
args.putString("profileDisplayUsername", account.getDisplayUsername());
|
|
||||||
Nav.go(getActivity(), ListTimelinesFragment.class, args);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -49,7 +48,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop {
|
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Map<String, Relationship> relationships=Collections.emptyMap();
|
private Map<String, Relationship> relationships=Collections.emptyMap();
|
||||||
private GetAccountRelationships relationshipsRequest;
|
private GetAccountRelationships relationshipsRequest;
|
||||||
@@ -138,11 +137,6 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
|
|
||||||
public AccountsAdapter(){
|
public AccountsAdapter(){
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.joinmastodon.android.fragments.discover;
|
package org.joinmastodon.android.fragments.discover;
|
||||||
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
|
import android.app.FragmentTransaction;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
@@ -17,10 +18,10 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.BuildConfig;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
|
import org.joinmastodon.android.fragments.ListTimelinesFragment;
|
||||||
import org.joinmastodon.android.ui.SimpleViewHolder;
|
import org.joinmastodon.android.ui.SimpleViewHolder;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayout;
|
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||||
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
import org.joinmastodon.android.ui.tabs.TabLayoutMediator;
|
||||||
@@ -36,7 +37,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
import me.grishka.appkit.fragments.OnBackPressedListener;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener, IsOnTop {
|
public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, OnBackPressedListener{
|
||||||
|
|
||||||
private TabLayout tabLayout;
|
private TabLayout tabLayout;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
@@ -53,10 +54,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
private DiscoverNewsFragment newsFragment;
|
private DiscoverNewsFragment newsFragment;
|
||||||
private DiscoverAccountsFragment accountsFragment;
|
private DiscoverAccountsFragment accountsFragment;
|
||||||
private SearchFragment searchFragment;
|
private SearchFragment searchFragment;
|
||||||
|
private LocalTimelineFragment localTimelineFragment;
|
||||||
|
private FederatedTimelineFragment federatedTimelineFragment;
|
||||||
|
private ListTimelinesFragment listTimelinesFragment;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||||
|
|
||||||
|
private static final boolean noFederated = BuildConfig.BUILD_TYPE.equals("noFederatedRelease");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -74,15 +80,19 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
tabLayout=view.findViewById(R.id.tabbar);
|
tabLayout=view.findViewById(R.id.tabbar);
|
||||||
pager=view.findViewById(R.id.pager);
|
pager=view.findViewById(R.id.pager);
|
||||||
|
|
||||||
tabViews=new FrameLayout[4];
|
tabViews=new FrameLayout[noFederated ? 6 : 7];
|
||||||
for(int i=0;i<tabViews.length;i++){
|
for(int i=0;i<tabViews.length;i++){
|
||||||
FrameLayout tabView=new FrameLayout(getActivity());
|
FrameLayout tabView=new FrameLayout(getActivity());
|
||||||
tabView.setId(switch(i){
|
int switchIndex = noFederated && i > 0 ? i + 1 : i;
|
||||||
case 0 -> R.id.discover_hashtags;
|
tabView.setId(switch(switchIndex){
|
||||||
case 1 -> R.id.discover_posts;
|
case 0 -> R.id.discover_local_timeline;
|
||||||
case 2 -> R.id.discover_news;
|
case 1 -> R.id.discover_federated_timeline;
|
||||||
case 3 -> R.id.discover_users;
|
case 2 -> R.id.discover_hashtags;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+i);
|
case 3 -> R.id.discover_posts;
|
||||||
|
case 4 -> R.id.discover_news;
|
||||||
|
case 5 -> R.id.discover_users;
|
||||||
|
case 6 -> R.id.discover_lists;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: "+switchIndex);
|
||||||
});
|
});
|
||||||
tabView.setVisibility(View.GONE);
|
tabView.setVisibility(View.GONE);
|
||||||
view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment
|
view.addView(tabView); // needed so the fragment manager will have somewhere to restore the tab fragment
|
||||||
@@ -92,9 +102,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
tabLayout.setTabTextSize(V.dp(16));
|
tabLayout.setTabTextSize(V.dp(16));
|
||||||
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
|
||||||
|
|
||||||
UiUtils.reduceSwipeSensitivity(pager);
|
|
||||||
pager.setOffscreenPageLimit(4);
|
pager.setOffscreenPageLimit(4);
|
||||||
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
|
|
||||||
pager.setAdapter(new DiscoverPagerAdapter());
|
pager.setAdapter(new DiscoverPagerAdapter());
|
||||||
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
|
||||||
@Override
|
@Override
|
||||||
@@ -109,7 +117,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(hashtagsFragment==null){
|
if(localTimelineFragment==null){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putBoolean("__is_tab", true);
|
args.putBoolean("__is_tab", true);
|
||||||
@@ -126,23 +134,41 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
accountsFragment=new DiscoverAccountsFragment();
|
accountsFragment=new DiscoverAccountsFragment();
|
||||||
accountsFragment.setArguments(args);
|
accountsFragment.setArguments(args);
|
||||||
|
|
||||||
|
localTimelineFragment=new LocalTimelineFragment();
|
||||||
|
localTimelineFragment.setArguments(args);
|
||||||
|
|
||||||
getChildFragmentManager().beginTransaction()
|
listTimelinesFragment=new ListTimelinesFragment();
|
||||||
|
listTimelinesFragment.setArguments(args);
|
||||||
|
|
||||||
|
FragmentTransaction transaction = getChildFragmentManager().beginTransaction()
|
||||||
.add(R.id.discover_posts, postsFragment)
|
.add(R.id.discover_posts, postsFragment)
|
||||||
|
.add(R.id.discover_local_timeline, localTimelineFragment)
|
||||||
.add(R.id.discover_hashtags, hashtagsFragment)
|
.add(R.id.discover_hashtags, hashtagsFragment)
|
||||||
.add(R.id.discover_news, newsFragment)
|
.add(R.id.discover_news, newsFragment)
|
||||||
.add(R.id.discover_users, accountsFragment)
|
.add(R.id.discover_users, accountsFragment)
|
||||||
.commit();
|
.add(R.id.discover_lists, listTimelinesFragment);
|
||||||
|
|
||||||
|
if (!noFederated) {
|
||||||
|
federatedTimelineFragment=new FederatedTimelineFragment();
|
||||||
|
federatedTimelineFragment.setArguments(args);
|
||||||
|
transaction.add(R.id.discover_federated_timeline, federatedTimelineFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
tabLayoutMediator=new TabLayoutMediator(tabLayout, pager, new TabLayoutMediator.TabConfigurationStrategy(){
|
||||||
@Override
|
@Override
|
||||||
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
|
||||||
|
if (noFederated && position > 0) position++;
|
||||||
tab.setText(switch(position){
|
tab.setText(switch(position){
|
||||||
case 0 -> R.string.hashtags;
|
case 0 -> R.string.local_timeline;
|
||||||
case 1 -> R.string.posts;
|
case 1 -> R.string.federated_timeline;
|
||||||
case 2 -> R.string.news;
|
case 2 -> R.string.hashtags;
|
||||||
case 3 -> R.string.for_you;
|
case 3 -> R.string.posts;
|
||||||
|
case 4 -> R.string.news;
|
||||||
|
case 5 -> R.string.for_you;
|
||||||
|
case 6 -> R.string.list_timelines;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+position);
|
default -> throw new IllegalStateException("Unexpected value: "+position);
|
||||||
});
|
});
|
||||||
tab.view.textView.setAllCaps(true);
|
tab.view.textView.setAllCaps(true);
|
||||||
@@ -227,26 +253,9 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return searchActive ? searchFragment.isOnTop()
|
|
||||||
: ((IsOnTop)getFragmentForPage(pager.getCurrentItem())).isOnTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onSelect() {
|
|
||||||
if (isOnTop()) selectSearch();
|
|
||||||
else scrollToTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectSearch() {
|
|
||||||
searchEdit.requestFocus();
|
|
||||||
onSearchEditFocusChanged(searchEdit, true);
|
|
||||||
getActivity().getSystemService(InputMethodManager.class).showSoftInput(searchEdit, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadData(){
|
public void loadData(){
|
||||||
if(hashtagsFragment!=null && !hashtagsFragment.loaded && !hashtagsFragment.dataLoading)
|
if(localTimelineFragment!=null && !localTimelineFragment.loaded && !localTimelineFragment.dataLoading)
|
||||||
hashtagsFragment.loadData();
|
localTimelineFragment.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
private void onSearchEditFocusChanged(View v, boolean hasFocus){
|
||||||
@@ -281,11 +290,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Fragment getFragmentForPage(int page){
|
private Fragment getFragmentForPage(int page){
|
||||||
|
if (noFederated && page > 0) page++;
|
||||||
return switch(page){
|
return switch(page){
|
||||||
case 0 -> hashtagsFragment;
|
case 0 -> localTimelineFragment;
|
||||||
case 1 -> postsFragment;
|
case 1 -> federatedTimelineFragment;
|
||||||
case 2 -> newsFragment;
|
case 2 -> hashtagsFragment;
|
||||||
case 3 -> accountsFragment;
|
case 3 -> postsFragment;
|
||||||
|
case 4 -> newsFragment;
|
||||||
|
case 5 -> accountsFragment;
|
||||||
|
case 6 -> listTimelinesFragment;
|
||||||
default -> throw new IllegalStateException("Unexpected value: "+page);
|
default -> throw new IllegalStateException("Unexpected value: "+page);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Card;
|
import org.joinmastodon.android.model.Card;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
@@ -35,7 +34,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop {
|
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
|
||||||
@@ -82,11 +81,6 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
|
||||||
public LinksAdapter(){
|
public LinksAdapter(){
|
||||||
super(imgLoader);
|
super(imgLoader);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.os.Bundle;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
|
||||||
@@ -13,16 +12,16 @@ import java.util.List;
|
|||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop {
|
public class DiscoverPostsFragment extends StatusListFragment{
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetTrendingStatuses(offset, count)
|
currentRequest=new GetTrendingStatuses(count)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
onDataLoaded(result, !result.isEmpty());
|
onDataLoaded(result, false);
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
}
|
}
|
||||||
@@ -32,9 +31,4 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
bannerHelper.maybeAddBanner(contentWrap);
|
bannerHelper.maybeAddBanner(contentWrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package org.joinmastodon.android.fragments.discover;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
import org.joinmastodon.android.fragments.FabStatusListFragment;
|
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
@@ -17,7 +15,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class FederatedTimelineFragment extends FabStatusListFragment {
|
public class FederatedTimelineFragment extends StatusListFragment{
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package org.joinmastodon.android.fragments.discover;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
||||||
import org.joinmastodon.android.fragments.FabStatusListFragment;
|
|
||||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
@@ -17,7 +15,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
public class LocalTimelineFragment extends FabStatusListFragment {
|
public class LocalTimelineFragment extends StatusListFragment{
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
|
||||||
private String maxID;
|
private String maxID;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
@@ -38,10 +37,11 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class SearchFragment extends BaseStatusListFragment<SearchResult> implements IsOnTop {
|
public class SearchFragment extends BaseStatusListFragment<SearchResult>{
|
||||||
private String currentQuery;
|
private String currentQuery;
|
||||||
private List<StatusDisplayItem> prevDisplayItems;
|
private List<StatusDisplayItem> prevDisplayItems;
|
||||||
private EnumSet<SearchResult.Type> currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
private EnumSet<SearchResult.Type> currentFilter=EnumSet.allOf(SearchResult.Type.class);
|
||||||
@@ -62,7 +62,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
loadData();
|
loadData();
|
||||||
resetEmptyText();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,16 +70,12 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
imm=activity.getSystemService(InputMethodManager.class);
|
imm=activity.getSystemService(InputMethodManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetEmptyText() {
|
|
||||||
setEmptyText(R.string.sk_recent_searches_placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
|
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
|
||||||
return switch(s.type){
|
return switch(s.type){
|
||||||
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
||||||
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
||||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null);
|
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +119,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
resetEmptyText();
|
|
||||||
if(isInRecentMode()){
|
if(isInRecentMode()){
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
|
||||||
if(getActivity()==null)
|
if(getActivity()==null)
|
||||||
@@ -134,13 +128,11 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
onDataLoaded(sr, false);
|
onDataLoaded(sr, false);
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
setEmptyText(R.string.sk_searching);
|
|
||||||
progressVisibilityListener.onProgressVisibilityChanged(true);
|
progressVisibilityListener.onProgressVisibilityChanged(true);
|
||||||
currentRequest=new GetSearchResults(currentQuery, null, true)
|
currentRequest=new GetSearchResults(currentQuery, null, true)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(SearchResults result){
|
public void onSuccess(SearchResults result){
|
||||||
setEmptyText(R.string.sk_no_results);
|
|
||||||
ArrayList<SearchResult> results=new ArrayList<>();
|
ArrayList<SearchResult> results=new ArrayList<>();
|
||||||
if(result.accounts!=null){
|
if(result.accounts!=null){
|
||||||
for(Account acc:result.accounts)
|
for(Account acc:result.accounts)
|
||||||
@@ -161,7 +153,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
resetEmptyText();
|
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
Activity a=getActivity();
|
Activity a=getActivity();
|
||||||
if(a==null)
|
if(a==null)
|
||||||
@@ -182,7 +173,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UiUtils.updateList(prevDisplayItems, displayItems, list, adapter, (i1, i2)->i1.parentID.equals(i2.parentID) && i1.index==i2.index && i1.getType()==i2.getType());
|
UiUtils.updateList(prevDisplayItems, displayItems, list, adapter, (i1, i2)->i1.parentID.equals(i2.parentID) && i1.index==i2.index && i1.getType()==i2.getType());
|
||||||
boolean recent=isInRecentMode() && !displayItems.isEmpty();
|
boolean recent=isInRecentMode();
|
||||||
if(recent!=headerAdapter.isVisible())
|
if(recent!=headerAdapter.isVisible())
|
||||||
headerAdapter.setVisible(recent);
|
headerAdapter.setVisible(recent);
|
||||||
imgLoader.forceUpdateImages();
|
imgLoader.forceUpdateImages();
|
||||||
@@ -308,11 +299,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ProgressVisibilityListener{
|
public interface ProgressVisibilityListener{
|
||||||
void onProgressVisibilityChanged(boolean visible);
|
void onProgressVisibilityChanged(boolean visible);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
|
||||||
import org.joinmastodon.android.fragments.IsOnTop;
|
|
||||||
import org.joinmastodon.android.fragments.ScrollableToTop;
|
import org.joinmastodon.android.fragments.ScrollableToTop;
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
@@ -24,7 +23,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop {
|
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
|
||||||
|
|
||||||
@@ -67,11 +66,6 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
|
|||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOnTop() {
|
|
||||||
return isRecyclerViewOnTop(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@@ -12,21 +12,19 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.joinmastodon.android.MainActivity;
|
import org.joinmastodon.android.MainActivity;
|
||||||
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
|
||||||
import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail;
|
import org.joinmastodon.android.api.requests.accounts.ResendConfirmationEmail;
|
||||||
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
|
||||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
import org.joinmastodon.android.api.session.AccountSession;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.fragments.HomeFragment;
|
import org.joinmastodon.android.fragments.HomeFragment;
|
||||||
import org.joinmastodon.android.fragments.SettingsFragment;
|
import org.joinmastodon.android.fragments.SettingsFragment;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -37,50 +35,40 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class AccountActivationFragment extends ToolbarFragment{
|
public class AccountActivationFragment extends AppKitFragment{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
|
||||||
private Button openEmailBtn, resendBtn;
|
private Button btn, backBtn;
|
||||||
private View contentView;
|
private View buttonBar;
|
||||||
private Handler uiHandler=new Handler(Looper.getMainLooper());
|
private Handler uiHandler=new Handler(Looper.getMainLooper());
|
||||||
private Runnable pollRunnable=this::tryGetAccount;
|
private Runnable pollRunnable=this::tryGetAccount;
|
||||||
private APIRequest currentRequest;
|
private APIRequest currentRequest;
|
||||||
private Runnable resendTimer=this::updateResendTimer;
|
|
||||||
private long lastResendTime;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
setTitle(R.string.confirm_email_title);
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
lastResendTime=session.activationInfo!=null ? session.activationInfo.lastEmailConfirmationResend : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||||
View view=inflater.inflate(R.layout.fragment_onboarding_activation, container, false);
|
View view=inflater.inflate(R.layout.fragment_onboarding_activation, container, false);
|
||||||
|
|
||||||
openEmailBtn=view.findViewById(R.id.btn_next);
|
btn=view.findViewById(R.id.btn_next);
|
||||||
openEmailBtn.setOnClickListener(this::onOpenEmailClick);
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
openEmailBtn.setOnLongClickListener(v->{
|
btn.setOnLongClickListener(v->{
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
Nav.go(getActivity(), SettingsFragment.class, args);
|
Nav.go(getActivity(), SettingsFragment.class, args);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
resendBtn=view.findViewById(R.id.btn_resend);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
resendBtn.setOnClickListener(this::onResendClick);
|
view.findViewById(R.id.btn_back).setOnClickListener(v->onBackButtonClick());
|
||||||
TextView text=view.findViewById(R.id.subtitle);
|
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
text.setText(getString(R.string.confirm_email_subtitle, session.activationInfo!=null ? session.activationInfo.email : "?"));
|
|
||||||
updateResendTimer();
|
|
||||||
|
|
||||||
contentView=view;
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,32 +80,14 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onUpdateToolbar(){
|
|
||||||
super.onUpdateToolbar();
|
|
||||||
getToolbar().setBackground(null);
|
|
||||||
getToolbar().setElevation(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean canGoBack(){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onToolbarNavigationClick(){
|
|
||||||
new AccountSwitcherSheet(getActivity()).show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
public void onApplyWindowInsets(WindowInsets insets){
|
||||||
if(Build.VERSION.SDK_INT>=27){
|
if(Build.VERSION.SDK_INT>=27){
|
||||||
int inset=insets.getSystemWindowInsetBottom();
|
int inset=insets.getSystemWindowInsetBottom();
|
||||||
contentView.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), 0));
|
||||||
}else{
|
}else{
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
||||||
@@ -141,7 +111,7 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOpenEmailClick(View v){
|
private void onButtonClick(){
|
||||||
try{
|
try{
|
||||||
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
startActivity(Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_EMAIL).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||||
}catch(ActivityNotFoundException x){
|
}catch(ActivityNotFoundException x){
|
||||||
@@ -149,21 +119,12 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onResendClick(View v){
|
private void onBackButtonClick(){
|
||||||
new ResendConfirmationEmail(null)
|
new ResendConfirmationEmail(null)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Object result){
|
public void onSuccess(Object result){
|
||||||
Toast.makeText(getActivity(), R.string.resent_email, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), R.string.resent_email, Toast.LENGTH_SHORT).show();
|
||||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
if(session.activationInfo==null){
|
|
||||||
session.activationInfo=new AccountActivationInfo("?", System.currentTimeMillis());
|
|
||||||
}else{
|
|
||||||
session.activationInfo.lastEmailConfirmationResend=System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
lastResendTime=session.activationInfo.lastEmailConfirmationResend;
|
|
||||||
AccountSessionManager.getInstance().writeAccountsFile();
|
|
||||||
updateResendTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -191,7 +152,7 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
AccountSessionManager mgr=AccountSessionManager.getInstance();
|
AccountSessionManager mgr=AccountSessionManager.getInstance();
|
||||||
AccountSession session=mgr.getAccount(accountID);
|
AccountSession session=mgr.getAccount(accountID);
|
||||||
mgr.removeAccount(accountID);
|
mgr.removeAccount(accountID);
|
||||||
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
|
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, true);
|
||||||
String newID=mgr.getLastActiveAccountID();
|
String newID=mgr.getLastActiveAccountID();
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", newID);
|
args.putString("account", newID);
|
||||||
@@ -228,25 +189,4 @@ public class AccountActivationFragment extends ToolbarFragment{
|
|||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
|
||||||
private void updateResendTimer(){
|
|
||||||
long sinceResend=System.currentTimeMillis()-lastResendTime;
|
|
||||||
if(sinceResend>59_000L){
|
|
||||||
resendBtn.setText(R.string.resend);
|
|
||||||
resendBtn.setEnabled(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int seconds=(int)((60_000L-sinceResend)/1000L);
|
|
||||||
resendBtn.setText(String.format("%s (%d)", getString(R.string.resend), seconds));
|
|
||||||
if(resendBtn.isEnabled())
|
|
||||||
resendBtn.setEnabled(false);
|
|
||||||
resendBtn.postDelayed(resendTimer, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView(){
|
|
||||||
super.onDestroyView();
|
|
||||||
resendBtn.removeCallbacks(resendTimer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,256 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.RadioButton;
|
|
||||||
import android.widget.Space;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
|
||||||
|
|
||||||
public class CustomWelcomeFragment extends InstanceCatalogFragment {
|
|
||||||
private View headerView;
|
|
||||||
|
|
||||||
public CustomWelcomeFragment() {
|
|
||||||
super(R.layout.fragment_welcome_custom, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context){
|
|
||||||
super.onAttach(context);
|
|
||||||
setRefreshEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
dataLoaded();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onUpdateToolbar(){
|
|
||||||
super.onUpdateToolbar();
|
|
||||||
|
|
||||||
if (!canGoBack()) {
|
|
||||||
ImageView toolbarLogo=new ImageView(getActivity());
|
|
||||||
toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
|
|
||||||
toolbarLogo.setImageResource(R.drawable.logo);
|
|
||||||
toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
|
|
||||||
|
|
||||||
FrameLayout logoWrap=new FrameLayout(getActivity());
|
|
||||||
FrameLayout.LayoutParams logoParams=new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER);
|
|
||||||
logoParams.setMargins(0, V.dp(2), 0, 0);
|
|
||||||
logoWrap.addView(toolbarLogo, logoParams);
|
|
||||||
|
|
||||||
getToolbar().addView(logoWrap, new Toolbar.LayoutParams(Gravity.CENTER));
|
|
||||||
} else {
|
|
||||||
setTitle(R.string.add_account);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void proceedWithAuthOrSignup(Instance instance) {
|
|
||||||
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateFilteredList(){
|
|
||||||
boolean addFakeInstance = currentSearchQuery.length()>0 && currentSearchQuery.matches("^\\S+\\.[^\\.]+$");
|
|
||||||
if(addFakeInstance){
|
|
||||||
fakeInstance.domain=fakeInstance.normalizedDomain=currentSearchQuery;
|
|
||||||
fakeInstance.description=getString(R.string.loading_instance);
|
|
||||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
|
||||||
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
|
|
||||||
ivh.rebind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(filteredData.isEmpty()){
|
|
||||||
filteredData.add(fakeInstance);
|
|
||||||
adapter.notifyItemInserted(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
|
||||||
filteredData.clear();
|
|
||||||
if(currentSearchQuery.length()>0){
|
|
||||||
boolean foundExactMatch=false;
|
|
||||||
for(CatalogInstance inst:data){
|
|
||||||
if(inst.normalizedDomain.contains(currentSearchQuery)){
|
|
||||||
filteredData.add(inst);
|
|
||||||
if(inst.normalizedDomain.equals(currentSearchQuery))
|
|
||||||
foundExactMatch=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!foundExactMatch && addFakeInstance) {
|
|
||||||
filteredData.add(0, fakeInstance);
|
|
||||||
adapter.notifyItemChanged(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals);
|
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
|
||||||
list.getChildAt(i).invalidateOutline();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorWindowBackground));
|
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
|
||||||
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_welcome_custom, list, false);
|
|
||||||
searchEdit=headerView.findViewById(R.id.search_edit);
|
|
||||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
|
||||||
|
|
||||||
headerView.findViewById(R.id.more).setVisibility(View.GONE);
|
|
||||||
headerView.findViewById(R.id.visibility).setVisibility(View.GONE);
|
|
||||||
headerView.findViewById(R.id.separator).setVisibility(View.GONE);
|
|
||||||
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
|
|
||||||
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
|
|
||||||
((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
|
|
||||||
((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
|
|
||||||
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
|
|
||||||
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);
|
|
||||||
|
|
||||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
|
||||||
nextButton.setEnabled(false);
|
|
||||||
chosenInstance = null;
|
|
||||||
searchEdit.removeCallbacks(searchDebouncer);
|
|
||||||
searchEdit.postDelayed(searchDebouncer, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s){}
|
|
||||||
});
|
|
||||||
|
|
||||||
mergeAdapter=new MergeRecyclerAdapter();
|
|
||||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
|
||||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
|
||||||
View spacer = new Space(getActivity());
|
|
||||||
spacer.setMinimumHeight(V.dp(8));
|
|
||||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(spacer));
|
|
||||||
return mergeAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder> {
|
|
||||||
public InstancesAdapter(){
|
|
||||||
super(imgLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new InstanceViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(InstanceViewHolder holder, int position){
|
|
||||||
holder.bind(filteredData.get(position));
|
|
||||||
chosenInstance = filteredData.get(position);
|
|
||||||
if (chosenInstance != fakeInstance) nextButton.setEnabled(true);
|
|
||||||
super.onBindViewHolder(holder, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount(){
|
|
||||||
return filteredData.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
|
||||||
private final TextView title, description, userCount, lang;
|
|
||||||
private final RadioButton radioButton;
|
|
||||||
|
|
||||||
public InstanceViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_instance_custom, list);
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
description=findViewById(R.id.description);
|
|
||||||
userCount=findViewById(R.id.user_count);
|
|
||||||
lang=findViewById(R.id.lang);
|
|
||||||
radioButton=findViewById(R.id.radiobtn);
|
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
|
|
||||||
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(CatalogInstance item){
|
|
||||||
title.setText(item.normalizedDomain);
|
|
||||||
description.setText(item.description);
|
|
||||||
if (item == fakeInstance) {
|
|
||||||
userCount.setVisibility(View.GONE);
|
|
||||||
lang.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
userCount.setVisibility(View.VISIBLE);
|
|
||||||
lang.setVisibility(View.VISIBLE);
|
|
||||||
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
|
|
||||||
lang.setText(item.language.toUpperCase());
|
|
||||||
}
|
|
||||||
radioButton.setChecked(chosenInstance==item);
|
|
||||||
radioButton.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(){
|
|
||||||
if(chosenInstance!=null){
|
|
||||||
int idx=filteredData.indexOf(chosenInstance);
|
|
||||||
if(idx!=-1){
|
|
||||||
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
|
|
||||||
if(holder instanceof InstanceViewHolder ivh){
|
|
||||||
ivh.radioButton.setChecked(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
radioButton.setChecked(true);
|
|
||||||
if(chosenInstance==null)
|
|
||||||
nextButton.setEnabled(true);
|
|
||||||
chosenInstance=item;
|
|
||||||
loadInstanceInfo(chosenInstance.domain, false);
|
|
||||||
onNextClick(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -16,7 +15,6 @@ import android.widget.TextView;
|
|||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
@@ -35,7 +33,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
|
||||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
@@ -49,7 +46,7 @@ import okhttp3.Request;
|
|||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
public class GoogleMadeMeAddThisFragment extends AppKitFragment{
|
||||||
private UsableRecyclerView list;
|
private UsableRecyclerView list;
|
||||||
private MergeRecyclerAdapter adapter;
|
private MergeRecyclerAdapter adapter;
|
||||||
private Button btn;
|
private Button btn;
|
||||||
@@ -63,7 +60,6 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
setTitle(R.string.privacy_policy_title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -86,24 +82,37 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||||
|
|
||||||
list=view.findViewById(R.id.list);
|
list=view.findViewById(R.id.list);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
||||||
TextView text=headerView.findViewById(R.id.text);
|
TextView title=headerView.findViewById(R.id.title);
|
||||||
text.setText(R.string.privacy_policy_subtitle);
|
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
||||||
|
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
|
||||||
|
title.setText(R.string.privacy_policy_title);
|
||||||
|
subtitle.setText(R.string.privacy_policy_subtitle);
|
||||||
|
|
||||||
adapter=new MergeRecyclerAdapter();
|
adapter=new MergeRecyclerAdapter();
|
||||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||||
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
adapter.addAdapter(itemsAdapter=new ItemsAdapter());
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
list.setSelector(null);
|
||||||
|
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||||
|
if(parent.getChildViewHolder(view) instanceof ItemViewHolder){
|
||||||
|
outRect.left=outRect.right=V.dp(18.5f);
|
||||||
|
outRect.top=V.dp(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
btn=view.findViewById(R.id.btn_next);
|
btn=view.findViewById(R.id.btn_next);
|
||||||
btn.setOnClickListener(v->onButtonClick());
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -111,15 +120,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onUpdateToolbar(){
|
|
||||||
super.onUpdateToolbar();
|
|
||||||
getToolbar().setBackground(null);
|
|
||||||
getToolbar().setElevation(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onButtonClick(){
|
protected void onButtonClick(){
|
||||||
@@ -191,17 +192,24 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
|
||||||
private final TextView title;
|
private final TextView domain, title;
|
||||||
|
private final ImageView favicon;
|
||||||
|
|
||||||
public ItemViewHolder(){
|
public ItemViewHolder(){
|
||||||
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
super(getActivity(), R.layout.item_privacy_policy_link, list);
|
||||||
|
domain=findViewById(R.id.domain);
|
||||||
title=findViewById(R.id.title);
|
title=findViewById(R.id.title);
|
||||||
title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
favicon=findViewById(R.id.favicon);
|
||||||
|
itemView.setOutlineProvider(OutlineProviders.roundedRect(10));
|
||||||
|
itemView.setClipToOutline(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(Item item){
|
public void onBind(Item item){
|
||||||
|
domain.setText(item.domain);
|
||||||
title.setText(item.title);
|
title.setText(item.title);
|
||||||
|
|
||||||
|
ViewImageLoader.load(favicon, null, new UrlImageLoaderRequest(item.faviconUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,30 +2,46 @@ package org.joinmastodon.android.fragments.onboarding;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.LocaleList;
|
import android.os.LocaleList;
|
||||||
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
import org.joinmastodon.android.api.requests.instance.GetInstance;
|
||||||
|
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
||||||
|
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
||||||
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
|
import org.joinmastodon.android.model.catalog.CatalogCategory;
|
||||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
||||||
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
|
import org.joinmastodon.android.ui.tabs.TabLayout;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.parceler.Parcels;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
@@ -43,53 +59,317 @@ import java.util.stream.Collectors;
|
|||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
import okhttp3.Call;
|
import okhttp3.Call;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstance>{
|
public class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInstance>{
|
||||||
protected RecyclerView.Adapter adapter;
|
private InstancesAdapter adapter;
|
||||||
protected MergeRecyclerAdapter mergeAdapter;
|
private MergeRecyclerAdapter mergeAdapter;
|
||||||
protected CatalogInstance chosenInstance;
|
private View headerView;
|
||||||
protected Button nextButton;
|
private CatalogInstance chosenInstance;
|
||||||
protected EditText searchEdit;
|
private List<CatalogInstance> filteredData=new ArrayList<>();
|
||||||
protected Runnable searchDebouncer=this::onSearchChangedDebounced;
|
private Button nextButton;
|
||||||
protected String currentSearchQuery;
|
private MastodonAPIRequest<?> getCategoriesRequest;
|
||||||
protected String loadingInstanceDomain;
|
private EditText searchEdit;
|
||||||
protected HashMap<String, Instance> instancesCache=new HashMap<>();
|
private TabLayout categoriesList;
|
||||||
protected View buttonBar;
|
private Runnable searchDebouncer=this::onSearchChangedDebounced;
|
||||||
protected List<CatalogInstance> filteredData=new ArrayList<>();
|
private String currentSearchQuery;
|
||||||
protected GetInstance loadingInstanceRequest;
|
private String currentCategory="all";
|
||||||
protected Call loadingInstanceRedirectRequest;
|
private List<CatalogCategory> categories=new ArrayList<>();
|
||||||
protected ProgressDialog instanceProgressDialog;
|
private String loadingInstanceDomain;
|
||||||
protected HashMap<String, String> redirects=new HashMap<>();
|
private GetInstance loadingInstanceRequest;
|
||||||
protected HashMap<String, String> redirectsInverse=new HashMap<>();
|
private Call loadingInstanceRedirectRequest;
|
||||||
protected boolean isSignup;
|
private HashMap<String, Instance> instancesCache=new HashMap<>();
|
||||||
protected CatalogInstance fakeInstance=new CatalogInstance();
|
private ProgressDialog instanceProgressDialog;
|
||||||
|
private View buttonBar;
|
||||||
|
private HashMap<String, String> redirects=new HashMap<>(), redirectsInverse=new HashMap<>();
|
||||||
|
|
||||||
|
private boolean isSignup;
|
||||||
|
|
||||||
private static final double DUNBAR=Math.log(800);
|
private static final double DUNBAR=Math.log(800);
|
||||||
|
|
||||||
public InstanceCatalogFragment(int layout, int perPage){
|
public InstanceCatalogFragment(){
|
||||||
super(layout, perPage);
|
super(R.layout.fragment_onboarding_common, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
isSignup=getArguments() != null && getArguments().getBoolean("signup");
|
isSignup=getArguments().getBoolean("signup");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void proceedWithAuthOrSignup(Instance instance);
|
@Override
|
||||||
|
public void onAttach(Context context){
|
||||||
|
super.onAttach(context);
|
||||||
|
setRefreshEnabled(false);
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
@Override
|
||||||
|
protected void doLoadData(int offset, int count){
|
||||||
|
currentRequest=new GetCatalogInstances(null, null)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<CatalogInstance> result){
|
||||||
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
Map<String, List<CatalogInstance>> byLang=result.stream().collect(Collectors.groupingBy(ci->ci.language));
|
||||||
|
for(List<CatalogInstance> group:byLang.values()){
|
||||||
|
Collections.sort(group, (a, b)->{
|
||||||
|
double aa=Math.abs(DUNBAR-Math.log(a.lastWeekUsers));
|
||||||
|
double bb=Math.abs(DUNBAR-Math.log(b.lastWeekUsers));
|
||||||
|
return Double.compare(aa, bb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// get the list of user-configured system languages
|
||||||
|
List<String> userLangs;
|
||||||
|
if(Build.VERSION.SDK_INT<24){
|
||||||
|
userLangs=Collections.singletonList(getResources().getConfiguration().locale.getLanguage());
|
||||||
|
}else{
|
||||||
|
LocaleList ll=getResources().getConfiguration().getLocales();
|
||||||
|
userLangs=new ArrayList<>(ll.size());
|
||||||
|
for(int i=0;i<ll.size();i++){
|
||||||
|
userLangs.add(ll.get(i).getLanguage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add instances in preferred languages to the top of the list, in the order of preference
|
||||||
|
ArrayList<CatalogInstance> sortedList=new ArrayList<>();
|
||||||
|
for(String lang:userLangs){
|
||||||
|
List<CatalogInstance> langInstances=byLang.remove(lang);
|
||||||
|
if(langInstances!=null){
|
||||||
|
sortedList.addAll(langInstances);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sort the remaining language groups by aggregate lastWeekUsers
|
||||||
|
class InstanceGroup{
|
||||||
|
public int activeUsers;
|
||||||
|
public List<CatalogInstance> instances;
|
||||||
|
}
|
||||||
|
byLang.values().stream().map(il->{
|
||||||
|
InstanceGroup group=new InstanceGroup();
|
||||||
|
group.instances=il;
|
||||||
|
for(CatalogInstance instance:il){
|
||||||
|
group.activeUsers+=instance.lastWeekUsers;
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}).sorted(Comparator.comparingInt((InstanceGroup g)->g.activeUsers).reversed()).forEachOrdered(ig->sortedList.addAll(ig.instances));
|
||||||
|
onDataLoaded(sortedList, false);
|
||||||
|
updateFilteredList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
error.showToast(getActivity());
|
||||||
|
onDataLoaded(Collections.emptyList(), false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execNoAuth("");
|
||||||
|
getCategoriesRequest=new GetCatalogCategories(null)
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<CatalogCategory> result){
|
||||||
|
getCategoriesRequest=null;
|
||||||
|
CatalogCategory all=new CatalogCategory();
|
||||||
|
all.category="all";
|
||||||
|
categories.add(all);
|
||||||
|
result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add);
|
||||||
|
updateCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
getCategoriesRequest=null;
|
||||||
|
error.showToast(getActivity());
|
||||||
|
CatalogCategory all=new CatalogCategory();
|
||||||
|
all.category="all";
|
||||||
|
categories.add(all);
|
||||||
|
updateCategories();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.execNoAuth("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCategories(){
|
||||||
|
categoriesList.removeAllTabs();
|
||||||
|
for(CatalogCategory cat:categories){
|
||||||
|
int titleRes=getTitleForCategory(cat.category);
|
||||||
|
TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
|
||||||
|
ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
|
||||||
|
emoji.setImageResource(getEmojiForCategory(cat.category));
|
||||||
|
categoriesList.addTab(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy(){
|
||||||
|
super.onDestroy();
|
||||||
|
if(getCategoriesRequest!=null)
|
||||||
|
getCategoriesRequest.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RecyclerView.Adapter getAdapter(){
|
||||||
|
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_instance_catalog, list, false);
|
||||||
|
searchEdit=headerView.findViewById(R.id.search_edit);
|
||||||
|
categoriesList=headerView.findViewById(R.id.categories_list);
|
||||||
|
categoriesList.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
|
||||||
|
@Override
|
||||||
|
public void onTabSelected(TabLayout.Tab tab){
|
||||||
|
CatalogCategory category=categories.get(tab.getPosition());
|
||||||
|
currentCategory=category.category;
|
||||||
|
updateFilteredList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabUnselected(TabLayout.Tab tab){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabReselected(TabLayout.Tab tab){
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
||||||
|
searchEdit.addTextChangedListener(new TextWatcher(){
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count){
|
||||||
|
searchEdit.removeCallbacks(searchDebouncer);
|
||||||
|
searchEdit.postDelayed(searchDebouncer, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s){
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mergeAdapter=new MergeRecyclerAdapter();
|
||||||
|
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||||
|
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
||||||
|
return mergeAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
nextButton=view.findViewById(R.id.btn_next);
|
||||||
|
nextButton.setOnClickListener(this::onNextClick);
|
||||||
|
nextButton.setEnabled(chosenInstance!=null);
|
||||||
|
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||||
|
list.setItemAnimator(new BetterItemAnimator());
|
||||||
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
|
||||||
|
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||||
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNextClick(View v){
|
||||||
|
String domain=chosenInstance.domain;
|
||||||
|
Instance instance=instancesCache.get(domain);
|
||||||
|
if(instance!=null){
|
||||||
|
proceedWithAuthOrSignup(instance);
|
||||||
|
}else{
|
||||||
|
showProgressDialog();
|
||||||
|
if(!domain.equals(loadingInstanceDomain)){
|
||||||
|
loadInstanceInfo(domain, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void proceedWithAuthOrSignup(Instance instance){
|
||||||
|
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
||||||
|
if(isSignup){
|
||||||
|
if(!instance.registrations){
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.instance_signup_closed)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Bundle args=new Bundle();
|
||||||
|
args.putParcelable("instance", Parcels.wrap(instance));
|
||||||
|
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
||||||
|
}else{
|
||||||
|
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private String getEmojiForCategory(String category){
|
||||||
|
// return switch(category){
|
||||||
|
// case "all" -> "💬";
|
||||||
|
// case "academia" -> "📚";
|
||||||
|
// case "activism" -> "✊";
|
||||||
|
// case "food" -> "🍕";
|
||||||
|
// case "furry" -> "🦁";
|
||||||
|
// case "games" -> "🕹";
|
||||||
|
// case "general" -> "🐘";
|
||||||
|
// case "journalism" -> "📰";
|
||||||
|
// case "lgbt" -> "🏳️🌈";
|
||||||
|
// case "regional" -> "📍";
|
||||||
|
// case "art" -> "🎨";
|
||||||
|
// case "music" -> "🎼";
|
||||||
|
// case "tech" -> "📱";
|
||||||
|
// default -> "❓";
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
private int getEmojiForCategory(String category){
|
||||||
|
return switch(category){
|
||||||
|
case "all" -> R.drawable.ic_category_all;
|
||||||
|
case "academia" -> R.drawable.ic_category_academia;
|
||||||
|
case "activism" -> R.drawable.ic_category_activism;
|
||||||
|
case "food" -> R.drawable.ic_category_food;
|
||||||
|
case "furry" -> R.drawable.ic_category_furry;
|
||||||
|
case "games" -> R.drawable.ic_category_games;
|
||||||
|
case "general" -> R.drawable.ic_category_general;
|
||||||
|
case "journalism" -> R.drawable.ic_category_journalism;
|
||||||
|
case "lgbt" -> R.drawable.ic_category_lgbt;
|
||||||
|
case "regional" -> R.drawable.ic_category_regional;
|
||||||
|
case "art" -> R.drawable.ic_category_art;
|
||||||
|
case "music" -> R.drawable.ic_category_music;
|
||||||
|
case "tech" -> R.drawable.ic_category_tech;
|
||||||
|
default -> R.drawable.ic_category_unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getTitleForCategory(String category){
|
||||||
|
return switch(category){
|
||||||
|
case "all" -> R.string.category_all;
|
||||||
|
case "academia" -> R.string.category_academia;
|
||||||
|
case "activism" -> R.string.category_activism;
|
||||||
|
case "food" -> R.string.category_food;
|
||||||
|
case "furry" -> R.string.category_furry;
|
||||||
|
case "games" -> R.string.category_games;
|
||||||
|
case "general" -> R.string.category_general;
|
||||||
|
case "journalism" -> R.string.category_journalism;
|
||||||
|
case "lgbt" -> R.string.category_lgbt;
|
||||||
|
case "regional" -> R.string.category_regional;
|
||||||
|
case "art" -> R.string.category_art;
|
||||||
|
case "music" -> R.string.category_music;
|
||||||
|
case "tech" -> R.string.category_tech;
|
||||||
|
default -> 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
|
||||||
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
|
||||||
return true;
|
return true;
|
||||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
||||||
@@ -105,73 +385,60 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onSearchChangedDebounced(){
|
private void onSearchChangedDebounced(){
|
||||||
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
currentSearchQuery=searchEdit.getText().toString().toLowerCase();
|
||||||
updateFilteredList();
|
updateFilteredList();
|
||||||
loadInstanceInfo(currentSearchQuery, false);
|
loadInstanceInfo(currentSearchQuery, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){
|
private void updateFilteredList(){
|
||||||
Map<String, List<CatalogInstance>> byLang=result.stream().collect(Collectors.groupingBy(ci->ci.language));
|
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
||||||
for(List<CatalogInstance> group:byLang.values()){
|
filteredData.clear();
|
||||||
Collections.sort(group, (a, b)->{
|
for(CatalogInstance instance:data){
|
||||||
double aa=Math.abs(DUNBAR-Math.log(a.lastWeekUsers));
|
if(currentCategory.equals("all") || instance.categories.contains(currentCategory)){
|
||||||
double bb=Math.abs(DUNBAR-Math.log(b.lastWeekUsers));
|
if(TextUtils.isEmpty(currentSearchQuery) || instance.domain.contains(currentSearchQuery)){
|
||||||
return Double.compare(aa, bb);
|
if(instance.domain.equals(currentSearchQuery) || !isSignup || !instance.approvalRequired)
|
||||||
});
|
filteredData.add(instance);
|
||||||
}
|
}
|
||||||
// get the list of user-configured system languages
|
|
||||||
List<String> userLangs;
|
|
||||||
if(Build.VERSION.SDK_INT<24){
|
|
||||||
userLangs=Collections.singletonList(getResources().getConfiguration().locale.getLanguage());
|
|
||||||
}else{
|
|
||||||
LocaleList ll=getResources().getConfiguration().getLocales();
|
|
||||||
userLangs=new ArrayList<>(ll.size());
|
|
||||||
for(int i=0;i<ll.size();i++){
|
|
||||||
userLangs.add(ll.get(i).getLanguage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add instances in preferred languages to the top of the list, in the order of preference
|
DiffUtil.calculateDiff(new DiffUtil.Callback(){
|
||||||
ArrayList<CatalogInstance> sortedList=new ArrayList<>();
|
@Override
|
||||||
for(String lang:userLangs){
|
public int getOldListSize(){
|
||||||
List<CatalogInstance> langInstances=byLang.remove(lang);
|
return prevData.size();
|
||||||
if(langInstances!=null){
|
|
||||||
sortedList.addAll(langInstances);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// sort the remaining language groups by aggregate lastWeekUsers
|
@Override
|
||||||
class InstanceGroup{
|
public int getNewListSize(){
|
||||||
public int activeUsers;
|
return filteredData.size();
|
||||||
public List<CatalogInstance> instances;
|
|
||||||
}
|
|
||||||
byLang.values().stream().map(il->{
|
|
||||||
InstanceGroup group=new InstanceGroup();
|
|
||||||
group.instances=il;
|
|
||||||
for(CatalogInstance instance:il){
|
|
||||||
group.activeUsers+=instance.lastWeekUsers;
|
|
||||||
}
|
}
|
||||||
return group;
|
|
||||||
}).sorted(Comparator.comparingInt((InstanceGroup g)->g.activeUsers).reversed()).forEachOrdered(ig->sortedList.addAll(ig.instances));
|
@Override
|
||||||
return sortedList;
|
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
|
||||||
|
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
|
||||||
|
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
||||||
|
}
|
||||||
|
}).dispatchUpdatesTo(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void updateFilteredList();
|
private void showProgressDialog(){
|
||||||
|
|
||||||
protected void showProgressDialog(){
|
|
||||||
instanceProgressDialog=new ProgressDialog(getActivity());
|
instanceProgressDialog=new ProgressDialog(getActivity());
|
||||||
instanceProgressDialog.setMessage(getString(R.string.loading_instance));
|
instanceProgressDialog.setMessage(getString(R.string.loading_instance));
|
||||||
instanceProgressDialog.setOnCancelListener(dialog->cancelLoadingInstanceInfo());
|
instanceProgressDialog.setOnCancelListener(dialog->cancelLoadingInstanceInfo());
|
||||||
instanceProgressDialog.show();
|
instanceProgressDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String normalizeInstanceDomain(String _domain){
|
private String normalizeInstanceDomain(String _domain){
|
||||||
if(TextUtils.isEmpty(_domain))
|
if(TextUtils.isEmpty(_domain))
|
||||||
return null;
|
return null;
|
||||||
if(_domain.contains(":")){
|
if(_domain.contains(":")){
|
||||||
try{
|
try{
|
||||||
_domain=Uri.parse(_domain).getAuthority();
|
_domain=Uri.parse(_domain).getAuthority();
|
||||||
}catch(Exception ignore){
|
}catch(Exception ignore){}
|
||||||
}
|
|
||||||
if(TextUtils.isEmpty(_domain))
|
if(TextUtils.isEmpty(_domain))
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -186,14 +453,12 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
return domain;
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
private void loadInstanceInfo(String _domain, boolean isFromRedirect){
|
||||||
if(TextUtils.isEmpty(_domain))
|
|
||||||
return;
|
|
||||||
String domain=normalizeInstanceDomain(_domain);
|
String domain=normalizeInstanceDomain(_domain);
|
||||||
Instance cachedInstance=instancesCache.get(domain);
|
Instance cachedInstance=instancesCache.get(domain);
|
||||||
if(cachedInstance!=null){
|
if(cachedInstance!=null){
|
||||||
for(CatalogInstance ci : filteredData){
|
for(CatalogInstance ci:filteredData){
|
||||||
if(ci.domain.equals(domain) && ci!=fakeInstance)
|
if(ci.domain.equals(domain))
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CatalogInstance ci=cachedInstance.toCatalogInstance();
|
CatalogInstance ci=cachedInstance.toCatalogInstance();
|
||||||
@@ -211,57 +476,44 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
loadingInstanceDomain=domain;
|
loadingInstanceDomain=domain;
|
||||||
loadingInstanceRequest=new GetInstance();
|
loadingInstanceRequest=new GetInstance();
|
||||||
loadingInstanceRequest.setCallback(new Callback<>(){
|
loadingInstanceRequest.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Instance result){
|
public void onSuccess(Instance result){
|
||||||
loadingInstanceRequest=null;
|
loadingInstanceRequest=null;
|
||||||
loadingInstanceDomain=null;
|
loadingInstanceDomain=null;
|
||||||
result.uri=domain; // needed for instances that use domain redirection
|
result.uri=domain; // needed for instances that use domain redirection
|
||||||
instancesCache.put(domain, result);
|
instancesCache.put(domain, result);
|
||||||
if(instanceProgressDialog!=null){
|
if(instanceProgressDialog!=null){
|
||||||
instanceProgressDialog.dismiss();
|
instanceProgressDialog.dismiss();
|
||||||
instanceProgressDialog=null;
|
instanceProgressDialog=null;
|
||||||
proceedWithAuthOrSignup(result);
|
proceedWithAuthOrSignup(result);
|
||||||
}
|
}
|
||||||
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
|
||||||
boolean found=false;
|
boolean found=false;
|
||||||
for(CatalogInstance ci:filteredData){
|
for(CatalogInstance ci:filteredData){
|
||||||
if(ci.domain.equals(domain) && ci!=fakeInstance){
|
if(ci.domain.equals(domain)){
|
||||||
found=true;
|
found=true;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!found){
|
||||||
|
CatalogInstance ci=result.toCatalogInstance();
|
||||||
|
filteredData.add(0, ci);
|
||||||
|
adapter.notifyItemInserted(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!found){
|
|
||||||
CatalogInstance ci=result.toCatalogInstance();
|
|
||||||
if(filteredData.size()==1 && filteredData.get(0)==fakeInstance){
|
|
||||||
filteredData.set(0, ci);
|
|
||||||
adapter.notifyItemChanged(0);
|
|
||||||
}else{
|
|
||||||
filteredData.add(0, ci);
|
|
||||||
adapter.notifyItemInserted(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
loadingInstanceRequest=null;
|
loadingInstanceRequest=null;
|
||||||
if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){
|
if(!isFromRedirect && error instanceof MastodonErrorResponse me && me.httpStatus==404){
|
||||||
fetchDomainFromHostMetaAndMaybeRetry(domain, error);
|
fetchDomainFromHostMetaAndMaybeRetry(domain, error);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
loadingInstanceDomain=null;
|
|
||||||
showInstanceInfoLoadError(domain, error);
|
|
||||||
if(fakeInstance!=null){
|
|
||||||
fakeInstance.description=getString(R.string.error);
|
|
||||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
|
||||||
if(list.findViewHolderForAdapterPosition(1) instanceof BindableViewHolder<?> ivh){
|
|
||||||
ivh.rebind();
|
|
||||||
}
|
}
|
||||||
|
loadingInstanceDomain=null;
|
||||||
|
showInstanceInfoLoadError(domain, error);
|
||||||
}
|
}
|
||||||
}
|
}).execNoAuth(domain);
|
||||||
}
|
|
||||||
}).execNoAuth(domain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelLoadingInstanceInfo(){
|
private void cancelLoadingInstanceInfo(){
|
||||||
@@ -332,7 +584,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
InputSource source=new InputSource(response.body().charStream());
|
InputSource source=new InputSource(response.body().charStream());
|
||||||
Document doc=DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(source);
|
Document doc=DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(source);
|
||||||
NodeList list=doc.getElementsByTagName("Link");
|
NodeList list=doc.getElementsByTagName("Link");
|
||||||
for(int i=0; i<list.getLength(); i++){
|
for(int i=0;i<list.getLength();i++){
|
||||||
if(list.item(i) instanceof Element el){
|
if(list.item(i) instanceof Element el){
|
||||||
String template=el.getAttribute("template");
|
String template=el.getAttribute("template");
|
||||||
if("lrdd".equals(el.getAttribute("rel")) && !TextUtils.isEmpty(template) && template.contains("{uri}")){
|
if("lrdd".equals(el.getAttribute("rel")) && !TextUtils.isEmpty(template) && template.contains("{uri}")){
|
||||||
@@ -364,26 +616,78 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder>{
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public InstancesAdapter(){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super(imgLoader);
|
||||||
nextButton=view.findViewById(R.id.btn_next);
|
}
|
||||||
nextButton.setOnClickListener(this::onNextClick);
|
|
||||||
nextButton.setEnabled(chosenInstance!=null);
|
@NonNull
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
@Override
|
||||||
setRefreshEnabled(false);
|
public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new InstanceViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(InstanceViewHolder holder, int position){
|
||||||
|
holder.bind(filteredData.get(position));
|
||||||
|
super.onBindViewHolder(holder, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return filteredData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onNextClick(View v){
|
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
||||||
String domain=chosenInstance.domain;
|
private final TextView title, description, userCount, lang;
|
||||||
Instance instance=instancesCache.get(domain);
|
private final RadioButton radioButton;
|
||||||
if(instance!=null){
|
|
||||||
proceedWithAuthOrSignup(instance);
|
public InstanceViewHolder(){
|
||||||
}else{
|
super(getActivity(), R.layout.item_instance_catalog, list);
|
||||||
showProgressDialog();
|
title=findViewById(R.id.title);
|
||||||
if(!domain.equals(loadingInstanceDomain)){
|
description=findViewById(R.id.description);
|
||||||
loadInstanceInfo(domain, false);
|
userCount=findViewById(R.id.user_count);
|
||||||
|
lang=findViewById(R.id.lang);
|
||||||
|
radioButton=findViewById(R.id.radiobtn);
|
||||||
|
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N){
|
||||||
|
UiUtils.fixCompoundDrawableTintOnAndroid6(userCount);
|
||||||
|
UiUtils.fixCompoundDrawableTintOnAndroid6(lang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(CatalogInstance item){
|
||||||
|
title.setText(item.normalizedDomain);
|
||||||
|
description.setText(item.description);
|
||||||
|
userCount.setText(UiUtils.abbreviateNumber(item.totalUsers));
|
||||||
|
lang.setText(item.language.toUpperCase());
|
||||||
|
radioButton.setChecked(chosenInstance==item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(){
|
||||||
|
if(chosenInstance==item)
|
||||||
|
return;
|
||||||
|
if(chosenInstance!=null){
|
||||||
|
int idx=filteredData.indexOf(chosenInstance);
|
||||||
|
if(idx!=-1){
|
||||||
|
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(mergeAdapter.getPositionForAdapter(adapter)+idx);
|
||||||
|
if(holder instanceof InstanceViewHolder ivh){
|
||||||
|
ivh.radioButton.setChecked(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
radioButton.setChecked(true);
|
||||||
|
if(chosenInstance==null)
|
||||||
|
nextButton.setEnabled(true);
|
||||||
|
chosenInstance=item;
|
||||||
|
loadInstanceInfo(chosenInstance.domain, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,713 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.AnimatorSet;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.graphics.drawable.LayerDrawable;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.WindowInsets;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.HorizontalScrollView;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.RadioButton;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogCategories;
|
|
||||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.catalog.CatalogCategory;
|
|
||||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
|
||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.joinmastodon.android.ui.views.FilterChipView;
|
|
||||||
import org.parceler.Parcels;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.Nav;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.fragments.OnBackPressedListener;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.utils.CubicBezierInterpolator;
|
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
|
||||||
|
|
||||||
public class InstanceCatalogSignupFragment extends InstanceCatalogFragment implements OnBackPressedListener{
|
|
||||||
private MastodonAPIRequest<?> getCategoriesRequest;
|
|
||||||
private String currentCategory="all";
|
|
||||||
private List<CatalogCategory> categories=new ArrayList<>();
|
|
||||||
private View topBar;
|
|
||||||
|
|
||||||
private List<String> languages=Collections.emptyList();
|
|
||||||
private PopupMenu langFilterMenu, speedFilterMenu;
|
|
||||||
private SignupSpeedFilter currentSignupSpeedFilter=SignupSpeedFilter.INSTANT;
|
|
||||||
private String currentLanguage=null;
|
|
||||||
private boolean searchQueryMode;
|
|
||||||
private LinearLayout filtersWrap;
|
|
||||||
private HorizontalScrollView filtersScroll;
|
|
||||||
private ImageButton backBtn, clearSearchBtn;
|
|
||||||
private View focusThing;
|
|
||||||
|
|
||||||
private FilterChipView categoryGeneral, categorySpecialInterests;
|
|
||||||
private List<FilterChipView> regionalFilters;
|
|
||||||
private CatalogInstance.Region chosenRegion;
|
|
||||||
private CategoryChoice categoryChoice;
|
|
||||||
|
|
||||||
public InstanceCatalogSignupFragment(){
|
|
||||||
super(R.layout.fragment_onboarding_common, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context){
|
|
||||||
super.onAttach(context);
|
|
||||||
setRefreshEnabled(false);
|
|
||||||
setRetainInstance(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
currentRequest=new GetCatalogInstances(null, null)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<CatalogInstance> result){
|
|
||||||
if(getActivity()==null)
|
|
||||||
return;
|
|
||||||
onDataLoaded(sortInstances(result), false);
|
|
||||||
|
|
||||||
if(langFilterMenu!=null){
|
|
||||||
Menu menu=langFilterMenu.getMenu();
|
|
||||||
menu.clear();
|
|
||||||
menu.add(0, 0, 0, R.string.server_filter_any_language);
|
|
||||||
languages=result.stream().map(i->i.language).distinct().filter(s->s.length()>0).sorted().collect(Collectors.toList());
|
|
||||||
int i=1;
|
|
||||||
for(String lang:languages){
|
|
||||||
Locale locale=Locale.forLanguageTag(lang);
|
|
||||||
String name=locale.getDisplayLanguage(locale);
|
|
||||||
if(name.equals(lang))
|
|
||||||
name=lang.toUpperCase();
|
|
||||||
else
|
|
||||||
name=name.substring(0, 1).toUpperCase()+name.substring(1);
|
|
||||||
menu.add(0, i, 0, name);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFilteredList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getActivity());
|
|
||||||
onDataLoaded(Collections.emptyList(), false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.execNoAuth("");
|
|
||||||
getCategoriesRequest=new GetCatalogCategories(null)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<CatalogCategory> result){
|
|
||||||
getCategoriesRequest=null;
|
|
||||||
CatalogCategory all=new CatalogCategory();
|
|
||||||
all.category="all";
|
|
||||||
categories.add(all);
|
|
||||||
result.stream().sorted(Comparator.comparingInt((CatalogCategory cc)->cc.serversCount).reversed()).forEach(categories::add);
|
|
||||||
updateCategories();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
getCategoriesRequest=null;
|
|
||||||
error.showToast(getActivity());
|
|
||||||
CatalogCategory all=new CatalogCategory();
|
|
||||||
all.category="all";
|
|
||||||
categories.add(all);
|
|
||||||
updateCategories();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.execNoAuth("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCategories(){
|
|
||||||
// categoriesList.removeAllTabs();
|
|
||||||
// for(CatalogCategory cat:categories){
|
|
||||||
// int titleRes=getTitleForCategory(cat.category);
|
|
||||||
// TabLayout.Tab tab=categoriesList.newTab().setText(titleRes!=0 ? getString(titleRes) : cat.category).setCustomView(R.layout.item_instance_category);
|
|
||||||
// ImageView emoji=tab.getCustomView().findViewById(R.id.emoji);
|
|
||||||
// emoji.setImageResource(getEmojiForCategory(cat.category));
|
|
||||||
// categoriesList.addTab(tab);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy(){
|
|
||||||
super.onDestroy();
|
|
||||||
if(getCategoriesRequest!=null)
|
|
||||||
getCategoriesRequest.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
|
||||||
View headerView=new View(getActivity());
|
|
||||||
headerView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1));
|
|
||||||
|
|
||||||
mergeAdapter=new MergeRecyclerAdapter();
|
|
||||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
|
||||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
|
||||||
return mergeAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
backBtn=view.findViewById(R.id.btn_back);
|
|
||||||
backBtn.setOnClickListener(v->{
|
|
||||||
if(searchQueryMode){
|
|
||||||
setSearchQueryMode(false);
|
|
||||||
}else{
|
|
||||||
Nav.finish(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
clearSearchBtn=view.findViewById(R.id.clear);
|
|
||||||
clearSearchBtn.setOnClickListener(v->searchEdit.setText(""));
|
|
||||||
nextButton.setEnabled(true);
|
|
||||||
list.setItemAnimator(new BetterItemAnimator());
|
|
||||||
setStatusBarColor(0);
|
|
||||||
topBar=view.findViewById(R.id.top_bar);
|
|
||||||
|
|
||||||
LayerDrawable topBg=(LayerDrawable) topBar.getBackground().mutate();
|
|
||||||
topBar.setBackground(topBg);
|
|
||||||
Drawable topOverlay=topBg.findDrawableByLayerId(R.id.color_overlay);
|
|
||||||
topOverlay.setAlpha(0);
|
|
||||||
|
|
||||||
LayerDrawable btmBg=(LayerDrawable) buttonBar.getBackground().mutate();
|
|
||||||
buttonBar.setBackground(btmBg);
|
|
||||||
Drawable btmOverlay=btmBg.findDrawableByLayerId(R.id.color_overlay);
|
|
||||||
btmOverlay.setAlpha(0);
|
|
||||||
|
|
||||||
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
|
|
||||||
private boolean isAtTop=true;
|
|
||||||
private Animator currentPanelsAnim;
|
|
||||||
@Override
|
|
||||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
|
|
||||||
boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop());
|
|
||||||
if(newAtTop!=isAtTop){
|
|
||||||
isAtTop=newAtTop;
|
|
||||||
if(currentPanelsAnim!=null)
|
|
||||||
currentPanelsAnim.cancel();
|
|
||||||
|
|
||||||
AnimatorSet set=new AnimatorSet();
|
|
||||||
set.playTogether(
|
|
||||||
ObjectAnimator.ofInt(topOverlay, "alpha", isAtTop ? 0 : 20),
|
|
||||||
ObjectAnimator.ofInt(btmOverlay, "alpha", isAtTop ? 0 : 20),
|
|
||||||
ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)),
|
|
||||||
ObjectAnimator.ofFloat(buttonBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3))
|
|
||||||
);
|
|
||||||
set.setDuration(150);
|
|
||||||
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
||||||
set.addListener(new AnimatorListenerAdapter(){
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation){
|
|
||||||
currentPanelsAnim=null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
set.start();
|
|
||||||
currentPanelsAnim=set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
searchEdit=view.findViewById(R.id.search_edit);
|
|
||||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
|
||||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
|
||||||
searchEdit.removeCallbacks(searchDebouncer);
|
|
||||||
searchEdit.postDelayed(searchDebouncer, 300);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s){
|
|
||||||
if((clearSearchBtn.getVisibility()==View.VISIBLE)!=(s.length()>0)){
|
|
||||||
clearSearchBtn.setVisibility(s.length()>0 ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
searchEdit.setOnFocusChangeListener((v, hasFocus)->{
|
|
||||||
if(hasFocus && !searchQueryMode){
|
|
||||||
setSearchQueryMode(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
FilterChipView langFilter=new FilterChipView(getActivity());
|
|
||||||
langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
|
|
||||||
if(currentLanguage==null){
|
|
||||||
langFilter.setText(R.string.server_filter_any_language);
|
|
||||||
}else{
|
|
||||||
Locale locale=Locale.forLanguageTag(currentLanguage);
|
|
||||||
langFilter.setText(locale.getDisplayLanguage(locale));
|
|
||||||
langFilter.setSelected(true);
|
|
||||||
}
|
|
||||||
langFilterMenu=new PopupMenu(getContext(), langFilter);
|
|
||||||
langFilter.setOnTouchListener(langFilterMenu.getDragToOpenListener());
|
|
||||||
langFilter.setOnClickListener(v->langFilterMenu.show());
|
|
||||||
filtersWrap=view.findViewById(R.id.filters_container);
|
|
||||||
filtersScroll=view.findViewById(R.id.filters_scroll);
|
|
||||||
filtersWrap.addView(langFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
|
|
||||||
FilterChipView speedFilter=new FilterChipView(getActivity());
|
|
||||||
speedFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
|
|
||||||
speedFilterMenu=new PopupMenu(getContext(), speedFilter);
|
|
||||||
speedFilterMenu.getMenu().add(0, 0, 0, R.string.server_filter_any_signup_speed);
|
|
||||||
speedFilterMenu.getMenu().add(0, 1, 0, R.string.server_filter_instant_signup);
|
|
||||||
speedFilterMenu.getMenu().add(0, 2, 0, R.string.server_filter_manual_review);
|
|
||||||
speedFilter.setOnTouchListener(speedFilterMenu.getDragToOpenListener());
|
|
||||||
speedFilter.setOnClickListener(v->speedFilterMenu.show());
|
|
||||||
speedFilter.setText(switch(currentSignupSpeedFilter){
|
|
||||||
case ANY -> R.string.server_filter_any_signup_speed;
|
|
||||||
case INSTANT -> R.string.server_filter_instant_signup;
|
|
||||||
case REVIEWED -> R.string.server_filter_manual_review;
|
|
||||||
});
|
|
||||||
speedFilter.setSelected(currentSignupSpeedFilter!=SignupSpeedFilter.ANY);
|
|
||||||
filtersWrap.addView(speedFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
|
|
||||||
speedFilterMenu.setOnMenuItemClickListener(item->{
|
|
||||||
speedFilter.setText(item.getTitle());
|
|
||||||
speedFilter.setSelected(item.getItemId()>0);
|
|
||||||
currentSignupSpeedFilter=SignupSpeedFilter.values()[item.getItemId()];
|
|
||||||
updateFilteredList();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
langFilterMenu.setOnMenuItemClickListener(item->{
|
|
||||||
langFilter.setText(item.getTitle());
|
|
||||||
langFilter.setSelected(item.getItemId()>0);
|
|
||||||
currentLanguage=item.getItemId()==0 ? null : languages.get(item.getItemId()-1);
|
|
||||||
updateFilteredList();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
View divider=new View(getActivity());
|
|
||||||
divider.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Outline));
|
|
||||||
filtersWrap.addView(divider, new LinearLayout.LayoutParams(V.dp(.5f), ViewGroup.LayoutParams.MATCH_PARENT));
|
|
||||||
|
|
||||||
categoryGeneral=new FilterChipView(getActivity());
|
|
||||||
categoryGeneral.setText(R.string.category_general);
|
|
||||||
categoryGeneral.setTag(CategoryChoice.GENERAL);
|
|
||||||
categoryGeneral.setOnClickListener(this::onCategoryFilterClick);
|
|
||||||
categoryGeneral.setSelected(categoryChoice==CategoryChoice.GENERAL);
|
|
||||||
filtersWrap.addView(categoryGeneral, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
categorySpecialInterests=new FilterChipView(getActivity());
|
|
||||||
categorySpecialInterests.setText(R.string.category_special_interests);
|
|
||||||
categorySpecialInterests.setTag(CategoryChoice.SPECIAL);
|
|
||||||
categorySpecialInterests.setOnClickListener(this::onCategoryFilterClick);
|
|
||||||
categorySpecialInterests.setSelected(categoryChoice==CategoryChoice.SPECIAL);
|
|
||||||
filtersWrap.addView(categorySpecialInterests, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
|
|
||||||
regionalFilters=Arrays.stream(CatalogInstance.Region.values()).map(r->{
|
|
||||||
FilterChipView fv=new FilterChipView(getActivity());
|
|
||||||
fv.setTag(r);
|
|
||||||
fv.setText(switch(r){
|
|
||||||
case EUROPE -> R.string.server_filter_region_europe;
|
|
||||||
case NORTH_AMERICA -> R.string.server_filter_region_north_america;
|
|
||||||
case SOUTH_AMERICA -> R.string.server_filter_region_south_america;
|
|
||||||
case AFRICA -> R.string.server_filter_region_africa;
|
|
||||||
case ASIA -> R.string.server_filter_region_asia;
|
|
||||||
case OCEANIA -> R.string.server_filter_region_oceania;
|
|
||||||
});
|
|
||||||
fv.setSelected(r==chosenRegion);
|
|
||||||
fv.setOnClickListener(this::onRegionFilterClick);
|
|
||||||
filtersWrap.addView(fv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
||||||
return fv;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
focusThing=view.findViewById(R.id.focus_thing);
|
|
||||||
focusThing.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRegionFilterClick(View v){
|
|
||||||
CatalogInstance.Region r=(CatalogInstance.Region) v.getTag();
|
|
||||||
if(chosenRegion==r){
|
|
||||||
chosenRegion=null;
|
|
||||||
v.setSelected(false);
|
|
||||||
}else{
|
|
||||||
if(chosenRegion!=null)
|
|
||||||
filtersWrap.findViewWithTag(chosenRegion).setSelected(false);
|
|
||||||
chosenRegion=r;
|
|
||||||
v.setSelected(true);
|
|
||||||
}
|
|
||||||
updateFilteredList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCategoryFilterClick(View v){
|
|
||||||
CategoryChoice c=(CategoryChoice) v.getTag();
|
|
||||||
if(categoryChoice==c){
|
|
||||||
categoryChoice=null;
|
|
||||||
v.setSelected(false);
|
|
||||||
}else{
|
|
||||||
if(categoryChoice!=null)
|
|
||||||
filtersWrap.findViewWithTag(categoryChoice).setSelected(false);
|
|
||||||
categoryChoice=c;
|
|
||||||
v.setSelected(true);
|
|
||||||
}
|
|
||||||
updateFilteredList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onNextClick(View v){
|
|
||||||
if(chosenInstance==null){
|
|
||||||
String lang=Locale.getDefault().getLanguage();
|
|
||||||
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
|
|
||||||
if(instances.isEmpty()){
|
|
||||||
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
if(instances.isEmpty()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
chosenInstance=instances.get(new Random().nextInt(instances.size()));
|
|
||||||
}
|
|
||||||
super.onNextClick(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void proceedWithAuthOrSignup(Instance instance){
|
|
||||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
|
||||||
if(!instance.registrations){
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.error)
|
|
||||||
.setMessage(R.string.instance_signup_closed)
|
|
||||||
.setPositiveButton(R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putParcelable("instance", Parcels.wrap(instance));
|
|
||||||
Nav.go(getActivity(), InstanceRulesFragment.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// private String getEmojiForCategory(String category){
|
|
||||||
// return switch(category){
|
|
||||||
// case "all" -> "💬";
|
|
||||||
// case "academia" -> "📚";
|
|
||||||
// case "activism" -> "✊";
|
|
||||||
// case "food" -> "🍕";
|
|
||||||
// case "furry" -> "🦁";
|
|
||||||
// case "games" -> "🕹";
|
|
||||||
// case "general" -> "🐘";
|
|
||||||
// case "journalism" -> "📰";
|
|
||||||
// case "lgbt" -> "🏳️🌈";
|
|
||||||
// case "regional" -> "📍";
|
|
||||||
// case "art" -> "🎨";
|
|
||||||
// case "music" -> "🎼";
|
|
||||||
// case "tech" -> "📱";
|
|
||||||
// default -> "❓";
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
private int getEmojiForCategory(String category){
|
|
||||||
return switch(category){
|
|
||||||
case "all" -> R.drawable.ic_category_all;
|
|
||||||
case "academia" -> R.drawable.ic_category_academia;
|
|
||||||
case "activism" -> R.drawable.ic_category_activism;
|
|
||||||
case "food" -> R.drawable.ic_category_food;
|
|
||||||
case "furry" -> R.drawable.ic_category_furry;
|
|
||||||
case "games" -> R.drawable.ic_category_games;
|
|
||||||
case "general" -> R.drawable.ic_category_general;
|
|
||||||
case "journalism" -> R.drawable.ic_category_journalism;
|
|
||||||
case "lgbt" -> R.drawable.ic_category_lgbt;
|
|
||||||
case "regional" -> R.drawable.ic_category_regional;
|
|
||||||
case "art" -> R.drawable.ic_category_art;
|
|
||||||
case "music" -> R.drawable.ic_category_music;
|
|
||||||
case "tech" -> R.drawable.ic_category_tech;
|
|
||||||
default -> R.drawable.ic_category_unknown;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getTitleForCategory(String category){
|
|
||||||
return switch(category){
|
|
||||||
case "all" -> R.string.category_all;
|
|
||||||
case "academia" -> R.string.category_academia;
|
|
||||||
case "activism" -> R.string.category_activism;
|
|
||||||
case "food" -> R.string.category_food;
|
|
||||||
case "furry" -> R.string.category_furry;
|
|
||||||
case "games" -> R.string.category_games;
|
|
||||||
case "general" -> R.string.category_general;
|
|
||||||
case "journalism" -> R.string.category_journalism;
|
|
||||||
case "lgbt" -> R.string.category_lgbt;
|
|
||||||
case "regional" -> R.string.category_regional;
|
|
||||||
case "art" -> R.string.category_art;
|
|
||||||
case "music" -> R.string.category_music;
|
|
||||||
case "tech" -> R.string.category_tech;
|
|
||||||
default -> 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateFilteredList(){
|
|
||||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
|
||||||
filteredData.clear();
|
|
||||||
if(searchQueryMode){
|
|
||||||
if(!TextUtils.isEmpty(currentSearchQuery)){
|
|
||||||
for(CatalogInstance instance:data){
|
|
||||||
if(instance.domain.contains(currentSearchQuery)){
|
|
||||||
filteredData.add(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
for(CatalogInstance instance:data){
|
|
||||||
if(categoryChoice==null || categoryChoice.matches(instance.category)){
|
|
||||||
if(chosenRegion==null || instance.region==chosenRegion){
|
|
||||||
boolean signupSpeedMatches=switch(currentSignupSpeedFilter){
|
|
||||||
case ANY -> true;
|
|
||||||
case INSTANT -> !instance.approvalRequired;
|
|
||||||
case REVIEWED -> instance.approvalRequired;
|
|
||||||
};
|
|
||||||
if(signupSpeedMatches){
|
|
||||||
if(currentLanguage==null || instance.languages.contains(currentLanguage)){
|
|
||||||
filteredData.add(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DiffUtil.calculateDiff(new DiffUtil.Callback(){
|
|
||||||
@Override
|
|
||||||
public int getOldListSize(){
|
|
||||||
return prevData.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getNewListSize(){
|
|
||||||
return filteredData.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition){
|
|
||||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition){
|
|
||||||
return prevData.get(oldItemPosition)==filteredData.get(newItemPosition);
|
|
||||||
}
|
|
||||||
}).dispatchUpdatesTo(adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onApplyWindowInsets(WindowInsets insets){
|
|
||||||
topBar.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0);
|
|
||||||
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onBackPressed(){
|
|
||||||
if(searchQueryMode){
|
|
||||||
setSearchQueryMode(false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSearchQueryMode(boolean enabled){
|
|
||||||
searchQueryMode=enabled;
|
|
||||||
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) searchEdit.getLayoutParams();
|
|
||||||
if(searchQueryMode){
|
|
||||||
filtersScroll.setVisibility(View.GONE);
|
|
||||||
lp.removeRule(RelativeLayout.END_OF);
|
|
||||||
backBtn.setScaleX(0.83333333f);
|
|
||||||
backBtn.setScaleY(0.83333333f);
|
|
||||||
backBtn.setTranslationX(V.dp(8));
|
|
||||||
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0));
|
|
||||||
}else{
|
|
||||||
filtersScroll.setVisibility(View.VISIBLE);
|
|
||||||
focusThing.requestFocus();
|
|
||||||
searchEdit.setText("");
|
|
||||||
lp.addRule(RelativeLayout.END_OF, R.id.btn_back);
|
|
||||||
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
|
|
||||||
backBtn.setScaleX(1);
|
|
||||||
backBtn.setScaleY(1);
|
|
||||||
backBtn.setTranslationX(0);
|
|
||||||
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant)));
|
|
||||||
}
|
|
||||||
updateFilteredList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder> implements ImageLoaderRecyclerAdapter{
|
|
||||||
public InstancesAdapter(){
|
|
||||||
super(imgLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public InstanceCatalogSignupFragment.InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new InstanceCatalogSignupFragment.InstanceViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(InstanceCatalogSignupFragment.InstanceViewHolder holder, int position){
|
|
||||||
holder.bind(filteredData.get(position));
|
|
||||||
super.onBindViewHolder(holder, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount(){
|
|
||||||
return filteredData.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getImageCountForItem(int position){
|
|
||||||
return filteredData.get(position).thumbnailRequest!=null ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImageLoaderRequest getImageRequest(int position, int image){
|
|
||||||
return filteredData.get(position).thumbnailRequest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable, ImageLoaderViewHolder{
|
|
||||||
private final TextView title, description;
|
|
||||||
private final RadioButton radioButton;
|
|
||||||
private final ImageView thumbnail;
|
|
||||||
private boolean enabled;
|
|
||||||
|
|
||||||
public InstanceViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_instance_catalog, list);
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
description=findViewById(R.id.description);
|
|
||||||
radioButton=findViewById(R.id.radiobtn);
|
|
||||||
thumbnail=findViewById(R.id.image);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(CatalogInstance item){
|
|
||||||
title.setText(item.normalizedDomain);
|
|
||||||
radioButton.setChecked(chosenInstance==item);
|
|
||||||
if(item.thumbnailRequest==null)
|
|
||||||
thumbnail.setImageDrawable(null);
|
|
||||||
Instance realInstance=instancesCache.get(item.normalizedDomain);
|
|
||||||
float alpha;
|
|
||||||
if(realInstance!=null && !realInstance.registrations){
|
|
||||||
alpha=0.38f;
|
|
||||||
description.setText(R.string.not_accepting_new_members);
|
|
||||||
enabled=false;
|
|
||||||
}else{
|
|
||||||
alpha=1f;
|
|
||||||
description.setText(item.description);
|
|
||||||
enabled=true;
|
|
||||||
}
|
|
||||||
title.setAlpha(alpha);
|
|
||||||
description.setAlpha(alpha);
|
|
||||||
radioButton.setAlpha(alpha);
|
|
||||||
thumbnail.setAlpha(alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(){
|
|
||||||
if(chosenInstance==item)
|
|
||||||
return;
|
|
||||||
if(chosenInstance!=null){
|
|
||||||
int idx=filteredData.indexOf(chosenInstance);
|
|
||||||
if(idx!=-1){
|
|
||||||
boolean found=false;
|
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
|
||||||
if(holder.getAbsoluteAdapterPosition()==mergeAdapter.getPositionForAdapter(adapter)+idx && holder instanceof InstanceViewHolder ivh){
|
|
||||||
ivh.radioButton.setChecked(false);
|
|
||||||
found=true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!found)
|
|
||||||
adapter.notifyItemChanged(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
radioButton.setChecked(true);
|
|
||||||
if(chosenInstance==null)
|
|
||||||
nextButton.setEnabled(true);
|
|
||||||
chosenInstance=item;
|
|
||||||
loadInstanceInfo(chosenInstance.domain, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setImage(int index, Drawable image){
|
|
||||||
thumbnail.setImageDrawable(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearImage(int index){
|
|
||||||
setImage(index, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled(){
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum SignupSpeedFilter{
|
|
||||||
ANY,
|
|
||||||
INSTANT,
|
|
||||||
REVIEWED
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum CategoryChoice{
|
|
||||||
GENERAL,
|
|
||||||
SPECIAL;
|
|
||||||
|
|
||||||
public boolean matches(String category){
|
|
||||||
boolean isGeneral=(category==null || "general".equals(category));
|
|
||||||
return (this==GENERAL)==isGeneral;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Outline;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.RectF;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.ViewOutlineProvider;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.RadioButton;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.catalog.GetCatalogInstances;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
import org.joinmastodon.android.model.catalog.CatalogInstance;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import me.grishka.appkit.api.Callback;
|
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
|
||||||
import me.grishka.appkit.utils.V;
|
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
|
||||||
|
|
||||||
public class InstanceChooserLoginFragment extends InstanceCatalogFragment{
|
|
||||||
private View headerView;
|
|
||||||
private boolean loadedAutocomplete;
|
|
||||||
private ImageButton clearBtn;
|
|
||||||
|
|
||||||
public InstanceChooserLoginFragment(){
|
|
||||||
super(R.layout.fragment_login, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState){
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
dataLoaded();
|
|
||||||
setTitle(R.string.login_title);
|
|
||||||
if(!loadedAutocomplete){
|
|
||||||
loadAutocompleteServers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void proceedWithAuthOrSignup(Instance instance){
|
|
||||||
AccountSessionManager.getInstance().authenticate(getActivity(), instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void updateFilteredList(){
|
|
||||||
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
|
|
||||||
filteredData.clear();
|
|
||||||
if(currentSearchQuery.length()>0){
|
|
||||||
boolean foundExactMatch=false;
|
|
||||||
for(CatalogInstance inst:data){
|
|
||||||
if(inst.normalizedDomain.contains(currentSearchQuery)){
|
|
||||||
filteredData.add(inst);
|
|
||||||
if(inst.normalizedDomain.equals(currentSearchQuery))
|
|
||||||
foundExactMatch=true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!foundExactMatch)
|
|
||||||
filteredData.add(0, fakeInstance);
|
|
||||||
}
|
|
||||||
UiUtils.updateList(prevData, filteredData, list, adapter, Objects::equals);
|
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
|
||||||
list.getChildAt(i).invalidateOutline();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadAutocompleteServers(){
|
|
||||||
loadedAutocomplete=true;
|
|
||||||
new GetCatalogInstances(null, null)
|
|
||||||
.setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<CatalogInstance> result){
|
|
||||||
data.clear();
|
|
||||||
data.addAll(sortInstances(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.execNoAuth("");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onUpdateToolbar(){
|
|
||||||
super.onUpdateToolbar();
|
|
||||||
Toolbar toolbar=getToolbar();
|
|
||||||
toolbar.setElevation(0);
|
|
||||||
toolbar.setBackground(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RecyclerView.Adapter getAdapter(){
|
|
||||||
headerView=getActivity().getLayoutInflater().inflate(R.layout.header_onboarding_login, list, false);
|
|
||||||
clearBtn=headerView.findViewById(R.id.search_clear);
|
|
||||||
searchEdit=headerView.findViewById(R.id.search_edit);
|
|
||||||
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
|
|
||||||
searchEdit.addTextChangedListener(new TextWatcher(){
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count){
|
|
||||||
searchEdit.removeCallbacks(searchDebouncer);
|
|
||||||
searchEdit.postDelayed(searchDebouncer, 300);
|
|
||||||
|
|
||||||
if(s.length()>0){
|
|
||||||
fakeInstance.domain=fakeInstance.normalizedDomain=s.toString();
|
|
||||||
fakeInstance.description=getString(R.string.loading_instance);
|
|
||||||
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
|
|
||||||
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
|
|
||||||
ivh.rebind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(filteredData.isEmpty()){
|
|
||||||
filteredData.add(fakeInstance);
|
|
||||||
adapter.notifyItemInserted(0);
|
|
||||||
}
|
|
||||||
clearBtn.setVisibility(View.VISIBLE);
|
|
||||||
}else{
|
|
||||||
clearBtn.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s){
|
|
||||||
}
|
|
||||||
});
|
|
||||||
clearBtn.setOnClickListener(v->searchEdit.setText(""));
|
|
||||||
|
|
||||||
mergeAdapter=new MergeRecyclerAdapter();
|
|
||||||
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
|
||||||
mergeAdapter.addAdapter(adapter=new InstancesAdapter());
|
|
||||||
return mergeAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
|
||||||
|
|
||||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
|
||||||
@Override
|
|
||||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
|
||||||
if(parent.getChildViewHolder(view) instanceof InstanceViewHolder){
|
|
||||||
outRect.left=outRect.right=V.dp(16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
((UsableRecyclerView)list).setDrawSelectorOnTop(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceViewHolder>{
|
|
||||||
public InstancesAdapter(){
|
|
||||||
super(imgLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public InstanceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
|
||||||
return new InstanceViewHolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(InstanceViewHolder holder, int position){
|
|
||||||
holder.bind(filteredData.get(position));
|
|
||||||
super.onBindViewHolder(holder, position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount(){
|
|
||||||
return filteredData.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.Clickable{
|
|
||||||
private final TextView title, description;
|
|
||||||
private final RadioButton radioButton;
|
|
||||||
|
|
||||||
public InstanceViewHolder(){
|
|
||||||
super(getActivity(), R.layout.item_instance_login, list);
|
|
||||||
title=findViewById(R.id.title);
|
|
||||||
description=findViewById(R.id.description);
|
|
||||||
radioButton=findViewById(R.id.radiobtn);
|
|
||||||
radioButton.setMinWidth(0);
|
|
||||||
radioButton.setMinHeight(0);
|
|
||||||
|
|
||||||
itemView.setOutlineProvider(new ViewOutlineProvider(){
|
|
||||||
@Override
|
|
||||||
public void getOutline(View view, Outline outline){
|
|
||||||
outline.setRoundRect(0, getAbsoluteAdapterPosition()==1 ? 0 : V.dp(-4), view.getWidth(), view.getHeight()+(getAbsoluteAdapterPosition()==filteredData.size() ? 0 : V.dp(4)), V.dp(4));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
itemView.setClipToOutline(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBind(CatalogInstance item){
|
|
||||||
title.setText(item.normalizedDomain);
|
|
||||||
description.setText(item.description);
|
|
||||||
radioButton.setChecked(chosenInstance==item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(){
|
|
||||||
if(chosenInstance==item)
|
|
||||||
return;
|
|
||||||
if(chosenInstance!=null){
|
|
||||||
int idx=filteredData.indexOf(chosenInstance);
|
|
||||||
if(idx!=-1){
|
|
||||||
boolean found=false;
|
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
|
||||||
if(holder.getAbsoluteAdapterPosition()==mergeAdapter.getPositionForAdapter(adapter)+idx && holder instanceof InstanceViewHolder ivh){
|
|
||||||
ivh.radioButton.setChecked(false);
|
|
||||||
found=true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!found)
|
|
||||||
adapter.notifyItemChanged(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
radioButton.setChecked(true);
|
|
||||||
if(chosenInstance==null)
|
|
||||||
nextButton.setEnabled(true);
|
|
||||||
chosenInstance=item;
|
|
||||||
loadInstanceInfo(chosenInstance.domain, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.joinmastodon.android.fragments.onboarding;
|
package org.joinmastodon.android.fragments.onboarding;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -23,14 +22,14 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.utils.BindableViewHolder;
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.UsableRecyclerView;
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
public class InstanceRulesFragment extends ToolbarFragment{
|
public class InstanceRulesFragment extends AppKitFragment{
|
||||||
private UsableRecyclerView list;
|
private UsableRecyclerView list;
|
||||||
private MergeRecyclerAdapter adapter;
|
private MergeRecyclerAdapter adapter;
|
||||||
private Button btn;
|
private Button btn;
|
||||||
@@ -48,28 +47,31 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
|||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
|
||||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||||
setTitle(R.string.instance_rules_title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
|
||||||
|
|
||||||
list=view.findViewById(R.id.list);
|
list=view.findViewById(R.id.list);
|
||||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
|
View headerView=inflater.inflate(R.layout.item_list_header, list, false);
|
||||||
TextView text=headerView.findViewById(R.id.text);
|
TextView title=headerView.findViewById(R.id.title);
|
||||||
text.setText(getString(R.string.instance_rules_subtitle, instance.uri));
|
TextView subtitle=headerView.findViewById(R.id.subtitle);
|
||||||
|
headerView.findViewById(R.id.step_counter).setVisibility(View.GONE);
|
||||||
|
title.setText(R.string.instance_rules_title);
|
||||||
|
subtitle.setText(getString(R.string.instance_rules_subtitle, instance.uri));
|
||||||
|
|
||||||
adapter=new MergeRecyclerAdapter();
|
adapter=new MergeRecyclerAdapter();
|
||||||
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
|
||||||
adapter.addAdapter(new ItemsAdapter());
|
adapter.addAdapter(new ItemsAdapter());
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
|
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 16, 16, DividerItemDecoration.NOT_FIRST));
|
||||||
|
|
||||||
btn=view.findViewById(R.id.btn_next);
|
btn=view.findViewById(R.id.btn_next);
|
||||||
btn.setOnClickListener(v->onButtonClick());
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -77,15 +79,7 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
// setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||||
// view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onUpdateToolbar(){
|
|
||||||
super.onUpdateToolbar();
|
|
||||||
getToolbar().setBackground(null);
|
|
||||||
getToolbar().setElevation(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onButtonClick(){
|
protected void onButtonClick(){
|
||||||
@@ -125,22 +119,23 @@ public class InstanceRulesFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ItemViewHolder extends BindableViewHolder<Instance.Rule>{
|
private class ItemViewHolder extends BindableViewHolder<Instance.Rule>{
|
||||||
private final TextView text, number;
|
private final TextView title, subtitle;
|
||||||
|
private final ImageView checkbox;
|
||||||
|
|
||||||
public ItemViewHolder(){
|
public ItemViewHolder(){
|
||||||
super(getActivity(), R.layout.item_server_rule, list);
|
super(getActivity(), R.layout.item_report_choice, list);
|
||||||
text=findViewById(R.id.text);
|
title=findViewById(R.id.title);
|
||||||
number=findViewById(R.id.number);
|
subtitle=findViewById(R.id.subtitle);
|
||||||
|
checkbox=findViewById(R.id.checkbox);
|
||||||
|
subtitle.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
|
||||||
@Override
|
@Override
|
||||||
public void onBind(Instance.Rule item){
|
public void onBind(Instance.Rule item){
|
||||||
if(item.parsedText==null){
|
if(item.parsedText==null){
|
||||||
item.parsedText=HtmlParser.parseLinks(item.text);
|
item.parsedText=HtmlParser.parseLinks(item.text);
|
||||||
}
|
}
|
||||||
text.setText(item.parsedText);
|
title.setText(item.parsedText);
|
||||||
number.setText(String.format("%d", getAbsoluteAdapterPosition()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,15 +25,14 @@ import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
|
||||||
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
|
||||||
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
|
import org.joinmastodon.android.api.requests.oauth.GetOauthToken;
|
||||||
import org.joinmastodon.android.api.session.AccountActivationInfo;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.Application;
|
import org.joinmastodon.android.model.Application;
|
||||||
import org.joinmastodon.android.model.Instance;
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Token;
|
import org.joinmastodon.android.model.Token;
|
||||||
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
|
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -50,28 +49,30 @@ import me.grishka.appkit.Nav;
|
|||||||
import me.grishka.appkit.api.APIRequest;
|
import me.grishka.appkit.api.APIRequest;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
import me.grishka.appkit.fragments.ToolbarFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
import me.grishka.appkit.imageloader.ViewImageLoader;
|
import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class SignupFragment extends ToolbarFragment{
|
public class SignupFragment extends AppKitFragment{
|
||||||
private static final int AVATAR_RESULT=198;
|
private static final int AVATAR_RESULT=198;
|
||||||
private static final String TAG="SignupFragment";
|
private static final String TAG="SignupFragment";
|
||||||
|
|
||||||
private Instance instance;
|
private Instance instance;
|
||||||
|
|
||||||
private EditText displayName, username, email, password, passwordConfirm, reason;
|
private EditText displayName, username, email, password, reason;
|
||||||
private FloatingHintEditTextLayout displayNameWrap, usernameWrap, emailWrap, passwordWrap, passwordConfirmWrap, reasonWrap;
|
|
||||||
private TextView reasonExplain;
|
private TextView reasonExplain;
|
||||||
private Button btn;
|
private Button btn;
|
||||||
private View buttonBar;
|
private View buttonBar;
|
||||||
private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState());
|
private TextWatcher buttonStateUpdater=new SimpleTextWatcher(e->updateButtonState());
|
||||||
|
private ImageView avatar;
|
||||||
private APIRequest currentBackgroundRequest;
|
private APIRequest currentBackgroundRequest;
|
||||||
private Application apiApplication;
|
private Application apiApplication;
|
||||||
private Token apiToken;
|
private Token apiToken;
|
||||||
private boolean submitAfterGettingToken;
|
private boolean submitAfterGettingToken;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
|
private Uri avatarUri;
|
||||||
|
private File avatarFile;
|
||||||
private HashSet<EditText> errorFields=new HashSet<>();
|
private HashSet<EditText> errorFields=new HashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,30 +81,25 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
|
||||||
createAppAndGetToken();
|
createAppAndGetToken();
|
||||||
setTitle(R.string.signup_title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState){
|
||||||
View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false);
|
View view=inflater.inflate(R.layout.fragment_onboarding_signup, container, false);
|
||||||
|
|
||||||
|
TextView title=view.findViewById(R.id.title);
|
||||||
TextView domain=view.findViewById(R.id.domain);
|
TextView domain=view.findViewById(R.id.domain);
|
||||||
displayName=view.findViewById(R.id.display_name);
|
displayName=view.findViewById(R.id.display_name);
|
||||||
username=view.findViewById(R.id.username);
|
username=view.findViewById(R.id.username);
|
||||||
email=view.findViewById(R.id.email);
|
email=view.findViewById(R.id.email);
|
||||||
password=view.findViewById(R.id.password);
|
password=view.findViewById(R.id.password);
|
||||||
passwordConfirm=view.findViewById(R.id.password_confirm);
|
avatar=view.findViewById(R.id.avatar);
|
||||||
reason=view.findViewById(R.id.reason);
|
reason=view.findViewById(R.id.reason);
|
||||||
reasonExplain=view.findViewById(R.id.reason_explain);
|
reasonExplain=view.findViewById(R.id.reason_explain);
|
||||||
|
View avaWrap=view.findViewById(R.id.ava_wrap);
|
||||||
|
|
||||||
displayNameWrap=view.findViewById(R.id.display_name_wrap);
|
title.setText(getString(R.string.signup_title, instance.uri));
|
||||||
usernameWrap=view.findViewById(R.id.username_wrap);
|
|
||||||
emailWrap=view.findViewById(R.id.email_wrap);
|
|
||||||
passwordWrap=view.findViewById(R.id.password_wrap);
|
|
||||||
passwordConfirmWrap=view.findViewById(R.id.password_confirm_wrap);
|
|
||||||
reasonWrap=view.findViewById(R.id.reason_wrap);
|
|
||||||
|
|
||||||
domain.setText('@'+instance.uri);
|
domain.setText('@'+instance.uri);
|
||||||
|
|
||||||
username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
username.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
@@ -118,20 +114,23 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
btn=view.findViewById(R.id.btn_next);
|
btn=view.findViewById(R.id.btn_next);
|
||||||
btn.setOnClickListener(v->onButtonClick());
|
btn.setOnClickListener(v->onButtonClick());
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
|
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
|
||||||
updateButtonState();
|
updateButtonState();
|
||||||
|
|
||||||
username.addTextChangedListener(buttonStateUpdater);
|
username.addTextChangedListener(buttonStateUpdater);
|
||||||
email.addTextChangedListener(buttonStateUpdater);
|
email.addTextChangedListener(buttonStateUpdater);
|
||||||
password.addTextChangedListener(buttonStateUpdater);
|
password.addTextChangedListener(buttonStateUpdater);
|
||||||
passwordConfirm.addTextChangedListener(buttonStateUpdater);
|
|
||||||
reason.addTextChangedListener(buttonStateUpdater);
|
reason.addTextChangedListener(buttonStateUpdater);
|
||||||
|
|
||||||
username.addTextChangedListener(new ErrorClearingListener(username));
|
username.addTextChangedListener(new ErrorClearingListener(username));
|
||||||
email.addTextChangedListener(new ErrorClearingListener(email));
|
email.addTextChangedListener(new ErrorClearingListener(email));
|
||||||
password.addTextChangedListener(new ErrorClearingListener(password));
|
password.addTextChangedListener(new ErrorClearingListener(password));
|
||||||
passwordConfirm.addTextChangedListener(new ErrorClearingListener(passwordConfirm));
|
|
||||||
reason.addTextChangedListener(new ErrorClearingListener(reason));
|
reason.addTextChangedListener(new ErrorClearingListener(reason));
|
||||||
|
|
||||||
|
avaWrap.setOutlineProvider(OutlineProviders.roundedRect(22));
|
||||||
|
avaWrap.setClipToOutline(true);
|
||||||
|
avaWrap.setOnClickListener(v->onAvatarClick());
|
||||||
|
|
||||||
if(!instance.approvalRequired){
|
if(!instance.approvalRequired){
|
||||||
reason.setVisibility(View.GONE);
|
reason.setVisibility(View.GONE);
|
||||||
reasonExplain.setVisibility(View.GONE);
|
reasonExplain.setVisibility(View.GONE);
|
||||||
@@ -143,23 +142,10 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLight));
|
||||||
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onUpdateToolbar(){
|
|
||||||
super.onUpdateToolbar();
|
|
||||||
getToolbar().setBackground(null);
|
|
||||||
getToolbar().setElevation(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onButtonClick(){
|
private void onButtonClick(){
|
||||||
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
|
|
||||||
passwordConfirm.setError(getString(R.string.signup_passwords_dont_match));
|
|
||||||
passwordConfirmWrap.setErrorState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showProgressDialog();
|
showProgressDialog();
|
||||||
if(currentBackgroundRequest!=null){
|
if(currentBackgroundRequest!=null){
|
||||||
submitAfterGettingToken=true;
|
submitAfterGettingToken=true;
|
||||||
@@ -174,8 +160,32 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void copyAvatar(Runnable onDone){
|
||||||
|
// Need to copy the avatar from the content provider to somewhere accessible in case the app gets killed between signup and account activation
|
||||||
|
Activity activity=getActivity();
|
||||||
|
MastodonAPIController.runInBackground(()->{
|
||||||
|
String origName=UiUtils.getFileName(avatarUri);
|
||||||
|
avatarFile=new File(activity.getCacheDir(), System.currentTimeMillis()+origName.substring(origName.lastIndexOf('.')));
|
||||||
|
try(InputStream in=activity.getContentResolver().openInputStream(avatarUri);
|
||||||
|
FileOutputStream out=new FileOutputStream(avatarFile)){
|
||||||
|
byte[] buf=new byte[10240];
|
||||||
|
int read;
|
||||||
|
while((read=in.read(buf))>0){
|
||||||
|
out.write(buf, 0, read);
|
||||||
|
}
|
||||||
|
}catch(IOException x){
|
||||||
|
Log.w(TAG, "copyAvatar: error copying", x);
|
||||||
|
}
|
||||||
|
activity.runOnUiThread(onDone);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void submit(){
|
private void submit(){
|
||||||
actuallySubmit();
|
if(avatarUri!=null && (avatarFile==null || !avatarFile.exists())){
|
||||||
|
copyAvatar(this::actuallySubmit);
|
||||||
|
}else{
|
||||||
|
actuallySubmit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void actuallySubmit(){
|
private void actuallySubmit(){
|
||||||
@@ -194,7 +204,9 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
fakeAccount.acct=fakeAccount.username=username;
|
fakeAccount.acct=fakeAccount.username=username;
|
||||||
fakeAccount.id="tmp"+System.currentTimeMillis();
|
fakeAccount.id="tmp"+System.currentTimeMillis();
|
||||||
fakeAccount.displayName=displayName.getText().toString();
|
fakeAccount.displayName=displayName.getText().toString();
|
||||||
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, new AccountActivationInfo(email, System.currentTimeMillis()));
|
if(avatarFile!=null)
|
||||||
|
fakeAccount.avatar=avatarFile.getAbsolutePath();
|
||||||
|
AccountSessionManager.getInstance().addAccount(instance, result, fakeAccount, apiApplication, false);
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
|
args.putString("account", AccountSessionManager.getInstance().getLastActiveAccountID());
|
||||||
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
|
||||||
@@ -213,7 +225,6 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
|
||||||
getFieldWrapByName(fieldName).setErrorState();
|
|
||||||
errorFields.add(field);
|
errorFields.add(field);
|
||||||
if(first){
|
if(first){
|
||||||
first=false;
|
first=false;
|
||||||
@@ -241,16 +252,6 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private FloatingHintEditTextLayout getFieldWrapByName(String name){
|
|
||||||
return switch(name){
|
|
||||||
case "email" -> emailWrap;
|
|
||||||
case "username" -> usernameWrap;
|
|
||||||
case "password" -> passwordWrap;
|
|
||||||
case "reason" -> reasonWrap;
|
|
||||||
default -> null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showProgressDialog(){
|
private void showProgressDialog(){
|
||||||
if(progressDialog==null){
|
if(progressDialog==null){
|
||||||
progressDialog=new ProgressDialog(getActivity());
|
progressDialog=new ProgressDialog(getActivity());
|
||||||
@@ -261,7 +262,7 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateButtonState(){
|
private void updateButtonState(){
|
||||||
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && passwordConfirm.length()>=8 && (!instance.approvalRequired || reason.length()>0));
|
btn.setEnabled(username.length()>0 && email.length()>0 && email.getText().toString().contains("@") && password.length()>=8 && (!instance.approvalRequired || reason.length()>0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAppAndGetToken(){
|
private void createAppAndGetToken(){
|
||||||
@@ -323,6 +324,20 @@ public class SignupFragment extends ToolbarFragment{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data){
|
||||||
|
if(requestCode==AVATAR_RESULT && resultCode==Activity.RESULT_OK){
|
||||||
|
avatarUri=data.getData();
|
||||||
|
if(avatarFile!=null && avatarFile.exists())
|
||||||
|
avatarFile.delete();
|
||||||
|
ViewImageLoader.load(avatar, getResources().getDrawable(R.drawable.default_avatar), new UrlImageLoaderRequest(avatarUri, V.dp(100), V.dp(100)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAvatarClick(){
|
||||||
|
startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*").addCategory(Intent.CATEGORY_OPENABLE), AVATAR_RESULT);
|
||||||
|
}
|
||||||
|
|
||||||
private class ErrorClearingListener implements TextWatcher{
|
private class ErrorClearingListener implements TextWatcher{
|
||||||
public final EditText editText;
|
public final EditText editText;
|
||||||
|
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false);
|
||||||
for(StatusDisplayItem item:items){
|
for(StatusDisplayItem item:items){
|
||||||
if(item instanceof ImageStatusDisplayItem isdi){
|
if(item instanceof ImageStatusDisplayItem isdi){
|
||||||
isdi.horizontalInset=V.dp(40+32);
|
isdi.horizontalInset=V.dp(40+32);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import android.view.ViewGroup;
|
|||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Switch;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
@@ -29,17 +28,15 @@ import java.util.ArrayList;
|
|||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
import me.grishka.appkit.fragments.ToolbarFragment;
|
||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class ReportCommentFragment extends MastodonToolbarFragment{
|
public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private Account reportAccount;
|
private Account reportAccount;
|
||||||
private Button btn;
|
private Button btn;
|
||||||
private View buttonBar, forwardReportItem;
|
private View buttonBar;
|
||||||
private TextView forwardReportText;
|
|
||||||
private Switch forwardReportSwitch;
|
|
||||||
private EditText commentEdit;
|
private EditText commentEdit;
|
||||||
private boolean forwardReport;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
@@ -80,17 +77,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
|||||||
view.findViewById(R.id.btn_back).setOnClickListener(this::onButtonClick);
|
view.findViewById(R.id.btn_back).setOnClickListener(this::onButtonClick);
|
||||||
buttonBar=view.findViewById(R.id.button_bar);
|
buttonBar=view.findViewById(R.id.button_bar);
|
||||||
commentEdit=view.findViewById(R.id.text);
|
commentEdit=view.findViewById(R.id.text);
|
||||||
forwardReportSwitch = view.findViewById(R.id.forward_report_switch);
|
|
||||||
forwardReportItem = view.findViewById(R.id.forward_report);
|
|
||||||
forwardReportText = view.findViewById(R.id.forward_report_text);
|
|
||||||
String domain = reportAccount.getDomain();
|
|
||||||
if (domain == null) {
|
|
||||||
forwardReportItem.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
forwardReportItem.setOnClickListener(this::onForwardReportClick);
|
|
||||||
forwardReportText.setText(getActivity().getString(R.string.sk_forward_report_to, domain));
|
|
||||||
forwardReportSwitch.setChecked(forwardReport = true);
|
|
||||||
}
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +102,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
|||||||
ReportReason reason=ReportReason.valueOf(getArguments().getString("reason"));
|
ReportReason reason=ReportReason.valueOf(getArguments().getString("reason"));
|
||||||
ArrayList<String> statusIDs=getArguments().getStringArrayList("statusIDs");
|
ArrayList<String> statusIDs=getArguments().getStringArrayList("statusIDs");
|
||||||
ArrayList<String> ruleIDs=getArguments().getStringArrayList("ruleIDs");
|
ArrayList<String> ruleIDs=getArguments().getStringArrayList("ruleIDs");
|
||||||
new SendReport(reportAccount.id, reason, statusIDs, ruleIDs, v.getId()==R.id.btn_back ? null : commentEdit.getText().toString(), forwardReport)
|
new SendReport(reportAccount.id, reason, statusIDs, ruleIDs, v.getId()==R.id.btn_back ? null : commentEdit.getText().toString(), true)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Object result){
|
public void onSuccess(Object result){
|
||||||
@@ -136,11 +123,6 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
|||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onForwardReportClick(View v) {
|
|
||||||
forwardReport = !forwardReport;
|
|
||||||
forwardReportSwitch.setChecked(forwardReport);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void onFinishReportFragments(FinishReportFragmentsEvent ev){
|
public void onFinishReportFragments(FinishReportFragmentsEvent ev){
|
||||||
if(ev.reportAccountID.equals(reportAccount.id))
|
if(ev.reportAccountID.equals(reportAccount.id))
|
||||||
|
|||||||
@@ -164,10 +164,6 @@ public class Account extends BaseModel{
|
|||||||
return '@'+acct;
|
return '@'+acct;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getShortUsername() {
|
|
||||||
return '@'+acct.split("@")[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "Account{"+
|
return "Account{"+
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package org.joinmastodon.android.model;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
|
||||||
import org.parceler.Parcel;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Parcel
|
|
||||||
public class Announcement extends BaseModel implements DisplayItemsParent {
|
|
||||||
@RequiredField
|
|
||||||
public String id;
|
|
||||||
@RequiredField
|
|
||||||
public String content;
|
|
||||||
public Instant startsAt;
|
|
||||||
public Instant endsAt;
|
|
||||||
public boolean published;
|
|
||||||
public boolean allDay;
|
|
||||||
public Instant publishedAt;
|
|
||||||
public Instant updatedAt;
|
|
||||||
public boolean read;
|
|
||||||
public List<Emoji> emojis;
|
|
||||||
public List<Mention> mentions;
|
|
||||||
public List<Hashtag> tags;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Announcement{" +
|
|
||||||
"id='" + id + '\'' +
|
|
||||||
", content='" + content + '\'' +
|
|
||||||
", startsAt=" + startsAt +
|
|
||||||
", endsAt=" + endsAt +
|
|
||||||
", published=" + published +
|
|
||||||
", allDay=" + allDay +
|
|
||||||
", publishedAt=" + publishedAt +
|
|
||||||
", updatedAt=" + updatedAt +
|
|
||||||
", read=" + read +
|
|
||||||
", emojis=" + emojis +
|
|
||||||
", mentions=" + mentions +
|
|
||||||
", tags=" + tags +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status toStatus() {
|
|
||||||
Status s = new Status();
|
|
||||||
s.id = id;
|
|
||||||
s.mediaAttachments = List.of();
|
|
||||||
s.createdAt = startsAt != null ? startsAt : publishedAt;
|
|
||||||
if (updatedAt != null) s.editedAt = updatedAt;
|
|
||||||
s.content = s.text = content;
|
|
||||||
s.spoilerText = "";
|
|
||||||
s.visibility = StatusPrivacy.PUBLIC;
|
|
||||||
s.mentions = List.of();
|
|
||||||
s.tags = List.of();
|
|
||||||
s.emojis = List.of();
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getID() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,11 +14,9 @@ import org.parceler.Parcel;
|
|||||||
import org.parceler.ParcelConstructor;
|
import org.parceler.ParcelConstructor;
|
||||||
import org.parceler.ParcelProperty;
|
import org.parceler.ParcelProperty;
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
public class Attachment extends BaseModel{
|
public class Attachment extends BaseModel{
|
||||||
// @RequiredField
|
@RequiredField
|
||||||
public String id;
|
public String id;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public Type type;
|
public Type type;
|
||||||
@@ -47,26 +45,26 @@ public class Attachment extends BaseModel{
|
|||||||
|
|
||||||
public int getWidth(){
|
public int getWidth(){
|
||||||
if(meta==null)
|
if(meta==null)
|
||||||
return 1920;
|
return 0;
|
||||||
if(meta.width>0)
|
if(meta.width>0)
|
||||||
return meta.width;
|
return meta.width;
|
||||||
if(meta.original!=null && meta.original.width>0)
|
if(meta.original!=null && meta.original.width>0)
|
||||||
return meta.original.width;
|
return meta.original.width;
|
||||||
if(meta.small!=null && meta.small.width>0)
|
if(meta.small!=null && meta.small.width>0)
|
||||||
return meta.small.width;
|
return meta.small.width;
|
||||||
return 1920;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(){
|
public int getHeight(){
|
||||||
if(meta==null)
|
if(meta==null)
|
||||||
return 1080;
|
return 0;
|
||||||
if(meta.height>0)
|
if(meta.height>0)
|
||||||
return meta.height;
|
return meta.height;
|
||||||
if(meta.original!=null && meta.original.height>0)
|
if(meta.original!=null && meta.original.height>0)
|
||||||
return meta.original.height;
|
return meta.original.height;
|
||||||
if(meta.small!=null && meta.small.height>0)
|
if(meta.small!=null && meta.small.height>0)
|
||||||
return meta.small.height;
|
return meta.small.height;
|
||||||
return 1080;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getDuration(){
|
public double getDuration(){
|
||||||
@@ -87,12 +85,6 @@ public class Attachment extends BaseModel{
|
|||||||
if(placeholder!=null)
|
if(placeholder!=null)
|
||||||
blurhashPlaceholder=new BlurHashDrawable(placeholder, getWidth(), getHeight());
|
blurhashPlaceholder=new BlurHashDrawable(placeholder, getWidth(), getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id == null) {
|
|
||||||
// akkoma servers doesn't provide IDs for attachments,
|
|
||||||
// but IDs are needed by the AudioPlayerService
|
|
||||||
id = "" + this.hashCode();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ import com.google.gson.annotations.SerializedName;
|
|||||||
|
|
||||||
import org.joinmastodon.android.api.ObjectValidationException;
|
import org.joinmastodon.android.api.ObjectValidationException;
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
import org.parceler.Parcel;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@Parcel
|
|
||||||
public class Filter extends BaseModel{
|
public class Filter extends BaseModel{
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public String id;
|
public String id;
|
||||||
@@ -23,7 +21,6 @@ public class Filter extends BaseModel{
|
|||||||
public Instant expiresAt;
|
public Instant expiresAt;
|
||||||
public boolean irreversible;
|
public boolean irreversible;
|
||||||
public boolean wholeWord;
|
public boolean wholeWord;
|
||||||
public FilterAction filterAction;
|
|
||||||
|
|
||||||
@SerializedName("context")
|
@SerializedName("context")
|
||||||
private List<FilterContext> _context;
|
private List<FilterContext> _context;
|
||||||
@@ -79,11 +76,4 @@ public class Filter extends BaseModel{
|
|||||||
@SerializedName("thread")
|
@SerializedName("thread")
|
||||||
THREAD
|
THREAD
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FilterAction{
|
|
||||||
@SerializedName("hide")
|
|
||||||
HIDE,
|
|
||||||
@SerializedName("warn")
|
|
||||||
WARN
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package org.joinmastodon.android.model;
|
|
||||||
|
|
||||||
import org.parceler.Parcel;
|
|
||||||
|
|
||||||
@Parcel
|
|
||||||
public class FilterResult extends BaseModel {
|
|
||||||
public Filter filter;
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ public class Instance extends BaseModel{
|
|||||||
/**
|
/**
|
||||||
* Admin-defined description of the Mastodon site.
|
* Admin-defined description of the Mastodon site.
|
||||||
*/
|
*/
|
||||||
// @RequiredField
|
@RequiredField
|
||||||
public String description;
|
public String description;
|
||||||
/**
|
/**
|
||||||
* A shorter description defined by the admin.
|
* A shorter description defined by the admin.
|
||||||
@@ -37,7 +37,7 @@ public class Instance extends BaseModel{
|
|||||||
/**
|
/**
|
||||||
* An email that may be contacted for any inquiries.
|
* An email that may be contacted for any inquiries.
|
||||||
*/
|
*/
|
||||||
// @RequiredField
|
@RequiredField
|
||||||
public String email;
|
public String email;
|
||||||
/**
|
/**
|
||||||
* The version of Mastodon installed on the instance.
|
* The version of Mastodon installed on the instance.
|
||||||
@@ -82,8 +82,6 @@ public class Instance extends BaseModel{
|
|||||||
// non-standard field in some Mastodon forks
|
// non-standard field in some Mastodon forks
|
||||||
public int maxTootChars;
|
public int maxTootChars;
|
||||||
|
|
||||||
public V2 v2;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postprocess() throws ObjectValidationException{
|
public void postprocess() throws ObjectValidationException{
|
||||||
super.postprocess();
|
super.postprocess();
|
||||||
@@ -178,19 +176,4 @@ public class Instance extends BaseModel{
|
|||||||
public int minExpiration;
|
public int minExpiration;
|
||||||
public int maxExpiration;
|
public int maxExpiration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcel
|
|
||||||
public static class V2 extends BaseModel {
|
|
||||||
public V2.Configuration configuration;
|
|
||||||
|
|
||||||
@Parcel
|
|
||||||
public static class Configuration {
|
|
||||||
public TranslationConfiguration translation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parcel
|
|
||||||
public static class TranslationConfiguration{
|
|
||||||
public boolean enabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.joinmastodon.android.model;
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.RequiredField;
|
import org.joinmastodon.android.api.RequiredField;
|
||||||
@@ -13,9 +11,9 @@ public class ListTimeline extends BaseModel {
|
|||||||
public String id;
|
public String id;
|
||||||
@RequiredField
|
@RequiredField
|
||||||
public String title;
|
public String title;
|
||||||
|
@RequiredField
|
||||||
public RepliesPolicy repliesPolicy;
|
public RepliesPolicy repliesPolicy;
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "List{" +
|
return "List{" +
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ public class Notification extends BaseModel implements DisplayItemsParent{
|
|||||||
@SerializedName("poll")
|
@SerializedName("poll")
|
||||||
POLL,
|
POLL,
|
||||||
@SerializedName("status")
|
@SerializedName("status")
|
||||||
STATUS,
|
STATUS
|
||||||
@SerializedName("update")
|
|
||||||
UPDATE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ public class Poll extends BaseModel{
|
|||||||
public String title;
|
public String title;
|
||||||
public Integer votesCount;
|
public Integer votesCount;
|
||||||
|
|
||||||
public Option() {}
|
|
||||||
public Option(String title) {
|
|
||||||
this.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "Option{"+
|
return "Option{"+
|
||||||
|
|||||||
@@ -43,11 +43,7 @@ public class PushNotification extends BaseModel{
|
|||||||
@SerializedName("follow")
|
@SerializedName("follow")
|
||||||
FOLLOW(R.string.notification_type_follow),
|
FOLLOW(R.string.notification_type_follow),
|
||||||
@SerializedName("poll")
|
@SerializedName("poll")
|
||||||
POLL(R.string.notification_type_poll),
|
POLL(R.string.notification_type_poll);
|
||||||
@SerializedName("status")
|
|
||||||
STATUS(R.string.sk_notification_type_status),
|
|
||||||
@SerializedName("update")
|
|
||||||
UPDATE(R.string.sk_notification_type_update);
|
|
||||||
|
|
||||||
@StringRes
|
@StringRes
|
||||||
public final int localizedName;
|
public final int localizedName;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user