Compare commits
2 Commits
2.1.4+fork
...
upstream/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdff407514 | ||
|
|
422c3c3809 |
3
.github/FUNDING.yml
vendored
@@ -1,7 +1,6 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: LucasGGamerM
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
custom: ["https://liberapay.com/LucasGGamerM/donate", liberapay.com]
|
|
||||||
patreon: # mastodon
|
patreon: # mastodon
|
||||||
open_collective: # Replace with a single Open Collective username e.g., user1
|
open_collective: # Replace with a single Open Collective username e.g., user1
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -22,6 +22,8 @@ Steps to reproduce the behavior:
|
|||||||
**Does this happen in the official app?**
|
**Does this happen in the official app?**
|
||||||
|
|
||||||
Does this issue also occur with the respective upstream release?
|
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
|
> No / Yes
|
||||||
|
|
||||||
> In case it does, please consider filing an [upstream bug report](https://github.com/mastodon/mastodon-android/issues) instead.
|
> In case it does, please consider filing an [upstream bug report](https://github.com/mastodon/mastodon-android/issues) instead.
|
||||||
@@ -33,7 +35,7 @@ If applicable, add screenshots (and screen recordings, if possible) to help expl
|
|||||||
|
|
||||||
**Version**
|
**Version**
|
||||||
|
|
||||||
Moshidon version: [e.g. v1.1.4+fork.#]
|
Megalodon version: [e.g. v1.1.4+fork.#]
|
||||||
|
|
||||||
**Crash log**
|
**Crash log**
|
||||||
|
|
||||||
|
|||||||
71
.github/workflows/nightly-builds.yml
vendored
@@ -1,71 +0,0 @@
|
|||||||
name: Nightly builds
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Appkit Repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
repository: grishka/appkit
|
|
||||||
|
|
||||||
- name: set up JDK 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'corretto'
|
|
||||||
cache: gradle
|
|
||||||
|
|
||||||
- name: Comment out signing config in appkits gradle file
|
|
||||||
run: |
|
|
||||||
sed -i 's/sign publishing\.publications\.release/\/\/ sign publishing.publications.release/' appkit/maven-push.gradle
|
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew for Appkit
|
|
||||||
run: chmod +x gradlew
|
|
||||||
|
|
||||||
- name: Compile appkit
|
|
||||||
run: ./gradlew publishToMavenLocal
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: set up JDK 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'corretto'
|
|
||||||
cache: gradle
|
|
||||||
|
|
||||||
- name: Get current date
|
|
||||||
id: date
|
|
||||||
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
|
||||||
run: chmod +x gradlew
|
|
||||||
|
|
||||||
- name: Decode Keystore
|
|
||||||
id: decode_keystore
|
|
||||||
uses: timheuer/base64-to-file@v1
|
|
||||||
with:
|
|
||||||
fileName: 'nightly_keystore.jks'
|
|
||||||
fileDir: './mastodon/keystore/'
|
|
||||||
encodedString: ${{ secrets.KEYSTORE }}
|
|
||||||
|
|
||||||
- name: Build with Gradle
|
|
||||||
run: ./gradlew assembleNightly
|
|
||||||
env:
|
|
||||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
|
||||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
|
||||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
|
||||||
CURRENT_DATE: ${{ steps.date.outputs.date }}
|
|
||||||
|
|
||||||
- name: Upload a Build Artifact
|
|
||||||
uses: actions/upload-artifact@v3.1.2
|
|
||||||
with:
|
|
||||||
name: moshidon-nightly.apk
|
|
||||||
path: ./mastodon/build/outputs/apk/nightly/moshidon-nightly.apk
|
|
||||||
2
.gitignore
vendored
@@ -9,5 +9,3 @@
|
|||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
*.jks
|
*.jks
|
||||||
*.keystore
|
|
||||||
/mastodon/keystore/nightly_keystore.keystore
|
|
||||||
|
|||||||
9
FAQ.md
@@ -1,9 +0,0 @@
|
|||||||
## F.A.Q
|
|
||||||
|
|
||||||
Q: What are the main differences between Moshidon and Megalodon?
|
|
||||||
|
|
||||||
A: There are many, but the most outstanding differences are: the ability to have other server's local timeline inside the app. It can be acessed in the "Add community" option in the top right corner of the Edit timelines screen. Other outstanding features that Moshidon has are some quality of life improvements, such as notification actions and allowing for unlisted replies by default. Most other features are pretty minor, such as profile notes directly available in the person's profile. Other features are quite minor usability and visibility improvements. All of which can be found in the settings page.
|
|
||||||
|
|
||||||
Q: Will there ever be a version of Moshidon for iOS?
|
|
||||||
|
|
||||||
A: No. As android and iOS apps do not share code, it is incredibly hard to port.
|
|
||||||
231
README.md
@@ -1,125 +1,128 @@
|
|||||||

|

|
||||||
|
|
||||||
# Moshidon, the material you mastodon client!
|
# 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://translate.codeberg.org/engage/megalodon/)
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
|
|
||||||
|
|
||||||
[](https://github.com/LucasGGamerM/moshidon-nightly/releases/latest/download/moshidon-nightly.apk)
|
|
||||||
|
|
||||||
|
|
||||||
[](https://translate.codeberg.org/engage/moshidon/)
|
|
||||||
|
|
||||||
[](https://github.com/LucasGGamerM/moshidon/actions/workflows/nightly-builds.yml)
|
[](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk)
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android.moshinda"><img height="50" alt="Get it on Google Play" src="img/google-play-badge.png"></a>
|
<a href="https://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>
|
||||||
|
|
||||||
<a href="https://f-droid.org/pt_BR/packages/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on F-Droid" src="img/f-droid-badge.png"></a>
|
<a href="#installation"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
||||||
|
|
||||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a>
|
|
||||||
|
|
||||||
## Help out the project by donating at: https://github.com/sponsors/LucasGGamerM!
|
> A fork of the [Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app, focusing on [Glitch](https://glitch-soc.github.io/docs) compatibility, a pretty UI and adding new features that I feel make using the Fediverse a more pleasant experience.
|
||||||
### We also support LiberaPay at: https://liberapay.com/LucasGGamerM/donate (Currently broken)
|
|
||||||
|
|
||||||
### You can also donate some Monero through this wallet address as well:
|
|
||||||
4886mdarcyB6Yf8Qc6vDJBK1fz6ibHFLZUmHb4GZZz9yLGNhcG3XC64e5UZ8dVQYTLZb82W6P9WhteowW4STJEec97Gf22j
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Key features
|
## Key features
|
||||||
|
|
||||||
### **The ability to add other server's local timeline to your timelines**
|
|
||||||
|
|
||||||
It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts!
|
|
||||||
|
|
||||||
### **View remote profiles**
|
|
||||||
|
|
||||||
You can now see all of a profile follows and followers, by directly loading them from the profile's home instance. In case of a failed lookup, the app will automatically fall back to the older method.
|
|
||||||
|
|
||||||
### **Translate posts easily**
|
|
||||||
|
|
||||||
Allows you to easily translate posts in another language with a translate button! Your instance must support translation, otherwise it will not work.
|
|
||||||
|
|
||||||
### **Show posts filtered with a warning**
|
|
||||||
|
|
||||||
Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:
|
|
||||||
|
|
||||||
Before | After
|
|
||||||
:-------------------------:|:-------------------------:
|
|
||||||
 | 
|
|
||||||
|
|
||||||
|
|
||||||
### **Color themes**
|
|
||||||
|
|
||||||
Allows you to change theme within the app. Supports Material You, purple, pink, green, blue, red, orange, yellow and Nord!
|
|
||||||
|
|
||||||
### **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 “Local”, “Community” and “Posts”).**
|
<details>
|
||||||
|
<p><summary>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”).</summary></p>
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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).
|
||||||
|
</details>
|
||||||
|
|
||||||
### **Federated timeline**
|
### **Federated timeline**
|
||||||
|
|
||||||
**This allows you to chronologically see all Public posts from people on all other Fediverse neighborhoods your home instance is connected to.**
|
<details>
|
||||||
|
<p><summary>This allows you to chronologically see all Public posts from people on all other Fediverse neighborhoods your home instance is connected to.</summary></p>
|
||||||
|
|
||||||
Despite being one of the main features of federated social media, the Federated timeline wasn’t included in the official Mastodon app – supposedly, because this conflicts with Google’s safety requirements for apps on the Play Store.
|
Despite being one of the main features of federated social media, the Federated timeline wasn’t included in the official Mastodon app – supposedly, because this conflicts with Google’s safety requirements for apps on the Play Store.
|
||||||
|
|
||||||
That’s one of the reasons why choosing a small, **well-moderated instance is important**. Instance admins and moderators should always make sure to ban abusive users and stop federating with instances who platform them. On well-moderated instances, the Federated timeline can be a welcoming place to meet new people!
|
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!
|
||||||
|
</details>
|
||||||
|
|
||||||
### **Image description viewer**
|
### **Customizable timelines**
|
||||||
|
|
||||||
**Allows you to quickly check whether an image or video has an alternative text attached to it.**
|
<details>
|
||||||
|
<p><summary>You can customize Megalodon’s home tab and not only add local and federated timelines, but also pin lists and hashtags.</summary></p>
|
||||||
|
|
||||||
This is important to **ensure the content you’re sharing is as accessible as possible** to people who can’t see the images and rely on software to read back the provided content descriptions. Thankfully, it’s quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way!
|
Even better: You can rename every timeline however you please and pick a distinct icon for each timeline. This way, you can pin the hashtag “#Caturday”, rename your timeline to “CUTENESS OVERLOAD” and set <img src="img/ic_fluent_animal_cat_24_regular.svg" alt="Cat icon from Microsoft Fluent UI icons"> as its icon. :3 You can find the timelines editor by opening your home tab, tapping the `⋮` button in the top right and going to “Edit timelines”.
|
||||||
|
</details>
|
||||||
|
|
||||||
### **Reminder to add alt text to attached media**
|
### **Draft and schedule posts**
|
||||||
|
|
||||||
By default, Moshidon will show a warning to add alt text if your post has any attachments without any alt text. This is for better accessibility, and it can easily be bypassed and disabled in settings.
|
<details>
|
||||||
|
<p><summary>
|
||||||
|
Allows to prepare a post and schedule it to send it automatically at a specific time.</summary></p>
|
||||||
|
|
||||||
### **Pinning posts**
|
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.
|
||||||
|
</details>
|
||||||
**This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in people’s profiles shows all the posts they pinned.**
|
|
||||||
|
|
||||||
On the Fediverse, it’s quite common for people to pin posts they want others to read before following them. You can pin/unpin posts yourself by clicking the `⋯` button in the top right corner of your posts.
|
|
||||||
|
|
||||||
### **Bookmarks**
|
|
||||||
|
|
||||||
**They allow for quickly saving posts and viewing them through the Bookmarks button on the top right of your profile.**
|
|
||||||
|
|
||||||
To bookmark a post, press the button between the Favorite and Share buttons on the bottom of the post. Bookmarks are saved privately, so the post authors won’t know you saved their post – the list of bookmarked posts is only visible to you.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
**Press the download button above to download the APK. Open the downloaded file on your Android device to install it. Moshidon will automatically notify you about new updates inside the app.**
|
### Google Play Store
|
||||||
|
|
||||||
To install this app on your Android device, download the [latest release from GitHub](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) and open it. You might have to accept installing APK files from your browser when trying to install it. You can also take a look at all releases on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
[https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk](https://play.google.com/store/apps/details?id=org.joinmastodon.android.sk)
|
||||||
|
|
||||||
Moshidon makes use of [Mastodon for Android](https://github.com/mastodon/mastodon-android)’s automatic update checker. Moshidon will check for new updates available on GitHub and offer to download and install them. You can also manually press “Check for updates” at the bottom of the settings page!
|
<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 via IzzyOnDroid
|
||||||
|
|
||||||
|
[https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk)
|
||||||
|
|
||||||
|
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.sk"><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)
|
||||||
|
|
||||||
|
### F-Droid via saunarepo
|
||||||
|
|
||||||
|
[https://repo.the-sauna.icu](https://repo.the-sauna.icu/)
|
||||||
|
|
||||||
|
<a href="https://repo.the-sauna.icu"><img height="28" alt="Get it on SaunaRepo" src="img/saunarepo-badge.svg"></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!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Moshidon is also available in [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda), compatible with all F-Droid clients. The APK provided here is the same as the one included in the Releases.
|
|
||||||
|
|
||||||
## Release variants
|
## Release variants
|
||||||
|
|
||||||
### Stable variant
|
All downloads can be found on the [Releases](https://github.com/sk22/megalodon/releases) page. When downloading a pre-release, expect to see unfinished features and bugs. If you don’t want that, just download the [latest full release](https://github.com/sk22/megalodon/releases/latest/download/megalodon.apk).
|
||||||
|
|
||||||
All stable version downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
|
**`megalodon.apk`**
|
||||||
|
|
||||||
**`moshidon.apk`**
|
Variant with an integrated updater. If you download Megalodon from here (and not from an app store), just download the regular `megalodon.apk`.
|
||||||
|
|
||||||
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
|
**`upstream-1234abc.apk`**
|
||||||
|
|
||||||
### Nightly variant
|
This is an **unmodified version** of the official [Mastodon for Android](https://github.com/mastodon/mastodon-android) app the respective Megalodon release is based on. Should you find any bugs in Megalodon (which you will), try to see if it occurs with this variant, too. The last 7 digits of the file name are important to know which version of the official app you're using.
|
||||||
|
|
||||||
All nightly builds can be downloaded at [Nightly Releases](https://github.com/LucasGGamerM/moshidon-nightly/releases) page.
|
<!-- **`megalodon-fdroid.apk`**
|
||||||
|
|
||||||
**`moshidon-nightly.apk`**
|
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
|
||||||
|
|
||||||
|
The translation for the base of the app is sourced from the upstream **Mastodon for Android** project, which you can contribute to on its Crowdin project: [https://crowdin.com/project/mastodon-for-android](https://crowdin.com/project/mastodon-for-android)
|
||||||
|
|
||||||
|
There's also a bunch of custom strings exclusive to this project that need to be translated. You can help translate **Megalodon** on Weblate: [https://translate.codeberg.org/projects/megalodon](https://translate.codeberg.org/projects/megalodon)
|
||||||
|
|
||||||
|
[](https://translate.codeberg.org/engage/megalodon)
|
||||||
|
|
||||||
Unstable variant with an integrated updater. It's for development and testing purposes. If you find any bugs with it, please file a bug report at our [issues](https://github.com/LucasGGamerM/moshidon/issues) page.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -128,25 +131,16 @@ Unstable variant with an integrated updater. It's for development and testing pu
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* [Adding the ability to view other server's local timelines](https://github.com/LucasGGamerM/moshidon/tree/feature/local-timelines)
|
|
||||||
* [Adding the ability to load followers and following from remote instance](https://github.com/LucasGGamerM/moshidon/tree/feature/remote-followers)
|
|
||||||
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
|
|
||||||
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
|
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
|
||||||
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
|
([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
|
||||||
* Adding a useful private profile note box
|
* [Add “Federation” tab and change Discover tab order](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/add-federated-timeline) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/8))
|
||||||
* Auto hiding the compose button on scroll
|
|
||||||
* Adding the ability to remind yourself to add alt text to images
|
|
||||||
* An indicator for if an image has alt text or not
|
|
||||||
* Adding the ability to have drafts
|
|
||||||
* Also adding the ability to view announcements from your instance
|
|
||||||
* Adding the ability to post for local timeline only (Only on instances that support it!)
|
|
||||||
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
|
||||||
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
|
||||||
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
|
||||||
* [Implement a bookmark button and list](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/bookmarks) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/22))
|
* [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 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))
|
* [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))
|
||||||
* [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)
|
||||||
@@ -156,13 +150,27 @@ Unstable variant with an integrated updater. It's for development and testing pu
|
|||||||
* [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)
|
||||||
* [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 push notification setting for post notifications](https://github.com/sk22/megalodon/commit/b190480d7739be47f23543d9e7644660f9b4b4ee)
|
||||||
|
* [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)
|
||||||
|
* [Pinnable custom timelines](https://github.com/sk22/megalodon/pull/338/commits)
|
||||||
|
* Support for local-only posts
|
||||||
|
* Support for copying the URL to posts/accounts/… in Pixel launcher’s Recent apps view
|
||||||
|
* Compatibility for Akkoma Bubble timeline
|
||||||
|
* Listings of followers/following/favorites/boosts can be loaded from the origin instance (there’s an option to disable this in in the settings)
|
||||||
|
* Allow opening posts/accounts in-app by sharing a URL/handle to Megalodon (Originally implemented in [Moshidon](https://github.com/LucasGGamerM/moshidon), [PR](https://github.com/sk22/megalodon/pull/531))
|
||||||
|
|
||||||
|
|
||||||
### Behavior
|
### Behavior
|
||||||
|
|
||||||
* Allow for confirmation before reblogging
|
|
||||||
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
|
|
||||||
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
|
||||||
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
|
||||||
* [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
|
* [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))
|
||||||
@@ -170,6 +178,22 @@ Unstable variant with an integrated updater. It's for development and testing pu
|
|||||||
* [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)
|
||||||
|
* [Support admin notifications](https://github.com/sk22/megalodon/commit/c12a6eaee6b609bc53eb0a45d9199f37d5241801) and [notifications for edited reblogged posts](https://github.com/sk22/megalodon/commit/900e8fb2e9353002c16d15e06b78d2731e121601)
|
||||||
|
* [Android file opener added back in addition to image picker](https://github.com/sk22/megalodon/commit/3a6ace53d5ab01e28077c9c930cb6ed487b78031)
|
||||||
|
* [Replies are inserted below the replied-to post in thread view](https://github.com/sk22/megalodon/commit/87c37df370ec24aeea0d2dbaeb29468aa4fb5808)
|
||||||
|
* Option to auto-reveal equal content warnings in threads
|
||||||
|
|
||||||
|
|
||||||
### Visual
|
### Visual
|
||||||
@@ -177,6 +201,17 @@ Unstable variant with an integrated updater. It's for development and testing pu
|
|||||||
* [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
|
||||||
|
* Header in timeline for followed hashtags
|
||||||
|
* [Indicator for missing alt texts](https://github.com/sk22/megalodon/commit/c0c276f03e793b78c478c17dfdef24a66ef7cedb)
|
||||||
|
* Visually grouped (by removing divider lines and reducing padding) threaded replies in thread view
|
||||||
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
@@ -187,18 +222,12 @@ As this app is using Java 17 features, you need JDK 17 or newer to build it. Oth
|
|||||||
./gradlew assembleRelease
|
./gradlew assembleRelease
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that Megalodon might be depending on an in-development version of [AppKit](https://github.com/grishka/appkit) – a library by Mastodon for Android’s developer. In case the used AppKit version isn’t published to Maven Central yet, you might have to clone, build and publish it to your local Maven repository. For more information, see [this GitHub issue](https://github.com/mastodon/mastodon-android/issues/375#issuecomment-1507678585).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is released under the [GPL-3 License](./LICENSE).
|
This project is released under the [GPL-3 License](./LICENSE).
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
[F.A.Q](FAQ.md)
|
<a rel="me" href="https://floss.social/@megalodon">@megalodon<wbr>@floss.social</a>
|
||||||
|
|
||||||
[Official matrix chatroom:](https://matrix.to/#/#moshidon:floss.social) https://matrix.to/#/#moshidon:floss.social
|
|
||||||
|
|
||||||
[Moshidon roadmap](https://github.com/users/LucasGGamerM/projects/1)
|
|
||||||
|
|
||||||
<a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
title: Moshidon
|
title: Megalodon
|
||||||
layout: default
|
layout: default
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Moshidon</title>
|
<title>Megalodon</title>
|
||||||
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
<link rel="icon" href="mastodon/src/main/res/mipmap-mdpi/ic_launcher_round.png">
|
||||||
<link rel="me" href="https://floss.social/@megalodon">
|
<link rel="me" href="https://floss.social/@megalodon">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css">
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ buildscript {
|
|||||||
includeModule 'com.github.UnifiedPush', 'android-connector'
|
includeModule 'com.github.UnifiedPush', 'android-connector'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mavenLocal()
|
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.0.0'
|
classpath 'com.android.tools.build:gradle:8.0.0'
|
||||||
|
|||||||
0
fix-metadata-markdown-lists.sh
Normal file → Executable file
@@ -20,4 +20,3 @@ android.enableJetifier=false
|
|||||||
android.defaults.buildfeatures.buildconfig=true
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
org.gradle.configuration-cache=true
|
|
||||||
@@ -11,41 +11,16 @@ java {
|
|||||||
android {
|
android {
|
||||||
compileSdk 33
|
compileSdk 33
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
manifestPlaceholders = [oAuthScheme:"moshidon-android-auth"]
|
archivesBaseName = "megalodon"
|
||||||
archivesBaseName = "moshidon"
|
applicationId "org.joinmastodon.android.sk"
|
||||||
applicationId "org.joinmastodon.android.moshinda"
|
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 103
|
versionCode 100
|
||||||
versionName "2.1.4+fork.103.moshinda"
|
versionName "2.1.4+fork.100"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
|
||||||
nightly{
|
|
||||||
storeFile = file("keystore/nightly_keystore.jks")
|
|
||||||
storePassword System.getenv("SIGNING_STORE_PASSWORD")
|
|
||||||
if (storePassword == null) {
|
|
||||||
Properties properties = new Properties()
|
|
||||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
|
||||||
storePassword = properties.getProperty('SIGNING_STORE_PASSWORD')
|
|
||||||
}
|
|
||||||
keyAlias System.getenv("SIGNING_KEY_ALIAS")
|
|
||||||
if (keyAlias == null) {
|
|
||||||
Properties properties = new Properties()
|
|
||||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
|
||||||
keyAlias = properties.getProperty('SIGNING_KEY_ALIAS')
|
|
||||||
}
|
|
||||||
keyPassword System.getenv("SIGNING_KEY_PASSWORD")
|
|
||||||
if (keyPassword == null) {
|
|
||||||
Properties properties = new Properties()
|
|
||||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
|
||||||
keyPassword = properties.getProperty('SIGNING_KEY_PASSWORD')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
@@ -56,31 +31,9 @@ android {
|
|||||||
debuggable true
|
debuggable true
|
||||||
versionNameSuffix '-debug'
|
versionNameSuffix '-debug'
|
||||||
applicationIdSuffix '.debug'
|
applicationIdSuffix '.debug'
|
||||||
manifestPlaceholders = [oAuthScheme:"moshidon-android-debug-auth"]
|
|
||||||
}
|
|
||||||
githubRelease{
|
|
||||||
initWith release
|
|
||||||
}
|
|
||||||
nightly{
|
|
||||||
if(System.getenv("CURRENT_DATE") != null){
|
|
||||||
versionNameSuffix '-nightly+@' + System.getenv("CURRENT_DATE")
|
|
||||||
} else {
|
|
||||||
Properties properties = new Properties()
|
|
||||||
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
|
||||||
versionNameSuffix '-nightly+@' + properties.getProperty('CURRENT_DATE')
|
|
||||||
}
|
|
||||||
applicationIdSuffix '.nightly'
|
|
||||||
|
|
||||||
signingConfig signingConfigs.nightly
|
|
||||||
manifestPlaceholders = [oAuthScheme:"moshidon-android-nightly-auth"]
|
|
||||||
}
|
|
||||||
playRelease{
|
|
||||||
initWith release
|
|
||||||
minifyEnabled true
|
|
||||||
shrinkResources true
|
|
||||||
versionNameSuffix '-play'
|
|
||||||
}
|
}
|
||||||
githubRelease { initWith release }
|
githubRelease { initWith release }
|
||||||
|
playRelease { initWith release }
|
||||||
fdroidRelease { initWith release }
|
fdroidRelease { initWith release }
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -93,7 +46,7 @@ android {
|
|||||||
setRoot "src/github"
|
setRoot "src/github"
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
setRoot "src/debug"
|
setRoot "src/github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace 'org.joinmastodon.android'
|
namespace 'org.joinmastodon.android'
|
||||||
@@ -117,7 +70,7 @@ dependencies {
|
|||||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||||
implementation 'me.grishka.litex:palette:1.0.0'
|
implementation 'me.grishka.litex:palette:1.0.0'
|
||||||
implementation 'me.grishka.appkit:appkit:1.2.14'
|
implementation 'me.grishka.appkit:appkit:1.2.9'
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'com.google.code.gson:gson:2.9.0'
|
||||||
implementation 'org.jsoup:jsoup:1.14.3'
|
implementation 'org.jsoup:jsoup:1.14.3'
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
|
|||||||
7
mastodon/proguard-rules.pro
vendored
@@ -45,13 +45,6 @@
|
|||||||
|
|
||||||
-keepattributes LineNumberTable
|
-keepattributes LineNumberTable
|
||||||
|
|
||||||
-keepattributes *
|
|
||||||
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
|
|
||||||
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
|
|
||||||
|
|
||||||
#-keep class javax.** { *; }
|
|
||||||
-keep class org.joinmastodon.android.** { *; }
|
|
||||||
|
|
||||||
# Parceler library
|
# Parceler library
|
||||||
-keep interface org.parceler.Parcel
|
-keep interface org.parceler.Parcel
|
||||||
-keep @org.parceler.Parcel class * { *; }
|
-keep @org.parceler.Parcel class * { *; }
|
||||||
|
|||||||
@@ -257,9 +257,5 @@ public class UiUtilsTest {
|
|||||||
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
||||||
makeField("pronouns", "-- * (asterisk) --")
|
makeField("pronouns", "-- * (asterisk) --")
|
||||||
)).orElseThrow());
|
)).orElseThrow());
|
||||||
|
|
||||||
assertEquals("they/(she?)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
|
|
||||||
makeField("pronouns", "they/(she?)...")
|
|
||||||
)).orElseThrow());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,9 +19,7 @@ public class StatusFilterPredicateTest {
|
|||||||
|
|
||||||
private static final Status
|
private static final Status
|
||||||
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
|
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
|
||||||
warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now()),
|
warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now());
|
||||||
noAltText = Status.ofFake(null, "display me with a warning", Instant.now()),
|
|
||||||
withAltText = Status.ofFake(null, "display me with a warning", Instant.now());
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
hideMeFilter.phrase = "hide me";
|
hideMeFilter.phrase = "hide me";
|
||||||
@@ -31,12 +29,6 @@ public class StatusFilterPredicateTest {
|
|||||||
warnMeFilter.phrase = "warning";
|
warnMeFilter.phrase = "warning";
|
||||||
warnMeFilter.filterAction = WARN;
|
warnMeFilter.filterAction = WARN;
|
||||||
warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
|
warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
|
||||||
|
|
||||||
noAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable());
|
|
||||||
withAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable());
|
|
||||||
for (Attachment mediaAttachment : withAltText.mediaAttachments) {
|
|
||||||
mediaAttachment.description = "Alt Text";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -86,16 +78,4 @@ public class StatusFilterPredicateTest {
|
|||||||
assertTrue("should pass because matching filter is for hiding",
|
assertTrue("should pass because matching filter is for hiding",
|
||||||
new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic));
|
new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAltTextFilterNoPass() {
|
|
||||||
assertFalse("should not pass because of no alt text",
|
|
||||||
new StatusFilterPredicate(allFilters, HOME).test(noAltText));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAltTextFilterPass() {
|
|
||||||
assertTrue("should pass because of alt text",
|
|
||||||
new StatusFilterPredicate(allFilters, HOME).test(withAltText));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
package org.joinmastodon.android.updater;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.DownloadManager;
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.pm.PackageInstaller;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.BuildConfig;
|
|
||||||
import org.joinmastodon.android.E;
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.MastodonApp;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
|
||||||
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import androidx.annotation.Keep;
|
|
||||||
import okhttp3.Call;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|
||||||
private static final long CHECK_PERIOD=6*3600*1000L;
|
|
||||||
private static final String TAG="GithubSelfUpdater";
|
|
||||||
|
|
||||||
private UpdateState state=UpdateState.NO_UPDATE;
|
|
||||||
private UpdateInfo info;
|
|
||||||
private long downloadID;
|
|
||||||
private BroadcastReceiver downloadCompletionReceiver=new BroadcastReceiver(){
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent){
|
|
||||||
if(downloadID!=0 && intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)==downloadID){
|
|
||||||
MastodonApp.context.unregisterReceiver(this);
|
|
||||||
setState(UpdateState.DOWNLOADED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public GithubSelfUpdaterImpl(){
|
|
||||||
SharedPreferences prefs=getPrefs();
|
|
||||||
int checkedByBuild=prefs.getInt("checkedByBuild", 0);
|
|
||||||
if(prefs.contains("version") && checkedByBuild==BuildConfig.VERSION_CODE){
|
|
||||||
info=new UpdateInfo();
|
|
||||||
info.version=prefs.getString("version", null);
|
|
||||||
info.size=prefs.getLong("apkSize", 0);
|
|
||||||
info.changelog=prefs.getString("changelog", null);
|
|
||||||
downloadID=prefs.getLong("downloadID", 0);
|
|
||||||
if(downloadID==0 || !getUpdateApkFile().exists()){
|
|
||||||
state=UpdateState.UPDATE_AVAILABLE;
|
|
||||||
}else{
|
|
||||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
|
||||||
state=dm.getUriForDownloadedFile(downloadID)==null ? UpdateState.DOWNLOADING : UpdateState.DOWNLOADED;
|
|
||||||
if(state==UpdateState.DOWNLOADING){
|
|
||||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else if(checkedByBuild!=BuildConfig.VERSION_CODE && checkedByBuild>0){
|
|
||||||
// We are in a new version, running for the first time after update. Gotta clean things up.
|
|
||||||
long id=getPrefs().getLong("downloadID", 0);
|
|
||||||
if(id!=0){
|
|
||||||
MastodonApp.context.getSystemService(DownloadManager.class).remove(id);
|
|
||||||
}
|
|
||||||
getUpdateApkFile().delete();
|
|
||||||
getPrefs().edit()
|
|
||||||
.remove("apkSize")
|
|
||||||
.remove("version")
|
|
||||||
.remove("apkURL")
|
|
||||||
.remove("checkedByBuild")
|
|
||||||
.remove("downloadID")
|
|
||||||
.remove("changelog")
|
|
||||||
.apply();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SharedPreferences getPrefs(){
|
|
||||||
return MastodonApp.context.getSharedPreferences("githubUpdater", Context.MODE_PRIVATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void maybeCheckForUpdates(){
|
|
||||||
if(state!=UpdateState.NO_UPDATE && state!=UpdateState.UPDATE_AVAILABLE)
|
|
||||||
return;
|
|
||||||
long timeSinceLastCheck=System.currentTimeMillis()-getPrefs().getLong("lastCheck", CHECK_PERIOD);
|
|
||||||
if(timeSinceLastCheck>=CHECK_PERIOD){
|
|
||||||
setState(UpdateState.CHECKING);
|
|
||||||
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkForUpdates() {
|
|
||||||
setState(UpdateState.CHECKING);
|
|
||||||
MastodonAPIController.runInBackground(this::actuallyCheckForUpdates);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void actuallyCheckForUpdates(){
|
|
||||||
Request req=new Request.Builder()
|
|
||||||
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases")
|
|
||||||
.build();
|
|
||||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
|
||||||
try(Response resp=call.execute()){
|
|
||||||
JsonArray arr=JsonParser.parseReader(resp.body().charStream()).getAsJsonArray();
|
|
||||||
for (JsonElement jsonElement : arr) {
|
|
||||||
JsonObject obj = jsonElement.getAsJsonObject();
|
|
||||||
if (obj.get("prerelease").getAsBoolean() && !GlobalUserPreferences.enablePreReleases) continue;
|
|
||||||
|
|
||||||
String tag=obj.get("tag_name").getAsString();
|
|
||||||
String changelog=obj.get("body").getAsString();
|
|
||||||
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)");
|
|
||||||
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();
|
|
||||||
|
|
||||||
UpdateInfo info=new UpdateInfo();
|
|
||||||
info.size=size;
|
|
||||||
info.version=version;
|
|
||||||
info.changelog=changelog;
|
|
||||||
this.info=info;
|
|
||||||
|
|
||||||
getPrefs().edit()
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}catch(Exception x){
|
|
||||||
Log.w(TAG, "actuallyCheckForUpdates", x);
|
|
||||||
}finally{
|
|
||||||
setState(info==null ? UpdateState.NO_UPDATE : UpdateState.UPDATE_AVAILABLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setState(UpdateState state){
|
|
||||||
this.state=state;
|
|
||||||
E.post(new SelfUpdateStateChangedEvent(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UpdateState getState(){
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public UpdateInfo getUpdateInfo(){
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getUpdateApkFile(){
|
|
||||||
return new File(MastodonApp.context.getExternalCacheDir(), "update.apk");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void downloadUpdate(){
|
|
||||||
if(state==UpdateState.DOWNLOADING)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
|
||||||
MastodonApp.context.registerReceiver(downloadCompletionReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
|
||||||
downloadID=dm.enqueue(
|
|
||||||
new DownloadManager.Request(Uri.parse(getPrefs().getString("apkURL", null)))
|
|
||||||
.setDestinationUri(Uri.fromFile(getUpdateApkFile()))
|
|
||||||
);
|
|
||||||
getPrefs().edit().putLong("downloadID", downloadID).apply();
|
|
||||||
setState(UpdateState.DOWNLOADING);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void installUpdate(Activity activity){
|
|
||||||
if(state!=UpdateState.DOWNLOADED)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
Uri uri;
|
|
||||||
Intent intent=new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
|
|
||||||
uri=new Uri.Builder().scheme("content").authority(activity.getPackageName()+".self_update_provider").path("update.apk").build();
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
||||||
}else{
|
|
||||||
uri=Uri.fromFile(getUpdateApkFile());
|
|
||||||
}
|
|
||||||
intent.setDataAndType(uri, "application/vnd.android.package-archive");
|
|
||||||
activity.startActivity(intent);
|
|
||||||
|
|
||||||
// TODO figure out how to restart the app when updating via this new API
|
|
||||||
/*
|
|
||||||
PackageInstaller installer=activity.getPackageManager().getPackageInstaller();
|
|
||||||
try{
|
|
||||||
final int sid=installer.createSession(new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL));
|
|
||||||
installer.registerSessionCallback(new PackageInstaller.SessionCallback(){
|
|
||||||
@Override
|
|
||||||
public void onCreated(int i){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBadgingChanged(int i){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActiveChanged(int i, boolean b){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgressChanged(int id, float progress){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFinished(int id, boolean success){
|
|
||||||
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
activity.getPackageManager().setComponentEnabledSetting(new ComponentName(activity, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
|
||||||
PackageInstaller.Session session=installer.openSession(sid);
|
|
||||||
try(OutputStream out=session.openWrite("mastodon.apk", 0, info.size); InputStream in=new FileInputStream(getUpdateApkFile())){
|
|
||||||
byte[] buffer=new byte[16384];
|
|
||||||
int read;
|
|
||||||
while((read=in.read(buffer))>0){
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// PendingIntent intent=PendingIntent.getBroadcast(activity, 1, new Intent(activity, InstallerStatusReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
|
|
||||||
PendingIntent intent=PendingIntent.getActivity(activity, 1, new Intent(activity, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
session.commit(intent.getIntentSender());
|
|
||||||
}catch(IOException x){
|
|
||||||
Log.w(TAG, "installUpdate", x);
|
|
||||||
Toast.makeText(activity, x.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getDownloadProgress(){
|
|
||||||
if(state!=UpdateState.DOWNLOADING)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
|
||||||
try(Cursor cursor=dm.query(new DownloadManager.Query().setFilterById(downloadID))){
|
|
||||||
if(cursor.moveToFirst()){
|
|
||||||
long loaded=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
|
|
||||||
long total=cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
|
|
||||||
// Log.d(TAG, "getDownloadProgress: "+loaded+" of "+total);
|
|
||||||
return total>0 ? (float)loaded/total : 0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancelDownload(){
|
|
||||||
if(state!=UpdateState.DOWNLOADING)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
DownloadManager dm=MastodonApp.context.getSystemService(DownloadManager.class);
|
|
||||||
dm.remove(downloadID);
|
|
||||||
downloadID=0;
|
|
||||||
getPrefs().edit().remove("downloadID").apply();
|
|
||||||
setState(UpdateState.UPDATE_AVAILABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleIntentFromInstaller(Intent intent, Activity activity){
|
|
||||||
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
|
|
||||||
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
|
|
||||||
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
|
||||||
activity.startActivity(confirmIntent);
|
|
||||||
}else if(status!=PackageInstaller.STATUS_SUCCESS){
|
|
||||||
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
|
||||||
Toast.makeText(activity, activity.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset(){
|
|
||||||
getPrefs().edit().clear().apply();
|
|
||||||
File apk=getUpdateApkFile();
|
|
||||||
if(apk.exists())
|
|
||||||
apk.delete();
|
|
||||||
state=UpdateState.NO_UPDATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*public static class InstallerStatusReceiver extends BroadcastReceiver{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent){
|
|
||||||
int status=intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
|
|
||||||
if(status==PackageInstaller.STATUS_PENDING_USER_ACTION){
|
|
||||||
Intent confirmIntent=intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
|
||||||
context.startActivity(confirmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
|
||||||
}else if(status!=PackageInstaller.STATUS_SUCCESS){
|
|
||||||
String msg=intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
|
||||||
Toast.makeText(context, context.getString(R.string.error)+":\n"+msg, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AfterUpdateRestartReceiver extends BroadcastReceiver{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent){
|
|
||||||
if(Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())){
|
|
||||||
context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, AfterUpdateRestartReceiver.class), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
|
||||||
Toast.makeText(context, R.string.update_installed, Toast.LENGTH_SHORT).show();
|
|
||||||
Intent restartIntent=new Intent(context, MainActivity.class)
|
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
.setPackage(context.getPackageName());
|
|
||||||
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.P){
|
|
||||||
context.startActivity(restartIntent);
|
|
||||||
}else{
|
|
||||||
// Bypass activity starting restrictions by starting it from a notification
|
|
||||||
NotificationManager nm=context.getSystemService(NotificationManager.class);
|
|
||||||
NotificationChannel chan=new NotificationChannel("selfUpdateRestart", context.getString(R.string.update_installed), NotificationManager.IMPORTANCE_HIGH);
|
|
||||||
nm.createNotificationChannel(chan);
|
|
||||||
Notification n=new Notification.Builder(context, "selfUpdateRestart")
|
|
||||||
.setContentTitle(context.getString(R.string.update_installed))
|
|
||||||
.setContentIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
|
|
||||||
.setFullScreenIntent(PendingIntent.getActivity(context, 1, restartIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE), true)
|
|
||||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
|
||||||
.build();
|
|
||||||
nm.notify(1, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package org.joinmastodon.android.updater;
|
|
||||||
|
|
||||||
import android.content.ContentProvider;
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
public class SelfUpdateContentProvider extends ContentProvider{
|
|
||||||
@Override
|
|
||||||
public boolean onCreate(){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public String getType(@NonNull Uri uri){
|
|
||||||
if(isCorrectUri(uri))
|
|
||||||
return "application/vnd.android.package-archive";
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs){
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException{
|
|
||||||
if(isCorrectUri(uri)){
|
|
||||||
return ParcelFileDescriptor.open(((GithubSelfUpdaterImpl)GithubSelfUpdater.getInstance()).getUpdateApkFile(), ParcelFileDescriptor.MODE_READ_ONLY);
|
|
||||||
}
|
|
||||||
throw new FileNotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCorrectUri(Uri uri){
|
|
||||||
return "/update.apk".equals(uri.getPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
|
||||||
<path android:pathData="M3.897 4.054L3.97 3.97c0.266-0.267 0.683-0.29 0.976-0.073L5.03 3.97 10 8.939l4.97-4.97c0.266-0.266 0.683-0.29 0.976-0.072L16.03 3.97c0.267 0.266 0.29 0.683 0.073 0.976L16.03 5.03 11.061 10l4.97 4.97c0.266 0.266 0.29 0.683 0.072 0.976L16.03 16.03c-0.266 0.267-0.683 0.29-0.976 0.073L14.97 16.03 10 11.061l-4.97 4.97c-0.266 0.266-0.683 0.29-0.976 0.072L3.97 16.03c-0.267-0.266-0.29-0.683-0.073-0.976L3.97 14.97 8.939 10l-4.97-4.97C3.704 4.764 3.68 4.347 3.898 4.054L3.97 3.97 3.897 4.054z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
||||||
<path android:pathData="M22 6.5c0 3.038-2.462 5.5-5.5 5.5S11 9.538 11 6.5 13.462 1 16.5 1 22 3.462 22 6.5zm-7.146-2.354c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708L15.793 6.5l-1.647 1.646c-0.195 0.196-0.195 0.512 0 0.707 0.196 0.196 0.512 0.196 0.708 0L16.5 7.208l1.646 1.647c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.707L17.207 6.5l1.647-1.646c0.195-0.196 0.195-0.512 0-0.708-0.196-0.195-0.512-0.195-0.708 0L16.5 5.793l-1.646-1.647zM19.5 14v-1.732c0.551-0.287 1.056-0.651 1.5-1.078v7.56c0 1.733-1.357 3.15-3.066 3.245L17.75 22H6.25c-1.733 0-3.15-1.357-3.245-3.066L3 18.75V7.25C3 5.517 4.356 4.1 6.066 4.005L6.25 4h4.248c-0.198 0.474-0.34 0.977-0.422 1.5H6.25c-0.918 0-1.671 0.707-1.744 1.606L4.5 7.25V14H9c0.38 0 0.694 0.282 0.743 0.648L9.75 14.75C9.75 15.993 10.757 17 12 17c1.19 0 2.166-0.925 2.245-2.096l0.005-0.154c0-0.38 0.282-0.694 0.648-0.743L15 14h4.5zm-15 1.5v3.25c0 0.918 0.707 1.671 1.606 1.744L6.25 20.5h11.5c0.918 0 1.671-0.707 1.744-1.607L19.5 18.75V15.5h-3.825c-0.335 1.648-1.75 2.904-3.475 2.995L12 18.5c-1.747 0-3.215-1.195-3.632-2.812L8.325 15.5H4.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
|
||||||
<path android:pathData="M26 7.5c0 3.59-2.91 6.5-6.5 6.5S13 11.09 13 7.5 15.91 1 19.5 1 26 3.91 26 7.5zm-9.146-3.354c-0.196-0.195-0.512-0.195-0.708 0-0.195 0.196-0.195 0.512 0 0.708L18.793 7.5l-2.647 2.646c-0.195 0.196-0.195 0.512 0 0.708 0.196 0.195 0.512 0.195 0.708 0L19.5 8.207l2.646 2.647c0.196 0.195 0.512 0.195 0.708 0 0.195-0.196 0.195-0.512 0-0.708L20.207 7.5l2.647-2.646c0.195-0.196 0.195-0.512 0-0.708-0.196-0.195-0.512-0.195-0.708 0L19.5 6.793l-2.646-2.647zM25 22.75V12.6c-0.443 0.476-0.947 0.896-1.5 1.245V16h-6l-0.102 0.007c-0.366 0.05-0.648 0.363-0.648 0.743 0 1.519-1.231 2.75-2.75 2.75s-2.75-1.231-2.75-2.75l-0.007-0.102C11.193 16.282 10.88 16 10.5 16h-6V7.25c0-0.966 0.784-1.75 1.75-1.75h6.02c0.145-0.525 0.345-1.028 0.595-1.5H6.25C4.455 4 3 5.455 3 7.25v15.5C3 24.545 4.455 26 6.25 26h15.5c1.795 0 3.25-1.455 3.25-3.25zm-20.5 0V17.5h5.316l0.041 0.204C10.291 19.592 11.982 21 14 21l0.215-0.005c1.994-0.1 3.627-1.574 3.969-3.495H23.5v5.25c0 0.966-0.784 1.75-1.75 1.75H6.25c-0.966 0-1.75-0.784-1.75-1.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
|
||||||
<path android:pathData="M10 2c4.418 0 8 3.582 8 8 0 2.706-1.142 4.5-3 4.5-1.226 0-2.14-0.781-2.62-2.09C11.784 13.393 10.781 14 9.5 14 7.36 14 6 12.307 6 10c0-2.337 1.313-4 3.5-4 1.052 0 1.901 0.385 2.5 1.044V6.5C12 6.224 12.224 6 12.5 6c0.245 0 0.45 0.177 0.492 0.41L13 6.5V10c0 2.223 0.813 3.5 2 3.5s2-1.277 2-3.5c0-3.866-3.134-7-7-7s-7 3.134-7 7 3.134 7 7 7c0.823 0 1.626-0.142 2.383-0.416 0.26-0.094 0.547 0.04 0.64 0.3 0.095 0.26-0.04 0.546-0.3 0.64C11.859 17.838 10.94 18 10 18c-4.418 0-8-3.582-8-8s3.582-8 8-8zM9.5 7C7.924 7 7 8.17 7 10c0 1.797 0.966 3 2.5 3s2.5-1.203 2.5-3c0-1.83-0.924-3-2.5-3z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
||||||
<path android:pathData="M6.25 4.5C5.283 4.5 4.5 5.284 4.5 6.25v11.5c0 0.966 0.783 1.75 1.75 1.75h11.5c0.966 0 1.75-0.784 1.75-1.75v-4c0-0.414 0.335-0.75 0.75-0.75 0.414 0 0.75 0.336 0.75 0.75v4c0 1.795-1.456 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V6.25C3 4.455 4.455 3 6.25 3h4C10.664 3 11 3.336 11 3.75S10.664 4.5 10.25 4.5h-4zM13 3.75C13 3.336 13.335 3 13.75 3h6.5C20.664 3 21 3.336 21 3.75v6.5c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75V5.56l-5.22 5.22c-0.293 0.293-0.768 0.293-1.06 0-0.293-0.293-0.293-0.768 0-1.06l5.22-5.22h-4.69C13.335 4.5 13 4.164 13 3.75z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
||||||
<path android:pathData="M8.502 11.5c0.554 0 1.002 0.448 1.002 1.002 0 0.553-0.448 1.002-1.002 1.002-0.553 0-1.002-0.449-1.002-1.002 0-0.554 0.449-1.003 1.002-1.003zM12 4.353v6.651h7.442L17.72 9.28c-0.267-0.266-0.29-0.683-0.073-0.977L17.72 8.22c0.266-0.266 0.683-0.29 0.976-0.072L18.78 8.22l2.997 2.998c0.266 0.266 0.29 0.682 0.073 0.976l-0.073 0.084-2.996 3.003c-0.293 0.294-0.767 0.294-1.06 0.002-0.267-0.266-0.292-0.683-0.075-0.977l0.073-0.084 1.713-1.717h-7.431L12 19.25c0 0.466-0.421 0.82-0.88 0.738l-8.5-1.501C2.26 18.424 2 18.112 2 17.748V5.75c0-0.368 0.266-0.681 0.628-0.74l8.5-1.396C11.585 3.539 12 3.89 12 4.354zm-1.5 0.883l-7 1.15v10.732l7 1.236V5.237zM13 18.5h0.765l0.102-0.007c0.366-0.05 0.649-0.364 0.648-0.744l-0.007-4.25H13v5zm0.002-8.502L13 8.726V5h0.745c0.38 0 0.693 0.281 0.743 0.647l0.007 0.101L14.502 10h-1.5z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
||||||
<path android:pathData="M14.704 3.44C14.895 3.667 15 3.953 15 4.248V19.75c0 0.69-0.56 1.25-1.25 1.25-0.296 0-0.582-0.105-0.808-0.296l-4.967-4.206H4.25c-1.243 0-2.25-1.008-2.25-2.25v-4.5c0-1.243 1.007-2.25 2.25-2.25h3.725l4.968-4.204c0.526-0.446 1.315-0.38 1.761 0.147zM13.5 4.787l-4.975 4.21H4.25c-0.414 0-0.75 0.337-0.75 0.75v4.5c0 0.415 0.336 0.75 0.75 0.75h4.275L13.5 19.21V4.787z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
|
||||||
<path android:pathData="M16.5 4.814c0-1.094-1.307-1.66-2.105-0.912l-4.937 4.63C9.134 8.836 8.706 9.005 8.261 9.005H5.25C3.455 9.005 2 10.46 2 12.255v3.492c0 1.795 1.455 3.25 3.25 3.25h3.012c0.444 0 0.872 0.17 1.196 0.473l4.937 4.626c0.799 0.748 2.105 0.182 2.105-0.912V4.814zm-6.016 4.812L15 5.39v17.216l-4.516-4.232c-0.602-0.564-1.397-0.878-2.222-0.878H5.25c-0.966 0-1.75-0.784-1.75-1.75v-3.492c0-0.966 0.784-1.75 1.75-1.75h3.011c0.826 0 1.62-0.314 2.223-0.88z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
|
||||||
<path android:pathData="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06L6.438 7.5H4.25C3.007 7.499 2 8.506 2 9.749v4.497c0 1.243 1.007 2.25 2.25 2.25h3.68c0.183 0 0.36 0.068 0.498 0.19l4.491 3.994C13.725 21.396 15 20.824 15 19.746V16.06l5.72 5.72c0.292 0.292 0.767 0.292 1.06 0 0.293-0.293 0.293-0.768 0-1.061L3.28 2.22zM13.5 14.56v4.629l-4.075-3.624c-0.412-0.366-0.944-0.569-1.495-0.569H4.25c-0.414 0-0.75-0.335-0.75-0.75V9.75C3.5 9.335 3.836 9 4.25 9h3.688l5.562 5.56zm0-9.753v5.511l1.5 1.5V4.25c0-1.079-1.274-1.65-2.08-0.934l-3.4 3.022 1.063 1.063L13.5 4.807zm3.641 9.152l1.138 1.138C18.741 14.163 19 13.111 19 12c0-1.203-0.304-2.338-0.84-3.328-0.198-0.364-0.653-0.5-1.017-0.303-0.364 0.197-0.5 0.653-0.303 1.017 0.42 0.777 0.66 1.666 0.66 2.614 0 0.691-0.127 1.351-0.359 1.96zm2.247 2.247l1.093 1.094C21.445 15.763 22 13.946 22 12c0-2.226-0.728-4.284-1.96-5.946-0.246-0.333-0.716-0.403-1.048-0.157-0.333 0.247-0.403 0.716-0.157 1.05C19.881 8.358 20.5 10.106 20.5 12c0 1.531-0.404 2.966-1.112 4.206z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="28dp" android:height="28dp" android:viewportWidth="28" android:viewportHeight="28">
|
|
||||||
<path android:pathData="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06l5.724 5.725H5.25C3.455 9.005 2 10.46 2 12.255v3.492c0 1.795 1.455 3.25 3.25 3.25h3.012c0.444 0 0.872 0.17 1.196 0.473l4.937 4.626c0.799 0.748 2.105 0.182 2.105-0.912v-5.623l8.22 8.22c0.292 0.292 0.767 0.292 1.06 0 0.293-0.293 0.293-0.768 0-1.061L3.28 2.22zM15 16.06v6.547l-4.516-4.231c-0.602-0.565-1.397-0.879-2.222-0.879H5.25c-0.966 0-1.75-0.783-1.75-1.75v-3.492c0-0.966 0.784-1.75 1.75-1.75h3.011c0.35 0 0.693-0.056 1.02-0.164L15 16.061zm-4.378-8.62l1.061 1.061L15 5.392v6.427l1.5 1.5V4.814c0-1.094-1.307-1.66-2.105-0.912L10.622 7.44zm9.55 9.55l1.137 1.137C21.912 16.88 22.25 15.478 22.25 14c0-2.136-0.706-4.11-1.897-5.697-0.249-0.332-0.719-0.399-1.05-0.15-0.332 0.249-0.399 0.719-0.15 1.05C20.156 10.54 20.75 12.199 20.75 14c0 1.058-0.205 2.067-0.578 2.99zm2.803 2.803l1.095 1.096c1.224-2.008 1.93-4.366 1.93-6.89 0-3.35-1.245-6.414-3.298-8.747-0.274-0.31-0.747-0.341-1.058-0.068-0.311 0.274-0.342 0.748-0.068 1.059C23.396 8.313 24.5 11.027 24.5 14c0 2.107-0.554 4.084-1.525 5.793z" android:fillColor="@color/fluent_default_icon_tint"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M54,90L54,90c-19.9,0 -36,-16.1 -36,-36v0c0,-19.9 16.1,-36 36,-36h0c19.9,0 36,16.1 36,36v0C90,73.9 73.9,90 54,90z"
|
|
||||||
android:strokeAlpha="0"
|
|
||||||
android:fillAlpha="0"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M52.5,41.6c-2.4,0 -4.3,0.9 -5.5,2.8l-1.2,2l-1.2,-2c-1.2,-1.9 -3.1,-2.8 -5.5,-2.8c-2.1,0 -3.8,0.8 -5.1,2.2c-1.2,1.4 -1.9,3.4 -1.9,5.9v12h4.7V50c0,-2.4 1.1,-3.7 3.1,-3.7c2.3,0 3.4,1.4 3.4,4.4v6.4h4.7v-6.4c0,-2.9 1.1,-4.4 3.4,-4.4c2.1,0 3.1,1.2 3.1,3.7v11.7h4.7v-12c0,-2.4 -0.6,-4.4 -1.9,-5.9C56.2,42.3 54.6,41.6 52.5,41.6z"
|
|
||||||
android:fillColor="#33D17A"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M65.9,58.1h0.8c0,0 0,0 -0.1,0c-0.6,-0.3 -1.1,-0.8 -1.4,-1.4c-0.3,-0.6 -0.5,-1.4 -0.5,-2.1c0,-0.8 0.2,-1.5 0.5,-2.1c0.4,-0.6 0.8,-1.1 1.4,-1.4c0.6,-0.3 1.2,-0.5 1.9,-0.5c0.7,0 1.3,0.2 1.9,0.5s1.1,0.8 1.4,1.4s0.5,1.3 0.5,2.1c0,0.2 0,0.4 0,0.6l0.7,0.7c0.4,0 0.8,0 1.1,0l1.5,-1.5l0.2,-0.2c-0.1,-1.2 -0.4,-2.3 -0.9,-3.4c-0.6,-1.1 -1.4,-2 -2.6,-2.6c-1.1,-0.6 -2.4,-1 -3.7,-1c-1.4,0 -2.7,0.3 -3.8,1c-1.1,0.6 -2,1.5 -2.6,2.6c-0.6,1.1 -0.9,2.4 -0.9,3.7s0.3,2.7 0.9,3.7c0.6,1.1 1.5,2 2.6,2.6c0.4,0.2 0.8,0.4 1.1,0.5v-1.8V58.1z"
|
|
||||||
android:fillColor="#33D17A"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M76,58.3l1.2,-1.2L76.2,56l-1.7,1.7c-0.4,-0.1 -0.7,-0.2 -1.1,-0.2s-0.8,0.1 -1.1,0.2L70.7,56l-1,1.1l1.2,1.2c-0.5,0.4 -1.1,0.9 -1.4,1.5h-2.1v1.5H69c0,0.2 -0.1,0.5 -0.1,0.8v0.8h-1.5v1.5h1.5v0.8c0,0.2 0,0.5 0.1,0.8h-1.6v1.5h2.1c0.8,1.4 2.3,2.3 4,2.3s3.1,-0.9 4,-2.3h2.1v-1.5H78c0,-0.2 0.1,-0.5 0.1,-0.8v-0.8h1.5v-1.5h-1.5v-0.8c0,-0.2 0,-0.5 -0.1,-0.8h1.6v-1.5h-2.1C77.1,59.2 76.6,58.8 76,58.3zM75,65.9H72v-1.5H75V65.9zM75,62.9H72v-1.5H75V62.9z"
|
|
||||||
android:fillColor="#33D17A"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M54,90L54,90c-19.9,0 -36,-16.1 -36,-36v0c0,-19.9 16.1,-36 36,-36h0c19.9,0 36,16.1 36,36v0C90,73.9 73.9,90 54,90z"
|
|
||||||
android:strokeAlpha="0"
|
|
||||||
android:fillAlpha="0"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M52.5,41.6c-2.4,0 -4.3,0.9 -5.5,2.8l-1.2,2l-1.2,-2c-1.2,-1.9 -3.1,-2.8 -5.5,-2.8c-2.1,0 -3.8,0.8 -5.1,2.2c-1.2,1.4 -1.9,3.4 -1.9,5.9v12h4.7V50c0,-2.4 1.1,-3.7 3.1,-3.7c2.3,0 3.4,1.4 3.4,4.4v6.4h4.7v-6.4c0,-2.9 1.1,-4.4 3.4,-4.4c2.1,0 3.1,1.2 3.1,3.7v11.7h4.7v-12c0,-2.4 -0.6,-4.4 -1.9,-5.9C56.2,42.3 54.6,41.6 52.5,41.6z"
|
|
||||||
android:fillColor="#33D17A"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M65.9,58.1h0.8c0,0 0,0 -0.1,0c-0.6,-0.3 -1.1,-0.8 -1.4,-1.4c-0.3,-0.6 -0.5,-1.4 -0.5,-2.1c0,-0.8 0.2,-1.5 0.5,-2.1c0.4,-0.6 0.8,-1.1 1.4,-1.4c0.6,-0.3 1.2,-0.5 1.9,-0.5c0.7,0 1.3,0.2 1.9,0.5s1.1,0.8 1.4,1.4s0.5,1.3 0.5,2.1c0,0.2 0,0.4 0,0.6l0.7,0.7c0.4,0 0.8,0 1.1,0l1.5,-1.5l0.2,-0.2c-0.1,-1.2 -0.4,-2.3 -0.9,-3.4c-0.6,-1.1 -1.4,-2 -2.6,-2.6c-1.1,-0.6 -2.4,-1 -3.7,-1c-1.4,0 -2.7,0.3 -3.8,1c-1.1,0.6 -2,1.5 -2.6,2.6c-0.6,1.1 -0.9,2.4 -0.9,3.7s0.3,2.7 0.9,3.7c0.6,1.1 1.5,2 2.6,2.6c0.4,0.2 0.8,0.4 1.1,0.5v-1.8V58.1z"
|
|
||||||
android:fillColor="#33D17A"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M76,58.3l1.2,-1.2L76.2,56l-1.7,1.7c-0.4,-0.1 -0.7,-0.2 -1.1,-0.2s-0.8,0.1 -1.1,0.2L70.7,56l-1,1.1l1.2,1.2c-0.5,0.4 -1.1,0.9 -1.4,1.5h-2.1v1.5H69c0,0.2 -0.1,0.5 -0.1,0.8v0.8h-1.5v1.5h1.5v0.8c0,0.2 0,0.5 0.1,0.8h-1.6v1.5h2.1c0.8,1.4 2.3,2.3 4,2.3s3.1,-0.9 4,-2.3h2.1v-1.5H78c0,-0.2 0.1,-0.5 0.1,-0.8v-0.8h1.5v-1.5h-1.5v-0.8c0,-0.2 0,-0.5 -0.1,-0.8h1.6v-1.5h-2.1C77.1,59.2 76.6,58.8 76,58.3zM75,65.9H72v-1.5H75V65.9zM75,62.9H72v-1.5H75V62.9z"
|
|
||||||
android:fillColor="#33D17A"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground_debug"/>
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground_monochrome_debug"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground_debug"/>
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground_monochrome_debug"/>
|
|
||||||
</adaptive-icon>
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 988 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">#000000</color>
|
|
||||||
</resources>
|
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="org.joinmastodon.android">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
|
||||||
|
|
||||||
|
<application>
|
||||||
<application
|
|
||||||
tools:replace="android:label"
|
|
||||||
android:label="@string/mo_app_name_debug">
|
|
||||||
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$InstallerStatusReceiver" android:exported="false"/>-->
|
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$InstallerStatusReceiver" android:exported="false"/>-->
|
||||||
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$AfterUpdateRestartReceiver" android:exported="true" android:enabled="false">-->
|
<!-- <receiver android:name=".updater.GithubSelfUpdaterImpl$AfterUpdateRestartReceiver" android:exported="true" android:enabled="false">-->
|
||||||
<!-- <intent-filter>-->
|
<!-- <intent-filter>-->
|
||||||
@@ -115,7 +115,7 @@ 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/LucasGGamerM/moshidon/releases")
|
.url("https://api.github.com/repos/sk22/megalodon/releases")
|
||||||
.build();
|
.build();
|
||||||
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
Call call=MastodonAPIController.getHttpClient().newCall(req);
|
||||||
try(Response resp=call.execute()){
|
try(Response resp=call.execute()){
|
||||||
@@ -154,7 +154,7 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
|
|||||||
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
Log.d(TAG, "actuallyCheckForUpdates: new version: "+version);
|
||||||
for(JsonElement el:obj.getAsJsonArray("assets")){
|
for(JsonElement el:obj.getAsJsonArray("assets")){
|
||||||
JsonObject asset=el.getAsJsonObject();
|
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())){
|
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();
|
long size=asset.get("size").getAsLong();
|
||||||
String url=asset.get("browser_download_url").getAsString();
|
String url=asset.get("browser_download_url").getAsString();
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="org.joinmastodon.android">
|
|
||||||
|
|
||||||
<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.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
|
||||||
<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"/>
|
||||||
<uses-feature
|
|
||||||
android:name="android.hardware.camera"
|
|
||||||
android:required="false" />
|
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
@@ -30,12 +25,11 @@
|
|||||||
<application
|
<application
|
||||||
android:name=".MastodonApp"
|
android:name=".MastodonApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:label="@string/mo_app_name"
|
android:label="@string/sk_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:theme="@style/Theme.Mastodon.AutoLightDark"
|
android:theme="@style/Theme.Mastodon.AutoLightDark"
|
||||||
android:windowSoftInputMode="adjustPan"
|
|
||||||
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">
|
||||||
@@ -64,7 +58,7 @@
|
|||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<data android:scheme="${oAuthScheme}" android:host="callback"/>
|
<data android:scheme="megalodon-android-auth" android:host="callback"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize"
|
<activity android:name=".ExternalShareActivity" android:exported="true" android:configChanges="orientation|screenSize" android:windowSoftInputMode="adjustResize"
|
||||||
@@ -104,16 +98,6 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="org.joinmastodon.android.utils.FileProvider"
|
|
||||||
android:authorities="${applicationId}.fileprovider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/file_paths" />
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,43 +1,56 @@
|
|||||||
13bells.com
|
13bells.com
|
||||||
1611.social
|
1611.social
|
||||||
4aem.com
|
4aem.com
|
||||||
5dollah.click
|
|
||||||
adachi.party
|
adachi.party
|
||||||
adtension.com
|
anime.website
|
||||||
annihilation.social
|
annihilation.social
|
||||||
anon-kenkai.com
|
anon-kenkai.com
|
||||||
asbestos.cafe
|
asbestos.cafe
|
||||||
bae.st
|
bae.st
|
||||||
|
bajax.us
|
||||||
banepo.st
|
banepo.st
|
||||||
|
baraag.net
|
||||||
bassam.social
|
bassam.social
|
||||||
battlepenguin.video
|
|
||||||
beefyboys.win
|
beefyboys.win
|
||||||
|
beepboop.ga
|
||||||
|
berserker.town
|
||||||
|
bikeshed.party
|
||||||
|
boks.moe
|
||||||
boymoder.biz
|
boymoder.biz
|
||||||
brainsoap.net
|
brainsoap.net
|
||||||
breastmilk.club
|
breastmilk.club
|
||||||
brighteon.social
|
brighteon.social
|
||||||
cachapa.xyz
|
bungle.online
|
||||||
canary.fedinuke.example.com
|
|
||||||
catgirl.life
|
|
||||||
cawfee.club
|
cawfee.club
|
||||||
childlove.space
|
|
||||||
clew.lol
|
clew.lol
|
||||||
clubcyberia.co
|
clubcyberia.co
|
||||||
|
collapsitarian.io
|
||||||
|
comfyboy.club
|
||||||
contrapointsfan.club
|
contrapointsfan.club
|
||||||
crucible.world
|
|
||||||
cum.camp
|
cum.camp
|
||||||
cum.salon
|
cum.salon
|
||||||
|
darknight-coffee.org
|
||||||
decayable.ink
|
decayable.ink
|
||||||
dembased.xyz
|
dembased.xyz
|
||||||
|
desupost.soy
|
||||||
detroitriotcity.com
|
detroitriotcity.com
|
||||||
djsumdog.com
|
eatthebugs.social
|
||||||
eientei.org
|
eientei.org
|
||||||
|
elementality.org
|
||||||
eveningzoo.club
|
eveningzoo.club
|
||||||
|
firedragonstudios.com
|
||||||
|
firefaithfellowship.com
|
||||||
fluf.club
|
fluf.club
|
||||||
|
foxfam.club
|
||||||
freak.university
|
freak.university
|
||||||
freeatlantis.com
|
freeatlantis.com
|
||||||
|
freedomstrike.org
|
||||||
|
freesoftwareextremist.com
|
||||||
|
freespeech.group
|
||||||
freespeechextremist.com
|
freespeechextremist.com
|
||||||
|
freetalklive.com
|
||||||
froth.zone
|
froth.zone
|
||||||
|
fulltermprivacy.com
|
||||||
gameliberty.club
|
gameliberty.club
|
||||||
gearlandia.haus
|
gearlandia.haus
|
||||||
genderheretics.xyz
|
genderheretics.xyz
|
||||||
@@ -46,34 +59,42 @@ gleasonator.com
|
|||||||
glee.li
|
glee.li
|
||||||
glindr.org
|
glindr.org
|
||||||
goyim.app
|
goyim.app
|
||||||
h5q.net
|
goyslop.cafe
|
||||||
haeder.net
|
haeder.net
|
||||||
handholding.io
|
handholding.io
|
||||||
hitchhiker.social
|
hitchhiker.social
|
||||||
|
hunk.city
|
||||||
iddqd.social
|
iddqd.social
|
||||||
|
intkos.link
|
||||||
|
justicewarrior.social
|
||||||
|
kawa-kun.com
|
||||||
kitsunemimi.club
|
kitsunemimi.club
|
||||||
kiwifarms.cc
|
kiwifarms.cc
|
||||||
|
kompost.cz
|
||||||
kurosawa.moe
|
kurosawa.moe
|
||||||
kyaruc.moe
|
|
||||||
leafposter.club
|
leafposter.club
|
||||||
|
leftychan.net
|
||||||
lewdieheaven.com
|
lewdieheaven.com
|
||||||
liberdon.com
|
liberdon.com
|
||||||
ligma.pro
|
ligma.pro
|
||||||
lolicon.rocks
|
lolicon.rocks
|
||||||
lolison.network
|
|
||||||
lolison.top
|
lolison.top
|
||||||
lovingexpressions.net
|
lovingexpressions.net
|
||||||
|
mahodou.moe
|
||||||
makemysarcophagus.com
|
makemysarcophagus.com
|
||||||
|
maladaptive.art
|
||||||
marsey.moe
|
marsey.moe
|
||||||
|
masochi.st
|
||||||
mastinator.com
|
mastinator.com
|
||||||
merovingian.club
|
merovingian.club
|
||||||
midwaytrades.com
|
midwaytrades.com
|
||||||
mirr0r.city
|
mirr0r.city
|
||||||
morale.ch
|
moa.st
|
||||||
mouse.services
|
mouse.services
|
||||||
mugicha.club
|
mugicha.club
|
||||||
narrativerry.xyz
|
narrativerry.xyz
|
||||||
natehiggers.online
|
natehiggers.online
|
||||||
|
neckbeard.xyz
|
||||||
needs.vodka
|
needs.vodka
|
||||||
neenster.org
|
neenster.org
|
||||||
nicecrew.digital
|
nicecrew.digital
|
||||||
@@ -82,18 +103,18 @@ noagendasocial.com
|
|||||||
noagendasocial.nl
|
noagendasocial.nl
|
||||||
noagendatube.com
|
noagendatube.com
|
||||||
nobodyhasthe.biz
|
nobodyhasthe.biz
|
||||||
norwoodzero.net
|
nukem.biz
|
||||||
nyanide.com
|
obo.sh
|
||||||
onionfarms.org
|
onionfarms.org
|
||||||
pawlicker.com
|
pawlicker.com
|
||||||
pawoo.net
|
pawoo.net
|
||||||
pedo.school
|
pedo.school
|
||||||
peervideo.club
|
|
||||||
piazza.today
|
piazza.today
|
||||||
pibvt.net
|
pibvt.net
|
||||||
pieville.net
|
pieville.net
|
||||||
pisskey.io
|
pisskey.io
|
||||||
plagu.ee
|
plagu.ee
|
||||||
|
pmth.us
|
||||||
poa.st
|
poa.st
|
||||||
poast.org
|
poast.org
|
||||||
poast.tv
|
poast.tv
|
||||||
@@ -102,18 +123,17 @@ prospeech.space
|
|||||||
quodverum.com
|
quodverum.com
|
||||||
r18.social
|
r18.social
|
||||||
rakket.app
|
rakket.app
|
||||||
rapemeat.express
|
|
||||||
rapemeat.solutions
|
rapemeat.solutions
|
||||||
rayci.st
|
rdrama.cc
|
||||||
rebelbase.site
|
rebelbase.site
|
||||||
|
retardedniggers.forsale
|
||||||
|
rojogato.com
|
||||||
ryona.agency
|
ryona.agency
|
||||||
sad.cab
|
|
||||||
schwartzwelt.xyz
|
schwartzwelt.xyz
|
||||||
seal.cafe
|
seal.cafe
|
||||||
shaw.app
|
|
||||||
shigusegubu.club
|
shigusegubu.club
|
||||||
shitpost.cloud
|
shitpost.cloud
|
||||||
shortstacksran.ch
|
shota.house
|
||||||
silliness.observer
|
silliness.observer
|
||||||
skinheads.eu
|
skinheads.eu
|
||||||
skinheads.io
|
skinheads.io
|
||||||
@@ -128,20 +148,23 @@ sneed.social
|
|||||||
sonichu.com
|
sonichu.com
|
||||||
spinster.xyz
|
spinster.xyz
|
||||||
springbo.cc
|
springbo.cc
|
||||||
|
starnix.network
|
||||||
strelizia.net
|
strelizia.net
|
||||||
|
syspxl.xyz
|
||||||
tastingtraffic.net
|
tastingtraffic.net
|
||||||
teci.world
|
teci.world
|
||||||
theapex.social
|
theapex.social
|
||||||
thechimp.zone
|
|
||||||
thenobody.club
|
|
||||||
thepostearthdestination.com
|
thepostearthdestination.com
|
||||||
tkammer.de
|
tkammer.de
|
||||||
trumpislovetrumpis.life
|
trumpislovetrumpis.life
|
||||||
truthsocial.co.in
|
truthsocial.co.in
|
||||||
usualsuspects.lol
|
urchan.org
|
||||||
varishangout.net
|
varishangout.net
|
||||||
vtuberfan.social
|
whinge.house
|
||||||
|
whinge.town
|
||||||
|
wideboys.org
|
||||||
wolfgirl.bar
|
wolfgirl.bar
|
||||||
xn--p1abe3d.xn--80asehdb
|
xn--p1abe3d.xn--80asehdb
|
||||||
yggdrasil.social
|
yggdrasil.social
|
||||||
youjo.love
|
youjo.love
|
||||||
|
zztails.gay
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 358 KiB |
@@ -1,202 +0,0 @@
|
|||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@@ -266,7 +266,7 @@ public class AudioPlayerService extends Service{
|
|||||||
private void updateNotification(boolean dismissable, boolean removeNotification){
|
private void updateNotification(boolean dismissable, boolean removeNotification){
|
||||||
Notification.Builder bldr=new Notification.Builder(this)
|
Notification.Builder bldr=new Notification.Builder(this)
|
||||||
.setSmallIcon(R.drawable.ic_ntf_logo)
|
.setSmallIcon(R.drawable.ic_ntf_logo)
|
||||||
.setContentTitle(status.account.getDisplayName())
|
.setContentTitle(status.account.displayName)
|
||||||
.setContentText(HtmlParser.strip(status.content))
|
.setContentText(HtmlParser.strip(status.content))
|
||||||
.setOngoing(!dismissable)
|
.setOngoing(!dismissable)
|
||||||
.setShowWhen(false)
|
.setShowWhen(false)
|
||||||
@@ -281,7 +281,7 @@ public class AudioPlayerService extends Service{
|
|||||||
|
|
||||||
if(playerReady){
|
if(playerReady){
|
||||||
boolean isPlaying=player.isPlaying();
|
boolean isPlaying=player.isPlaying();
|
||||||
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_fluent_pause_24_filled : R.drawable.ic_fluent_play_24_filled),
|
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_pause_24 : R.drawable.ic_play_24),
|
||||||
getString(isPlaying ? R.string.pause : R.string.play),
|
getString(isPlaying ? R.string.pause : R.string.play),
|
||||||
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
|
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
|
||||||
.build());
|
.build());
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
|||||||
UiUtils.setUserPreferredTheme(this);
|
UiUtils.setUserPreferredTheme(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
|
|
||||||
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
|
||||||
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
|
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
|
||||||
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
|
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
package org.joinmastodon.android;
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
||||||
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.MATERIAL3;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
|
|
||||||
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.model.ContentType;
|
import org.joinmastodon.android.model.ContentType;
|
||||||
@@ -28,6 +25,8 @@ import java.util.HashSet;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
public class GlobalUserPreferences{
|
public class GlobalUserPreferences{
|
||||||
private static final String TAG="GlobalUserPreferences";
|
private static final String TAG="GlobalUserPreferences";
|
||||||
|
|
||||||
@@ -42,6 +41,7 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean showNewPostsButton;
|
public static boolean showNewPostsButton;
|
||||||
public static boolean toolbarMarquee;
|
public static boolean toolbarMarquee;
|
||||||
public static boolean disableSwipe;
|
public static boolean disableSwipe;
|
||||||
|
public static boolean voteButtonForSingleChoice;
|
||||||
public static boolean enableDeleteNotifications;
|
public static boolean enableDeleteNotifications;
|
||||||
public static boolean translateButtonOpenedOnly;
|
public static boolean translateButtonOpenedOnly;
|
||||||
public static boolean uniformNotificationIcon;
|
public static boolean uniformNotificationIcon;
|
||||||
@@ -53,34 +53,18 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean collapseLongPosts;
|
public static boolean collapseLongPosts;
|
||||||
public static boolean spectatorMode;
|
public static boolean spectatorMode;
|
||||||
public static boolean autoHideFab;
|
public static boolean autoHideFab;
|
||||||
|
public static boolean compactReblogReplyLine;
|
||||||
public static boolean allowRemoteLoading;
|
public static boolean allowRemoteLoading;
|
||||||
public static boolean forwardReportDefault;
|
public static boolean forwardReportDefault;
|
||||||
public static AutoRevealMode autoRevealEqualSpoilers;
|
public static AutoRevealMode autoRevealEqualSpoilers;
|
||||||
|
public static ColorPreference color;
|
||||||
public static boolean disableM3PillActiveIndicator;
|
public static boolean disableM3PillActiveIndicator;
|
||||||
public static boolean showNavigationLabels;
|
public static boolean showNavigationLabels;
|
||||||
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
|
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
|
||||||
public static boolean overlayMedia;
|
public static boolean overlayMedia;
|
||||||
public static boolean showSuicideHelp;
|
public static boolean showSuicideHelp;
|
||||||
public static boolean underlinedLinks;
|
|
||||||
public static ColorPreference color;
|
|
||||||
public static boolean likeIcon;
|
|
||||||
|
|
||||||
// MOSHIDON
|
private static SharedPreferences getPrefs(){
|
||||||
public static boolean showDividers;
|
|
||||||
public static boolean relocatePublishButton;
|
|
||||||
public static boolean defaultToUnlistedReplies;
|
|
||||||
public static boolean doubleTapToSearch;
|
|
||||||
public static boolean doubleTapToSwipe;
|
|
||||||
public static boolean confirmBeforeReblog;
|
|
||||||
public static boolean hapticFeedback;
|
|
||||||
public static boolean replyLineAboveHeader;
|
|
||||||
public static boolean swapBookmarkWithBoostAction;
|
|
||||||
public static boolean loadRemoteAccountFollowers;
|
|
||||||
public static boolean mentionRebloggerAutomatically;
|
|
||||||
public static boolean showPostsWithoutAlt;
|
|
||||||
public static boolean showMediaPreview;
|
|
||||||
|
|
||||||
public static SharedPreferences getPrefs(){
|
|
||||||
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +100,7 @@ public class GlobalUserPreferences{
|
|||||||
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
|
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
|
||||||
toolbarMarquee=prefs.getBoolean("toolbarMarquee", true);
|
toolbarMarquee=prefs.getBoolean("toolbarMarquee", true);
|
||||||
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
disableSwipe=prefs.getBoolean("disableSwipe", false);
|
||||||
|
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
|
||||||
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
|
||||||
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
|
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
|
||||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
||||||
@@ -127,6 +112,7 @@ public class GlobalUserPreferences{
|
|||||||
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
||||||
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
||||||
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
autoHideFab=prefs.getBoolean("autoHideFab", true);
|
||||||
|
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
|
||||||
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
||||||
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
||||||
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
|
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
|
||||||
@@ -137,28 +123,6 @@ public class GlobalUserPreferences{
|
|||||||
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
|
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
|
||||||
overlayMedia=prefs.getBoolean("overlayMedia", false);
|
overlayMedia=prefs.getBoolean("overlayMedia", false);
|
||||||
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
|
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
|
||||||
underlinedLinks=prefs.getBoolean("underlinedLinks", true);
|
|
||||||
color=ColorPreference.valueOf(prefs.getString("color", MATERIAL3.name()));
|
|
||||||
likeIcon=prefs.getBoolean("likeIcon", false);
|
|
||||||
|
|
||||||
// MOSHIDON
|
|
||||||
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
|
|
||||||
showDividers =prefs.getBoolean("showDividers", false);
|
|
||||||
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
|
|
||||||
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
|
|
||||||
doubleTapToSearch =prefs.getBoolean("doubleTapToSearch", true);
|
|
||||||
doubleTapToSwipe =prefs.getBoolean("doubleTapToSwipe", true);
|
|
||||||
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
|
|
||||||
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
|
|
||||||
hapticFeedback=prefs.getBoolean("hapticFeedback", true);
|
|
||||||
swapBookmarkWithBoostAction=prefs.getBoolean("swapBookmarkWithBoostAction", false);
|
|
||||||
loadRemoteAccountFollowers=prefs.getBoolean("loadRemoteAccountFollowers", true);
|
|
||||||
mentionRebloggerAutomatically=prefs.getBoolean("mentionRebloggerAutomatically", false);
|
|
||||||
showPostsWithoutAlt=prefs.getBoolean("showPostsWithoutAlt", true);
|
|
||||||
showMediaPreview=prefs.getBoolean("showMediaPreview", true);
|
|
||||||
|
|
||||||
theme=ThemePreference.values()[prefs.getInt("theme", 0)];
|
|
||||||
|
|
||||||
|
|
||||||
if (prefs.contains("prefixRepliesWithRe")) {
|
if (prefs.contains("prefixRepliesWithRe")) {
|
||||||
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
||||||
@@ -169,11 +133,14 @@ public class GlobalUserPreferences{
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE);
|
try {
|
||||||
if(migrationLevel < 61)
|
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
||||||
migrateToUpstreamVersion61();
|
} catch (IllegalArgumentException|ClassCastException ignored) {
|
||||||
if(migrationLevel < BuildConfig.VERSION_CODE)
|
// invalid color name or color was previously saved as integer
|
||||||
prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply();
|
color=ColorPreference.PINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(prefs.getInt("migrationLevel", 0) < 61) migrateToUpstreamVersion61();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void save(){
|
public static void save(){
|
||||||
@@ -203,6 +170,8 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("collapseLongPosts", collapseLongPosts)
|
.putBoolean("collapseLongPosts", collapseLongPosts)
|
||||||
.putBoolean("spectatorMode", spectatorMode)
|
.putBoolean("spectatorMode", spectatorMode)
|
||||||
.putBoolean("autoHideFab", autoHideFab)
|
.putBoolean("autoHideFab", autoHideFab)
|
||||||
|
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
|
||||||
|
.putString("color", color.name())
|
||||||
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||||
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
||||||
.putBoolean("forwardReportDefault", forwardReportDefault)
|
.putBoolean("forwardReportDefault", forwardReportDefault)
|
||||||
@@ -213,50 +182,9 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
|
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
|
||||||
.putBoolean("overlayMedia", overlayMedia)
|
.putBoolean("overlayMedia", overlayMedia)
|
||||||
.putBoolean("showSuicideHelp", showSuicideHelp)
|
.putBoolean("showSuicideHelp", showSuicideHelp)
|
||||||
.putBoolean("underlinedLinks", underlinedLinks)
|
|
||||||
.putString("color", color.name())
|
|
||||||
.putBoolean("likeIcon", likeIcon)
|
|
||||||
|
|
||||||
// MOSHIDON
|
|
||||||
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
|
|
||||||
.putBoolean("doubleTapToSearch", doubleTapToSearch)
|
|
||||||
.putBoolean("doubleTapToSwipe", doubleTapToSwipe)
|
|
||||||
.putBoolean("replyLineAboveHeader", replyLineAboveHeader)
|
|
||||||
.putBoolean("confirmBeforeReblog", confirmBeforeReblog)
|
|
||||||
.putBoolean("swapBookmarkWithBoostAction", swapBookmarkWithBoostAction)
|
|
||||||
.putBoolean("loadRemoteAccountFollowers", loadRemoteAccountFollowers)
|
|
||||||
.putBoolean("hapticFeedback", hapticFeedback)
|
|
||||||
.putBoolean("mentionRebloggerAutomatically", mentionRebloggerAutomatically)
|
|
||||||
.putBoolean("showDividers", showDividers)
|
|
||||||
.putBoolean("relocatePublishButton", relocatePublishButton)
|
|
||||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
|
||||||
.putBoolean("showPostsWithoutAlt", showPostsWithoutAlt)
|
|
||||||
.putBoolean("showMediaPreview", showMediaPreview)
|
|
||||||
|
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ThemePreference{
|
|
||||||
AUTO,
|
|
||||||
LIGHT,
|
|
||||||
DARK
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum AutoRevealMode {
|
|
||||||
NEVER,
|
|
||||||
THREADS,
|
|
||||||
DISCUSSIONS
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PrefixRepliesMode {
|
|
||||||
NEVER,
|
|
||||||
ALWAYS,
|
|
||||||
TO_OTHERS
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//region preferences migrations
|
|
||||||
|
|
||||||
private static void migrateToUpstreamVersion61(){
|
private static void migrateToUpstreamVersion61(){
|
||||||
Log.d(TAG, "Migrating preferences to upstream version 61!!");
|
Log.d(TAG, "Migrating preferences to upstream version 61!!");
|
||||||
|
|
||||||
@@ -303,8 +231,49 @@ public class GlobalUserPreferences{
|
|||||||
|
|
||||||
localPrefs.save();
|
localPrefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prefs.edit().putInt("migrationLevel", 61).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
public enum ColorPreference{
|
||||||
|
MATERIAL3,
|
||||||
|
PINK,
|
||||||
|
PURPLE,
|
||||||
|
GREEN,
|
||||||
|
BLUE,
|
||||||
|
BROWN,
|
||||||
|
RED,
|
||||||
|
YELLOW;
|
||||||
|
|
||||||
|
public @StringRes int getName() {
|
||||||
|
return switch(this){
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ThemePreference{
|
||||||
|
AUTO,
|
||||||
|
LIGHT,
|
||||||
|
DARK
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AutoRevealMode {
|
||||||
|
NEVER,
|
||||||
|
THREADS,
|
||||||
|
DISCUSSIONS
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PrefixRepliesMode {
|
||||||
|
NEVER,
|
||||||
|
ALWAYS,
|
||||||
|
TO_OTHERS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
package org.joinmastodon.android;
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
import static org.joinmastodon.android.fragments.ComposeFragment.CAMERA_PERMISSION_CODE;
|
|
||||||
import static org.joinmastodon.android.fragments.ComposeFragment.CAMERA_PIC_REQUEST_CODE;
|
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.net.Uri;
|
|
||||||
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.provider.MediaStore;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
@@ -24,7 +17,6 @@ import org.joinmastodon.android.api.ObjectValidationException;
|
|||||||
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
import org.joinmastodon.android.api.requests.search.GetSearchResults;
|
||||||
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.TakePictureRequestEvent;
|
|
||||||
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;
|
||||||
@@ -39,44 +31,17 @@ import org.joinmastodon.android.utils.ProvidesAssistContent;
|
|||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
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;
|
||||||
|
|
||||||
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
|
||||||
private static final String TAG="MainActivity";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState){
|
protected void onCreate(@Nullable Bundle savedInstanceState){
|
||||||
AccountSession session=getCurrentSession();
|
UiUtils.setUserPreferredTheme(this);
|
||||||
UiUtils.setUserPreferredTheme(this, session);
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Thread.UncaughtExceptionHandler defaultHandler=Thread.getDefaultUncaughtExceptionHandler();
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler((t, e)->{
|
|
||||||
File file=new File(MastodonApp.context.getFilesDir(), "crash.log");
|
|
||||||
try(FileOutputStream out=new FileOutputStream(file)){
|
|
||||||
PrintWriter writer=new PrintWriter(out);
|
|
||||||
writer.println(BuildConfig.VERSION_NAME+" ("+BuildConfig.VERSION_CODE+")");
|
|
||||||
writer.println(Instant.now().toString());
|
|
||||||
writer.println();
|
|
||||||
e.printStackTrace(writer);
|
|
||||||
writer.flush();
|
|
||||||
}catch(IOException x){
|
|
||||||
Log.e(TAG, "Error writing crash.log", x);
|
|
||||||
}finally{
|
|
||||||
defaultHandler.uncaughtException(t, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(savedInstanceState==null){
|
if(savedInstanceState==null){
|
||||||
restartHomeFragment();
|
restartHomeFragment();
|
||||||
}
|
}
|
||||||
@@ -235,24 +200,6 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onActivityResult(int requestCode, int resultCode, Intent data){
|
|
||||||
// if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode== Activity.RESULT_OK){
|
|
||||||
// E.post(new TakePictureRequestEvent());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
|
|
||||||
if (requestCode == CAMERA_PERMISSION_CODE && (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
|
||||||
E.post(new TakePictureRequestEvent());
|
|
||||||
} else {
|
|
||||||
Toast.makeText(this, R.string.permission_required, Toast.LENGTH_SHORT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Fragment getCurrentFragment() {
|
public Fragment getCurrentFragment() {
|
||||||
for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
|
for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
|
||||||
FrameLayout fl = fragmentContainers.get(i);
|
FrameLayout fl = fragmentContainers.get(i);
|
||||||
@@ -270,36 +217,6 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
|||||||
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountSession getCurrentSession(){
|
|
||||||
AccountSession session;
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
Intent intent=getIntent();
|
|
||||||
if(intent.hasExtra("fromExternalShare")) {
|
|
||||||
return AccountSessionManager.getInstance()
|
|
||||||
.getAccount(intent.getStringExtra("account"));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
|
|
||||||
boolean hasNotification = intent.hasExtra("notification");
|
|
||||||
if(fromNotification){
|
|
||||||
String accountID=intent.getStringExtra("accountID");
|
|
||||||
try{
|
|
||||||
session=AccountSessionManager.getInstance().getAccount(accountID);
|
|
||||||
if(!hasNotification) args.putString("tab", "notifications");
|
|
||||||
}catch(IllegalStateException x){
|
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
session=AccountSessionManager.getInstance().getLastActiveAccount();
|
|
||||||
}
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restartActivity(){
|
|
||||||
finish();
|
|
||||||
startActivity(new Intent(this, MainActivity.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void restartHomeFragment(){
|
public void restartHomeFragment(){
|
||||||
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
|
||||||
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
showFragmentClearingBackStack(new CustomWelcomeFragment());
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package org.joinmastodon.android;
|
package org.joinmastodon.android;
|
||||||
|
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.ALWAYS;
|
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.*;
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.TO_OTHERS;
|
|
||||||
import static org.joinmastodon.android.GlobalUserPreferences.getPrefs;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
@@ -14,14 +12,13 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.opengl.Visibility;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIController;
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
|
|
||||||
import org.joinmastodon.android.api.requests.notifications.GetNotificationByID;
|
import org.joinmastodon.android.api.requests.notifications.GetNotificationByID;
|
||||||
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
import org.joinmastodon.android.api.requests.statuses.CreateStatus;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
||||||
@@ -36,7 +33,7 @@ import org.joinmastodon.android.model.Preferences;
|
|||||||
import org.joinmastodon.android.model.PushNotification;
|
import org.joinmastodon.android.model.PushNotification;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
@@ -141,10 +138,9 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
switch (NotificationAction.values()[intent.getIntExtra("notificationAction", 0)]) {
|
switch (NotificationAction.values()[intent.getIntExtra("notificationAction", 0)]) {
|
||||||
case FAVORITE -> new SetStatusFavorited(statusID, true).exec(accountID);
|
case FAVORITE -> new SetStatusFavorited(statusID, true).exec(accountID);
|
||||||
case BOOKMARK -> new SetStatusBookmarked(statusID, true).exec(accountID);
|
case BOOKMARK -> new SetStatusBookmarked(statusID, true).exec(accountID);
|
||||||
case BOOST -> new SetStatusReblogged(notification.status.id, true, preferences.postingDefaultVisibility).exec(accountID);
|
case REBLOG -> new SetStatusReblogged(notification.status.id, true, preferences.postingDefaultVisibility).exec(accountID);
|
||||||
case UNBOOST -> new SetStatusReblogged(notification.status.id, false, preferences.postingDefaultVisibility).exec(accountID);
|
case UNDO_REBLOG -> new SetStatusReblogged(notification.status.id, false, preferences.postingDefaultVisibility).exec(accountID);
|
||||||
case REPLY -> handleReplyAction(context, accountID, intent, notification, notificationId, preferences);
|
case REPLY -> handleReplyAction(context, accountID, intent, notification, notificationId, preferences);
|
||||||
case FOLLOW_BACK -> new SetAccountFollowed(notification.account.id, true, true, false).exec(accountID);
|
|
||||||
default -> Log.w(TAG, "onReceive: Failed to get NotificationAction");
|
default -> Log.w(TAG, "onReceive: Failed to get NotificationAction");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,12 +207,12 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
.setCategory(Notification.CATEGORY_SOCIAL)
|
.setCategory(Notification.CATEGORY_SOCIAL)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setLights(context.getColor(R.color.primary_700), 500, 1000)
|
.setLights(UiUtils.getThemeColor(context, android.R.attr.colorAccent), 500, 1000)
|
||||||
.setColor(context.getColor(R.color.shortcut_icon_background));
|
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
|
||||||
|
|
||||||
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
if (!GlobalUserPreferences.uniformNotificationIcon) {
|
||||||
builder.setSmallIcon(switch (pn.notificationType) {
|
builder.setSmallIcon(switch (pn.notificationType) {
|
||||||
case FAVORITE -> GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled;
|
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
|
||||||
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
|
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
|
||||||
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
|
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
|
||||||
case MENTION -> R.drawable.ic_fluent_mention_24_filled;
|
case MENTION -> R.drawable.ic_fluent_mention_24_filled;
|
||||||
@@ -258,23 +254,14 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
builder.addAction(buildReplyAction(context, id, accountID, notification));
|
builder.addAction(buildReplyAction(context, id, accountID, notification));
|
||||||
}
|
}
|
||||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.button_favorite), NotificationAction.FAVORITE));
|
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.button_favorite), NotificationAction.FAVORITE));
|
||||||
if(GlobalUserPreferences.swapBookmarkWithBoostAction){
|
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.add_bookmark), NotificationAction.BOOKMARK));
|
||||||
if(notification.status.visibility != StatusPrivacy.DIRECT) {
|
if(notification.status.visibility != StatusPrivacy.DIRECT) {
|
||||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.button_reblog), NotificationAction.BOOST));
|
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.button_reblog), NotificationAction.REBLOG));
|
||||||
}else{
|
|
||||||
// This is just so there is a bookmark action if you cannot reblog the toot
|
|
||||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.add_bookmark), NotificationAction.BOOKMARK));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.add_bookmark), NotificationAction.BOOKMARK));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case UPDATE -> {
|
case UPDATE -> {
|
||||||
if(notification.status.reblogged)
|
if(notification.status.reblogged)
|
||||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.sk_undo_reblog), NotificationAction.UNBOOST));
|
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.sk_undo_reblog), NotificationAction.UNDO_REBLOG));
|
||||||
}
|
|
||||||
case FOLLOW -> {
|
|
||||||
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.follow_back), NotificationAction.FOLLOW_BACK));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,7 +325,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
|||||||
CreateStatus.Request req=new CreateStatus.Request();
|
CreateStatus.Request req=new CreateStatus.Request();
|
||||||
req.status = initialText + input.toString();
|
req.status = initialText + input.toString();
|
||||||
req.language = notification.status.language;
|
req.language = notification.status.language;
|
||||||
req.visibility = (notification.status.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : notification.status.visibility);
|
req.visibility = notification.status.visibility;
|
||||||
req.inReplyToId = notification.status.id;
|
req.inReplyToId = notification.status.id;
|
||||||
|
|
||||||
if (notification.status.hasSpoiler() &&
|
if (notification.status.hasSpoiler() &&
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -70,11 +69,12 @@ public class CacheController{
|
|||||||
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
|
||||||
status.postprocess();
|
status.postprocess();
|
||||||
int flags=cursor.getInt(1);
|
int flags=cursor.getInt(1);
|
||||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0) ? status.id : null;
|
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
||||||
newMaxID=status.id;
|
newMaxID=status.id;
|
||||||
result.add(status);
|
result.add(status);
|
||||||
}while(cursor.moveToNext());
|
}while(cursor.moveToNext());
|
||||||
String _newMaxID=newMaxID;
|
String _newMaxID=newMaxID;
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||||
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,9 @@ public class CacheController{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
callback.onSuccess(new CacheablePaginatedResponse<>(result, result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
ArrayList<Status> filtered=new ArrayList<>(result);
|
||||||
|
AccountSessionManager.get(accountID).filterStatuses(filtered, FilterContext.HOME);
|
||||||
|
callback.onSuccess(new CacheablePaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||||
putHomeTimeline(result, maxID==null);
|
putHomeTimeline(result, maxID==null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,14 +116,12 @@ public class CacheController{
|
|||||||
values.put("id", s.id);
|
values.put("id", s.id);
|
||||||
values.put("json", MastodonAPIController.gson.toJson(s));
|
values.put("json", MastodonAPIController.gson.toJson(s));
|
||||||
int flags=0;
|
int flags=0;
|
||||||
if(Objects.equals(s.hasGapAfter, s.id))
|
if(s.hasGapAfter)
|
||||||
flags|=POST_FLAG_GAP_AFTER;
|
flags|=POST_FLAG_GAP_AFTER;
|
||||||
values.put("flags", flags);
|
values.put("flags", flags);
|
||||||
values.put("time", s.createdAt.getEpochSecond());
|
values.put("time", s.createdAt.getEpochSecond());
|
||||||
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||||
}
|
}
|
||||||
if(!clear)
|
|
||||||
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"1000"});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,28 +273,6 @@ public class CacheController{
|
|||||||
|
|
||||||
public void deleteStatus(String id){
|
public void deleteStatus(String id){
|
||||||
runOnDbThread((db)->{
|
runOnDbThread((db)->{
|
||||||
String gapId=null;
|
|
||||||
int gapFlags=0;
|
|
||||||
// select to-be-removed and newer row
|
|
||||||
try(Cursor cursor=db.query("home_timeline", new String[]{"id", "flags"}, "`time`>=(SELECT `time` FROM `home_timeline` WHERE `id`=?)", new String[]{id}, null, null, "`time` ASC", "2")){
|
|
||||||
boolean hadGapAfter=false;
|
|
||||||
// always either one or two iterations (only one if there's no newer post)
|
|
||||||
while(cursor.moveToNext()){
|
|
||||||
String currentId=cursor.getString(0);
|
|
||||||
int currentFlags=cursor.getInt(1);
|
|
||||||
if(currentId.equals(id)){
|
|
||||||
hadGapAfter=((currentFlags & POST_FLAG_GAP_AFTER)!=0);
|
|
||||||
}else if(hadGapAfter){
|
|
||||||
gapFlags=currentFlags|POST_FLAG_GAP_AFTER;
|
|
||||||
gapId=currentId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(gapId!=null){
|
|
||||||
ContentValues values=new ContentValues();
|
|
||||||
values.put("flags", gapFlags);
|
|
||||||
db.update("home_timeline", values, "`id`=?", new String[]{gapId});
|
|
||||||
}
|
|
||||||
db.delete("home_timeline", "`id`=?", new String[]{id});
|
db.delete("home_timeline", "`id`=?", new String[]{id});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class MastodonAPIController{
|
|||||||
.create();
|
.create();
|
||||||
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
|
||||||
private static OkHttpClient httpClient=new OkHttpClient.Builder()
|
private static OkHttpClient httpClient=new OkHttpClient.Builder()
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(5, TimeUnit.MINUTES)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private AccountSession session;
|
private AccountSession session;
|
||||||
@@ -89,13 +89,13 @@ public class MastodonAPIController{
|
|||||||
final boolean isBad = host == null || badDomains.stream().anyMatch(h -> h.equalsIgnoreCase(host) || host.toLowerCase().endsWith("." + h));
|
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 (isBad) throw new IllegalArgumentException();
|
||||||
if(req.canceled)
|
if(req.canceled)
|
||||||
return;
|
return;
|
||||||
Request.Builder builder=new Request.Builder()
|
Request.Builder builder=new Request.Builder()
|
||||||
.url(req.getURL().toString())
|
.url(req.getURL().toString())
|
||||||
.method(req.getMethod(), req.getRequestBody())
|
.method(req.getMethod(), req.getRequestBody())
|
||||||
.header("User-Agent", "MoshidonAndroid/"+BuildConfig.VERSION_NAME);
|
.header("User-Agent", "MegalodonAndroid/"+BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
String token=null;
|
String token=null;
|
||||||
if(session!=null)
|
if(session!=null)
|
||||||
@@ -113,13 +113,13 @@ public class MastodonAPIController{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Request hreq=builder.build();
|
Request hreq=builder.build();
|
||||||
OkHttpClient client=req.timeout>0
|
Call call=httpClient.newCall(hreq);
|
||||||
? httpClient.newBuilder().readTimeout(req.timeout, TimeUnit.MILLISECONDS).build()
|
|
||||||
: httpClient;
|
|
||||||
Call call=client.newCall(hreq);
|
|
||||||
synchronized(req){
|
synchronized(req){
|
||||||
req.okhttpCall=call;
|
req.okhttpCall=call;
|
||||||
}
|
}
|
||||||
|
if(req.timeout>0){
|
||||||
|
call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
if(BuildConfig.DEBUG)
|
if(BuildConfig.DEBUG)
|
||||||
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);
|
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);
|
||||||
|
|||||||
@@ -153,9 +153,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
|||||||
headers.put(key, value);
|
headers.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MastodonAPIRequest<T> setTimeout(long timeout){
|
protected void setTimeout(long timeout){
|
||||||
this.timeout=timeout;
|
this.timeout=timeout;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getPathPrefix(){
|
protected String getPathPrefix(){
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class PushSubscriptionManager{
|
|||||||
deviceToken=getPrefs().getString("deviceToken", null);
|
deviceToken=getPrefs().getString("deviceToken", null);
|
||||||
int tokenVersion=getPrefs().getInt("version", 0);
|
int tokenVersion=getPrefs().getInt("version", 0);
|
||||||
if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){
|
if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){
|
||||||
registerAllAccountsForPush(false);
|
registerAllAccountsForPush(true); // TODO: revert this before release
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");
|
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");
|
||||||
|
|||||||
@@ -6,12 +6,8 @@ import org.joinmastodon.android.E;
|
|||||||
import org.joinmastodon.android.MastodonApp;
|
import org.joinmastodon.android.MastodonApp;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
|
|
||||||
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
|
||||||
import org.joinmastodon.android.events.ReblogDeletedEvent;
|
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
|
||||||
import org.joinmastodon.android.events.StatusDeletedEvent;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.model.StatusPrivacy;
|
import org.joinmastodon.android.model.StatusPrivacy;
|
||||||
|
|
||||||
@@ -27,7 +23,6 @@ public class StatusInteractionController{
|
|||||||
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<>();
|
||||||
private final HashMap<String, SetStatusMuted> runningMuteRequests=new HashMap<>();
|
|
||||||
|
|
||||||
public StatusInteractionController(String accountID, boolean updateCounters) {
|
public StatusInteractionController(String accountID, boolean updateCounters) {
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
@@ -53,7 +48,7 @@ public class StatusInteractionController{
|
|||||||
runningFavoriteRequests.remove(status.id);
|
runningFavoriteRequests.remove(status.id);
|
||||||
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -62,13 +57,13 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.favourited=!favorited;
|
status.favourited=!favorited;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if (updateCounters) 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 (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
|
||||||
@@ -83,15 +78,11 @@ public class StatusInteractionController{
|
|||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status reblog){
|
public void onSuccess(Status reblog){
|
||||||
Status result=reblog.getContentStatus();
|
Status result = reblog.getContentStatus();
|
||||||
runningReblogRequests.remove(status.id);
|
runningReblogRequests.remove(status.id);
|
||||||
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
|
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if(updateCounters){
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
E.post(new StatusCountersUpdatedEvent(result));
|
|
||||||
if(reblogged) E.post(new StatusCreatedEvent(reblog, accountID));
|
|
||||||
else E.post(new ReblogDeletedEvent(status.id, accountID));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -100,13 +91,13 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.reblogged=!reblogged;
|
status.reblogged=!reblogged;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
if (updateCounters) 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 (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBookmarked(Status status, boolean bookmarked){
|
public void setBookmarked(Status status, boolean bookmarked){
|
||||||
@@ -127,7 +118,7 @@ public class StatusInteractionController{
|
|||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
runningBookmarkRequests.remove(status.id);
|
runningBookmarkRequests.remove(status.id);
|
||||||
cb.accept(result);
|
cb.accept(result);
|
||||||
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -136,12 +127,12 @@ public class StatusInteractionController{
|
|||||||
error.showToast(MastodonApp.context);
|
error.showToast(MastodonApp.context);
|
||||||
status.bookmarked=!bookmarked;
|
status.bookmarked=!bookmarked;
|
||||||
cb.accept(status);
|
cb.accept(status);
|
||||||
if(updateCounters) 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));
|
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.accounts;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.Relationship;
|
|
||||||
|
|
||||||
public class SetPrivateNote extends MastodonAPIRequest<Relationship>{
|
|
||||||
public SetPrivateNote(String id, String comment){
|
|
||||||
super(MastodonAPIRequest.HttpMethod.POST, "/accounts/"+id+"/note", Relationship.class);
|
|
||||||
Request req = new Request(comment);
|
|
||||||
setRequestBody(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Request{
|
|
||||||
public String comment;
|
|
||||||
public Request(String comment){
|
|
||||||
this.comment=comment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.instance;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.DomainBlock;
|
|
||||||
import org.joinmastodon.android.model.ExtendedDescription;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GetDomainBlocks extends MastodonAPIRequest<List<DomainBlock>>{
|
|
||||||
public GetDomainBlocks(){
|
|
||||||
super(HttpMethod.GET, "/instance/domain_blocks", new TypeToken<>(){});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.instance;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.ExtendedDescription;
|
|
||||||
import org.joinmastodon.android.model.Instance;
|
|
||||||
|
|
||||||
public class GetExtendedDescription extends MastodonAPIRequest<ExtendedDescription>{
|
|
||||||
public GetExtendedDescription(){
|
|
||||||
super(HttpMethod.GET, "/instance/extended_description", ExtendedDescription.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.instance;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.WeeklyActivity;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GetWeeklyActivity extends MastodonAPIRequest<List<WeeklyActivity>>{
|
|
||||||
public GetWeeklyActivity(){
|
|
||||||
super(HttpMethod.GET, "/instance/activity", new TypeToken<>(){});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class AddList extends MastodonAPIRequest<Object> {
|
|
||||||
public AddList(String listName){
|
|
||||||
super(HttpMethod.POST, "/lists", Object.class);
|
|
||||||
Request req = new Request();
|
|
||||||
req.title = listName;
|
|
||||||
setRequestBody(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request{
|
|
||||||
public String title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class EditListName extends MastodonAPIRequest<Object> {
|
|
||||||
public EditListName(String newListName, String listId){
|
|
||||||
super(HttpMethod.PUT, "/lists/"+listId, Object.class);
|
|
||||||
Request req = new Request();
|
|
||||||
req.title = newListName;
|
|
||||||
setRequestBody(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request{
|
|
||||||
public String title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.lists;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class RemoveList extends MastodonAPIRequest<Object> {
|
|
||||||
public RemoveList(String listId){
|
|
||||||
super(HttpMethod.DELETE, "/lists/"+listId, Object.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ import okhttp3.MultipartBody;
|
|||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
|
|
||||||
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
|
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
|
||||||
private final String maxID;
|
private String maxID;
|
||||||
public PleromaMarkNotificationsRead(String maxID) {
|
public PleromaMarkNotificationsRead(String maxID) {
|
||||||
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
|
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
|
||||||
this.maxID = maxID;
|
this.maxID = maxID;
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class Request{
|
private static class Request{
|
||||||
public String clientName="Moshidon";
|
public String clientName="Megalodon";
|
||||||
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
public String redirectUris=AccountSessionManager.REDIRECT_URI;
|
||||||
public String scopes=AccountSessionManager.SCOPE;
|
public String scopes=AccountSessionManager.SCOPE;
|
||||||
public String website="https://github.com/LucasGGamerM/moshidon";
|
public String website="https://sk22.github.io/megalodon";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.AkkomaTranslation;
|
|
||||||
|
|
||||||
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
|
|
||||||
public AkkomaTranslateStatus(String id, String lang){
|
|
||||||
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -48,8 +48,6 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
|
|||||||
public String quoteId;
|
public String quoteId;
|
||||||
public ContentType contentType;
|
public ContentType contentType;
|
||||||
|
|
||||||
public boolean preview;
|
|
||||||
|
|
||||||
public static class Poll{
|
public static class Poll{
|
||||||
public ArrayList<String> options=new ArrayList<>();
|
public ArrayList<String> options=new ArrayList<>();
|
||||||
public int expiresIn;
|
public int expiresIn;
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ public class GetStatusEditHistory extends MastodonAPIRequest<List<Status>>{
|
|||||||
s.visibility=StatusPrivacy.PUBLIC;
|
s.visibility=StatusPrivacy.PUBLIC;
|
||||||
s.mentions=Collections.emptyList();
|
s.mentions=Collections.emptyList();
|
||||||
s.tags=Collections.emptyList();
|
s.tags=Collections.emptyList();
|
||||||
if (s.poll != null)
|
|
||||||
s.poll.id="fakeID"+i;
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
super.validateAndPostprocessResponse(respObj, httpResponse);
|
super.validateAndPostprocessResponse(respObj, httpResponse);
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.joinmastodon.android.api.requests.statuses;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
|
|
||||||
public class SetStatusMuted extends MastodonAPIRequest<Status>{
|
|
||||||
public SetStatusMuted(String id, boolean muted){
|
|
||||||
super(HttpMethod.POST, "/statuses/"+id+"/"+(muted ? "mute" : "unmute"), Status.class);
|
|
||||||
setRequestBody(new Object());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,23 +6,13 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
|
|||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import androidx.annotation.StringRes;
|
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.model.ContentType;
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.Emoji;
|
|
||||||
import org.joinmastodon.android.model.Emoji;
|
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class AccountLocalPreferences{
|
public class AccountLocalPreferences{
|
||||||
private final SharedPreferences prefs;
|
private final SharedPreferences prefs;
|
||||||
@@ -46,20 +36,12 @@ public class AccountLocalPreferences{
|
|||||||
public String publishButtonText;
|
public String publishButtonText;
|
||||||
public String timelineReplyVisibility; // akkoma-only
|
public String timelineReplyVisibility; // akkoma-only
|
||||||
public boolean keepOnlyLatestNotification;
|
public boolean keepOnlyLatestNotification;
|
||||||
|
|
||||||
public boolean emojiReactionsEnabled;
|
public boolean emojiReactionsEnabled;
|
||||||
public ShowEmojiReactions showEmojiReactions;
|
public ShowEmojiReactions showEmojiReactions;
|
||||||
public ColorPreference color;
|
|
||||||
public ArrayList<Emoji> recentCustomEmoji;
|
|
||||||
|
|
||||||
private final static Type recentLanguagesType=new TypeToken<ArrayList<String>>() {}.getType();
|
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
|
||||||
private final static Type timelinesType=new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
|
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
|
||||||
private final static Type recentCustomEmojiType=new TypeToken<ArrayList<Emoji>>() {}.getType();
|
|
||||||
|
|
||||||
// MOSHIDON
|
|
||||||
// private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
|
||||||
// public Map<String, Integer> recentEmojis;
|
|
||||||
private final static Type notificationFiltersType = new TypeToken<PushSubscription.Alerts>() {}.getType();
|
|
||||||
public PushSubscription.Alerts notificationFilters;
|
|
||||||
|
|
||||||
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
|
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
|
||||||
this.prefs=prefs;
|
this.prefs=prefs;
|
||||||
@@ -84,12 +66,6 @@ public class AccountLocalPreferences{
|
|||||||
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
|
||||||
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
|
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
|
||||||
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
|
||||||
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
|
|
||||||
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
|
|
||||||
|
|
||||||
// MOSHIDON
|
|
||||||
// recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
|
|
||||||
notificationFilters=fromJson(prefs.getString("notificationFilters", gson.toJson(PushSubscription.Alerts.ofAll())), notificationFiltersType, PushSubscription.Alerts.ofAll());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getNotificationsPauseEndTime(){
|
public long getNotificationsPauseEndTime(){
|
||||||
@@ -100,10 +76,6 @@ public class AccountLocalPreferences{
|
|||||||
prefs.edit().putLong("notificationsPauseTime", time).apply();
|
prefs.edit().putLong("notificationsPauseTime", time).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColorPreference getCurrentColor(){
|
|
||||||
return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save(){
|
public void save(){
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putBoolean("interactionCounts", showInteractionCounts)
|
.putBoolean("interactionCounts", showInteractionCounts)
|
||||||
@@ -127,43 +99,9 @@ public class AccountLocalPreferences{
|
|||||||
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
|
||||||
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
|
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
|
||||||
.putString("showEmojiReactions", showEmojiReactions.name())
|
.putString("showEmojiReactions", showEmojiReactions.name())
|
||||||
.putString("color", color!=null ? color.name() : null)
|
|
||||||
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))
|
|
||||||
|
|
||||||
// MOSHIDON
|
|
||||||
// .putString("recentEmojis", gson.toJson(recentEmojis))
|
|
||||||
.putString("notificationFilters", gson.toJson(notificationFilters))
|
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ColorPreference{
|
|
||||||
MATERIAL3,
|
|
||||||
PURPLE,
|
|
||||||
PINK,
|
|
||||||
GREEN,
|
|
||||||
BLUE,
|
|
||||||
BROWN,
|
|
||||||
RED,
|
|
||||||
YELLOW,
|
|
||||||
NORD,
|
|
||||||
WHITE;
|
|
||||||
|
|
||||||
public @StringRes int getName() {
|
|
||||||
return switch(this){
|
|
||||||
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;
|
|
||||||
case NORD -> R.string.mo_color_palette_nord;
|
|
||||||
case WHITE -> R.string.mo_color_palette_black_and_white;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ShowEmojiReactions{
|
public enum ShowEmojiReactions{
|
||||||
HIDE_EMPTY,
|
HIDE_EMPTY,
|
||||||
ONLY_OPENED,
|
ONLY_OPENED,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import java.util.Objects;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
import me.grishka.appkit.api.ErrorResponse;
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
@@ -218,7 +219,7 @@ public class AccountSession{
|
|||||||
|
|
||||||
public void savePreferencesIfPending(){
|
public void savePreferencesIfPending(){
|
||||||
if(preferencesNeedSaving){
|
if(preferencesNeedSaving){
|
||||||
new UpdateAccountCredentialsPreferences(preferences, self.locked, self.discoverable, self.source.indexable)
|
new UpdateAccountCredentialsPreferences(preferences, null, self.discoverable, self.source.indexable)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Account result){
|
public void onSuccess(Account result){
|
||||||
@@ -254,73 +255,52 @@ public class AccountSession{
|
|||||||
filterStatusContainingObjects(objects, extractor, context, null);
|
filterStatusContainingObjects(objects, extractor, context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean statusIsOnOwnProfile(Status s, Account profile){
|
|
||||||
return self != null && profile != null && s.account != null
|
|
||||||
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isFilteredType(Status s){
|
|
||||||
AccountLocalPreferences localPreferences = getLocalPreferences();
|
|
||||||
return (!localPreferences.showReplies && s.inReplyToId != null)
|
|
||||||
|| (!localPreferences.showBoosts && s.reblog != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
|
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
|
||||||
AccountLocalPreferences localPreferences = getLocalPreferences();
|
Predicate<Status> statusIsOnOwnProfile = (s) -> self != null && profile != null && s.account != null
|
||||||
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
|
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
|
||||||
Status s=extractor.apply(obj);
|
|
||||||
if(s!=null && s.filtered!=null){
|
|
||||||
localPreferences.serverSideFiltersSupported=true;
|
|
||||||
localPreferences.save();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<T> removeUs=new ArrayList<>();
|
if(getLocalPreferences().serverSideFiltersSupported){
|
||||||
for(int i=0; i<objects.size(); i++){
|
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
|
||||||
T o=objects.get(i);
|
objects.removeIf(o->{
|
||||||
if(filterStatusContainingObject(o, extractor, context, profile)){
|
|
||||||
Status s=extractor.apply(o);
|
Status s=extractor.apply(o);
|
||||||
removeUs.add(o);
|
|
||||||
if(s!=null && s.hasGapAfter!=null && i>0){
|
|
||||||
// oops, we're about to remove an item that has a gap after...
|
|
||||||
// gotta find the previous status that's not also about to be removed
|
|
||||||
for(int j=i-1; j>=0; j--){
|
|
||||||
T p=objects.get(j);
|
|
||||||
Status prev=extractor.apply(objects.get(j));
|
|
||||||
if(prev!=null && !removeUs.contains(p)){
|
|
||||||
prev.hasGapAfter=s.hasGapAfter;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
objects.removeAll(removeUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> boolean filterStatusContainingObject(T object, Function<T, Status> extractor, FilterContext context, Account profile){
|
|
||||||
Status s=extractor.apply(object);
|
|
||||||
if(s==null)
|
if(s==null)
|
||||||
return false;
|
return false;
|
||||||
// don't hide own posts in own profile
|
if(s.filtered==null)
|
||||||
if(statusIsOnOwnProfile(s, profile))
|
|
||||||
return false;
|
return false;
|
||||||
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
|
// don't hide own posts in own profile
|
||||||
return true;
|
if (statusIsOnOwnProfile.test(s))
|
||||||
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
|
return false;
|
||||||
if(getLocalPreferences().serverSideFiltersSupported){
|
for(FilterResult filter:s.filtered){
|
||||||
for(FilterResult filter : s.filtered){
|
|
||||||
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
|
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}else if(wordFilters!=null){
|
return false;
|
||||||
for(LegacyFilter filter : wordFilters){
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(wordFilters==null)
|
||||||
|
return;
|
||||||
|
for(T obj:objects){
|
||||||
|
Status s=extractor.apply(obj);
|
||||||
|
if(s!=null && s.filtered!=null){
|
||||||
|
getLocalPreferences().serverSideFiltersSupported=true;
|
||||||
|
getLocalPreferences().save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
objects.removeIf(o->{
|
||||||
|
Status s=extractor.apply(o);
|
||||||
|
if(s==null)
|
||||||
|
return false;
|
||||||
|
// don't hide own posts in own profile
|
||||||
|
if (statusIsOnOwnProfile.test(s))
|
||||||
|
return false;
|
||||||
|
for(LegacyFilter filter:wordFilters){
|
||||||
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
|
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAccountInfo(){
|
public void updateAccountInfo(){
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.joinmastodon.android.api.session;
|
package org.joinmastodon.android.api.session;
|
||||||
|
|
||||||
import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -36,7 +34,6 @@ import org.joinmastodon.android.model.EmojiCategory;
|
|||||||
import org.joinmastodon.android.model.LegacyFilter;
|
import org.joinmastodon.android.model.LegacyFilter;
|
||||||
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.unifiedpush.android.connector.UnifiedPush;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@@ -64,7 +61,7 @@ import me.grishka.appkit.api.ErrorResponse;
|
|||||||
public class AccountSessionManager{
|
public class AccountSessionManager{
|
||||||
private static final String TAG="AccountSessionManager";
|
private static final String TAG="AccountSessionManager";
|
||||||
public static final String SCOPE="read write follow push";
|
public static final String SCOPE="read write follow push";
|
||||||
public static final String REDIRECT_URI = getRedirectURI();
|
public static final String REDIRECT_URI="megalodon-android-auth://callback";
|
||||||
|
|
||||||
private static final AccountSessionManager instance=new AccountSessionManager();
|
private static final AccountSessionManager instance=new AccountSessionManager();
|
||||||
|
|
||||||
@@ -83,17 +80,6 @@ public class AccountSessionManager{
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getRedirectURI() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("moshidon-android-");
|
|
||||||
if (BuildConfig.BUILD_TYPE.equals("debug") || BuildConfig.BUILD_TYPE.equals("nightly")) {
|
|
||||||
builder.append(BuildConfig.BUILD_TYPE);
|
|
||||||
builder.append('-');
|
|
||||||
}
|
|
||||||
builder.append("auth://callback");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private AccountSessionManager(){
|
private AccountSessionManager(){
|
||||||
prefs=MastodonApp.context.getSharedPreferences("account_manager", Context.MODE_PRIVATE);
|
prefs=MastodonApp.context.getSharedPreferences("account_manager", Context.MODE_PRIVATE);
|
||||||
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
|
||||||
@@ -115,7 +101,6 @@ public class AccountSessionManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
|
||||||
Context context = MastodonApp.context;
|
|
||||||
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, activationInfo==null, activationInfo);
|
||||||
sessions.put(session.getID(), session);
|
sessions.put(session.getID(), session);
|
||||||
@@ -128,14 +113,7 @@ public class AccountSessionManager{
|
|||||||
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
|
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
|
||||||
|
|
||||||
updateMoreInstanceInfo(instance, instance.uri);
|
updateMoreInstanceInfo(instance, instance.uri);
|
||||||
if (!UnifiedPush.getDistributor(context).isEmpty()) {
|
if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
||||||
UnifiedPush.registerApp(
|
|
||||||
context,
|
|
||||||
session.getID(),
|
|
||||||
new ArrayList<>(),
|
|
||||||
context.getPackageName()
|
|
||||||
);
|
|
||||||
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
|
|
||||||
session.getPushSubscriptionManager().registerAccountForPush(null);
|
session.getPushSubscriptionManager().registerAccountForPush(null);
|
||||||
}
|
}
|
||||||
maybeUpdateShortcuts();
|
maybeUpdateShortcuts();
|
||||||
@@ -261,7 +239,7 @@ public class AccountSessionManager{
|
|||||||
.path("/oauth/authorize")
|
.path("/oauth/authorize")
|
||||||
.appendQueryParameter("response_type", "code")
|
.appendQueryParameter("response_type", "code")
|
||||||
.appendQueryParameter("client_id", result.clientId)
|
.appendQueryParameter("client_id", result.clientId)
|
||||||
.appendQueryParameter("redirect_uri", REDIRECT_URI)
|
.appendQueryParameter("redirect_uri", "megalodon-android-auth://callback")
|
||||||
.appendQueryParameter("scope", SCOPE)
|
.appendQueryParameter("scope", SCOPE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class ReblogDeletedEvent{
|
|
||||||
public final String statusID;
|
|
||||||
public final String accountID;
|
|
||||||
|
|
||||||
public ReblogDeletedEvent(String statusID, String accountID){
|
|
||||||
this.statusID=statusID;
|
|
||||||
this.accountID=accountID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.joinmastodon.android.events;
|
package org.joinmastodon.android.events;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.ScheduledStatus;
|
||||||
|
|
||||||
public class ScheduledStatusDeletedEvent{
|
public class ScheduledStatusDeletedEvent{
|
||||||
public final String id;
|
public final String id;
|
||||||
public final String accountID;
|
public final String accountID;
|
||||||
|
|||||||
@@ -9,6 +9,5 @@ public class StatusCreatedEvent{
|
|||||||
public StatusCreatedEvent(Status status, String accountID){
|
public StatusCreatedEvent(Status status, String accountID){
|
||||||
this.status=status;
|
this.status=status;
|
||||||
this.accountID=accountID;
|
this.accountID=accountID;
|
||||||
status.fromStatusCreated=true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
|
|
||||||
public class StatusMuteChangedEvent{
|
|
||||||
public String id;
|
|
||||||
public boolean muted;
|
|
||||||
public Status status;
|
|
||||||
|
|
||||||
public StatusMuteChangedEvent(Status s){
|
|
||||||
id=s.id;
|
|
||||||
muted=s.muted;
|
|
||||||
status=s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package org.joinmastodon.android.events;
|
|
||||||
|
|
||||||
public class TakePictureRequestEvent {
|
|
||||||
public TakePictureRequestEvent(){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,16 +9,19 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
|
||||||
|
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
import org.joinmastodon.android.events.StatusUnpinnedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
import me.grishka.appkit.api.SimpleCallback;
|
||||||
|
|
||||||
@@ -52,14 +55,15 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetAccountStatuses(user.id, getMaxID(), null, count, filter)
|
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(getActivity()==null) return;
|
if(getActivity()==null) return;
|
||||||
boolean more=applyMaxID(result);
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
|
boolean empty=result.isEmpty();
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
|
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
|
||||||
onDataLoaded(result, more);
|
onDataLoaded(result, !empty);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Announcement> result){
|
public void onSuccess(List<Announcement> result){
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
|
|
||||||
// get unread items first
|
// get unread items first
|
||||||
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList());
|
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList());
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.graphics.Rect;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
@@ -16,23 +17,15 @@ import android.view.animation.TranslateAnimation;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
|
||||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
||||||
import org.joinmastodon.android.api.requests.statuses.AkkomaTranslateStatus;
|
|
||||||
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
|
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.AkkomaTranslation;
|
|
||||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||||
import org.joinmastodon.android.model.Poll;
|
import org.joinmastodon.android.model.Poll;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
@@ -41,6 +34,7 @@ import org.joinmastodon.android.model.Translation;
|
|||||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||||
|
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||||
@@ -49,16 +43,13 @@ import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
|||||||
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.PreviewlessMediaGridStatusDisplayItem;
|
|
||||||
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
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.displayitems.WarningFilteredStatusDisplayItem;
|
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||||
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
|
|
||||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||||
import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||||
@@ -70,7 +61,6 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -80,7 +70,6 @@ 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.fragments.BaseRecyclerFragment;
|
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
|
||||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||||
@@ -100,10 +89,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
protected HashMap<String, Relationship> relationships=new HashMap<>();
|
||||||
protected Rect tmpRect=new Rect();
|
protected Rect tmpRect=new Rect();
|
||||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
|
||||||
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, PreviewlessMediaAttachmentViewController> previewlessAttachmentViewsPool=new TypedObjectPool<>(this::makeNewPreviewlessMediaAttachmentView);
|
|
||||||
|
|
||||||
protected boolean currentlyScrolling;
|
protected boolean currentlyScrolling;
|
||||||
protected String maxID;
|
|
||||||
|
|
||||||
public BaseStatusListFragment(){
|
public BaseStatusListFragment(){
|
||||||
super(20);
|
super(20);
|
||||||
@@ -170,8 +156,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected String getMaxID(){
|
protected String getMaxID(){
|
||||||
if(refreshing) return null;
|
|
||||||
if(maxID!=null) return maxID;
|
|
||||||
if(!preloadedData.isEmpty())
|
if(!preloadedData.isEmpty())
|
||||||
return preloadedData.get(preloadedData.size()-1).getID();
|
return preloadedData.get(preloadedData.size()-1).getID();
|
||||||
else if(!data.isEmpty())
|
else if(!data.isEmpty())
|
||||||
@@ -180,12 +164,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean applyMaxID(List<Status> result){
|
|
||||||
boolean empty=result.isEmpty();
|
|
||||||
if(!empty) maxID=result.get(result.size()-1).id;
|
|
||||||
return !empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
|
protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
|
||||||
protected abstract void addAccountToKnown(T s);
|
protected abstract void addAccountToKnown(T s);
|
||||||
|
|
||||||
@@ -301,79 +279,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void openPreviewlessMediaPhotoViewer(String parentID, Status _status, int attachmentIndex, PreviewlessMediaGridStatusDisplayItem.Holder gridHolder){
|
|
||||||
final Status status=_status.getContentStatus();
|
|
||||||
currentPhotoViewer=new PhotoViewer(getActivity(), status.mediaAttachments, attachmentIndex, new PhotoViewer.Listener(){
|
|
||||||
private PreviewlessMediaAttachmentViewController transitioningHolder;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPhotoViewVisibility(int index, boolean visible){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
|
|
||||||
PreviewlessMediaAttachmentViewController holder=findPhotoViewHolder(index);
|
|
||||||
if(holder!=null && list!=null){
|
|
||||||
transitioningHolder=holder;
|
|
||||||
View view=transitioningHolder.inner;
|
|
||||||
int[] pos={0, 0};
|
|
||||||
view.getLocationOnScreen(pos);
|
|
||||||
outRect.set(pos[0], pos[1], pos[0]+view.getWidth(), pos[1]+view.getHeight());
|
|
||||||
list.setClipChildren(false);
|
|
||||||
gridHolder.setClipChildren(false);
|
|
||||||
transitioningHolder.view.setElevation(1f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTransitioningViewTransform(float translateX, float translateY, float scale){
|
|
||||||
View view=transitioningHolder.inner;
|
|
||||||
view.setTranslationX(translateX);
|
|
||||||
view.setTranslationY(translateY);
|
|
||||||
view.setScaleX(scale);
|
|
||||||
view.setScaleY(scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void endPhotoViewTransition(){
|
|
||||||
View view=transitioningHolder.inner;
|
|
||||||
view.setTranslationX(0f);
|
|
||||||
view.setTranslationY(0f);
|
|
||||||
view.setScaleX(1f);
|
|
||||||
view.setScaleY(1f);
|
|
||||||
transitioningHolder.view.setElevation(0f);
|
|
||||||
if(list!=null)
|
|
||||||
list.setClipChildren(true);
|
|
||||||
gridHolder.setClipChildren(true);
|
|
||||||
transitioningHolder=null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public Drawable getPhotoViewCurrentDrawable(int index){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void photoViewerDismissed(){
|
|
||||||
currentPhotoViewer=null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissions(String[] permissions){
|
|
||||||
requestPermissions(permissions, PhotoViewer.PERMISSION_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PreviewlessMediaAttachmentViewController findPhotoViewHolder(int index){
|
|
||||||
return gridHolder.getViewController(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable View getFab() {
|
public @Nullable View getFab() {
|
||||||
if (getParentFragment() instanceof HasFab l) return l.getFab();
|
if (getParentFragment() instanceof HasFab l) return l.getFab();
|
||||||
@@ -446,14 +351,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
list.addItemDecoration(new StatusListItemDecoration());
|
list.addItemDecoration(new StatusListItemDecoration());
|
||||||
list.addItemDecoration(new InsetStatusItemDecoration(this));
|
|
||||||
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
|
||||||
private Rect tmpRect=new Rect();
|
private Rect tmpRect=new Rect();
|
||||||
@Override
|
@Override
|
||||||
public void getSelectorBounds(View view, Rect outRect){
|
public void getSelectorBounds(View view, Rect outRect){
|
||||||
if(list!=view.getParent()) return;
|
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
|
||||||
boolean hasDescendant=false, hasAncestor=false, isWarning=false;
|
int lastIndex = -1, firstIndex = -1;
|
||||||
int lastIndex=-1, firstIndex=-1;
|
|
||||||
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
|
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
|
||||||
list.getDecoratedBoundsWithMargins(view, outRect);
|
list.getDecoratedBoundsWithMargins(view, outRect);
|
||||||
}else{
|
}else{
|
||||||
@@ -558,14 +461,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
protected void updatePoll(String itemID, Status status, Poll poll){
|
protected void updatePoll(String itemID, Status status, Poll poll){
|
||||||
status.poll=poll;
|
status.poll=poll;
|
||||||
int firstOptionIndex=-1, footerIndex=-1;
|
int firstOptionIndex=-1, footerIndex=-1;
|
||||||
int spoilerFirstOptionIndex=-1, spoilerFooterIndex=-1;
|
|
||||||
SpoilerStatusDisplayItem spoilerItem=null;
|
|
||||||
int i=0;
|
int i=0;
|
||||||
for(StatusDisplayItem item:displayItems){
|
for(StatusDisplayItem item:displayItems){
|
||||||
if(item.parentID.equals(itemID)){
|
if(item.parentID.equals(itemID)){
|
||||||
if(item instanceof SpoilerStatusDisplayItem){
|
if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
|
||||||
spoilerItem=(SpoilerStatusDisplayItem) item;
|
|
||||||
}else if(item instanceof PollOptionStatusDisplayItem && firstOptionIndex==-1){
|
|
||||||
firstOptionIndex=i;
|
firstOptionIndex=i;
|
||||||
}else if(item instanceof PollFooterStatusDisplayItem){
|
}else if(item instanceof PollFooterStatusDisplayItem){
|
||||||
footerIndex=i;
|
footerIndex=i;
|
||||||
@@ -578,16 +477,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
throw new IllegalStateException("Can't find all poll items in displayItems");
|
throw new IllegalStateException("Can't find all poll items in displayItems");
|
||||||
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
|
List<StatusDisplayItem> pollItems=displayItems.subList(firstOptionIndex, footerIndex+1);
|
||||||
int prevSize=pollItems.size();
|
int prevSize=pollItems.size();
|
||||||
if(spoilerItem!=null){
|
|
||||||
spoilerFirstOptionIndex=spoilerItem.contentItems.indexOf(pollItems.get(0));
|
|
||||||
spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1));
|
|
||||||
}
|
|
||||||
pollItems.clear();
|
pollItems.clear();
|
||||||
StatusDisplayItem.buildPollItems(itemID, this, poll, status, pollItems);
|
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems);
|
||||||
if(spoilerItem!=null){
|
|
||||||
spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear();
|
|
||||||
spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems);
|
|
||||||
}
|
|
||||||
if(prevSize!=pollItems.size()){
|
if(prevSize!=pollItems.size()){
|
||||||
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
|
adapter.notifyItemRangeRemoved(firstOptionIndex, prevSize);
|
||||||
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
|
adapter.notifyItemRangeInserted(firstOptionIndex, pollItems.size());
|
||||||
@@ -599,8 +490,7 @@ 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;
|
||||||
// MEGALODON: always show vote button
|
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);
|
boolean optionContained=poll.selectedOptions.contains(option);
|
||||||
@@ -615,7 +505,7 @@ 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(!poll.multiple && vh instanceof PollOptionStatusDisplayItem.Holder item){
|
||||||
if(item!=holder) item.itemView.setSelected(false);
|
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())){
|
||||||
@@ -624,9 +514,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }else{
|
}else{
|
||||||
// submitPollVote(holder.getItemID(), poll.id, Collections.singletonList(poll.options.indexOf(option)));
|
submitPollVote(holder.getItemID(), poll.id, Collections.singletonList(poll.options.indexOf(option)));
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){
|
public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){
|
||||||
@@ -634,14 +524,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
submitPollVote(holder.getItemID(), poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
|
submitPollVote(holder.getItemID(), poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPollViewResultsButtonClick(PollFooterStatusDisplayItem.Holder holder, boolean shown){
|
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
|
||||||
if(list.getChildViewHolder(list.getChildAt(i)) instanceof PollOptionStatusDisplayItem.Holder item && item.getItemID().equals(holder.getItemID())){
|
|
||||||
item.showResults(shown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void submitPollVote(String parentID, String pollID, List<Integer> choices){
|
protected void submitPollVote(String parentID, String pollID, List<Integer> choices){
|
||||||
if(refreshing)
|
if(refreshing)
|
||||||
return;
|
return;
|
||||||
@@ -663,39 +545,37 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
|
|
||||||
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
|
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
|
||||||
Status status=holder.getItem().status;
|
Status status=holder.getItem().status;
|
||||||
boolean isForQuote=holder.getItem().isForQuote;
|
toggleSpoiler(status, holder.getItemID());
|
||||||
toggleSpoiler(status, isForQuote, holder.getItemID());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
|
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
|
||||||
Status status = holder.getItem().status;
|
Status status = holder.getItem().status;
|
||||||
if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false);
|
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
|
||||||
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
|
if (mediaGrid != null) {
|
||||||
if(mediaGrid!=null){
|
if (!status.sensitiveRevealed) mediaGrid.revealSensitive();
|
||||||
if(!status.sensitiveRevealed) mediaGrid.revealSensitive();
|
|
||||||
else mediaGrid.hideSensitive();
|
else mediaGrid.hideSensitive();
|
||||||
}else{
|
} else {
|
||||||
status.sensitiveRevealed=false;
|
// media grid's methods normally change the status' state - we still want to be able
|
||||||
notifyItemChangedAfter(holder.getItem(), MediaGridStatusDisplayItem.class);
|
// to do this if the media grid is not bound, tho - so, doing it ourselves here
|
||||||
|
status.sensitiveRevealed = !status.sensitiveRevealed;
|
||||||
}
|
}
|
||||||
|
holder.rebind();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
|
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
|
||||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
||||||
if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true);
|
if(header != null) header.rebind();
|
||||||
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void toggleSpoiler(Status status, boolean isForQuote, String itemID){
|
protected void toggleSpoiler(Status status, String itemID){
|
||||||
status.spoilerRevealed=!status.spoilerRevealed;
|
status.spoilerRevealed=!status.spoilerRevealed;
|
||||||
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
|
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
|
||||||
status.sensitiveRevealed = false;
|
status.sensitiveRevealed = false;
|
||||||
|
|
||||||
List<SpoilerStatusDisplayItem.Holder> spoilers=findAllHoldersOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
|
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
|
||||||
SpoilerStatusDisplayItem.Holder spoiler=spoilers.size() > 1 && isForQuote ? spoilers.get(1) : spoilers.get(0);
|
if(spoiler!=null)
|
||||||
if(spoiler!=null) spoiler.rebind();
|
spoiler.rebind();
|
||||||
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
|
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
|
||||||
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(spoiler.getItem());
|
|
||||||
|
|
||||||
int index=displayItems.indexOf(spoilerItem);
|
int index=displayItems.indexOf(spoilerItem);
|
||||||
if(status.spoilerRevealed){
|
if(status.spoilerRevealed){
|
||||||
@@ -706,29 +586,39 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
|
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyItemChanged(itemID, TextStatusDisplayItem.class);
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
|
if(text!=null)
|
||||||
|
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
|
||||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||||
if(header!=null) header.rebind();
|
if(header!=null)
|
||||||
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
|
header.rebind();
|
||||||
|
|
||||||
list.invalidateItemDecorations();
|
list.invalidateItemDecorations();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
|
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
|
||||||
Status s=holder.getItem().status;
|
if (holder.getItem().status.textExpandable != expandable && list != null) {
|
||||||
if(s.textExpandable!=expandable && list!=null) {
|
holder.getItem().status.textExpandable = expandable;
|
||||||
s.textExpandable=expandable;
|
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
||||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
|
if (header != null) header.rebind();
|
||||||
if(header!=null) header.bindCollapseButton();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onToggleExpanded(Status status, String itemID) {
|
public void onToggleExpanded(Status status, String itemID) {
|
||||||
status.textExpanded = !status.textExpanded;
|
status.textExpanded = !status.textExpanded;
|
||||||
notifyItemChanged(itemID, TextStatusDisplayItem.class);
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
|
||||||
if(header!=null) header.animateExpandToggle();
|
if (text != null) text.rebind();
|
||||||
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
|
if (header != null) header.rebind();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateEmojiReactions(Status status, String itemID){
|
||||||
|
EmojiReactionsStatusDisplayItem.Holder reactions=findHolderOfType(itemID, EmojiReactionsStatusDisplayItem.Holder.class);
|
||||||
|
if(reactions != null){
|
||||||
|
reactions.getItem().status.reactions.clear();
|
||||||
|
reactions.getItem().status.reactions.addAll(status.reactions);
|
||||||
|
reactions.rebind();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
|
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
|
||||||
@@ -787,61 +677,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this as a fallback if findHolderOfType fails to find the ViewHolder.
|
|
||||||
* It might still be bound but off-screen and therefore not a child of the RecyclerView -
|
|
||||||
* resulting in the ViewHolder displaying an outdated state once scrolled back into view.
|
|
||||||
*/
|
|
||||||
protected <I extends StatusDisplayItem> int notifyItemChanged(String id, Class<I> type){
|
|
||||||
boolean encounteredParent=false;
|
|
||||||
for(int i=0; i<displayItems.size(); i++){
|
|
||||||
StatusDisplayItem item=displayItems.get(i);
|
|
||||||
boolean idEquals=id.equals(item.parentID);
|
|
||||||
if(!encounteredParent && idEquals) encounteredParent=true; // reached top of the parent
|
|
||||||
else if(encounteredParent && !idEquals) break; // passed by bottom of the parent. man muss ja wissen wann schluss is
|
|
||||||
if(idEquals && type.isInstance(item)){
|
|
||||||
adapter.notifyItemChanged(i);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <I extends StatusDisplayItem> int notifyItemChangedAfter(StatusDisplayItem afterThis, Class<I> type){
|
|
||||||
int startIndex=displayItems.indexOf(afterThis);
|
|
||||||
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem");
|
|
||||||
String parentID=afterThis.parentID;
|
|
||||||
for(int i=startIndex; i<displayItems.size(); i++){
|
|
||||||
StatusDisplayItem item=displayItems.get(i);
|
|
||||||
if(!parentID.equals(item.parentID)) break; // didn't find anything
|
|
||||||
if(type.isInstance(item)){
|
|
||||||
// found it
|
|
||||||
adapter.notifyItemChanged(i);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <I extends StatusDisplayItem> int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class<I> type){
|
|
||||||
int startIndex=displayItems.indexOf(beforeThis);
|
|
||||||
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem");
|
|
||||||
String parentID=beforeThis.parentID;
|
|
||||||
for(int i=startIndex; i>=0; i--){
|
|
||||||
StatusDisplayItem item=displayItems.get(i);
|
|
||||||
if(!parentID.equals(item.parentID)) break; // didn't find anything
|
|
||||||
if(type.isInstance(item)){
|
|
||||||
// found it
|
|
||||||
adapter.notifyItemChanged(i);
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
|
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
|
||||||
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 StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
|
if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
|
||||||
return type.cast(holder);
|
return type.cast(holder);
|
||||||
@@ -929,18 +767,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
return new MediaAttachmentViewController(getActivity(), type);
|
return new MediaAttachmentViewController(getActivity(), type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PreviewlessMediaAttachmentViewController makeNewPreviewlessMediaAttachmentView(MediaGridStatusDisplayItem.GridItemType type){
|
|
||||||
return new PreviewlessMediaAttachmentViewController(getActivity(), type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> getAttachmentViewsPool(){
|
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> getAttachmentViewsPool(){
|
||||||
return attachmentViewsPool;
|
return attachmentViewsPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, PreviewlessMediaAttachmentViewController> getPreviewlessAttachmentViewsPool(){
|
|
||||||
return previewlessAttachmentViewsPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProvideAssistContent(AssistContent assistContent) {
|
public void onProvideAssistContent(AssistContent assistContent) {
|
||||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||||
@@ -959,83 +789,45 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
status.translationState=Status.TranslationState.SHOWN;
|
status.translationState=Status.TranslationState.SHOWN;
|
||||||
}else{
|
}else{
|
||||||
status.translationState=Status.TranslationState.LOADING;
|
status.translationState=Status.TranslationState.LOADING;
|
||||||
Consumer<Translation> successCallback=(result)->{
|
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
|
||||||
status.translation=result;
|
.setCallback(new Callback<>(){
|
||||||
status.translationState=Status.TranslationState.SHOWN;
|
|
||||||
updateTranslation(itemID);
|
|
||||||
};
|
|
||||||
MastodonAPIRequest<?> req=isInstanceAkkoma()
|
|
||||||
? new AkkomaTranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(AkkomaTranslation result){
|
|
||||||
if(getActivity()!=null) successCallback.accept(result.toTranslation());
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
if(getActivity()!=null) translationCallbackError(status, itemID);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
: new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Translation result){
|
public void onSuccess(Translation result){
|
||||||
if(getActivity()!=null) successCallback.accept(result);
|
if(getActivity()==null)
|
||||||
|
return;
|
||||||
|
status.translation=result;
|
||||||
|
status.translationState=Status.TranslationState.SHOWN;
|
||||||
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
|
if(text!=null){
|
||||||
|
text.updateTranslation(true);
|
||||||
|
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
if(getActivity()!=null) translationCallbackError(status, itemID);
|
if(getActivity()==null)
|
||||||
}
|
return;
|
||||||
});
|
|
||||||
|
|
||||||
// 1 minute
|
|
||||||
req.setTimeout(60000).exec(accountID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateTranslation(itemID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void translationCallbackError(Status status, String itemID) {
|
|
||||||
status.translationState=Status.TranslationState.HIDDEN;
|
status.translationState=Status.TranslationState.HIDDEN;
|
||||||
updateTranslation(itemID);
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
|
if(text!=null){
|
||||||
|
text.updateTranslation(true);
|
||||||
|
}
|
||||||
new M3AlertDialogBuilder(getActivity())
|
new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(R.string.translation_failed)
|
.setMessage(R.string.translation_failed)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
})
|
||||||
private void updateTranslation(String itemID) {
|
.exec(accountID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||||
if(text!=null){
|
if(text!=null){
|
||||||
text.updateTranslation(true);
|
text.updateTranslation(true);
|
||||||
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
|
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
|
||||||
}else{
|
|
||||||
notifyItemChanged(itemID, TextStatusDisplayItem.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isInstanceAkkoma())
|
|
||||||
return;
|
|
||||||
|
|
||||||
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
|
|
||||||
if(spoiler!=null){
|
|
||||||
spoiler.rebind();
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaGridStatusDisplayItem.Holder media=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
|
|
||||||
if (media!=null) {
|
|
||||||
media.rebind();
|
|
||||||
}
|
|
||||||
|
|
||||||
PreviewlessMediaGridStatusDisplayItem.Holder previewLessMedia=findHolderOfType(itemID, PreviewlessMediaGridStatusDisplayItem.Holder.class);
|
|
||||||
if (previewLessMedia!=null) {
|
|
||||||
previewLessMedia.rebind();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
|
||||||
if(list.getChildViewHolder(list.getChildAt(i)) instanceof PollOptionStatusDisplayItem.Holder item){
|
|
||||||
item.rebind();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,7 +846,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
if(getContext()==null) return;
|
if(getContext()==null) return;
|
||||||
super.onDataLoaded(d, more);
|
super.onDataLoaded(d, more);
|
||||||
// more available, but the page isn't even full yet? seems wrong, let's load some more
|
// more available, but the page isn't even full yet? seems wrong, let's load some more
|
||||||
if(more && data.size() < itemsPerPage){
|
if(more && d.size() < itemsPerPage){
|
||||||
preloader.onScrolledToLastItem();
|
preloader.onScrolledToLastItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1108,9 +900,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||||||
private Paint dividerPaint=new Paint();
|
private Paint dividerPaint=new Paint();
|
||||||
|
|
||||||
{
|
{
|
||||||
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.showDividers ? R.attr.colorM3OutlineVariant : R.attr.colorM3Surface));
|
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OutlineVariant));
|
||||||
dividerPaint.setStyle(Paint.Style.STROKE);
|
dividerPaint.setStyle(Paint.Style.STROKE);
|
||||||
dividerPaint.setStrokeWidth(V.dp(1f));
|
dividerPaint.setStrokeWidth(V.dp(0.5f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Status> result){
|
public void onSuccess(HeaderPaginationList<Status> result){
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -5,15 +5,12 @@ import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.T
|
|||||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.DRAFTS_AFTER_INSTANT;
|
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.DRAFTS_AFTER_INSTANT;
|
||||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDraftInstant;
|
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDraftInstant;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.animation.ObjectAnimator;
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.DatePickerDialog;
|
import android.app.DatePickerDialog;
|
||||||
import android.app.TimePickerDialog;
|
import android.app.TimePickerDialog;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Outline;
|
import android.graphics.Outline;
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
@@ -32,6 +29,7 @@ import android.text.TextWatcher;
|
|||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.text.style.BackgroundColorSpan;
|
import android.text.style.BackgroundColorSpan;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -56,7 +54,6 @@ import android.widget.TextView;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.github.bottomSoftwareFoundation.bottom.Bottom;
|
import com.github.bottomSoftwareFoundation.bottom.Bottom;
|
||||||
import com.squareup.otto.Subscribe;
|
|
||||||
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
import com.twitter.twittertext.TwitterTextEmojiRegex;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
@@ -69,7 +66,6 @@ import org.joinmastodon.android.api.requests.statuses.EditStatus;
|
|||||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
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.TakePictureRequestEvent;
|
|
||||||
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
import org.joinmastodon.android.events.ScheduledStatusCreatedEvent;
|
||||||
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
import org.joinmastodon.android.events.ScheduledStatusDeletedEvent;
|
||||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||||
@@ -95,8 +91,6 @@ import org.joinmastodon.android.ui.text.ComposeAutocompleteSpan;
|
|||||||
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
import org.joinmastodon.android.ui.text.ComposeHashtagOrMentionSpan;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.utils.FileProvider;
|
|
||||||
import org.joinmastodon.android.utils.TransferSpeedTracker;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.ComposeAutocompleteViewController;
|
import org.joinmastodon.android.ui.viewcontrollers.ComposeAutocompleteViewController;
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.ComposeLanguageAlertViewController;
|
import org.joinmastodon.android.ui.viewcontrollers.ComposeLanguageAlertViewController;
|
||||||
@@ -109,12 +103,6 @@ import org.joinmastodon.android.utils.MastodonLanguage;
|
|||||||
import org.joinmastodon.android.utils.StatusTextEncoder;
|
import org.joinmastodon.android.utils.StatusTextEncoder;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InterruptedIOException;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
@@ -123,11 +111,11 @@ import java.time.format.DateTimeFormatter;
|
|||||||
import java.time.format.FormatStyle;
|
import java.time.format.FormatStyle;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -151,8 +139,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private static final Pattern GLITCH_LOCAL_ONLY_PATTERN = Pattern.compile("[\\s\\S]*" + GLITCH_LOCAL_ONLY_SUFFIX + "[\uFE00-\uFE0F]*");
|
private static final Pattern GLITCH_LOCAL_ONLY_PATTERN = Pattern.compile("[\\s\\S]*" + GLITCH_LOCAL_ONLY_SUFFIX + "[\uFE00-\uFE0F]*");
|
||||||
|
|
||||||
private static final String TAG="ComposeFragment";
|
private static final String TAG="ComposeFragment";
|
||||||
public static final int CAMERA_PERMISSION_CODE = 626938;
|
|
||||||
public static final int CAMERA_PIC_REQUEST_CODE = 6242069;
|
|
||||||
|
|
||||||
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
@@ -165,7 +151,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
public LinearLayout mainLayout;
|
public LinearLayout mainLayout;
|
||||||
private SizeListenerLinearLayout contentView;
|
private SizeListenerLinearLayout contentView;
|
||||||
private TextView selfName, selfUsername, selfExtraText, extraText;
|
private TextView selfName, selfUsername, selfExtraText, extraText, pronouns;
|
||||||
private ImageView selfAvatar;
|
private ImageView selfAvatar;
|
||||||
private Account self;
|
private Account self;
|
||||||
private String instanceDomain;
|
private String instanceDomain;
|
||||||
@@ -176,8 +162,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
private int charCount, charLimit, trimmedCharCount;
|
private int charCount, charLimit, trimmedCharCount;
|
||||||
|
|
||||||
private Button publishButton, languageButton, scheduleTimeBtn;
|
private Button publishButton, languageButton, scheduleTimeBtn;
|
||||||
private PopupMenu contentTypePopup, visibilityPopup, draftOptionsPopup;
|
private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup;
|
||||||
private ImageButton publishButtonRelocated, mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn;
|
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn;
|
||||||
private View sensitiveBtn;
|
private View sensitiveBtn;
|
||||||
private TextView replyText;
|
private TextView replyText;
|
||||||
private LinearLayout scheduleDraftView;
|
private LinearLayout scheduleDraftView;
|
||||||
@@ -215,11 +201,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
public Instance instance;
|
public Instance instance;
|
||||||
|
|
||||||
public Status editingStatus;
|
public Status editingStatus;
|
||||||
public ScheduledStatus scheduledStatus;
|
private ScheduledStatus scheduledStatus;
|
||||||
private boolean redraftStatus;
|
private boolean redraftStatus;
|
||||||
|
|
||||||
private Uri photoUri;
|
|
||||||
|
|
||||||
private ContentType contentType;
|
private ContentType contentType;
|
||||||
private MastodonLanguage.LanguageResolver languageResolver;
|
private MastodonLanguage.LanguageResolver languageResolver;
|
||||||
|
|
||||||
@@ -239,7 +223,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
E.register(this);
|
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
|
|
||||||
accountID=getArguments().getString("account");
|
accountID=getArguments().getString("account");
|
||||||
@@ -288,7 +271,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
@Override
|
@Override
|
||||||
public void onDestroy(){
|
public void onDestroy(){
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
E.unregister(this);
|
|
||||||
mediaViewController.cancelAllUploads();
|
mediaViewController.cancelAllUploads();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +294,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
|
||||||
creatingView=true;
|
creatingView=true;
|
||||||
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), accountID, customEmojis, instanceDomain);
|
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), customEmojis, instanceDomain);
|
||||||
emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){
|
emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){
|
||||||
@Override
|
@Override
|
||||||
public void onEmojiSelected(Emoji emoji){
|
public void onEmojiSelected(Emoji emoji){
|
||||||
@@ -334,37 +316,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
});
|
});
|
||||||
|
|
||||||
View view=inflater.inflate(R.layout.fragment_compose, container, false);
|
View view=inflater.inflate(R.layout.fragment_compose, container, false);
|
||||||
|
|
||||||
if(GlobalUserPreferences.relocatePublishButton){
|
|
||||||
publishButtonRelocated=view.findViewById(R.id.publish);
|
|
||||||
// publishButton.setText(editingStatus==null || redraftStatus ? R.string.publish : R.string.save);
|
|
||||||
// publishButton.setEllipsize(TextUtils.TruncateAt.END);
|
|
||||||
publishButtonRelocated.setOnClickListener(v -> {
|
|
||||||
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
|
|
||||||
checkAltTextsAndPublish();
|
|
||||||
else
|
|
||||||
publish();
|
|
||||||
});
|
|
||||||
publishButtonRelocated.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
draftsBtn=view.findViewById(R.id.drafts_btn);
|
|
||||||
draftsBtn.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
charCounter=view.findViewById(R.id.char_counter);
|
|
||||||
charCounter.setVisibility(View.VISIBLE);
|
|
||||||
charCounter.setText(String.valueOf(charLimit));
|
|
||||||
}
|
|
||||||
|
|
||||||
mainLayout=view.findViewById(R.id.compose_main_ll);
|
mainLayout=view.findViewById(R.id.compose_main_ll);
|
||||||
mainEditText=view.findViewById(R.id.toot_text);
|
mainEditText=view.findViewById(R.id.toot_text);
|
||||||
mainEditTextWrap=view.findViewById(R.id.toot_text_wrap);
|
mainEditTextWrap=view.findViewById(R.id.toot_text_wrap);
|
||||||
|
charCounter=view.findViewById(R.id.char_counter);
|
||||||
|
charCounter.setText(String.valueOf(charLimit));
|
||||||
scrollView=view.findViewById(R.id.scroll_view);
|
scrollView=view.findViewById(R.id.scroll_view);
|
||||||
|
|
||||||
selfName=view.findViewById(R.id.self_name);
|
selfName=view.findViewById(R.id.self_name);
|
||||||
selfUsername=view.findViewById(R.id.self_username);
|
selfUsername=view.findViewById(R.id.self_username);
|
||||||
selfAvatar=view.findViewById(R.id.self_avatar);
|
selfAvatar=view.findViewById(R.id.self_avatar);
|
||||||
selfExtraText=view.findViewById(R.id.self_extra_text);
|
selfExtraText=view.findViewById(R.id.self_extra_text);
|
||||||
HtmlParser.setTextWithCustomEmoji(selfName, self.getDisplayName(), self.emojis);
|
HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis);
|
||||||
selfUsername.setText('@'+self.username+'@'+instanceDomain);
|
selfUsername.setText('@'+self.username+'@'+instanceDomain);
|
||||||
if(self.avatar!=null)
|
if(self.avatar!=null)
|
||||||
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
|
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
|
||||||
@@ -391,27 +354,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
sensitiveBtn=view.findViewById(R.id.sensitive_item);
|
sensitiveBtn=view.findViewById(R.id.sensitive_item);
|
||||||
replyText=view.findViewById(R.id.reply_text);
|
replyText=view.findViewById(R.id.reply_text);
|
||||||
|
|
||||||
|
if (UiUtils.isPhotoPickerAvailable()) {
|
||||||
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
|
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
|
||||||
attachPopup.inflate(R.menu.attach);
|
attachPopup.inflate(R.menu.attach);
|
||||||
if(UiUtils.isPhotoPickerAvailable())
|
|
||||||
attachPopup.getMenu().findItem(R.id.media).setVisible(true);
|
|
||||||
|
|
||||||
attachPopup.setOnMenuItemClickListener(i -> {
|
attachPopup.setOnMenuItemClickListener(i -> {
|
||||||
if (i.getItemId() == R.id.camera){
|
|
||||||
try {
|
|
||||||
openCamera();
|
|
||||||
} catch (IOException e){
|
|
||||||
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_SHORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
openFilePicker(i.getItemId() == R.id.media);
|
openFilePicker(i.getItemId() == R.id.media);
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
UiUtils.enablePopupMenuIcons(getContext(), attachPopup);
|
UiUtils.enablePopupMenuIcons(getContext(), attachPopup);
|
||||||
mediaBtn.setOnClickListener(v->attachPopup.show());
|
mediaBtn.setOnClickListener(v->attachPopup.show());
|
||||||
mediaBtn.setOnTouchListener(attachPopup.getDragToOpenListener());
|
mediaBtn.setOnTouchListener(attachPopup.getDragToOpenListener());
|
||||||
|
} else {
|
||||||
|
mediaBtn.setOnClickListener(v -> openFilePicker(false));
|
||||||
|
}
|
||||||
if (isInstancePixelfed()) pollBtn.setVisibility(View.GONE);
|
if (isInstancePixelfed()) pollBtn.setVisibility(View.GONE);
|
||||||
pollBtn.setOnClickListener(v->togglePoll());
|
pollBtn.setOnClickListener(v->togglePoll());
|
||||||
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
||||||
@@ -558,19 +513,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
|
|
||||||
if (requestCode == CAMERA_PERMISSION_CODE && (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
|
||||||
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
||||||
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST_CODE);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), R.string.permission_required, Toast.LENGTH_SHORT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume(){
|
public void onResume(){
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -685,6 +627,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
});
|
});
|
||||||
View originalPost=view.findViewById(R.id.original_post);
|
View originalPost=view.findViewById(R.id.original_post);
|
||||||
extraText=view.findViewById(R.id.extra_text);
|
extraText=view.findViewById(R.id.extra_text);
|
||||||
|
pronouns=view.findViewById(R.id.pronouns);
|
||||||
originalPost.setVisibility(View.VISIBLE);
|
originalPost.setVisibility(View.VISIBLE);
|
||||||
originalPost.setOnClickListener(v->{
|
originalPost.setOnClickListener(v->{
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
@@ -724,7 +667,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
moreBtn.setBackground(null);
|
moreBtn.setBackground(null);
|
||||||
|
|
||||||
TextView name = view.findViewById(R.id.name);
|
TextView name = view.findViewById(R.id.name);
|
||||||
name.setText(HtmlParser.parseCustomEmoji(status.account.getDisplayName(), status.account.emojis));
|
name.setText(HtmlParser.parseCustomEmoji(status.account.displayName, status.account.emojis));
|
||||||
UiUtils.loadCustomEmojiInTextView(name);
|
UiUtils.loadCustomEmojiInTextView(name);
|
||||||
|
|
||||||
String time = status==null || status.editedAt==null
|
String time = status==null || status.editedAt==null
|
||||||
@@ -758,8 +701,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
|
.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
|
||||||
}
|
}
|
||||||
|
|
||||||
replyText.setText(HtmlParser.parseCustomEmoji(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.getDisplayName()), status.account.emojis));
|
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.displayName));
|
||||||
UiUtils.loadCustomEmojiInTextView(replyText);
|
|
||||||
int visibilityNameRes = switch (status.visibility) {
|
int visibilityNameRes = switch (status.visibility) {
|
||||||
case PUBLIC -> R.string.visibility_public;
|
case PUBLIC -> R.string.visibility_public;
|
||||||
case UNLISTED -> R.string.sk_visibility_unlisted;
|
case UNLISTED -> R.string.sk_visibility_unlisted;
|
||||||
@@ -767,21 +709,15 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
case DIRECT -> R.string.visibility_private;
|
case DIRECT -> R.string.visibility_private;
|
||||||
case LOCAL -> R.string.sk_local_only;
|
case LOCAL -> R.string.sk_local_only;
|
||||||
};
|
};
|
||||||
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.getDisplayName()) + ", " + getString(visibilityNameRes));
|
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.displayName) + ", " + getString(visibilityNameRes));
|
||||||
replyText.setOnClickListener(v->{
|
replyText.setOnClickListener(v->{
|
||||||
scrollView.smoothScrollTo(0, 0);
|
scrollView.smoothScrollTo(0, 0);
|
||||||
});
|
});
|
||||||
replyText.setOnClickListener(v->{
|
|
||||||
scrollView.smoothScrollTo(0, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
ArrayList<String> mentions=new ArrayList<>();
|
ArrayList<String> mentions=new ArrayList<>();
|
||||||
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
|
||||||
if(!status.account.id.equals(ownID))
|
if(!status.account.id.equals(ownID))
|
||||||
mentions.add('@'+status.account.acct);
|
mentions.add('@'+status.account.acct);
|
||||||
if(status.rebloggedBy != null && GlobalUserPreferences.mentionRebloggerAutomatically)
|
|
||||||
mentions.add('@'+status.rebloggedBy.acct);
|
|
||||||
for(Mention mention:status.mentions){
|
for(Mention mention:status.mentions){
|
||||||
if(mention.id.equals(ownID))
|
if(mention.id.equals(ownID))
|
||||||
continue;
|
continue;
|
||||||
@@ -801,7 +737,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|
||||||
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
|
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
|
||||||
&& !status.spoilerText.startsWith("re: ") ? "re: " : "";
|
&& !status.spoilerText.startsWith("re: ") ? "re: " : "";
|
||||||
spoilerEdit.setText(prefix + status.spoilerText);
|
spoilerEdit.setText(prefix + replyTo.spoilerText);
|
||||||
spoilerBtn.setSelected(true);
|
spoilerBtn.setSelected(true);
|
||||||
}
|
}
|
||||||
if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language);
|
if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language);
|
||||||
@@ -862,7 +798,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
|
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
|
||||||
@@ -872,77 +807,40 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
actionItem.setActionView(wrap);
|
actionItem.setActionView(wrap);
|
||||||
actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
|
||||||
if(!GlobalUserPreferences.relocatePublishButton){
|
|
||||||
publishButton = wrap.findViewById(R.id.publish_btn);
|
|
||||||
publishButton.setOnClickListener(v -> {
|
|
||||||
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
|
|
||||||
checkAltTextsAndPublish();
|
|
||||||
else
|
|
||||||
publish();
|
|
||||||
});
|
|
||||||
publishButton.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
draftsBtn = wrap.findViewById(R.id.drafts_btn);
|
draftsBtn = wrap.findViewById(R.id.drafts_btn);
|
||||||
draftsBtn.setVisibility(View.VISIBLE);
|
draftOptionsPopup = new PopupMenu(getContext(), draftsBtn);
|
||||||
}else{
|
|
||||||
charCounter = wrap.findViewById(R.id.char_counter);
|
|
||||||
charCounter.setVisibility(View.VISIBLE);
|
|
||||||
charCounter.setText(String.valueOf(charLimit));
|
|
||||||
}
|
|
||||||
|
|
||||||
// draftsBtn=wrap.findViewById(R.id.drafts_btn);
|
|
||||||
draftOptionsPopup=new PopupMenu(getContext(), draftsBtn);
|
|
||||||
draftOptionsPopup.inflate(R.menu.compose_more);
|
draftOptionsPopup.inflate(R.menu.compose_more);
|
||||||
Menu draftOptionsMenu=draftOptionsPopup.getMenu();
|
draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft);
|
||||||
draftMenuItem=draftOptionsMenu.findItem(R.id.draft);
|
undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft);
|
||||||
undraftMenuItem=draftOptionsMenu.findItem(R.id.undraft);
|
scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule);
|
||||||
scheduleMenuItem=draftOptionsMenu.findItem(R.id.schedule);
|
unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule);
|
||||||
unscheduleMenuItem=draftOptionsMenu.findItem(R.id.unschedule);
|
|
||||||
draftOptionsMenu.findItem(R.id.preview).setVisible(isInstanceAkkoma());
|
|
||||||
draftOptionsPopup.setOnMenuItemClickListener(i->{
|
draftOptionsPopup.setOnMenuItemClickListener(i->{
|
||||||
int id=i.getItemId();
|
int id = i.getItemId();
|
||||||
if(id==R.id.draft) updateScheduledAt(getDraftInstant());
|
if (id == R.id.draft) updateScheduledAt(getDraftInstant());
|
||||||
else if(id==R.id.schedule) pickScheduledDateTime();
|
else if (id == R.id.schedule) pickScheduledDateTime();
|
||||||
else if(id==R.id.unschedule || id==R.id.undraft) updateScheduledAt(null);
|
else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null);
|
||||||
else if(id==R.id.drafts) navigateToUnsentPosts();
|
else navigateToUnsentPosts();
|
||||||
else if(id==R.id.preview) publish(true);
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
|
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
|
||||||
|
|
||||||
|
publishButton = wrap.findViewById(R.id.publish_btn);
|
||||||
languageButton = wrap.findViewById(R.id.language_btn);
|
languageButton = wrap.findViewById(R.id.language_btn);
|
||||||
languageButton.setOnClickListener(v->showLanguageAlert());
|
languageButton.setOnClickListener(v->showLanguageAlert());
|
||||||
languageButton.setOnLongClickListener(v->{
|
languageButton.setOnLongClickListener(v->{
|
||||||
|
languageButton.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
if(!getLocalPrefs().bottomEncoding){
|
if(!getLocalPrefs().bottomEncoding){
|
||||||
getLocalPrefs().bottomEncoding=true;
|
getLocalPrefs().bottomEncoding=true;
|
||||||
getLocalPrefs().save();
|
getLocalPrefs().save();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
if (!GlobalUserPreferences.relocatePublishButton)
|
|
||||||
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
|
|
||||||
|
|
||||||
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setOnClickListener(v->{
|
publishButton.setOnClickListener(v -> {
|
||||||
Consumer<Boolean> draftCheckComplete=(isDraft)->{
|
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
|
||||||
if(GlobalUserPreferences.altTextReminders && !isDraft) checkAltTextsAndPublish();
|
checkAltTextsAndPublish();
|
||||||
else publish();
|
else
|
||||||
};
|
publish();
|
||||||
|
|
||||||
boolean isAlreadyDraft=scheduledAt!=null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
|
|
||||||
if(editingStatus!=null && scheduledAt!=null && isAlreadyDraft) {
|
|
||||||
new M3AlertDialogBuilder(getActivity())
|
|
||||||
.setTitle(R.string.sk_save_draft)
|
|
||||||
.setMessage(R.string.sk_save_draft_message)
|
|
||||||
.setPositiveButton(R.string.save, (d, w)->draftCheckComplete.accept(isAlreadyDraft))
|
|
||||||
.setNegativeButton(R.string.publish, (d, w)->{
|
|
||||||
updateScheduledAt(null);
|
|
||||||
draftCheckComplete.accept(false);
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}else{
|
|
||||||
draftCheckComplete.accept(isAlreadyDraft);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
|
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
|
||||||
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
|
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
|
||||||
@@ -954,8 +852,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
? languageResolver.fromOrFallback(prefs.postingDefaultLanguage)
|
? languageResolver.fromOrFallback(prefs.postingDefaultLanguage)
|
||||||
: languageResolver.getDefault());
|
: languageResolver.getDefault());
|
||||||
|
|
||||||
if(isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
|
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
|
||||||
if(isInstancePixelfed() || (editingStatus!=null && !redraftStatus)) {
|
if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) {
|
||||||
// editing an already published post
|
// editing an already published post
|
||||||
draftsBtn.setVisibility(View.GONE);
|
draftsBtn.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@@ -1031,9 +929,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
private void resetPublishButtonText() {
|
private void resetPublishButtonText() {
|
||||||
int publishText = editingStatus==null || redraftStatus ? R.string.publish : R.string.save;
|
int publishText = editingStatus==null || redraftStatus ? R.string.publish : R.string.save;
|
||||||
if(GlobalUserPreferences.relocatePublishButton){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
||||||
if (publishText == R.string.publish && !TextUtils.isEmpty(prefs.publishButtonText)) {
|
if (publishText == R.string.publish && !TextUtils.isEmpty(prefs.publishButtonText)) {
|
||||||
publishButton.setText(prefs.publishButtonText);
|
publishButton.setText(prefs.publishButtonText);
|
||||||
@@ -1044,10 +939,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
public void updatePublishButtonState(){
|
public void updatePublishButtonState(){
|
||||||
uuid=null;
|
uuid=null;
|
||||||
if(GlobalUserPreferences.relocatePublishButton && publishButtonRelocated != null){
|
|
||||||
publishButtonRelocated.setEnabled((!isInstancePixelfed() || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(publishButton==null)
|
if(publishButton==null)
|
||||||
return;
|
return;
|
||||||
publishButton.setEnabled((!isInstancePixelfed() || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
|
publishButton.setEnabled((!isInstancePixelfed() || !mediaViewController.isEmpty()) && (trimmedCharCount>0 || !mediaViewController.isEmpty()) && charCount<=charLimit && mediaViewController.getNonDoneAttachmentCount()==0 && (pollViewController.isEmpty() || pollViewController.getNonEmptyOptionsCount()>1));
|
||||||
@@ -1086,7 +977,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getNavigationIconDrawableResource(){
|
protected int getNavigationIconDrawableResource(){
|
||||||
return R.drawable.ic_fluent_dismiss_24_regular;
|
return R.drawable.ic_baseline_close_24;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1145,10 +1036,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void publish(){
|
private void publish(){
|
||||||
publish(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publish(boolean preview){
|
|
||||||
sendingOverlay=new View(getActivity());
|
sendingOverlay=new View(getActivity());
|
||||||
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
|
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
|
||||||
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
|
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
|
||||||
@@ -1159,25 +1046,26 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
overlayParams.token=mainEditText.getWindowToken();
|
overlayParams.token=mainEditText.getWindowToken();
|
||||||
wm.addView(sendingOverlay, overlayParams);
|
wm.addView(sendingOverlay, overlayParams);
|
||||||
|
|
||||||
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(false);
|
publishButton.setEnabled(false);
|
||||||
V.setVisibilityAnimated(sendProgress, View.VISIBLE);
|
V.setVisibilityAnimated(sendProgress, View.VISIBLE);
|
||||||
|
|
||||||
mediaViewController.saveAltTextsBeforePublishing(
|
mediaViewController.saveAltTextsBeforePublishing(this::actuallyPublish, this::handlePublishError);
|
||||||
()->actuallyPublish(preview),
|
|
||||||
this::handlePublishError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void actuallyPublish(boolean preview){
|
private void actuallyPublish(){
|
||||||
|
actuallyPublish(false);
|
||||||
|
}
|
||||||
|
private void actuallyPublish(boolean force){
|
||||||
String text=mainEditText.getText().toString();
|
String text=mainEditText.getText().toString();
|
||||||
CreateStatus.Request req=new CreateStatus.Request();
|
CreateStatus.Request req=new CreateStatus.Request();
|
||||||
if("bottom".equals(postLang.encoding)){
|
if ("bottom".equals(postLang.encoding)) {
|
||||||
text=new StatusTextEncoder(Bottom::encode).encode(text);
|
text = new StatusTextEncoder(Bottom::encode).encode(text);
|
||||||
req.spoilerText="bottom-encoded emoji spam";
|
req.spoilerText = "bottom-encoded emoji spam";
|
||||||
}
|
}
|
||||||
if(localOnly &&
|
if (localOnly &&
|
||||||
AccountSessionManager.get(accountID).getLocalPreferences().glitchInstance &&
|
AccountSessionManager.get(accountID).getLocalPreferences().glitchInstance &&
|
||||||
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()){
|
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()) {
|
||||||
text+=" "+GLITCH_LOCAL_ONLY_SUFFIX;
|
text += " " + GLITCH_LOCAL_ONLY_SUFFIX;
|
||||||
}
|
}
|
||||||
req.status=text;
|
req.status=text;
|
||||||
req.localOnly=localOnly;
|
req.localOnly=localOnly;
|
||||||
@@ -1185,13 +1073,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
req.sensitive=sensitive;
|
req.sensitive=sensitive;
|
||||||
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
|
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
|
||||||
req.scheduledAt=scheduledAt;
|
req.scheduledAt=scheduledAt;
|
||||||
req.preview=preview;
|
|
||||||
if(!mediaViewController.isEmpty()){
|
if(!mediaViewController.isEmpty()){
|
||||||
req.mediaIds=mediaViewController.getAttachmentIDs();
|
req.mediaIds=mediaViewController.getAttachmentIDs();
|
||||||
if(editingStatus != null){
|
if(editingStatus != null){
|
||||||
req.mediaAttributes=mediaViewController.getAttachmentAttributes();
|
req.mediaAttributes=mediaViewController.getAttachmentAttributes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ask whether to publish now when editing an existing draft
|
||||||
|
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.sk_save_draft)
|
||||||
|
.setMessage(R.string.sk_save_draft_message)
|
||||||
|
.setPositiveButton(R.string.save, (d, w) -> actuallyPublish(true))
|
||||||
|
.setNegativeButton(R.string.publish, (d, w) -> {
|
||||||
|
updateScheduledAt(null);
|
||||||
|
actuallyPublish();
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
|
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
|
||||||
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
|
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
|
||||||
}
|
}
|
||||||
@@ -1213,12 +1113,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
Callback<Status> resCallback=new Callback<>(){
|
Callback<Status> resCallback=new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(Status result){
|
public void onSuccess(Status result){
|
||||||
if(preview){
|
maybeDeleteScheduledPost(() -> {
|
||||||
openPreview(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
maybeDeleteScheduledPost(()->{
|
|
||||||
wm.removeView(sendingOverlay);
|
wm.removeView(sendingOverlay);
|
||||||
sendingOverlay=null;
|
sendingOverlay=null;
|
||||||
if(editingStatus==null || redraftStatus){
|
if(editingStatus==null || redraftStatus){
|
||||||
@@ -1240,10 +1135,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
E.post(new StatusUpdatedEvent(editedStatus));
|
E.post(new StatusUpdatedEvent(editedStatus));
|
||||||
}
|
}
|
||||||
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()){
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
|
||||||
Nav.finish(ComposeFragment.this);
|
Nav.finish(ComposeFragment.this);
|
||||||
}
|
}
|
||||||
if(getArguments().getBoolean("navigateToStatus", false)){
|
if (getArguments().getBoolean("navigateToStatus", false)) {
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
args.putString("account", accountID);
|
args.putString("account", accountID);
|
||||||
args.putParcelable("status", Parcels.wrap(result));
|
args.putParcelable("status", Parcels.wrap(result));
|
||||||
@@ -1259,11 +1154,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(editingStatus!=null && !redraftStatus && !preview){
|
if(editingStatus!=null && !redraftStatus){
|
||||||
new EditStatus(req, editingStatus.id)
|
new EditStatus(req, editingStatus.id)
|
||||||
.setCallback(resCallback)
|
.setCallback(resCallback)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
}else if(req.scheduledAt == null || preview){
|
}else if(req.scheduledAt == null){
|
||||||
new CreateStatus(req, uuid)
|
new CreateStatus(req, uuid)
|
||||||
.setCallback(resCallback)
|
.setCallback(resCallback)
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -1303,7 +1198,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
wm.removeView(sendingOverlay);
|
wm.removeView(sendingOverlay);
|
||||||
sendingOverlay=null;
|
sendingOverlay=null;
|
||||||
V.setVisibilityAnimated(sendProgress, View.GONE);
|
V.setVisibilityAnimated(sendProgress, View.GONE);
|
||||||
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(true);
|
publishButton.setEnabled(true);
|
||||||
if(error instanceof MastodonErrorResponse me){
|
if(error instanceof MastodonErrorResponse me){
|
||||||
new M3AlertDialogBuilder(getActivity())
|
new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(R.string.post_failed)
|
.setTitle(R.string.post_failed)
|
||||||
@@ -1316,25 +1211,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openPreview(Status result){
|
|
||||||
result.preview=true;
|
|
||||||
wm.removeView(sendingOverlay);
|
|
||||||
sendingOverlay=null;
|
|
||||||
publishButton.setEnabled(true);
|
|
||||||
V.setVisibilityAnimated(sendProgress, View.GONE);
|
|
||||||
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
|
||||||
imm.hideSoftInputFromWindow(contentView.getWindowToken(), 0);
|
|
||||||
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
args.putParcelable("status", Parcels.wrap(result));
|
|
||||||
if(replyTo!=null){
|
|
||||||
args.putParcelable("inReplyTo", Parcels.wrap(replyTo));
|
|
||||||
args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo.account));
|
|
||||||
}
|
|
||||||
Nav.go(getActivity(), ThreadFragment.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateRecentLanguages() {
|
private void updateRecentLanguages() {
|
||||||
if (postLang == null || postLang.language == null) return;
|
if (postLang == null || postLang.language == null) return;
|
||||||
String language = postLang.language.getLanguage();
|
String language = postLang.language.getLanguage();
|
||||||
@@ -1410,20 +1286,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void confirmDiscardDraftAndFinish(){
|
private void confirmDiscardDraftAndFinish(){
|
||||||
boolean attachmentsPending=mediaViewController.areAnyAttachmentsNotDone();
|
boolean attachmentsPending = mediaViewController.areAnyAttachmentsNotDone();
|
||||||
if(attachmentsPending) new M3AlertDialogBuilder(getActivity())
|
if (attachmentsPending) new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(R.string.sk_unfinished_attachments)
|
.setTitle(R.string.sk_unfinished_attachments)
|
||||||
.setMessage(R.string.sk_unfinished_attachments_message)
|
.setMessage(R.string.sk_unfinished_attachments_message)
|
||||||
.setPositiveButton(R.string.ok, (d, w)->{})
|
.setPositiveButton(R.string.edit, (d, w) -> {})
|
||||||
.setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
|
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
|
||||||
.show();
|
.show();
|
||||||
else new M3AlertDialogBuilder(getActivity())
|
else new M3AlertDialogBuilder(getActivity())
|
||||||
.setTitle(editingStatus!=null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
|
.setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
|
||||||
.setPositiveButton(R.string.save, (d, w)->{
|
.setPositiveButton(R.string.save, (d, w) -> {
|
||||||
updateScheduledAt(scheduledAt==null ? getDraftInstant() : scheduledAt);
|
updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt);
|
||||||
publish();
|
publish();
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
|
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1478,39 +1354,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode==Activity.RESULT_OK){
|
|
||||||
onAddMediaAttachmentFromEditText(photoUri, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
public void onTakePictureRequest(TakePictureRequestEvent ev) {
|
|
||||||
if(isVisible()) {
|
|
||||||
try {
|
|
||||||
openCamera();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_SHORT);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openCamera() throws IOException {
|
|
||||||
if (getContext().checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
File photoFile = File.createTempFile("img", ".jpg");
|
|
||||||
photoUri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".fileprovider", photoFile);
|
|
||||||
|
|
||||||
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
||||||
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
|
|
||||||
if(getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)){
|
|
||||||
startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST_CODE);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), R.string.mo_camera_not_available, Toast.LENGTH_SHORT);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
getActivity().requestPermissions(new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1550,7 +1393,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
|
|
||||||
public void updateSensitive() {
|
public void updateSensitive() {
|
||||||
sensitiveBtn.setVisibility(View.GONE);
|
sensitiveBtn.setVisibility(View.GONE);
|
||||||
if (!mediaViewController.isEmpty()) sensitiveBtn.setVisibility(View.VISIBLE);
|
if (!mediaViewController.isEmpty() && !hasSpoiler) sensitiveBtn.setVisibility(View.VISIBLE);
|
||||||
if (mediaViewController.isEmpty()) sensitive = false;
|
if (mediaViewController.isEmpty()) sensitive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1561,8 +1404,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
.withMinute(0);
|
.withMinute(0);
|
||||||
new DatePickerDialog(getActivity(), (datePicker, year, arrayMonth, dayOfMonth) -> {
|
new DatePickerDialog(getActivity(), (datePicker, year, arrayMonth, dayOfMonth) -> {
|
||||||
new TimePickerDialog(getActivity(), (timePicker, hour, minute) -> {
|
new TimePickerDialog(getActivity(), (timePicker, hour, minute) -> {
|
||||||
LocalDateTime at=LocalDateTime.of(year, arrayMonth + 1, dayOfMonth, hour, minute);
|
updateScheduledAt(LocalDateTime.of(year, arrayMonth + 1, dayOfMonth, hour, minute)
|
||||||
updateScheduledAt(at.toInstant(ZoneId.systemDefault().getRules().getOffset(at)));
|
.toInstant(OffsetDateTime.now().getOffset()));
|
||||||
}, soon.getHour(), soon.getMinute(), DateFormat.is24HourFormat(getActivity())).show();
|
}, soon.getHour(), soon.getMinute(), DateFormat.is24HourFormat(getActivity())).show();
|
||||||
}, soon.getYear(), soon.getMonthValue() - 1, soon.getDayOfMonth()).show();
|
}, soon.getYear(), soon.getMonthValue() - 1, soon.getDayOfMonth()).show();
|
||||||
}
|
}
|
||||||
@@ -1587,15 +1430,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
scheduleDraftDismiss.setTooltipText(getString(R.string.sk_compose_no_draft));
|
scheduleDraftDismiss.setTooltipText(getString(R.string.sk_compose_no_draft));
|
||||||
}
|
}
|
||||||
scheduleDraftDismiss.setContentDescription(getString(R.string.sk_compose_no_draft));
|
scheduleDraftDismiss.setContentDescription(getString(R.string.sk_compose_no_draft));
|
||||||
draftsBtn.setImageDrawable(getContext().getDrawable(GlobalUserPreferences.relocatePublishButton ? R.drawable.ic_fluent_drafts_24_regular : R.drawable.ic_fluent_drafts_20_filled));
|
draftsBtn.setImageResource(R.drawable.ic_fluent_drafts_20_filled);
|
||||||
|
|
||||||
if(GlobalUserPreferences.relocatePublishButton){
|
|
||||||
publishButtonRelocated.setImageResource(scheduledStatus != null && scheduledStatus.scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)
|
|
||||||
? R.drawable.ic_fluent_save_24_selector : R.drawable.ic_fluent_drafts_24_selector);
|
|
||||||
}else{
|
|
||||||
publishButton.setText(scheduledStatus != null && scheduledStatus.scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)
|
publishButton.setText(scheduledStatus != null && scheduledStatus.scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)
|
||||||
? R.string.save : R.string.sk_draft);
|
? R.string.save : R.string.sk_draft);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scheduleMenuItem.setVisible(false);
|
scheduleMenuItem.setVisible(false);
|
||||||
unscheduleMenuItem.setVisible(true);
|
unscheduleMenuItem.setVisible(true);
|
||||||
@@ -1608,28 +1445,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
scheduleDraftDismiss.setTooltipText(getString(R.string.sk_compose_no_schedule));
|
scheduleDraftDismiss.setTooltipText(getString(R.string.sk_compose_no_schedule));
|
||||||
}
|
}
|
||||||
scheduleDraftDismiss.setContentDescription(getString(R.string.sk_compose_no_schedule));
|
scheduleDraftDismiss.setContentDescription(getString(R.string.sk_compose_no_schedule));
|
||||||
draftsBtn.setImageDrawable(getContext().getDrawable(GlobalUserPreferences.relocatePublishButton ? R.drawable.ic_fluent_clock_24_filled : R.drawable.ic_fluent_clock_20_filled));
|
draftsBtn.setImageResource(R.drawable.ic_fluent_clock_20_filled);
|
||||||
if(GlobalUserPreferences.relocatePublishButton)
|
|
||||||
{
|
|
||||||
publishButtonRelocated.setImageResource(scheduledStatus != null && scheduledStatus.scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)
|
|
||||||
? R.drawable.ic_fluent_save_24_selector : R.drawable.ic_fluent_clock_24_selector);
|
|
||||||
}else{
|
|
||||||
publishButton.setText(scheduledStatus != null && scheduledStatus.scheduledAt.equals(scheduledAt)
|
publishButton.setText(scheduledStatus != null && scheduledStatus.scheduledAt.equals(scheduledAt)
|
||||||
? R.string.save : R.string.sk_schedule);
|
? R.string.save : R.string.sk_schedule);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
draftsBtn.setImageDrawable(getContext().getDrawable(GlobalUserPreferences.relocatePublishButton ? R.drawable.ic_fluent_clock_24_regular : R.drawable.ic_fluent_clock_20_regular));
|
draftsBtn.setImageResource(R.drawable.ic_fluent_clock_20_regular);
|
||||||
if(GlobalUserPreferences.relocatePublishButton){
|
|
||||||
publishButtonRelocated.setImageResource(R.drawable.ic_fluent_send_24_regular);
|
|
||||||
}
|
|
||||||
resetPublishButtonText();
|
resetPublishButtonText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateHeaders() {
|
private void updateHeaders() {
|
||||||
UiUtils.setExtraTextInfo(getContext(), selfExtraText, false, false, localOnly, null);
|
UiUtils.setExtraTextInfo(getContext(), selfExtraText, null, false, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null);
|
||||||
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
|
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, pronouns, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildVisibilityPopup(View v){
|
private void buildVisibilityPopup(View v){
|
||||||
@@ -1701,7 +1529,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentTypePopup.setOnMenuItemClickListener(i->{
|
contentTypePopup.setOnMenuItemClickListener(i->{
|
||||||
uuid=null;
|
|
||||||
int index=i.getItemId();
|
int index=i.getItemId();
|
||||||
contentType=ContentType.values()[index];
|
contentType=ContentType.values()[index];
|
||||||
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());
|
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());
|
||||||
@@ -1733,10 +1560,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
|||||||
menu.show();
|
menu.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
private void loadDefaultStatusVisibility(Bundle savedInstanceState){
|
||||||
if(replyTo != null) {
|
if(replyTo != null) statusVisibility = replyTo.visibility;
|
||||||
statusVisibility = (replyTo.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : replyTo.visibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountSessionManager asm = AccountSessionManager.getInstance();
|
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||||
Preferences prefs=asm.getAccount(accountID).preferences;
|
Preferences prefs=asm.getAccount(accountID).preferences;
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ import android.widget.ImageView;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
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.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.model.Attachment;
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||||
import org.joinmastodon.android.ui.utils.ColorPalette;
|
import org.joinmastodon.android.ui.utils.ColorPalette;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
@@ -54,17 +54,16 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState){
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
accountID=getArguments().getString("account");
|
||||||
|
attachmentID=getArguments().getString("attachment");
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity){
|
public void onAttach(Activity activity){
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
accountID=getArguments().getString("account");
|
|
||||||
attachmentID=getArguments().getString("attachment");
|
|
||||||
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
|
||||||
ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().getCurrentColor())
|
ColorPalette.palettes.get(GlobalUserPreferences.color).apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
|
||||||
.apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
|
|
||||||
setTitle(R.string.add_alt_text);
|
setTitle(R.string.add_alt_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
package org.joinmastodon.android.fragments;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
|
|
||||||
import org.joinmastodon.android.model.Filter;
|
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
|
||||||
import org.joinmastodon.android.model.Status;
|
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
|
||||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import me.grishka.appkit.api.SimpleCallback;
|
|
||||||
|
|
||||||
public class CustomLocalTimelineFragment extends PinnableStatusListFragment implements ProvidesAssistContent.ProvidesWebUri{
|
|
||||||
// private String name;
|
|
||||||
private String domain;
|
|
||||||
|
|
||||||
private String maxID;
|
|
||||||
@Override
|
|
||||||
protected boolean wantsComposeButton() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Activity activity){
|
|
||||||
super.onAttach(activity);
|
|
||||||
domain=getArguments().getString("domain");
|
|
||||||
updateTitle(domain);
|
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateTitle(String domain) {
|
|
||||||
this.domain = domain;
|
|
||||||
setTitle(this.domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doLoadData(int offset, int count){
|
|
||||||
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
|
|
||||||
.setCallback(new SimpleCallback<>(this){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Status> result){
|
|
||||||
if(!result.isEmpty())
|
|
||||||
maxID=result.get(result.size()-1).id;
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
result=result.stream().filter(new StatusFilterPredicate(accountID, FilterContext.PUBLIC)).collect(Collectors.toList());
|
|
||||||
result.stream().forEach(status -> {
|
|
||||||
status.account.acct += "@"+domain;
|
|
||||||
status.mentions.forEach(mention -> mention.id = null);
|
|
||||||
status.isRemote = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
onDataLoaded(result, !result.isEmpty());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.execNoAuth(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onShown(){
|
|
||||||
super.onShown();
|
|
||||||
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.custom_local_timelines, menu);
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
UiUtils.enableOptionsMenuIcons(getContext(), menu, R.id.pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected FilterContext getFilterContext() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Uri getWebUri(Uri.Builder base) {
|
|
||||||
return Uri.parse(domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TimelineDefinition makeTimelineDefinition() {
|
|
||||||
return TimelineDefinition.ofCustomLocalTimeline(domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import android.annotation.SuppressLint;
|
|||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputType;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -19,7 +18,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
@@ -41,9 +39,6 @@ import org.joinmastodon.android.api.requests.lists.GetLists;
|
|||||||
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
|
||||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.api.session.AccountSession;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.model.CustomLocalTimeline;
|
|
||||||
import org.joinmastodon.android.model.Hashtag;
|
import org.joinmastodon.android.model.Hashtag;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.ListTimeline;
|
import org.joinmastodon.android.model.ListTimeline;
|
||||||
@@ -51,7 +46,6 @@ import org.joinmastodon.android.model.TimelineDefinition;
|
|||||||
import org.joinmastodon.android.ui.DividerItemDecoration;
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -64,56 +58,54 @@ 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;
|
||||||
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 EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop{
|
public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private TimelinesAdapter adapter;
|
private TimelinesAdapter adapter;
|
||||||
private final ItemTouchHelper itemTouchHelper;
|
private final ItemTouchHelper itemTouchHelper;
|
||||||
private Menu optionsMenu;
|
private Menu optionsMenu;
|
||||||
private boolean updated;
|
private boolean updated;
|
||||||
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem=new HashMap<>();
|
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>();
|
||||||
private final List<ListTimeline> listTimelines=new ArrayList<>();
|
private final List<ListTimeline> listTimelines = new ArrayList<>();
|
||||||
private final List<Hashtag> hashtags=new ArrayList<>();
|
private final List<Hashtag> hashtags = new ArrayList<>();
|
||||||
private MenuItem addHashtagItem;
|
private MenuItem addHashtagItem;
|
||||||
private final List<CustomLocalTimeline> localTimelines = new ArrayList<>();
|
|
||||||
|
|
||||||
public EditTimelinesFragment(){
|
public EditTimelinesFragment() {
|
||||||
super(10);
|
super(10);
|
||||||
ItemTouchHelper.SimpleCallback itemTouchCallback=new ItemTouchHelperCallback();
|
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ;
|
||||||
itemTouchHelper=new ItemTouchHelper(itemTouchCallback);
|
itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState){
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
setTitle(R.string.sk_timelines);
|
setTitle(R.string.sk_timelines);
|
||||||
accountID=getArguments().getString("account");
|
accountID = getArguments().getString("account");
|
||||||
|
|
||||||
new GetLists().setCallback(new Callback<>(){
|
new GetLists().setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<ListTimeline> result){
|
public void onSuccess(List<ListTimeline> result) {
|
||||||
listTimelines.addAll(result);
|
listTimelines.addAll(result);
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error) {
|
||||||
error.showToast(getContext());
|
error.showToast(getContext());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
|
|
||||||
new GetFollowedHashtags().setCallback(new Callback<>(){
|
new GetFollowedHashtags().setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
public void onSuccess(HeaderPaginationList<Hashtag> result) {
|
||||||
hashtags.addAll(result);
|
hashtags.addAll(result);
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error) {
|
||||||
error.showToast(getContext());
|
error.showToast(getContext());
|
||||||
}
|
}
|
||||||
}).exec(accountID);
|
}).exec(accountID);
|
||||||
@@ -126,7 +118,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
itemTouchHelper.attachToRecyclerView(list);
|
itemTouchHelper.attachToRecyclerView(list);
|
||||||
refreshLayout.setEnabled(false);
|
refreshLayout.setEnabled(false);
|
||||||
@@ -134,22 +126,18 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
this.optionsMenu=menu;
|
this.optionsMenu = menu;
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item){
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if(item.getItemId()==R.id.menu_back){
|
if (item.getItemId() == R.id.menu_back) {
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
|
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (item.getItemId() == R.id.menu_add_local_timelines) {
|
|
||||||
addNewLocalTimeline();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
TimelineDefinition tl = timelineByMenuItem.get(item);
|
TimelineDefinition tl = timelineByMenuItem.get(item);
|
||||||
if (tl != null) {
|
if (tl != null) {
|
||||||
addTimeline(tl);
|
addTimeline(tl);
|
||||||
@@ -161,81 +149,58 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTimeline(TimelineDefinition tl){
|
private void addTimeline(TimelineDefinition tl) {
|
||||||
data.add(tl.copy());
|
data.add(tl.copy());
|
||||||
adapter.notifyItemInserted(data.size());
|
adapter.notifyItemInserted(data.size());
|
||||||
saveTimelines();
|
saveTimelines();
|
||||||
updateOptionsMenu();
|
updateOptionsMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addNewLocalTimeline() {
|
|
||||||
FrameLayout inputWrap = new FrameLayout(getContext());
|
|
||||||
EditText input = new EditText(getContext());
|
|
||||||
input.setHint(R.string.sk_example_domain);
|
|
||||||
input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
|
|
||||||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
|
||||||
params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16));
|
|
||||||
input.setLayoutParams(params);
|
|
||||||
inputWrap.addView(input);
|
|
||||||
new M3AlertDialogBuilder(getContext()).setTitle(R.string.mo_add_custom_server_local_timeline).setView(inputWrap)
|
|
||||||
.setPositiveButton(R.string.save, (d, which) -> {
|
|
||||||
TimelineDefinition tl = TimelineDefinition.ofCustomLocalTimeline(input.getText().toString().trim());
|
|
||||||
data.add(tl);
|
|
||||||
saveTimelines();
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.cancel, (d, which) -> {
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
|
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
|
||||||
if (data.contains(tl)) return;
|
if (data.contains(tl)) return;
|
||||||
MenuItem item = addOptionsItem(menu, tl.getTitle(getContext()), tl.getIcon().iconRes);
|
MenuItem item = addOptionsItem(menu, tl.getTitle(getContext()), tl.getIcon().iconRes);
|
||||||
timelineByMenuItem.put(item, tl);
|
timelineByMenuItem.put(item, tl);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon){
|
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon) {
|
||||||
MenuItem item=menu.add(0, View.generateViewId(), Menu.NONE, name);
|
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, name);
|
||||||
item.setIcon(icon);
|
item.setIcon(icon);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOptionsMenu(){
|
private void updateOptionsMenu() {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
optionsMenu.clear();
|
optionsMenu.clear();
|
||||||
timelineByMenuItem.clear();
|
timelineByMenuItem.clear();
|
||||||
|
|
||||||
SubMenu menu=optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
|
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().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
|
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
|
||||||
|
|
||||||
SubMenu timelinesMenu=menu.addSubMenu(R.string.sk_timeline);
|
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline);
|
||||||
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
|
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
|
||||||
SubMenu listsMenu=menu.addSubMenu(R.string.sk_list);
|
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list);
|
||||||
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
|
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
|
||||||
SubMenu hashtagsMenu=menu.addSubMenu(R.string.sk_hashtag);
|
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag);
|
||||||
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
|
||||||
|
|
||||||
MenuItem addLocalTimelines = menu.add(0, R.id.menu_add_local_timelines, NONE, R.string.local_timeline);
|
|
||||||
addLocalTimelines.setIcon(R.drawable.ic_fluent_add_24_regular);
|
|
||||||
|
|
||||||
makeBackItem(timelinesMenu);
|
makeBackItem(timelinesMenu);
|
||||||
makeBackItem(listsMenu);
|
makeBackItem(listsMenu);
|
||||||
makeBackItem(hashtagsMenu);
|
makeBackItem(hashtagsMenu);
|
||||||
|
|
||||||
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl->addTimelineToOptions(tl, timelinesMenu));
|
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
|
||||||
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl->addTimelineToOptions(tl, listsMenu));
|
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
|
||||||
addHashtagItem=addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
|
addHashtagItem = addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
|
||||||
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl->addTimelineToOptions(tl, hashtagsMenu));
|
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
|
||||||
|
|
||||||
timelinesMenu.getItem().setVisible(timelinesMenu.size()>0);
|
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0);
|
||||||
listsMenu.getItem().setVisible(listsMenu.size()>0);
|
listsMenu.getItem().setVisible(listsMenu.size() > 0);
|
||||||
hashtagsMenu.getItem().setVisible(hashtagsMenu.size()>0);
|
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0);
|
||||||
|
|
||||||
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveTimelines(){
|
private void saveTimelines() {
|
||||||
updated=true;
|
updated=true;
|
||||||
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
|
||||||
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
|
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
|
||||||
@@ -243,7 +208,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
prefs.save();
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeTimeline(int position){
|
private void removeTimeline(int position) {
|
||||||
data.remove(position);
|
data.remove(position);
|
||||||
adapter.notifyItemRemoved(position);
|
adapter.notifyItemRemoved(position);
|
||||||
saveTimelines();
|
saveTimelines();
|
||||||
@@ -257,29 +222,29 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter(){
|
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() {
|
||||||
return adapter=new TimelinesAdapter();
|
return adapter = new TimelinesAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop() {
|
||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy(){
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if(updated) UiUtils.restartApp();
|
if (updated) UiUtils.restartApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags){
|
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
|
||||||
if(tags==null || tags.isEmpty()) return false;
|
if (tags == null || tags.isEmpty()) return false;
|
||||||
editText.setText(tags);
|
editText.setText(tags);
|
||||||
editText.chipifyAllUnterminatedTokens();
|
editText.chipifyAllUnterminatedTokens();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NachoTextView prepareChipTextView(NachoTextView nacho){
|
private NachoTextView prepareChipTextView(NachoTextView nacho) {
|
||||||
//I’ll Be Back
|
//I’ll Be Back
|
||||||
nacho.setChipTerminators(
|
nacho.setChipTerminators(
|
||||||
Map.of(
|
Map.of(
|
||||||
@@ -290,25 +255,25 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
nacho.enableEditChipOnTouch(true, true);
|
nacho.enableEditChipOnTouch(true, true);
|
||||||
nacho.setOnFocusChangeListener((v, hasFocus)->nacho.chipifyAllUnterminatedTokens());
|
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
|
||||||
return nacho;
|
return nacho;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove){
|
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove) {
|
||||||
Context ctx=getContext();
|
Context ctx = getContext();
|
||||||
View view=getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
|
View view = getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
|
||||||
|
|
||||||
View divider=view.findViewById(R.id.divider);
|
View divider = view.findViewById(R.id.divider);
|
||||||
Button advancedBtn=view.findViewById(R.id.advanced);
|
Button advancedBtn = view.findViewById(R.id.advanced);
|
||||||
EditText editText=view.findViewById(R.id.input);
|
EditText editText = view.findViewById(R.id.input);
|
||||||
if(item!=null) editText.setText(item.getCustomTitle());
|
if (item != null) editText.setText(item.getCustomTitle());
|
||||||
editText.setHint(item!=null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
|
editText.setHint(item != null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
|
||||||
|
|
||||||
LinearLayout tagWrap=view.findViewById(R.id.tag_wrap);
|
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap);
|
||||||
boolean hashtagOptionsAvailable=item==null || item.getType()==TimelineDefinition.TimelineType.HASHTAG;
|
boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG;
|
||||||
advancedBtn.setVisibility(hashtagOptionsAvailable ? View.VISIBLE : View.GONE);
|
advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE);
|
||||||
advancedBtn.setOnClickListener(l->{
|
advancedBtn.setOnClickListener(l -> {
|
||||||
advancedBtn.setSelected(!advancedBtn.isSelected());
|
advancedBtn.setSelected(!advancedBtn.isSelected());
|
||||||
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
|
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
|
||||||
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
|
||||||
@@ -316,25 +281,25 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
UiUtils.beginLayoutTransition((ViewGroup) view);
|
UiUtils.beginLayoutTransition((ViewGroup) view);
|
||||||
});
|
});
|
||||||
|
|
||||||
Switch localOnlySwitch=view.findViewById(R.id.local_only_switch);
|
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch);
|
||||||
view.findViewById(R.id.local_only).setOnClickListener(l->localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
|
view.findViewById(R.id.local_only)
|
||||||
|
.setOnClickListener(l -> localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
|
||||||
|
|
||||||
EditText tagMain=view.findViewById(R.id.tag_main);
|
EditText tagMain = view.findViewById(R.id.tag_main);
|
||||||
NachoTextView tagsAny=prepareChipTextView(view.findViewById(R.id.tags_any));
|
NachoTextView tagsAny = prepareChipTextView(view.findViewById(R.id.tags_any));
|
||||||
NachoTextView tagsAll=prepareChipTextView(view.findViewById(R.id.tags_all));
|
NachoTextView tagsAll = prepareChipTextView(view.findViewById(R.id.tags_all));
|
||||||
NachoTextView tagsNone=prepareChipTextView(view.findViewById(R.id.tags_none));
|
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none));
|
||||||
|
if (item != null) {
|
||||||
if(item!=null && hashtagOptionsAvailable){
|
|
||||||
tagMain.setText(item.getHashtagName());
|
tagMain.setText(item.getHashtagName());
|
||||||
boolean hasAdvanced=!TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
|
boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
|
||||||
hasAdvanced=setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
|
hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
|
||||||
hasAdvanced=setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
|
hasAdvanced = setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
|
||||||
hasAdvanced=setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
|
hasAdvanced = setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
|
||||||
if(item.isHashtagLocalOnly()){
|
if (item.isHashtagLocalOnly()) {
|
||||||
localOnlySwitch.setChecked(true);
|
localOnlySwitch.setChecked(true);
|
||||||
hasAdvanced=true;
|
hasAdvanced = true;
|
||||||
}
|
}
|
||||||
if(hasAdvanced){
|
if (hasAdvanced) {
|
||||||
advancedBtn.setSelected(true);
|
advancedBtn.setSelected(true);
|
||||||
advancedBtn.setText(R.string.sk_advanced_options_hide);
|
advancedBtn.setText(R.string.sk_advanced_options_hide);
|
||||||
tagWrap.setVisibility(View.VISIBLE);
|
tagWrap.setVisibility(View.VISIBLE);
|
||||||
@@ -342,62 +307,58 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton btn=view.findViewById(R.id.button);
|
ImageButton btn = view.findViewById(R.id.button);
|
||||||
PopupMenu popup=new PopupMenu(ctx, btn);
|
PopupMenu popup = new PopupMenu(ctx, btn);
|
||||||
TimelineDefinition.Icon currentIcon=item!=null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
|
TimelineDefinition.Icon currentIcon = item != null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
|
||||||
btn.setImageResource(currentIcon.iconRes);
|
btn.setImageResource(currentIcon.iconRes);
|
||||||
btn.setTag(currentIcon.ordinal());
|
btn.setTag(currentIcon.ordinal());
|
||||||
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
|
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
|
||||||
btn.setOnTouchListener(popup.getDragToOpenListener());
|
btn.setOnTouchListener(popup.getDragToOpenListener());
|
||||||
btn.setOnClickListener(l->popup.show());
|
btn.setOnClickListener(l -> popup.show());
|
||||||
|
|
||||||
Menu menu=popup.getMenu();
|
Menu menu = popup.getMenu();
|
||||||
TimelineDefinition.Icon defaultIcon=item!=null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
|
TimelineDefinition.Icon defaultIcon = item != null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
|
||||||
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
|
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
|
||||||
if(!currentIcon.equals(defaultIcon)){
|
if (!currentIcon.equals(defaultIcon)) {
|
||||||
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
|
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
|
||||||
}
|
}
|
||||||
for(TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()){
|
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
|
||||||
if(icon.hidden || icon.ordinal()==(int) btn.getTag()) continue;
|
if (icon.hidden || icon.ordinal() == (int) btn.getTag()) continue;
|
||||||
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
|
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
|
||||||
}
|
}
|
||||||
UiUtils.enablePopupMenuIcons(ctx, popup);
|
UiUtils.enablePopupMenuIcons(ctx, popup);
|
||||||
|
|
||||||
popup.setOnMenuItemClickListener(menuItem->{
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[menuItem.getItemId()];
|
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
|
||||||
btn.setImageResource(icon.iconRes);
|
btn.setImageResource(icon.iconRes);
|
||||||
btn.setTag(menuItem.getItemId());
|
btn.setTag(menuItem.getItemId());
|
||||||
btn.setContentDescription(ctx.getString(icon.nameRes));
|
btn.setContentDescription(ctx.getString(icon.nameRes));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
AlertDialog.Builder builder=new M3AlertDialogBuilder(ctx)
|
AlertDialog.Builder builder = new M3AlertDialogBuilder(ctx)
|
||||||
.setTitle(item==null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
|
.setTitle(item == null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
|
||||||
.setView(view)
|
.setView(view)
|
||||||
.setPositiveButton(R.string.save, (d, which)->{
|
.setPositiveButton(R.string.save, (d, which) -> {
|
||||||
String name=editText.getText().toString().trim();
|
|
||||||
|
|
||||||
String mainHashtag=tagMain.getText().toString().trim();
|
|
||||||
if(item != null && item.getType()==TimelineDefinition.TimelineType.HASHTAG){
|
|
||||||
tagsAny.chipifyAllUnterminatedTokens();
|
tagsAny.chipifyAllUnterminatedTokens();
|
||||||
tagsAll.chipifyAllUnterminatedTokens();
|
tagsAll.chipifyAllUnterminatedTokens();
|
||||||
tagsNone.chipifyAllUnterminatedTokens();
|
tagsNone.chipifyAllUnterminatedTokens();
|
||||||
if(TextUtils.isEmpty(mainHashtag)){
|
String name = editText.getText().toString().trim();
|
||||||
mainHashtag=name;
|
String mainHashtag = tagMain.getText().toString().trim();
|
||||||
name=null;
|
if (TextUtils.isEmpty(mainHashtag)) {
|
||||||
|
mainHashtag = name;
|
||||||
|
name = null;
|
||||||
}
|
}
|
||||||
if(TextUtils.isEmpty(mainHashtag) && (item!=null && item.getType()==TimelineDefinition.TimelineType.HASHTAG)){
|
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) {
|
||||||
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
|
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
|
||||||
onSave.accept(null);
|
onSave.accept(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TimelineDefinition tl=item!=null ? item : TimelineDefinition.ofHashtag(name);
|
TimelineDefinition tl = item != null ? item : TimelineDefinition.ofHashtag(name);
|
||||||
TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[(int) btn.getTag()];
|
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[(int) btn.getTag()];
|
||||||
tl.setIcon(icon);
|
tl.setIcon(icon);
|
||||||
tl.setTitle(name);
|
tl.setTitle(name);
|
||||||
if(item == null || item.getType()==TimelineDefinition.TimelineType.HASHTAG){
|
|
||||||
tl.setTagOptions(
|
tl.setTagOptions(
|
||||||
mainHashtag,
|
mainHashtag,
|
||||||
tagsAny.getChipValues(),
|
tagsAny.getChipValues(),
|
||||||
@@ -405,12 +366,11 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
tagsNone.getChipValues(),
|
tagsNone.getChipValues(),
|
||||||
localOnlySwitch.isChecked()
|
localOnlySwitch.isChecked()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
onSave.accept(tl);
|
onSave.accept(tl);
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.cancel, (d, which)->{});
|
.setNegativeButton(R.string.cancel, (d, which) -> {});
|
||||||
|
|
||||||
if(onRemove!=null) builder.setNeutralButton(R.string.sk_remove, (d, which)->onRemove.run());
|
if (onRemove != null) builder.setNeutralButton(R.string.sk_remove, (d, which) -> onRemove.run());
|
||||||
|
|
||||||
builder.show();
|
builder.show();
|
||||||
btn.requestFocus();
|
btn.requestFocus();
|
||||||
@@ -424,12 +384,12 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position){
|
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) {
|
||||||
holder.bind(data.get(position));
|
holder.bind(data.get(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount(){
|
public int getItemCount() {
|
||||||
return data.size();
|
return data.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,12 +406,12 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public void onBind(TimelineDefinition item){
|
public void onBind(TimelineDefinition item) {
|
||||||
title.setText(item.getTitle(getContext()));
|
title.setText(item.getTitle(getContext()));
|
||||||
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
|
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
|
||||||
dragger.setVisibility(View.VISIBLE);
|
dragger.setVisibility(View.VISIBLE);
|
||||||
dragger.setOnTouchListener((View v, MotionEvent event)->{
|
dragger.setOnTouchListener((View v, MotionEvent event) -> {
|
||||||
if(event.getAction()==MotionEvent.ACTION_DOWN){
|
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
itemTouchHelper.startDrag(this);
|
itemTouchHelper.startDrag(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -459,34 +419,34 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSave(TimelineDefinition tl){
|
private void onSave(TimelineDefinition tl) {
|
||||||
saveTimelines();
|
saveTimelines();
|
||||||
rebind();
|
rebind();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onRemove(){
|
private void onRemove() {
|
||||||
removeTimeline(getAbsoluteAdapterPosition());
|
removeTimeline(getAbsoluteAdapterPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
@Override
|
@Override
|
||||||
public void onClick(){
|
public void onClick() {
|
||||||
makeTimelineEditor(item, this::onSave, this::onRemove);
|
makeTimelineEditor(item, this::onSave, this::onRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback{
|
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
|
||||||
public ItemTouchHelperCallback(){
|
public ItemTouchHelperCallback() {
|
||||||
super(ItemTouchHelper.UP|ItemTouchHelper.DOWN, ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT);
|
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target){
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||||
int fromPosition=viewHolder.getAbsoluteAdapterPosition();
|
int fromPosition = viewHolder.getAbsoluteAdapterPosition();
|
||||||
int toPosition=target.getAbsoluteAdapterPosition();
|
int toPosition = target.getAbsoluteAdapterPosition();
|
||||||
if(Math.max(fromPosition, toPosition)>=data.size() || Math.min(fromPosition, toPosition)<0){
|
if (Math.max(fromPosition, toPosition) >= data.size() || Math.min(fromPosition, toPosition) < 0) {
|
||||||
return false;
|
return false;
|
||||||
}else{
|
} else {
|
||||||
Collections.swap(data, fromPosition, toPosition);
|
Collections.swap(data, fromPosition, toPosition);
|
||||||
adapter.notifyItemMoved(fromPosition, toPosition);
|
adapter.notifyItemMoved(fromPosition, toPosition);
|
||||||
saveTimelines();
|
saveTimelines();
|
||||||
@@ -495,21 +455,21 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
|
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
|
||||||
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG && viewHolder!=null){
|
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
|
||||||
viewHolder.itemView.animate().alpha(0.65f);
|
viewHolder.itemView.animate().alpha(0.65f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
super.clearView(recyclerView, viewHolder);
|
super.clearView(recyclerView, viewHolder);
|
||||||
viewHolder.itemView.animate().alpha(1f);
|
viewHolder.itemView.animate().alpha(1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction){
|
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||||
int position=viewHolder.getAbsoluteAdapterPosition();
|
int position = viewHolder.getAbsoluteAdapterPosition();
|
||||||
removeTimeline(position);
|
removeTimeline(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class FavoritedStatusListFragment extends StatusListFragment{
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Status> result){
|
public void onSuccess(HeaderPaginationList<Status> result){
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
|
|||||||
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.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Relationship;
|
import org.joinmastodon.android.model.Relationship;
|
||||||
import org.joinmastodon.android.ui.OutlineProviders;
|
import org.joinmastodon.android.ui.OutlineProviders;
|
||||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
@@ -82,7 +83,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
public void onSuccess(HeaderPaginationList<Account> result){
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
@@ -270,7 +271,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||||
relationship=relationships.get(item.account.id);
|
relationship=relationships.get(item.account.id);
|
||||||
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account);
|
UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
|
||||||
|
|
||||||
if(relationship==null || !relationship.followedBy){
|
if(relationship==null || !relationship.followedBy){
|
||||||
actionWrap.setVisibility(View.GONE);
|
actionWrap.setVisibility(View.GONE);
|
||||||
@@ -365,9 +366,9 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
|
|||||||
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
|
||||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
|
||||||
if(account.emojis.isEmpty()){
|
if(account.emojis.isEmpty()){
|
||||||
parsedName= account.getDisplayName();
|
parsedName=account.displayName;
|
||||||
}else{
|
}else{
|
||||||
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis);
|
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
|
||||||
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
public void onSuccess(HeaderPaginationList<Hashtag> result){
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
if(result.nextPageUri!=null)
|
if(result.nextPageUri!=null)
|
||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
@@ -72,7 +72,6 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
|
|||||||
return new HashtagsAdapter();
|
return new HashtagsAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop() {
|
public void scrollToTop() {
|
||||||
smoothScrollRecyclerViewToTop(list);
|
smoothScrollRecyclerViewToTop(list);
|
||||||
|
|||||||
@@ -16,22 +16,12 @@ import android.widget.ProgressBar;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||||
import org.joinmastodon.android.api.requests.filters.CreateFilter;
|
|
||||||
import org.joinmastodon.android.api.requests.filters.DeleteFilter;
|
|
||||||
import org.joinmastodon.android.api.requests.filters.GetFilters;
|
|
||||||
import org.joinmastodon.android.api.requests.tags.GetTag;
|
import org.joinmastodon.android.api.requests.tags.GetTag;
|
||||||
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
|
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
|
||||||
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
|
||||||
import org.joinmastodon.android.model.Filter;
|
|
||||||
import org.joinmastodon.android.model.FilterAction;
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
import org.joinmastodon.android.model.FilterKeyword;
|
|
||||||
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.model.TimelineDefinition;
|
||||||
@@ -40,12 +30,10 @@ import org.joinmastodon.android.ui.utils.UiUtils;
|
|||||||
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
import org.joinmastodon.android.ui.views.ProgressBarButton;
|
||||||
import org.parceler.Parcels;
|
import org.parceler.Parcels;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
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;
|
||||||
@@ -60,7 +48,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
|||||||
private TextView headerTitle, headerSubtitle;
|
private TextView headerTitle, headerSubtitle;
|
||||||
private ProgressBarButton followButton;
|
private ProgressBarButton followButton;
|
||||||
private ProgressBar followProgress;
|
private ProgressBar followProgress;
|
||||||
private MenuItem followMenuItem, pinMenuItem, muteMenuItem;
|
private MenuItem followMenuItem, pinMenuItem;
|
||||||
private boolean followRequestRunning;
|
private boolean followRequestRunning;
|
||||||
private boolean toolbarContentVisible;
|
private boolean toolbarContentVisible;
|
||||||
|
|
||||||
@@ -72,8 +60,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
|||||||
private Menu optionsMenu;
|
private Menu optionsMenu;
|
||||||
private MenuInflater optionsMenuInflater;
|
private MenuInflater optionsMenuInflater;
|
||||||
|
|
||||||
private Optional<Filter> filter = Optional.empty();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean wantsComposeButton() {
|
protected boolean wantsComposeButton() {
|
||||||
return true;
|
return true;
|
||||||
@@ -97,71 +83,18 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
|||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMuteState(boolean newMute) {
|
|
||||||
muteMenuItem.setTitle(getString(newMute ? R.string.unmute_user : R.string.mute_user, "#" + hashtag));
|
|
||||||
muteMenuItem.setIcon(newMute ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showMuteDialog(boolean mute) {
|
|
||||||
UiUtils.showConfirmationAlert(getContext(),
|
|
||||||
mute ? R.string.mo_unmute_hashtag : R.string.mo_mute_hashtag,
|
|
||||||
mute ? R.string.mo_confirm_to_unmute_hashtag : R.string.mo_confirm_to_mute_hashtag,
|
|
||||||
mute ? R.string.do_unmute : R.string.do_mute,
|
|
||||||
mute ? R.drawable.ic_fluent_speaker_2_28_regular : R.drawable.ic_fluent_speaker_off_28_regular,
|
|
||||||
mute ? this::unmuteHashtag : this::muteHashtag
|
|
||||||
);
|
|
||||||
}
|
|
||||||
private void unmuteHashtag() {
|
|
||||||
//safe to get, this only called if filter is present
|
|
||||||
new DeleteFilter(filter.get().id).setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Void result){
|
|
||||||
filter=Optional.empty();
|
|
||||||
updateMuteState(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void muteHashtag() {
|
|
||||||
FilterKeyword hashtagFilter=new FilterKeyword();
|
|
||||||
hashtagFilter.wholeWord=true;
|
|
||||||
hashtagFilter.keyword="#"+hashtagName;
|
|
||||||
new CreateFilter("#"+hashtagName, EnumSet.of(FilterContext.HOME), FilterAction.HIDE, 0 , List.of(hashtagFilter)).setCallback(new Callback<>(){
|
|
||||||
@Override
|
|
||||||
public void onSuccess(Filter result){
|
|
||||||
filter=Optional.of(result);
|
|
||||||
updateMuteState(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
error.showToast(getContext());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TimelineDefinition makeTimelineDefinition() {
|
protected TimelineDefinition makeTimelineDefinition() {
|
||||||
return TimelineDefinition.ofHashtag(hashtagName);
|
return TimelineDefinition.ofHashtag(hashtag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
currentRequest=new GetHashtagTimeline(hashtagName, getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetHashtagTimeline(hashtagName, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
if(getActivity()==null) return;
|
onDataLoaded(result, !result.isEmpty());
|
||||||
boolean more=applyMaxID(result);
|
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
|
||||||
onDataLoaded(result, more);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -204,11 +137,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onFabLongClick(View v) {
|
|
||||||
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtagName+' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFabClick(View v){
|
public void onFabClick(View v){
|
||||||
Bundle args=new Bundle();
|
Bundle args=new Bundle();
|
||||||
@@ -289,24 +217,15 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
|||||||
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
|
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
|
||||||
pinMenuItem=optionsMenu.findItem(R.id.pin);
|
pinMenuItem=optionsMenu.findItem(R.id.pin);
|
||||||
followMenuItem.setVisible(toolbarContentVisible);
|
followMenuItem.setVisible(toolbarContentVisible);
|
||||||
// pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
|
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
|
||||||
|
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||||
|
pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
super.updatePinButton(pinMenuItem);
|
super.updatePinButton(pinMenuItem);
|
||||||
|
if(toolbarContentVisible){
|
||||||
muteMenuItem = optionsMenu.findItem(R.id.mute_hashtag);
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu);
|
||||||
updateMuteState(filter.isPresent());
|
}else{
|
||||||
new GetFilters().setCallback(new Callback<>() {
|
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.pin);
|
||||||
@Override
|
|
||||||
public void onSuccess(List<Filter> filters) {
|
|
||||||
if (getActivity() == null) return;
|
|
||||||
filter=filters.stream().filter(filter->filter.title.equals("#"+hashtagName)).findAny();
|
|
||||||
updateMuteState(filter.isPresent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(ErrorResponse error) {
|
|
||||||
error.showToast(getActivity());
|
|
||||||
}
|
|
||||||
}).exec(accountID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -329,9 +248,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
|||||||
if (super.onOptionsItemSelected(item)) return true;
|
if (super.onOptionsItemSelected(item)) return true;
|
||||||
if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) {
|
if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) {
|
||||||
setFollowed(!hashtag.following);
|
setFollowed(!hashtag.following);
|
||||||
} else if (item.getItemId() == R.id.mute_hashtag) {
|
|
||||||
showMuteDialog(filter.isPresent());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -344,7 +260,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateHeader(){
|
private void updateHeader(){
|
||||||
if(hashtag==null || getActivity()==null)
|
if(hashtag==null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(hashtag.history!=null && !hashtag.history.isEmpty()){
|
if(hashtag.history!=null && !hashtag.history.isEmpty()){
|
||||||
@@ -387,10 +303,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
|
|||||||
if(followMenuItem!=null){
|
if(followMenuItem!=null){
|
||||||
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
|
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
|
||||||
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
|
||||||
}
|
UiUtils.insetPopupMenuIcon(getContext(), followMenuItem);
|
||||||
if(muteMenuItem!=null){
|
|
||||||
muteMenuItem.setTitle(getString(filter.isPresent() ? R.string.unmute_user : R.string.mute_user, "#" + hashtag));
|
|
||||||
muteMenuItem.setIcon(filter.isPresent() ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import android.app.Fragment;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.graphics.drawable.RippleDrawable;
|
import android.graphics.drawable.RippleDrawable;
|
||||||
import android.content.Intent;
|
|
||||||
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.service.notification.StatusBarNotification;
|
||||||
@@ -33,8 +31,8 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
|||||||
import org.joinmastodon.android.events.NotificationsMarkerUpdatedEvent;
|
import org.joinmastodon.android.events.NotificationsMarkerUpdatedEvent;
|
||||||
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
|
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
|
||||||
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
|
||||||
import org.joinmastodon.android.fragments.onboarding.OnboardingFollowSuggestionsFragment;
|
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
|
import org.joinmastodon.android.model.Instance;
|
||||||
import org.joinmastodon.android.model.Notification;
|
import org.joinmastodon.android.model.Notification;
|
||||||
import org.joinmastodon.android.model.PaginatedResponse;
|
import org.joinmastodon.android.model.PaginatedResponse;
|
||||||
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
import org.joinmastodon.android.ui.AccountSwitcherSheet;
|
||||||
@@ -49,7 +47,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import me.grishka.appkit.FragmentStackActivity;
|
import me.grishka.appkit.FragmentStackActivity;
|
||||||
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.AppKitFragment;
|
import me.grishka.appkit.fragments.AppKitFragment;
|
||||||
@@ -74,12 +71,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
private TextView notificationsBadge;
|
private TextView notificationsBadge;
|
||||||
|
|
||||||
private String accountID;
|
private String accountID;
|
||||||
|
private boolean isAkkoma;
|
||||||
|
|
||||||
@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.mo_app_name);
|
setTitle(R.string.sk_app_name);
|
||||||
|
isAkkoma = getInstance().map(Instance::isAkkoma).orElse(false);
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
@@ -90,6 +89,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
homeTabFragment=new HomeTabFragment();
|
homeTabFragment=new HomeTabFragment();
|
||||||
homeTabFragment.setArguments(args);
|
homeTabFragment.setArguments(args);
|
||||||
args=new Bundle(args);
|
args=new Bundle(args);
|
||||||
|
args.putBoolean("disableDiscover", isAkkoma);
|
||||||
args.putBoolean("noAutoLoad", true);
|
args.putBoolean("noAutoLoad", true);
|
||||||
discoverFragment=new DiscoverFragment();
|
discoverFragment=new DiscoverFragment();
|
||||||
discoverFragment.setArguments(args);
|
discoverFragment.setArguments(args);
|
||||||
@@ -264,10 +264,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 && newFragment instanceof ScrollableToTop scrollable) {
|
||||||
if (tab == R.id.tab_search && GlobalUserPreferences.doubleTapToSearch)
|
|
||||||
discoverFragment.openSearch();
|
|
||||||
else if(newFragment instanceof ScrollableToTop scrollable)
|
|
||||||
scrollable.scrollToTop();
|
scrollable.scrollToTop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -299,24 +296,16 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
|
|||||||
if(tab==R.id.tab_profile){
|
if(tab==R.id.tab_profile){
|
||||||
ArrayList<String> options=new ArrayList<>();
|
ArrayList<String> options=new ArrayList<>();
|
||||||
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
|
||||||
options.add(session.self.getDisplayName()+"\n("+session.self.username+"@"+session.domain+")");
|
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
|
||||||
}
|
}
|
||||||
new AccountSwitcherSheet(getActivity(), this).show();
|
new AccountSwitcherSheet(getActivity(), this).show();
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if(tab==R.id.tab_search){
|
||||||
if(tab==R.id.tab_search){
|
|
||||||
if(currentTab!=R.id.tab_search){
|
|
||||||
onTabSelected(R.id.tab_search);
|
|
||||||
tabBar.selectTab(R.id.tab_search);
|
tabBar.selectTab(R.id.tab_search);
|
||||||
}
|
onTabSelected(R.id.tab_search);
|
||||||
discoverFragment.openSearch();
|
discoverFragment.openSearch();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if(tab==R.id.tab_home){
|
|
||||||
Bundle args=new Bundle();
|
|
||||||
args.putString("account", accountID);
|
|
||||||
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,10 +122,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
fragments=new Fragment[count];
|
fragments=new Fragment[count];
|
||||||
tabViews=new FrameLayout[count];
|
tabViews=new FrameLayout[count];
|
||||||
timelines=new TimelineDefinition[count];
|
timelines=new TimelineDefinition[count];
|
||||||
if(GlobalUserPreferences.toolbarMarquee){
|
|
||||||
setTitleMarqueeEnabled(false);
|
|
||||||
setSubtitleMarqueeEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -237,25 +233,21 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
|
|
||||||
ViewTreeObserver vto = getToolbar().getViewTreeObserver();
|
ViewTreeObserver vto = getToolbar().getViewTreeObserver();
|
||||||
if (vto.isAlive()) {
|
if (vto.isAlive()) {
|
||||||
vto.addOnGlobalLayoutListener(()->{
|
vto.addOnGlobalLayoutListener(() -> {
|
||||||
Toolbar t=getToolbar();
|
Toolbar t = getToolbar();
|
||||||
if(t==null) return;
|
if (t == null) return;
|
||||||
int toolbarWidth=t.getWidth();
|
int toolbarWidth = t.getWidth();
|
||||||
if(toolbarWidth==0) return;
|
if (toolbarWidth == 0) return;
|
||||||
|
|
||||||
int toolbarFrameWidth=toolbarFrame.getWidth();
|
int toolbarFrameWidth = toolbarFrame.getWidth();
|
||||||
int actionsWidth=toolbarWidth-toolbarFrameWidth;
|
int padding = toolbarWidth - toolbarFrameWidth;
|
||||||
// margin (4) + padding (12) + icon (24) + margin (8) + chevron (16) + padding (12)
|
FrameLayout parent = ((FrameLayout) toolbarShowNewPostsBtn.getParent());
|
||||||
int switcherWidth=V.dp(76);
|
if (padding == parent.getPaddingStart()) return;
|
||||||
FrameLayout parent=((FrameLayout) toolbarShowNewPostsBtn.getParent());
|
|
||||||
if(actionsWidth==parent.getPaddingStart()) return;
|
|
||||||
int paddingMax=Math.max(actionsWidth, switcherWidth);
|
|
||||||
int paddingEnd=(Math.max(0, switcherWidth-actionsWidth));
|
|
||||||
|
|
||||||
// toolbar frame goes from screen edge to beginning of right-aligned option buttons.
|
// toolbar frame goes from screen edge to beginning of right-aligned option buttons.
|
||||||
// centering button by applying the same space on the left
|
// centering button by applying the same space on the left
|
||||||
parent.setPaddingRelative(paddingMax, 0, paddingEnd, 0);
|
parent.setPaddingRelative(padding, 0, 0, 0);
|
||||||
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth-paddingMax*2);
|
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth - padding * 2);
|
||||||
|
|
||||||
switcher.setPivotX(V.dp(28)); // padding + half of icon
|
switcher.setPivotX(V.dp(28)); // padding + half of icon
|
||||||
switcher.setPivotY(switcher.getHeight() / 2f);
|
switcher.setPivotY(switcher.getHeight() / 2f);
|
||||||
@@ -295,7 +287,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
new GetAnnouncements(false).setCallback(new Callback<>() {
|
new GetAnnouncements(false).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Announcement> result) {
|
public void onSuccess(List<Announcement> result) {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
if (result.stream().anyMatch(a -> !a.read)) {
|
if (result.stream().anyMatch(a -> !a.read)) {
|
||||||
announcementsBadged = true;
|
announcementsBadged = true;
|
||||||
announcements.setVisible(false);
|
announcements.setVisible(false);
|
||||||
@@ -389,7 +381,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateOverflowMenu() {
|
private void updateOverflowMenu() {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
Menu m = overflowPopup.getMenu();
|
Menu m = overflowPopup.getMenu();
|
||||||
m.clear();
|
m.clear();
|
||||||
overflowPopup.inflate(R.menu.home_overflow);
|
overflowPopup.inflate(R.menu.home_overflow);
|
||||||
@@ -408,9 +400,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
addListsToOverflowMenu();
|
addListsToOverflowMenu();
|
||||||
addHashtagsToOverflowMenu();
|
addHashtagsToOverflowMenu();
|
||||||
|
|
||||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI())
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
|
||||||
m.setGroupDividerEnabled(true);
|
m.setGroupDividerEnabled(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
|
||||||
@@ -538,12 +531,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop(){
|
||||||
if (((IsOnTop) fragments[pager.getCurrentItem()]).isOnTop() &&
|
|
||||||
GlobalUserPreferences.doubleTapToSwipe && !newPostsBtnShown) {
|
|
||||||
int nextPage = (pager.getCurrentItem() + 1) % count;
|
|
||||||
navigateTo(nextPage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
|
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,8 +604,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
|
|||||||
|
|
||||||
private void onNewPostsBtnClick(View view) {
|
private void onNewPostsBtnClick(View view) {
|
||||||
if(newPostsBtnShown){
|
if(newPostsBtnShown){
|
||||||
scrollToTop();
|
|
||||||
hideNewPostsButton();
|
hideNewPostsButton();
|
||||||
|
scrollToTop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import org.joinmastodon.android.GlobalUserPreferences;
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
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.AccountLocalPreferences;
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
@@ -19,7 +20,6 @@ import org.joinmastodon.android.model.TimelineMarkers;
|
|||||||
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 java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -49,6 +49,16 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean typeFilterPredicate(Status s) {
|
||||||
|
AccountLocalPreferences lp=getLocalPrefs();
|
||||||
|
return (lp.showReplies || s.inReplyToId == null) &&
|
||||||
|
(lp.showBoosts || s.reblog == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Status> filterPosts(List<Status> items) {
|
||||||
|
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count){
|
protected void doLoadData(int offset, int count){
|
||||||
AccountSessionManager.getInstance()
|
AccountSessionManager.getInstance()
|
||||||
@@ -56,12 +66,11 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
boolean empty=result.items.isEmpty();
|
List<Status> filteredItems = filterPosts(result.items);
|
||||||
maxID=result.maxID;
|
maxID=result.maxID;
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result.items, getFilterContext());
|
onDataLoaded(filteredItems, !result.items.isEmpty());
|
||||||
onDataLoaded(result.items, !empty);
|
if(result.isFromCache())
|
||||||
if(result.isFromCache() && GlobalUserPreferences.loadNewPosts)
|
|
||||||
loadNewPosts();
|
loadNewPosts();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -74,7 +83,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
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(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
|
||||||
parent.hideNewPostsButton();
|
parent.hideNewPostsButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +96,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
if(!getArguments().getBoolean("noAutoLoad")){
|
if(!getArguments().getBoolean("noAutoLoad")){
|
||||||
if(!loaded && !dataLoading){
|
if(!loaded && !dataLoading){
|
||||||
loadData();
|
loadData();
|
||||||
}else if(!dataLoading && GlobalUserPreferences.loadNewPosts){
|
}else if(!dataLoading){
|
||||||
loadNewPosts();
|
loadNewPosts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,46 +126,38 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onStatusCreated(Status status){
|
public void onStatusCreated(Status status){
|
||||||
if(status.reblog!=null) return;
|
|
||||||
prependItems(Collections.singletonList(status), true);
|
prependItems(Collections.singletonList(status), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadNewPosts(){
|
private void loadNewPosts(){
|
||||||
|
if (!GlobalUserPreferences.loadNewPosts) return;
|
||||||
dataLoading=true;
|
dataLoading=true;
|
||||||
// we only care about the data that was actually retrieved from the timeline api since
|
|
||||||
// user-created statuses are probably in the wrong position
|
|
||||||
List<Status> dataFromTimeline=data.stream().filter(s->!s.fromStatusCreated).collect(Collectors.toList());
|
|
||||||
// The idea here is that we request the timeline such that if there are fewer than `limit` posts,
|
// The idea here is that we request the timeline such that if there are fewer than `limit` posts,
|
||||||
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
// we'll get the currently topmost post as last in the response. This way we know there's no gap
|
||||||
// between the existing and newly loaded parts of the timeline.
|
// between the existing and newly loaded parts of the timeline.
|
||||||
String sinceID=dataFromTimeline.size()>1 ? dataFromTimeline.get(1).id : "1";
|
String sinceID=data.size()>1 ? data.get(1).id : "1";
|
||||||
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new Callback<>(){
|
.setCallback(new Callback<>(){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result){
|
public void onSuccess(List<Status> result){
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
refreshDone();
|
result = filterPosts(result);
|
||||||
if(result.isEmpty() || getActivity()==null)
|
if(result.isEmpty() || getActivity()==null)
|
||||||
return;
|
return;
|
||||||
Status last=result.get(result.size()-1);
|
Status last=result.get(result.size()-1);
|
||||||
List<Status> toAdd;
|
List<Status> toAdd;
|
||||||
if(!dataFromTimeline.isEmpty() && last.id.equals(dataFromTimeline.get(0).id)){ // This part intersects with the existing one
|
if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one
|
||||||
toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post
|
toAdd=result.subList(0, result.size()-1); // Remove the already known last post
|
||||||
}else{
|
}else{
|
||||||
last.hasGapAfter=last.id;
|
result.get(result.size()-1).hasGapAfter=true;
|
||||||
toAdd=result;
|
toAdd=result;
|
||||||
}
|
}
|
||||||
if(!toAdd.isEmpty())
|
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(new ArrayList<>(toAdd), false);
|
|
||||||
// removing statuses that come up as duplicates (hopefully only posts and boosts that were locally created
|
|
||||||
// and thus were already prepended to the timeline earlier)
|
|
||||||
List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList());
|
|
||||||
toAdd.removeIf(s->existingIds.contains(s.getID()));
|
|
||||||
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
|
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
|
||||||
if(!toAdd.isEmpty()){
|
if(!toAdd.isEmpty()){
|
||||||
prependItems(toAdd, true);
|
prependItems(toAdd, true);
|
||||||
if(parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
|
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
|
||||||
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +165,6 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
public void onError(ErrorResponse error){
|
public void onError(ErrorResponse error){
|
||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
refreshDone();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
@@ -182,10 +182,10 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
gap.loading=true;
|
gap.loading=true;
|
||||||
dataLoading=true;
|
dataLoading=true;
|
||||||
|
|
||||||
String maxID=null;
|
String maxID = null;
|
||||||
String minID=null;
|
String minID = null;
|
||||||
if (downwards) {
|
if (downwards) {
|
||||||
maxID=item.getItem().getMaxID();
|
maxID = item.getItemID();
|
||||||
} else {
|
} else {
|
||||||
int gapPos=displayItems.indexOf(gap);
|
int gapPos=displayItems.indexOf(gap);
|
||||||
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
|
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
|
||||||
@@ -202,17 +202,15 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
int gapPos=displayItems.indexOf(gap);
|
int gapPos=displayItems.indexOf(gap);
|
||||||
if(gapPos==-1)
|
if(gapPos==-1)
|
||||||
return;
|
return;
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
|
||||||
if(result.isEmpty()){
|
if(result.isEmpty()){
|
||||||
displayItems.remove(gapPos);
|
displayItems.remove(gapPos);
|
||||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||||
Status gapStatus=getStatusByID(gap.parentID);
|
Status gapStatus=getStatusByID(gap.parentID);
|
||||||
if(gapStatus!=null){
|
if(gapStatus!=null){
|
||||||
gapStatus.hasGapAfter=null;
|
gapStatus.hasGapAfter=false;
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
// TODO: refactor this code. it's too long. incomprehensible, even
|
|
||||||
if(downwards) {
|
if(downwards) {
|
||||||
Set<String> idsBelowGap=new HashSet<>();
|
Set<String> idsBelowGap=new HashSet<>();
|
||||||
boolean belowGap=false;
|
boolean belowGap=false;
|
||||||
@@ -222,7 +220,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
idsBelowGap.add(s.id);
|
idsBelowGap.add(s.id);
|
||||||
}else if(s.id.equals(gap.parentID)){
|
}else if(s.id.equals(gap.parentID)){
|
||||||
belowGap=true;
|
belowGap=true;
|
||||||
s.hasGapAfter=null;
|
s.hasGapAfter=false;
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||||
}else{
|
}else{
|
||||||
gapPostIndex++;
|
gapPostIndex++;
|
||||||
@@ -235,8 +233,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(endIndex==result.size()){
|
if(endIndex==result.size()){
|
||||||
Status last=result.get(result.size()-1);
|
result.get(result.size()-1).hasGapAfter=true;
|
||||||
last.hasGapAfter=last.id;
|
|
||||||
}else{
|
}else{
|
||||||
result=result.subList(0, endIndex);
|
result=result.subList(0, endIndex);
|
||||||
}
|
}
|
||||||
@@ -281,7 +278,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
.filter(s->Objects.equals(s.id, gap.parentID))
|
.filter(s->Objects.equals(s.id, gap.parentID))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
if (gapStatus.isPresent()) {
|
if (gapStatus.isPresent()) {
|
||||||
gapStatus.get().hasGapAfter=null;
|
gapStatus.get().hasGapAfter=false;
|
||||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
|
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
|
||||||
}
|
}
|
||||||
targetList.clear();
|
targetList.clear();
|
||||||
@@ -332,7 +329,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||||||
currentRequest=null;
|
currentRequest=null;
|
||||||
dataLoading=false;
|
dataLoading=false;
|
||||||
}
|
}
|
||||||
if(parent!=null) parent.hideNewPostsButton();
|
if (parent != null) parent.hideNewPostsButton();
|
||||||
super.onRefresh();
|
super.onRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import org.joinmastodon.android.R;
|
|||||||
import org.joinmastodon.android.api.requests.lists.GetList;
|
import org.joinmastodon.android.api.requests.lists.GetList;
|
||||||
import org.joinmastodon.android.api.requests.lists.UpdateList;
|
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.api.session.AccountSessionManager;
|
|
||||||
import org.joinmastodon.android.events.ListDeletedEvent;
|
import org.joinmastodon.android.events.ListDeletedEvent;
|
||||||
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
|
||||||
import org.joinmastodon.android.model.FilterContext;
|
import org.joinmastodon.android.model.FilterContext;
|
||||||
@@ -26,8 +25,10 @@ import org.joinmastodon.android.model.TimelineDefinition;
|
|||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.views.ListEditor;
|
import org.joinmastodon.android.ui.views.ListEditor;
|
||||||
|
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import me.grishka.appkit.Nav;
|
import me.grishka.appkit.Nav;
|
||||||
import me.grishka.appkit.api.Callback;
|
import me.grishka.appkit.api.Callback;
|
||||||
@@ -62,7 +63,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
new GetList(listID).setCallback(new Callback<>() {
|
new GetList(listID).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(ListTimeline listTimeline) {
|
public void onSuccess(ListTimeline listTimeline) {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
// TODO: save updated info
|
// TODO: save updated info
|
||||||
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
|
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
|
||||||
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
|
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
|
||||||
@@ -100,7 +101,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(ListTimeline list) {
|
public void onSuccess(ListTimeline list) {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
setTitle(list.title);
|
setTitle(list.title);
|
||||||
listTitle = list.title;
|
listTitle = list.title;
|
||||||
repliesPolicy = list.repliesPolicy;
|
repliesPolicy = list.repliesPolicy;
|
||||||
@@ -133,14 +134,13 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoadData(int offset, int count) {
|
protected void doLoadData(int offset, int count) {
|
||||||
currentRequest=new GetListTimeline(listID, getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
|
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
|
||||||
.setCallback(new SimpleCallback<>(this) {
|
.setCallback(new SimpleCallback<>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<Status> result) {
|
public void onSuccess(List<Status> result) {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
boolean more=applyMaxID(result);
|
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||||
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
|
onDataLoaded(result, !result.isEmpty());
|
||||||
onDataLoaded(result, more);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.exec(accountID);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
|
|||||||
.setCallback(new SimpleCallback<>(this) {
|
.setCallback(new SimpleCallback<>(this) {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<ListTimeline> lists) {
|
public void onSuccess(List<ListTimeline> lists) {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
|
||||||
userInList.putAll(userInListBefore);
|
userInList.putAll(userInListBefore);
|
||||||
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
|
||||||
@@ -149,7 +149,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
|
|||||||
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
|
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<ListTimeline> allLists) {
|
public void onSuccess(List<ListTimeline> allLists) {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
List<ListTimeline> newLists = new ArrayList<>();
|
List<ListTimeline> newLists = new ArrayList<>();
|
||||||
for (ListTimeline l : allLists) {
|
for (ListTimeline l : allLists) {
|
||||||
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package org.joinmastodon.android.fragments;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
@@ -46,7 +44,6 @@ public abstract class MastodonRecyclerFragment<T> extends BaseRecyclerFragment<T
|
|||||||
list.addOnScrollListener(elevationOnScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, getViewsForElevationEffect()));
|
list.addOnScrollListener(elevationOnScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, getViewsForElevationEffect()));
|
||||||
if(refreshLayout!=null)
|
if(refreshLayout!=null)
|
||||||
setRefreshLayoutColors(refreshLayout);
|
setRefreshLayoutColors(refreshLayout);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.assist.AssistContent;
|
import android.app.assist.AssistContent;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -16,6 +15,10 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
|
||||||
import org.joinmastodon.android.E;
|
import org.joinmastodon.android.E;
|
||||||
@@ -28,8 +31,6 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
|
|||||||
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
import org.joinmastodon.android.events.FollowRequestHandledEvent;
|
||||||
import org.joinmastodon.android.model.Account;
|
import org.joinmastodon.android.model.Account;
|
||||||
import org.joinmastodon.android.model.HeaderPaginationList;
|
import org.joinmastodon.android.model.HeaderPaginationList;
|
||||||
import org.joinmastodon.android.model.PushSubscription;
|
|
||||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
|
||||||
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;
|
||||||
@@ -38,11 +39,6 @@ import org.joinmastodon.android.utils.ElevationOnScrollListener;
|
|||||||
import org.joinmastodon.android.utils.ObjectIdComparator;
|
import org.joinmastodon.android.utils.ObjectIdComparator;
|
||||||
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
import org.joinmastodon.android.utils.ProvidesAssistContent;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
|
||||||
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;
|
||||||
@@ -50,7 +46,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
|
|||||||
import me.grishka.appkit.utils.V;
|
import me.grishka.appkit.utils.V;
|
||||||
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
import me.grishka.appkit.views.FragmentRootLinearLayout;
|
||||||
|
|
||||||
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent, HasElevationOnScrollListener, HasAccountID {
|
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent, HasElevationOnScrollListener {
|
||||||
|
|
||||||
TabLayout tabLayout;
|
TabLayout tabLayout;
|
||||||
private ViewPager2 pager;
|
private ViewPager2 pager;
|
||||||
@@ -58,7 +54,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
private View tabsDivider;
|
private View tabsDivider;
|
||||||
private TabLayoutMediator tabLayoutMediator;
|
private TabLayoutMediator tabLayoutMediator;
|
||||||
String unreadMarker, realUnreadMarker;
|
String unreadMarker, realUnreadMarker;
|
||||||
private MenuItem markAllReadItem, filterItem;
|
private MenuItem markAllReadItem;
|
||||||
private NotificationsListFragment allNotificationsFragment, mentionsFragment;
|
private NotificationsListFragment allNotificationsFragment, mentionsFragment;
|
||||||
private ElevationOnScrollListener elevationOnScrollListener;
|
private ElevationOnScrollListener elevationOnScrollListener;
|
||||||
|
|
||||||
@@ -96,10 +92,9 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
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);
|
menu.findItem(R.id.clear_notifications).setVisible(GlobalUserPreferences.enableDeleteNotifications);
|
||||||
filterItem=menu.findItem(R.id.filter_notifications).setVisible(true);
|
|
||||||
markAllReadItem=menu.findItem(R.id.mark_all_read);
|
markAllReadItem=menu.findItem(R.id.mark_all_read);
|
||||||
updateMarkAllReadButton();
|
updateMarkAllReadButton();
|
||||||
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests, R.id.mark_all_read, R.id.filter_notifications);
|
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.follow_requests, R.id.mark_all_read);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,61 +116,11 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
if (getCurrentFragment() instanceof NotificationsListFragment nlf) {
|
if (getCurrentFragment() instanceof NotificationsListFragment nlf) {
|
||||||
nlf.resetUnreadBackground();
|
nlf.resetUnreadBackground();
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.filter_notifications) {
|
|
||||||
Context ctx = getToolbarContext();
|
|
||||||
String[] listItems = {
|
|
||||||
ctx.getString(R.string.notification_type_mentions_and_replies),
|
|
||||||
ctx.getString(R.string.notification_type_reblog),
|
|
||||||
ctx.getString(R.string.notification_type_favorite),
|
|
||||||
ctx.getString(R.string.notification_type_follow),
|
|
||||||
ctx.getString(R.string.notification_type_poll),
|
|
||||||
ctx.getString(R.string.sk_notification_type_update),
|
|
||||||
ctx.getString(R.string.sk_notification_type_posts)
|
|
||||||
};
|
|
||||||
|
|
||||||
boolean[] checkedItems = {
|
|
||||||
getLocalPrefs().notificationFilters.mention,
|
|
||||||
getLocalPrefs().notificationFilters.reblog,
|
|
||||||
getLocalPrefs().notificationFilters.favourite,
|
|
||||||
getLocalPrefs().notificationFilters.follow,
|
|
||||||
getLocalPrefs().notificationFilters.poll,
|
|
||||||
getLocalPrefs().notificationFilters.update,
|
|
||||||
getLocalPrefs().notificationFilters.status,
|
|
||||||
};
|
|
||||||
|
|
||||||
M3AlertDialogBuilder dialogBuilder = new M3AlertDialogBuilder(ctx);
|
|
||||||
dialogBuilder.setTitle(R.string.sk_settings_filters);
|
|
||||||
dialogBuilder.setMultiChoiceItems(listItems, checkedItems, (dialog, which, isChecked) ->checkedItems[which] = isChecked);
|
|
||||||
|
|
||||||
dialogBuilder.setPositiveButton(R.string.save, (d, which) -> {
|
|
||||||
saveFilters(checkedItems);
|
|
||||||
this.allNotificationsFragment.reload();
|
|
||||||
}).setNeutralButton(R.string.mo_notification_filter_reset, (d, which) -> {
|
|
||||||
Arrays.fill(checkedItems, true);
|
|
||||||
saveFilters(checkedItems);
|
|
||||||
this.allNotificationsFragment.reload();
|
|
||||||
}).setNegativeButton(R.string.cancel, (d, which) -> {});
|
|
||||||
|
|
||||||
dialogBuilder.create().show();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveFilters(boolean[] checkedItems) {
|
|
||||||
PushSubscription.Alerts filter = getLocalPrefs().notificationFilters;
|
|
||||||
filter.mention = checkedItems[0];
|
|
||||||
filter.reblog = checkedItems[1];
|
|
||||||
filter.favourite = checkedItems[2];
|
|
||||||
filter.follow = checkedItems[3];
|
|
||||||
filter.poll = checkedItems[4];
|
|
||||||
filter.update = checkedItems[5];
|
|
||||||
filter.status = checkedItems[6];
|
|
||||||
getLocalPrefs().save();
|
|
||||||
}
|
|
||||||
|
|
||||||
void markAsRead(){
|
void markAsRead(){
|
||||||
if(allNotificationsFragment.getData().isEmpty()) return;
|
if(allNotificationsFragment.getData().isEmpty()) return;
|
||||||
String id=allNotificationsFragment.getData().get(0).id;
|
String id=allNotificationsFragment.getData().get(0).id;
|
||||||
@@ -239,7 +184,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
public void onPageSelected(int position){
|
public void onPageSelected(int position){
|
||||||
if (elevationOnScrollListener != null && getCurrentFragment() instanceof IsOnTop f)
|
if (elevationOnScrollListener != null && getCurrentFragment() instanceof IsOnTop f)
|
||||||
elevationOnScrollListener.handleScroll(getContext(), f.isOnTop());
|
elevationOnScrollListener.handleScroll(getContext(), f.isOnTop());
|
||||||
filterItem.setVisible(position==0);
|
|
||||||
if(position==0)
|
if(position==0)
|
||||||
return;
|
return;
|
||||||
Fragment _page=getFragmentForPage(position);
|
Fragment _page=getFragmentForPage(position);
|
||||||
@@ -310,7 +254,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Account> accounts) {
|
public void onSuccess(HeaderPaginationList<Account> accounts) {
|
||||||
if(getActivity()==null) return;
|
if (getActivity() == null) return;
|
||||||
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
|
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,15 +270,9 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void scrollToTop(){
|
public void scrollToTop(){
|
||||||
if (getFragmentForPage(pager.getCurrentItem()).isOnTop() && GlobalUserPreferences.doubleTapToSwipe) {
|
|
||||||
int nextPage = (pager.getCurrentItem() + 1) % tabViews.length;
|
|
||||||
pager.setCurrentItem(nextPage, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
|
getFragmentForPage(pager.getCurrentItem()).scrollToTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void loadData(){
|
public void loadData(){
|
||||||
refreshFollowRequestsBadge();
|
refreshFollowRequestsBadge();
|
||||||
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
|
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
|
||||||
@@ -365,11 +303,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
|
|||||||
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
|
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAccountID(){
|
|
||||||
return accountID;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import org.parceler.Parcels;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -88,47 +87,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
protected List<StatusDisplayItem> buildDisplayItems(Notification n){
|
||||||
if(!onlyMentions && !onlyPosts){
|
|
||||||
switch(n.type){
|
|
||||||
case MENTION -> {
|
|
||||||
if(!getLocalPrefs().notificationFilters.mention)
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
case REBLOG -> {
|
|
||||||
if(!getLocalPrefs().notificationFilters.reblog)
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
case FAVORITE, REACTION -> {
|
|
||||||
if(!getLocalPrefs().notificationFilters.favourite)
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
case FOLLOW, FOLLOW_REQUEST -> {
|
|
||||||
if(!getLocalPrefs().notificationFilters.follow)
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
case POLL -> {
|
|
||||||
if(!getLocalPrefs().notificationFilters.poll)
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
case UPDATE -> {
|
|
||||||
if(!getLocalPrefs().notificationFilters.update)
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
case STATUS -> {
|
|
||||||
if(!getLocalPrefs().notificationFilters.status)
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
default -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationHeaderStatusDisplayItem titleItem;
|
NotificationHeaderStatusDisplayItem titleItem;
|
||||||
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
|
if(n.type==Notification.Type.MENTION || n.type==Notification.Type.STATUS){
|
||||||
titleItem=null;
|
titleItem=null;
|
||||||
}else{
|
}else{
|
||||||
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
titleItem=new NotificationHeaderStatusDisplayItem(n.id, this, n, accountID);
|
||||||
}
|
}
|
||||||
if (n.type == Notification.Type.FOLLOW_REQUEST || n.type == Notification.Type.FOLLOW) {
|
if (n.type == Notification.Type.FOLLOW_REQUEST) {
|
||||||
ArrayList<StatusDisplayItem> items = new ArrayList<>();
|
ArrayList<StatusDisplayItem> items = new ArrayList<>();
|
||||||
items.add(titleItem);
|
items.add(titleItem);
|
||||||
items.add(new AccountCardStatusDisplayItem(n.id, this, accountID, n.account, n));
|
items.add(new AccountCardStatusDisplayItem(n.id, this, accountID, n.account, n));
|
||||||
@@ -138,8 +103,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS); // | StatusDisplayItem.FLAG_NO_HEADER);
|
int flags=titleItem==null ? 0 : (StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS); // | StatusDisplayItem.FLAG_NO_HEADER);
|
||||||
if (GlobalUserPreferences.spectatorMode)
|
if (GlobalUserPreferences.spectatorMode)
|
||||||
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
|
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
|
||||||
if (!GlobalUserPreferences.showMediaPreview)
|
|
||||||
flags |= StatusDisplayItem.FLAG_NO_MEDIA_PREVIEW;
|
|
||||||
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, null, flags);
|
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, null, flags);
|
||||||
if(titleItem!=null)
|
if(titleItem!=null)
|
||||||
items.add(0, titleItem);
|
items.add(0, titleItem);
|
||||||
@@ -169,16 +132,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
public void onSuccess(PaginatedResponse<List<Notification>> result){
|
||||||
if(getActivity()==null)
|
if(getActivity()==null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Set<String> needRelationships=result.items.stream()
|
|
||||||
.filter(ntf->ntf.status==null && !relationships.containsKey(ntf.account.id))
|
|
||||||
.map(ntf->ntf.account.id)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
loadRelationships(needRelationships);
|
|
||||||
|
|
||||||
maxID=result.maxID;
|
maxID=result.maxID;
|
||||||
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
|
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
|
||||||
if(bannerHelper!=null) bannerHelper.onBannerBecameVisible();
|
|
||||||
reloadingFromCache=false;
|
reloadingFromCache=false;
|
||||||
if (getParentFragment() instanceof NotificationsFragment nf) {
|
if (getParentFragment() instanceof NotificationsFragment nf) {
|
||||||
nf.updateMarkAllReadButton();
|
nf.updateMarkAllReadButton();
|
||||||
@@ -187,17 +142,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRelationshipsLoaded(){
|
|
||||||
if(getActivity()==null)
|
|
||||||
return;
|
|
||||||
for(int i=0;i<list.getChildCount();i++){
|
|
||||||
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
|
|
||||||
if(holder instanceof AccountCardStatusDisplayItem.Holder accountHolder)
|
|
||||||
accountHolder.rebind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onShown(){
|
protected void onShown(){
|
||||||
super.onShown();
|
super.onShown();
|
||||||
@@ -229,6 +173,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
@Override
|
@Override
|
||||||
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 RecyclerView.ItemDecoration(){
|
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
private Paint paint=new Paint();
|
private Paint paint=new Paint();
|
||||||
private Rect tmpRect=new Rect();
|
private Rect tmpRect=new Rect();
|
||||||
@@ -292,7 +237,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||||||
continue;
|
continue;
|
||||||
Status contentStatus=ntf.status.getContentStatus();
|
Status contentStatus=ntf.status.getContentStatus();
|
||||||
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
|
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
|
||||||
updatePoll(ntf.id, contentStatus, ev.poll);
|
updatePoll(ntf.id, ntf.status, ev.poll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||