Compare commits

..

18 Commits

Author SHA1 Message Date
LucasGGamerM
cd749e9c9c feat: rework SettingsCategoryItem, and add visual appearance settings 2023-04-23 12:47:49 -03:00
LucasGGamerM
1d25d80186 Merge remote branch 'FineFindus/feat/settings-redesign' 2023-04-23 12:09:49 -03:00
FineFindus
cdeeb24eac refactor(settings): add red header item 2023-04-23 15:20:57 +02:00
FineFindus
5a5181fde8 refactor(settings): move about to about page 2023-04-23 15:10:47 +02:00
FineFindus
69420b2399 refactor: move account and instance setttings to account page 2023-04-23 15:02:26 +02:00
FineFindus
aaa80f80f1 refactor: move miscellanious settings 2023-04-23 14:50:00 +02:00
FineFindus
9b11fbbbfe refactor: move notifications to NotificationsFragment 2023-04-23 14:45:57 +02:00
FineFindus
57f513048a refactor: move timelines to TimelineFragment 2023-04-23 12:44:40 +02:00
FineFindus
c5e35e550c refactor: add compose behaviour to behaviour page 2023-04-23 12:38:51 +02:00
FineFindus
2751b804fe refactor: move behaviour settings to behaviour page 2023-04-23 12:33:03 +02:00
FineFindus
d310673f92 refactor: use SettingsCategory to move between pages 2023-04-23 12:24:06 +02:00
FineFindus
afca57501f fix: move method to fix compiler error 2023-04-23 12:10:43 +02:00
LucasGGamerM
34443726e2 feat: trying to add the theme settings. Still missing some things
What is an enclosed class? What am I missing?
2023-04-22 23:01:31 -03:00
LucasGGamerM
fc9ffc9aef feat: add settings category view holder
Also adds a small proof of concept
2023-04-22 22:35:39 -03:00
LucasGGamerM
44b1bc70af feat: add all previously missing view holders
This includes: Updater View holder, theme view holder and notification policy view holder
2023-04-22 18:02:54 -03:00
LucasGGamerM
45796000c4 Merge https://github.com/LucasGGamerM/moshidon into HEAD 2023-04-22 17:22:46 -03:00
FineFindus
f2c13ed379 refactor: add abstract settingsbase fragment 2023-04-21 21:17:20 +02:00
FineFindus
fd21b9e568 feat: move Settingsfragment to settings folder 2023-04-21 18:34:00 +02:00
382 changed files with 3600 additions and 12508 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1,7 +1,6 @@
# These are supported funding model platforms # These are supported funding model platforms
github: LucasGGamerM github: LucasGGamerM
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

View File

@@ -10,28 +10,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: 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 - uses: actions/checkout@v3
- name: set up JDK 17 - name: set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v3

9
FAQ.md
View File

@@ -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 versjon of Moshidon for iOS?
A: No. As android and iOS apps do not share code, it is incredibly hard to port.

View File

@@ -18,31 +18,27 @@
   
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.joinmastodon.android.moshinda"><img height="50" alt="Get it on IzzyOnDroid" src="img/izzy-badge.png"></a> <a href="https://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! ---
### We also support LiberaPay at: https://liberapay.com/LucasGGamerM/donate!
### You can also donate some Monero through this wallet address as well: ## F.A.Q
4886mdarcyB6Yf8Qc6vDJBK1fz6ibHFLZUmHb4GZZz9yLGNhcG3XC64e5UZ8dVQYTLZb82W6P9WhteowW4STJEec97Gf22j
### 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.
--- ---
## Key features ## Key features
### **The ability to add other server's local timeline to your timelines** ### **The ability to add new custom local timelines!**
It can be accessed in the "Edit timelines" menu, where you can add a new "Community" to see other server's local posts! #### 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** ### **Material you theme support on Android 12+ devices!**
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. ### **Show posts filtered with a warning!**
### **Translate posts easily** **Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:**
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 Before | After
:-------------------------:|:-------------------------: :-------------------------:|:-------------------------:
@@ -51,7 +47,7 @@ Before | After
### **Color themes** ### **Color themes**
Allows you to change theme within the app. Supports Material You, purple, pink, green, blue, red, orange, yellow and Nord! **Allows you to change theme within the app. Supports Purple, pink, green, blue, red, orange, yellow and Nord!**
### **Unlisted posting** ### **Unlisted posting**
@@ -75,10 +71,6 @@ Thats one of the reasons why choosing a small, **well-moderated instance is i
This is important to **ensure the content youre sharing is as accessible as possible** to people who cant see the images and rely on software to read back the provided content descriptions. Thankfully, its quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way! This is important to **ensure the content youre sharing is as accessible as possible** to people who cant see the images and rely on software to read back the provided content descriptions. Thankfully, its quite common for people on the Fediverse to provide such alt texts, and hopefully things stay this way!
### **Reminder to add alt text to attached media**
By default, Moshidon will show a warning to add alt text if your post has any attachments without any alt text. This is for better accessibility, and it can easily be bypassed and disabled in settings.
### **Pinning posts** ### **Pinning posts**
**This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in peoples profiles shows all the posts they pinned.** **This lets you can highlight important posts on your profile. A dedicated “Pinned” tab in peoples profiles shows all the posts they pinned.**
@@ -103,22 +95,12 @@ Moshidon is also available in [IzzyOnDroid repo](https://apt.izzysoft.de/fdroid/
## Release variants ## Release variants
### Stable variant All downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
All stable version downloads can be found on the [Releases](https://github.com/LucasGGamerM/moshidon/releases) page.
**`moshidon.apk`** **`moshidon.apk`**
Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`. Variant with an integrated updater. If you download Moshidon from here (and not from an app store), just download the regular `moshidon.apk`.
### Nightly variant
All nightly builds can be downloaded at [Nightly Releases](https://github.com/LucasGGamerM/moshidon-nightly/releases) page.
**`moshidon-nightly.apk`**
Unstable variant with an integrated updater. It's for development and testing purposes. If you find any bugs with it, please file a bug report at our [issues](https://github.com/LucasGGamerM/moshidon/issues) page.
--- ---
@@ -126,18 +108,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) * [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 * Adding a useful private profile note box!*
* Auto hiding the compose button on scroll * Auto hiding the compose button on scroll!*
* Adding the ability to remind yourself to add alt text to images * Adding the ability to remind yourself to add alt text to images!*
* An indicator for if an image has alt text or not * An indicator for if an image has alt text or not*
* Adding the ability to have drafts * Adding the ability to have drafts!*
* Also adding the ability to view announcements from your instance * 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!) * 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))
@@ -159,7 +139,6 @@ Unstable variant with an integrated updater. It's for development and testing pu
### Behavior ### Behavior
* Allow for confirmation before reblogging
* Adding a bottom option for the publish button, allowing for easier use on larger screens! * 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))
@@ -191,12 +170,6 @@ This project is released under the [GPL-3 License](./LICENSE).
## Links ## Links
[F.A.Q](FAQ.md) [Official matrix chatroom:](https://matrix.to/#/#moshidon:matrix.org) https://matrix.to/#/#moshidon:matrix.org
[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> <a rel="me" href="https://floss.social/@moshidon">@moshidon<wbr>@floss.social</a>
---

View File

@@ -3,7 +3,6 @@ buildscript {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
mavenLocal()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.0.0' classpath 'com.android.tools.build:gradle:8.0.0'

View File

@@ -1,9 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<style>
path { fill: black; }
@media (prefers-color-scheme: dark) {
path { fill: white; }
}
</style>
<path d="M15.4925 3.50673C14.652 3.58251 13.9933 4.28929 13.9933 5.15V10C13.9933 10.4142 13.6577 10.75 13.2437 10.75C11.8002 10.75 10.7863 11.3378 10.0365 12.238C9.26389 13.1656 8.7607 14.444 8.44554 15.7954C8.13254 17.1376 8.01871 18.4912 7.98453 19.5172C7.97182 19.8987 7.9702 20.2324 7.97313 20.5H14.9928V19.75C14.9928 18.5074 13.986 17.5 12.744 17.5H11.4947C11.0807 17.5 10.7451 17.1642 10.7451 16.75C10.7451 16.3358 11.0807 16 11.4947 16H12.744C14.8139 16 16.4919 17.6789 16.4919 19.75V20.5H17.2415C17.6555 20.5 17.9911 20.1642 17.9911 19.75V9.75C17.9911 9.33579 18.3267 9 18.7407 9H19.2472C20.2264 9 20.8249 7.92404 20.309 7.09132L19.6893 6.09132C19.4615 5.72367 19.0599 5.5 18.6275 5.5H16.2421C15.8281 5.5 15.4925 5.16421 15.4925 4.75V3.50673ZM6.47388 20.5C6.47098 20.2156 6.47293 19.8655 6.4862 19.4672C6.52229 18.3838 6.64271 16.9249 6.98559 15.4546C7.32631 13.9935 7.90065 12.4594 8.88484 11.2777C9.75681 10.2307 10.9399 9.47669 12.4942 9.29318V5.15C12.4942 3.4103 13.9037 2 15.6424 2C16.3876 2 16.9916 2.60442 16.9916 3.35V4H18.6275C19.5787 4 20.4622 4.49207 20.9634 5.30092L21.5831 6.30092C22.6749 8.06291 21.4985 10.32 19.4903 10.4898V19.75C19.4903 20.9926 18.4835 22 17.2415 22H7.24708L7.24537 22H5.79625C3.69964 22 2 20.2994 2 18.2016C2 17.2395 2.36489 16.3133 3.02098 15.6099L4.15612 14.393C4.92005 13.5741 5.17521 12.4027 4.82117 11.3399C4.67114 10.8896 4.41837 10.4804 4.08288 10.1447L2.96914 9.03042C2.67641 8.73753 2.67639 8.26266 2.96912 7.96976C3.26184 7.67686 3.73645 7.67685 4.02919 7.96974L5.14293 9.08405C5.643 9.58438 6.01977 10.1943 6.2434 10.8656C6.77114 12.4497 6.3908 14.1958 5.25209 15.4165L4.11695 16.6334C3.71996 17.059 3.49916 17.6195 3.49916 18.2016C3.49916 19.471 4.52761 20.5 5.79625 20.5H6.47388Z" fill="#212121"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -16,11 +16,11 @@ android {
applicationId "org.joinmastodon.android.moshinda" applicationId "org.joinmastodon.android.moshinda"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 101 versionCode 99
versionName "1.2.3+fork.101.moshinda" versionName "1.2.0+fork.99.moshinda"
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 { signingConfigs {
nightly{ nightly{
@@ -62,6 +62,7 @@ android {
initWith release initWith release
} }
nightly{ nightly{
initWith release
if(System.getenv("CURRENT_DATE") != null){ if(System.getenv("CURRENT_DATE") != null){
versionNameSuffix '-nightly+@' + System.getenv("CURRENT_DATE") versionNameSuffix '-nightly+@' + System.getenv("CURRENT_DATE")
} else { } else {
@@ -70,7 +71,6 @@ android {
versionNameSuffix '-nightly+@' + properties.getProperty('CURRENT_DATE') versionNameSuffix '-nightly+@' + properties.getProperty('CURRENT_DATE')
} }
applicationIdSuffix '.nightly' applicationIdSuffix '.nightly'
signingConfig signingConfigs.nightly signingConfig signingConfigs.nightly
manifestPlaceholders = [oAuthScheme:"moshidon-android-nightly-auth"] manifestPlaceholders = [oAuthScheme:"moshidon-android-nightly-auth"]
} }
@@ -91,7 +91,7 @@ android {
setRoot "src/github" setRoot "src/github"
} }
debug { debug {
setRoot "src/debug" setRoot "src/github"
} }
} }
namespace 'org.joinmastodon.android' namespace 'org.joinmastodon.android'
@@ -114,7 +114,7 @@ dependencies {
implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03' implementation 'me.grishka.litex:dynamicanimation:1.1.0-alpha03'
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.appkit:appkit:1.2.8' implementation 'me.grishka.appkit:appkit:1.2.7'
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'

View File

@@ -30,9 +30,6 @@
*; *;
} }
# i don't know how proguard works
-keep class org.joinmastodon.android.** { *; }
# Keep all enums for debugging purposes # Keep all enums for debugging purposes
-keepnames public enum * { -keepnames public enum * {
*; *;
@@ -56,39 +53,3 @@
-keep interface org.parceler.Parcel -keep interface org.parceler.Parcel
-keep @org.parceler.Parcel class * { *; } -keep @org.parceler.Parcel class * { *; }
-keep class **$$Parcelable { *; } -keep class **$$Parcelable { *; }
##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
##---------------End: proguard configuration for Gson ----------
-dontobfuscate

View File

@@ -1,113 +0,0 @@
package org.joinmastodon.android.fragments;
import static org.junit.Assert.*;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusContext;
import org.junit.Test;
import java.time.Instant;
import java.util.List;
public class ThreadFragmentTest {
private Status fakeStatus(String id, String inReplyTo) {
Status status = Status.ofFake(id, null, null);
status.inReplyToId = inReplyTo;
return status;
}
private ThreadFragment.NeighborAncestryInfo fakeInfo(Status s, Status d, Status a) {
return new ThreadFragment.NeighborAncestryInfo(s, d, a);
}
@Test
public void mapNeighborhoodAncestry() {
StatusContext context = new StatusContext();
context.ancestors = List.of(
fakeStatus("oldest ancestor", null),
fakeStatus("younger ancestor", "oldest ancestor")
);
Status mainStatus = fakeStatus("main status", "younger ancestor");
context.descendants = List.of(
fakeStatus("first reply", "main status"),
fakeStatus("reply to first reply", "first reply"),
fakeStatus("third level reply", "reply to first reply"),
fakeStatus("another reply", "main status")
);
List<ThreadFragment.NeighborAncestryInfo> neighbors =
ThreadFragment.mapNeighborhoodAncestry(mainStatus, context);
assertEquals(List.of(
fakeInfo(context.ancestors.get(0), context.ancestors.get(1), null),
fakeInfo(context.ancestors.get(1), mainStatus, context.ancestors.get(0)),
fakeInfo(mainStatus, context.descendants.get(0), context.ancestors.get(1)),
fakeInfo(context.descendants.get(0), context.descendants.get(1), mainStatus),
fakeInfo(context.descendants.get(1), context.descendants.get(2), context.descendants.get(0)),
fakeInfo(context.descendants.get(2), null, context.descendants.get(1)),
fakeInfo(context.descendants.get(3), null, null)
), neighbors);
}
@Test
public void maybeApplyMainStatus() {
ThreadFragment fragment = new ThreadFragment();
fragment.contextInitiallyRendered = true;
fragment.mainStatus = Status.ofFake("123456", "original text", Instant.EPOCH);
Status update1 = Status.ofFake("123456", "updated text", Instant.EPOCH);
update1.editedAt = Instant.ofEpochSecond(1);
fragment.updatedStatus = update1;
StatusUpdatedEvent event1 = (StatusUpdatedEvent) fragment.maybeApplyMainStatus();
assertEquals("fired update event", update1, event1.status);
assertEquals("updated main status", update1, fragment.mainStatus);
Status update2 = Status.ofFake("123456", "updated text", Instant.EPOCH);
update2.favouritesCount = 123;
fragment.updatedStatus = update2;
StatusCountersUpdatedEvent event2 = (StatusCountersUpdatedEvent) fragment.maybeApplyMainStatus();
assertEquals("only fired counter update event", update2.id, event2.id);
assertEquals("updated counter is correct", 123, event2.favorites);
assertEquals("updated main status", update2, fragment.mainStatus);
Status update3 = Status.ofFake("123456", "whatever", Instant.EPOCH);
fragment.contextInitiallyRendered = false;
fragment.updatedStatus = update3;
assertNull("no update when context hasn't been rendered", fragment.maybeApplyMainStatus());
}
@Test
public void sortStatusContext() {
StatusContext context = new StatusContext();
context.ancestors = List.of(
fakeStatus("younger ancestor", "oldest ancestor"),
fakeStatus("oldest ancestor", null)
);
context.descendants = List.of(
fakeStatus("reply to first reply", "first reply"),
fakeStatus("third level reply", "reply to first reply"),
fakeStatus("first reply", "main status"),
fakeStatus("another reply", "main status")
);
ThreadFragment.sortStatusContext(
fakeStatus("main status", "younger ancestor"),
context
);
List<Status> expectedAncestors = List.of(
fakeStatus("oldest ancestor", null),
fakeStatus("younger ancestor", "oldest ancestor")
);
List<Status> expectedDescendants = List.of(
fakeStatus("first reply", "main status"),
fakeStatus("reply to first reply", "first reply"),
fakeStatus("third level reply", "reply to first reply"),
fakeStatus("another reply", "main status")
);
// TODO: ??? i have no idea how this code works. it certainly doesn't return what i'd expect
}
}

View File

@@ -1,106 +0,0 @@
package org.joinmastodon.android.ui.utils;
import static org.junit.Assert.*;
import android.util.Pair;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Instance;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Optional;
public class UiUtilsTest {
@BeforeClass
public static void createDummySession() {
Instance dummyInstance = new Instance();
dummyInstance.uri = "test.tld";
Account dummyAccount = new Account();
dummyAccount.id = "123456";
AccountSessionManager.getInstance().addAccount(dummyInstance, null, dummyAccount, null, null);
}
@AfterClass
public static void cleanUp() {
AccountSessionManager.getInstance().removeAccount("test.tld_123456");
}
@Test
public void parseFediverseHandle() {
assertEquals(
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
UiUtils.parseFediverseHandle("megalodon@floss.social")
);
assertEquals(
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
UiUtils.parseFediverseHandle("@megalodon@floss.social")
);
assertEquals(
Optional.of(Pair.create("megalodon", Optional.empty())),
UiUtils.parseFediverseHandle("@megalodon")
);
assertEquals(
Optional.of(Pair.create("megalodon", Optional.of("floss.social"))),
UiUtils.parseFediverseHandle("mailto:megalodon@floss.social")
);
assertEquals(
Optional.empty(),
UiUtils.parseFediverseHandle("megalodon")
);
assertEquals(
Optional.empty(),
UiUtils.parseFediverseHandle("this is not a fedi handle")
);
assertEquals(
Optional.empty(),
UiUtils.parseFediverseHandle("not@a-domain")
);
}
@Test
public void acctMatches() {
assertTrue("local account, domain not specified", UiUtils.acctMatches(
"test.tld_123456",
"someone",
"someone",
null
));
assertTrue("domain not specified", UiUtils.acctMatches(
"test.tld_123456",
"someone@somewhere.social",
"someone",
null
));
assertTrue("local account, domain specified, different casing", UiUtils.acctMatches(
"test.tld_123456",
"SomeOne",
"someone",
"Test.TLD"
));
assertFalse("username doesn't match", UiUtils.acctMatches(
"test.tld_123456",
"someone-else@somewhere.social",
"someone",
"somewhere.social"
));
assertFalse("domain doesn't match", UiUtils.acctMatches(
"test.tld_123456",
"someone@somewhere.social",
"someone",
"somewhere.else"
));
}
}

View File

@@ -1,81 +0,0 @@
package org.joinmastodon.android.utils;
import static org.joinmastodon.android.model.Filter.FilterAction.*;
import static org.joinmastodon.android.model.Filter.FilterContext.*;
import static org.junit.Assert.*;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status;
import org.junit.Test;
import java.time.Instant;
import java.util.EnumSet;
import java.util.List;
public class StatusFilterPredicateTest {
private static final Filter hideMeFilter = new Filter(), warnMeFilter = new Filter();
private static final List<Filter> allFilters = List.of(hideMeFilter, warnMeFilter);
private static final Status
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now());
static {
hideMeFilter.phrase = "hide me";
hideMeFilter.filterAction = HIDE;
hideMeFilter.context = EnumSet.of(PUBLIC, HOME);
warnMeFilter.phrase = "warning";
warnMeFilter.filterAction = WARN;
warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
}
@Test
public void testHide() {
assertFalse("should not pass because matching filter applies to given context",
new StatusFilterPredicate(allFilters, HOME).test(hideInHomePublic));
}
@Test
public void testHideRegardlessOfContext() {
assertTrue("filters without context should always pass",
new StatusFilterPredicate(allFilters, null).test(hideInHomePublic));
}
@Test
public void testHideInDifferentContext() {
assertTrue("should pass because matching filter does not apply to given context",
new StatusFilterPredicate(allFilters, THREAD).test(hideInHomePublic));
}
@Test
public void testHideWithWarningText() {
assertTrue("should pass because matching filter is for warnings",
new StatusFilterPredicate(allFilters, HOME).test(warnInHomePublic));
}
@Test
public void testWarn() {
assertFalse("should not pass because filter applies to given context",
new StatusFilterPredicate(allFilters, HOME, WARN).test(warnInHomePublic));
}
@Test
public void testWarnRegardlessOfContext() {
assertTrue("filters without context should always pass",
new StatusFilterPredicate(allFilters, null, WARN).test(warnInHomePublic));
}
@Test
public void testWarnInDifferentContext() {
assertTrue("should pass because filter does not apply to given context",
new StatusFilterPredicate(allFilters, THREAD, WARN).test(warnInHomePublic));
}
@Test
public void testWarnWithHideText() {
assertTrue("should pass because matching filter is for hiding",
new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic));
}
}

View File

@@ -1,369 +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();
}
}
/*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);
}
}
}
}*/
}

View File

@@ -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());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 988 B

After

Width:  |  Height:  |  Size: 988 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -5,7 +5,6 @@
<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"/>
@@ -41,22 +40,6 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".PanicResponderActivity"
android:exported="true"
android:launchMode="singleInstance"
android:noHistory="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ExitActivity"
android:exported="false"
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".OAuthActivity" android:exported="true" android:configChanges="orientation|screenSize" android:launchMode="singleTask"> <activity android:name=".OAuthActivity" android:exported="true" android:configChanges="orientation|screenSize" android:launchMode="singleTask">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
@@ -93,16 +76,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>

View File

@@ -0,0 +1,89 @@
# lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
# https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
# This list contains domains of toxic mastodon instances
# Last-Modified: 1672044500
# gab - a neonazi social network
gab.ai
gab.com
gab.protohype.net
# consequence-free speech
social.unzensiert.to
freeatlantis.com
# reactionary bigotry and hatespeech against magrinalized groups
poa.st
freespeechextremist.com
rdrama.cc
outpoa.st
anime.website
gameliberty.club
social.byoblu.com
yggdrasil.social
smuglo.li
dogeposting.social
unsafe.space
freezepeach.xyz
# + CSAM
rojogato.com
# antivaxxer shitposting & fearmongering
shadowsocial.org
# Kiwifarms
kiwifarms.net
kiwifarms.cc
kiwifarms.is
kiwifarms.pleroma.net
# https://mastodon.art/@Curator/109649354849593592
poa.st antisemitic racist homophobic
nicecrew.digital antisemitic
beefyboys.win antisemitic racist homophobic harassment
cawfee.club antisemitic racist homophobic
comfyboy.club antisemitic racist homophobic
freespeechextremist.com racist homophobic
cum.salon racist misogynist
bae.st racist
natehiggers.online racist
rapemeat.solutions misogynist
rapist.town misogynist
rapefeminists.network misogynist
kiwifarms.cc harassment
noagendasocial.com noagenda
posting.lolicon.rocks underage
urchan.org harassment homophobic racist
ryona.agency harassment
yggdrasil.social antisemitic homophobic racist
genderheretics.xyz transphobic
baraag.net underage
lolison.top underage
shota.house underage
shota.social underage
aethy.com underage
taullo.social underage
childpawn.shop underage
posting.lolicon.rocks underage
loli.best underage
gothloli.club underage
smuglo.li underage
youjo.love underage
pedo.school underage
lolison.network underage
freak.university underage
mirr0r.city underage
xhais.love underage
refusal.biz underage
refusal.llc underage
mirr0r.city underage
nnia.space underage
ignorelist.com malicious
repl.co malicious
# custom
pawoo.net csam
1 # lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0
2 # https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv
3 # This list contains domains of toxic mastodon instances
4 # Last-Modified: 1672044500
5 # gab - a neonazi social network
6 gab.ai
7 gab.com
8 gab.protohype.net
9 # consequence-free speech
10 social.unzensiert.to
11 freeatlantis.com
12 # reactionary bigotry and hatespeech against magrinalized groups
13 poa.st
14 freespeechextremist.com
15 rdrama.cc
16 outpoa.st
17 anime.website
18 gameliberty.club
19 social.byoblu.com
20 yggdrasil.social
21 smuglo.li
22 dogeposting.social
23 unsafe.space
24 freezepeach.xyz
25 # + CSAM
26 rojogato.com
27 # antivaxxer shitposting & fearmongering
28 shadowsocial.org
29 # Kiwifarms
30 kiwifarms.net
31 kiwifarms.cc
32 kiwifarms.is
33 kiwifarms.pleroma.net
34 # https://mastodon.art/@Curator/109649354849593592
35 poa.st antisemitic racist homophobic
36 nicecrew.digital antisemitic
37 beefyboys.win antisemitic racist homophobic harassment
38 cawfee.club antisemitic racist homophobic
39 comfyboy.club antisemitic racist homophobic
40 freespeechextremist.com racist homophobic
41 cum.salon racist misogynist
42 bae.st racist
43 natehiggers.online racist
44 rapemeat.solutions misogynist
45 rapist.town misogynist
46 rapefeminists.network misogynist
47 kiwifarms.cc harassment
48 noagendasocial.com noagenda
49 posting.lolicon.rocks underage
50 urchan.org harassment homophobic racist
51 ryona.agency harassment
52 yggdrasil.social antisemitic homophobic racist
53 genderheretics.xyz transphobic
54 baraag.net underage
55 lolison.top underage
56 shota.house underage
57 shota.social underage
58 aethy.com underage
59 taullo.social underage
60 childpawn.shop underage
61 posting.lolicon.rocks underage
62 loli.best underage
63 gothloli.club underage
64 smuglo.li underage
65 youjo.love underage
66 pedo.school underage
67 lolison.network underage
68 freak.university underage
69 mirr0r.city underage
70 xhais.love underage
71 refusal.biz underage
72 refusal.llc underage
73 mirr0r.city underage
74 nnia.space underage
75 ignorelist.com malicious
76 repl.co malicious
77 # custom
78 pawoo.net csam

View File

@@ -1,171 +0,0 @@
13bells.com
4aem.com
aethy.com
anime.website
annihilation.social
anon-kenkai.com
asbestos.cafe
bae.st
bajax.us
banepo.st
baraag.net
beefyboys.win
beepboop.ga
berserker.town
bikeshed.party
boks.moe
brainsoap.net
breastmilk.club
brighteon.social
cawfee.club
clew.lol
clubcyberia.co
collapsitarian.io
comfyboy.club
contrapointsfan.club
cum.camp
cum.salon
cybercriminal.eu
darknight-coffee.org
dembased.xyz
desupost.soy
detroitriotcity.com
eatthebugs.social
eientei.org
elementality.org
eveningzoo.club
firedragonstudios.com
firefaithfellowship.com
fluf.club
foxfam.club
freak.university
freeatlantis.com
freecumextremist.com
freedomstrike.org
freesoftwareextremist.com
freespeech.group
freespeechextremist.com
freetalklive.com
froth.zone
fulltermprivacy.com
gameliberty.club
gearlandia.haus
genderheretics.xyz
geofront.rocks
gleasonator.com
glee.li
glindr.org
goyim.app
goyslop.cafe
haeder.net
handholding.io
hidamari.apartments
hitchhiker.social
hunk.city
iddqd.social
intkos.link
justicewarrior.social
kawa-kun.com
kitsunemimi.club
kiwifarms.cc
kompost.cz
kurosawa.moe
leafposter.club
leftychan.net
lewdieheaven.com
liberdon.com
ligma.pro
lizards.live
lolicon.rocks
lolison.top
lovingexpressions.net
lucasvl.nl
mahodou.moe
makemysarcophagus.com
maladaptive.art
masochi.st
mastinator.com
merovingian.club
midwaytrades.com
mirr0r.city
moa.st
mouse.services
mugicha.club
narrativerry.xyz
natehiggers.online
neckbeard.xyz
needs.vodka
neenster.org
nicecrew.digital
nnia.space
noagendasocial.com
noagendasocial.nl
noagendatube.com
nobodyhasthe.biz
nukem.biz
obo.sh
onionfarms.org
outpoa.st
pawlicker.com
pawoo.net
pedo.school
piazza.today
pibvt.net
pieville.net
pisskey.io
plagu.ee
pmth.us
poa.st
poast.org
poast.tv
poster.place
prospeech.space
quodverum.com
rakket.app
rapemeat.solutions
rdrama.cc
rebelbase.site
retardedniggers.forsale
rojogato.com
ryona.agency
schwartzwelt.xyz
seal.cafe
shigusegubu.club
shitpost.cloud
shitposter.club
shota.house
silliness.observer
skinheads.eu
skinheads.io
skinheads.social
skinheads.uk
skippers-bin.com
skyshanty.xyz
slash.cl
sleepy.cafe
smuglo.li
sneed.social
sonichu.com
spinster.xyz
springbo.cc
starnix.network
stereophonic.space
strelizia.net
syspxl.xyz
tastingtraffic.net
teci.world
theapex.social
thepostearthdestination.com
tkammer.de
trumpislovetrumpis.life
truthsocial.co.in
urchan.org
varishangout.net
whinge.house
whinge.town
wideboys.org
wolfgirl.bar
xn--p1abe3d.xn--80asehdb
yggdrasil.social
youjo.love
zztails.gay

View File

@@ -1,24 +0,0 @@
package org.joinmastodon.android;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class ExitActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
finishAndRemoveTask();
}
public static void exit(Context context) {
Intent intent = new Intent(context, ExitActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
}
}

View File

@@ -1,15 +1,14 @@
package org.joinmastodon.android; package org.joinmastodon.android;
import android.app.Fragment; import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.ClipData; import android.content.ClipData;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment; import org.joinmastodon.android.fragments.ComposeFragment;
@@ -20,8 +19,6 @@ import org.jsoup.internal.StringUtil;
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.Optional;
import java.util.function.BiConsumer;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
@@ -33,51 +30,22 @@ public class ExternalShareActivity extends FragmentStackActivity{
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if(savedInstanceState==null){ if(savedInstanceState==null){
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT)); String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle); boolean isMastodonURL = UiUtils.looksLikeMastodonUrl(text);
boolean isFediUrl = text.map(UiUtils::looksLikeMastodonUrl).orElse(false);
boolean isOpenable = isFediUrl || fediHandle.isPresent();
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts(); List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
if (sessions.isEmpty()){ if(sessions.isEmpty()){
Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.err_not_logged_in, Toast.LENGTH_SHORT).show();
finish(); finish();
} else if (isOpenable || sessions.size() > 1) { }else if(sessions.size()==1 && !isMastodonURL){
AccountSwitcherSheet sheet = new AccountSwitcherSheet(this, null, true, isOpenable);
sheet.setOnClick((accountId, open) -> {
if (open && text.isPresent()) {
BiConsumer<Class<? extends Fragment>, Bundle> callback = (clazz, args) -> {
if (clazz == null) {
Toast.makeText(this, R.string.sk_open_in_app_failed, Toast.LENGTH_SHORT).show();
// TODO: do something about the window getting leaked
sheet.dismiss();
finish();
return;
}
args.putString("fromExternalShare", clazz.getSimpleName());
Intent intent = new Intent(this, MainActivity.class);
intent.putExtras(args);
finish();
startActivity(intent);
};
fediHandle
.<MastodonAPIRequest<?>>map(handle ->
UiUtils.lookupAccountHandle(this, accountId, handle, callback))
.or(() ->
UiUtils.lookupURL(this, accountId, text.get(), callback))
.ifPresent(req ->
req.wrapProgress(this, R.string.loading, true, d -> {
UiUtils.transformDialogForLookup(this, accountId, isFediUrl ? text.get() : null, d);
d.setOnDismissListener((x) -> finish());
}));
} else {
openComposeFragment(accountId);
}
});
sheet.show();
} else if (sessions.size() == 1) {
openComposeFragment(sessions.get(0).getID()); openComposeFragment(sessions.get(0).getID());
}else{
new AccountSwitcherSheet(this, false, false, isMastodonURL, accountSession -> {
if(accountSession!=null)
openComposeFragment(accountSession.getID());
else
UiUtils.openURL(this, AccountSessionManager.getInstance().getLastActiveAccountID(), text);
}).show();
} }
} }
} }
@@ -140,4 +108,11 @@ public class ExternalShareActivity extends FragmentStackActivity{
return null; return null;
return new ArrayList<>(l); return new ArrayList<>(l);
} }
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
}
} }

View File

@@ -9,7 +9,6 @@ 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.model.ContentType;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@@ -31,48 +30,41 @@ public class GlobalUserPreferences{
public static boolean alwaysExpandContentWarnings; public static boolean alwaysExpandContentWarnings;
public static boolean disableMarquee; public static boolean disableMarquee;
public static boolean disableSwipe; public static boolean disableSwipe;
public static boolean showDividers; public static boolean disableDividers;
public static boolean voteButtonForSingleChoice; public static boolean voteButtonForSingleChoice;
public static boolean enableDeleteNotifications;
public static boolean translateButtonOpenedOnly;
public static boolean uniformNotificationIcon; public static boolean uniformNotificationIcon;
public static boolean enableDeleteNotifications;
public static boolean relocatePublishButton; public static boolean relocatePublishButton;
public static boolean reduceMotion; public static boolean reduceMotion;
public static boolean keepOnlyLatestNotification; public static boolean keepOnlyLatestNotification;
public static boolean enableFabAutoHide;
public static boolean disableAltTextReminder; public static boolean disableAltTextReminder;
public static boolean showAltIndicator; public static boolean showAltIndicator;
public static boolean showNoAltIndicator; public static boolean showNoAltIndicator;
public static boolean enablePreReleases; public static boolean enablePreReleases;
public static PrefixRepliesMode prefixReplies; public static boolean prefixRepliesWithRe;
public static boolean bottomEncoding; public static boolean bottomEncoding;
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 defaultToUnlistedReplies; public static boolean defaultToUnlistedReplies;
public static boolean doubleTapToSwipe; public static boolean disableDoubleTapToSwipe;
public static boolean compactReblogReplyLine; public static boolean compactReblogReplyLine;
public static boolean confirmBeforeReblog; public static boolean confirmBeforeReblog;
public static boolean replyLineAboveHeader; public static boolean replyLineAboveHeader;
public static boolean swapBookmarkWithBoostAction; public static boolean swapBookmarkWithBoostAction;
public static boolean loadRemoteAccountFollowers; public static boolean loadRemoteAccountFollowers;
public static boolean mentionRebloggerAutomatically; public static boolean mentionRebloggerAutomatically;
public static boolean allowRemoteLoading;
public static boolean forwardReportDefault;
public static AutoRevealMode autoRevealEqualSpoilers;
public static String publishButtonText; public static String publishButtonText;
public static ThemePreference theme; public static ThemePreference theme;
public static ColorPreference color; public static ColorPreference color;
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
public static Map<String, List<String>> recentLanguages; public static Map<String, List<String>> recentLanguages;
public static Map<String, List<TimelineDefinition>> pinnedTimelines; public static Map<String, List<TimelineDefinition>> pinnedTimelines;
public static Set<String> accountsWithLocalOnlySupport; public static Set<String> accountsWithLocalOnlySupport;
public static Set<String> accountsInGlitchMode; public static Set<String> accountsInGlitchMode;
public static Set<String> accountsWithContentTypesEnabled;
public static Map<String, ContentType> accountsDefaultContentTypes;
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType(); private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
public static Map<String, Integer> recentEmojis; public static Map<String, Integer> recentEmojis;
@@ -82,6 +74,7 @@ public class GlobalUserPreferences{
*/ */
public static String replyVisibility; public static String replyVisibility;
public static SharedPreferences getPrefs(){ public static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE); return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
} }
@@ -92,16 +85,6 @@ public class GlobalUserPreferences{
catch (JsonSyntaxException ignored) { return orElse; } catch (JsonSyntaxException ignored) { return orElse; }
} }
public static void removeAccount(String accountId) {
recentLanguages.remove(accountId);
pinnedTimelines.remove(accountId);
accountsInGlitchMode.remove(accountId);
accountsWithLocalOnlySupport.remove(accountId);
accountsWithContentTypesEnabled.remove(accountId);
accountsDefaultContentTypes.remove(accountId);
save();
}
public static void load(){ public static void load(){
SharedPreferences prefs=getPrefs(); SharedPreferences prefs=getPrefs();
playGifs=prefs.getBoolean("playGifs", true); playGifs=prefs.getBoolean("playGifs", true);
@@ -116,26 +99,25 @@ public class GlobalUserPreferences{
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false); alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
disableMarquee=prefs.getBoolean("disableMarquee", false); disableMarquee=prefs.getBoolean("disableMarquee", false);
disableSwipe=prefs.getBoolean("disableSwipe", false); disableSwipe=prefs.getBoolean("disableSwipe", false);
showDividers =prefs.getBoolean("showDividers", false); disableDividers=prefs.getBoolean("disableDividers", true);
relocatePublishButton=prefs.getBoolean("relocatePublishButton", true); relocatePublishButton=prefs.getBoolean("relocatePublishButton", true);
voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true); voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true);
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false); enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", false);
translateButtonOpenedOnly=prefs.getBoolean("translateButtonOpenedOnly", false);
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
reduceMotion=prefs.getBoolean("reduceMotion", false); reduceMotion=prefs.getBoolean("reduceMotion", false);
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false); keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
enableFabAutoHide=prefs.getBoolean("enableFabAutoHide", true);
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false); disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
showAltIndicator=prefs.getBoolean("showAltIndicator", true); showAltIndicator=prefs.getBoolean("showAltIndicator", true);
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true); showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
enablePreReleases=prefs.getBoolean("enablePreReleases", false); enablePreReleases=prefs.getBoolean("enablePreReleases", false);
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name())); prefixRepliesWithRe=prefs.getBoolean("prefixRepliesWithRe", false);
bottomEncoding=prefs.getBoolean("bottomEncoding", false); bottomEncoding=prefs.getBoolean("bottomEncoding", false);
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); compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false); defaultToUnlistedReplies=prefs.getBoolean("defaultToUnlistedReplies", false);
doubleTapToSwipe =prefs.getBoolean("doubleTapToSwipe", true); disableDoubleTapToSwipe=prefs.getBoolean("disableDoubleTapToSwipe", false);
replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true); replyLineAboveHeader=prefs.getBoolean("replyLineAboveHeader", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true); compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false); confirmBeforeReblog=prefs.getBoolean("confirmBeforeReblog", false);
@@ -151,20 +133,6 @@ public class GlobalUserPreferences{
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>()); accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>()); accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
replyVisibility=prefs.getString("replyVisibility", null); replyVisibility=prefs.getString("replyVisibility", null);
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
if (prefs.contains("prefixRepliesWithRe")) {
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
? PrefixRepliesMode.TO_OTHERS : PrefixRepliesMode.NEVER;
prefs.edit()
.putString("prefixReplies", prefixReplies.name())
.remove("prefixRepliesWithRe")
.apply();
}
try { try {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
@@ -191,27 +159,25 @@ public class GlobalUserPreferences{
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings) .putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
.putBoolean("disableMarquee", disableMarquee) .putBoolean("disableMarquee", disableMarquee)
.putBoolean("disableSwipe", disableSwipe) .putBoolean("disableSwipe", disableSwipe)
.putBoolean("enableDeleteNotifications", enableDeleteNotifications) .putBoolean("disableDividers", disableDividers)
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
.putBoolean("showDividers", showDividers)
.putBoolean("relocatePublishButton", relocatePublishButton) .putBoolean("relocatePublishButton", relocatePublishButton)
.putBoolean("uniformNotificationIcon", uniformNotificationIcon) .putBoolean("uniformNotificationIcon", uniformNotificationIcon)
.putBoolean("enableDeleteNotifications", enableDeleteNotifications) .putBoolean("enableDeleteNotifications", enableDeleteNotifications)
.putBoolean("reduceMotion", reduceMotion) .putBoolean("reduceMotion", reduceMotion)
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification) .putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("enableFabAutoHide", enableFabAutoHide)
.putBoolean("disableAltTextReminder", disableAltTextReminder) .putBoolean("disableAltTextReminder", disableAltTextReminder)
.putBoolean("showAltIndicator", showAltIndicator) .putBoolean("showAltIndicator", showAltIndicator)
.putBoolean("showNoAltIndicator", showNoAltIndicator) .putBoolean("showNoAltIndicator", showNoAltIndicator)
.putBoolean("enablePreReleases", enablePreReleases) .putBoolean("enablePreReleases", enablePreReleases)
.putString("prefixReplies", prefixReplies.name()) .putBoolean("prefixRepliesWithRe", prefixRepliesWithRe)
.putBoolean("collapseLongPosts", collapseLongPosts) .putBoolean("collapseLongPosts", collapseLongPosts)
.putBoolean("spectatorMode", spectatorMode) .putBoolean("spectatorMode", spectatorMode)
.putBoolean("autoHideFab", autoHideFab) .putBoolean("autoHideFab", autoHideFab)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putString("publishButtonText", publishButtonText) .putString("publishButtonText", publishButtonText)
.putBoolean("bottomEncoding", bottomEncoding) .putBoolean("bottomEncoding", bottomEncoding)
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies) .putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
.putBoolean("doubleTapToSwipe", doubleTapToSwipe) .putBoolean("disableDoubleTapToSwipe", disableDoubleTapToSwipe)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine) .putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putBoolean("replyLineAboveHeader", replyLineAboveHeader) .putBoolean("replyLineAboveHeader", replyLineAboveHeader)
.putBoolean("confirmBeforeReblog", confirmBeforeReblog) .putBoolean("confirmBeforeReblog", confirmBeforeReblog)
@@ -226,11 +192,6 @@ public class GlobalUserPreferences{
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport) .putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
.putStringSet("accountsInGlitchMode", accountsInGlitchMode) .putStringSet("accountsInGlitchMode", accountsInGlitchMode)
.putString("replyVisibility", replyVisibility) .putString("replyVisibility", replyVisibility)
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
.putBoolean("allowRemoteLoading", allowRemoteLoading)
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
.putBoolean("forwardReportDefault", forwardReportDefault)
.apply(); .apply();
} }
@@ -251,16 +212,5 @@ public class GlobalUserPreferences{
LIGHT, LIGHT,
DARK DARK
} }
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
} }

View File

@@ -1,28 +1,18 @@
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.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.widget.FrameLayout;
import android.widget.Toast;
import org.joinmastodon.android.api.ObjectValidationException; import org.joinmastodon.android.api.ObjectValidationException;
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;
@@ -32,13 +22,13 @@ import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater; import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels; import org.parceler.Parcels;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent { public class MainActivity extends FragmentStackActivity{
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState){ protected void onCreate(@Nullable Bundle savedInstanceState){
UiUtils.setUserPreferredTheme(this); UiUtils.setUserPreferredTheme(this);
@@ -48,18 +38,10 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){ if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment()); showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{ }else{
AccountSessionManager.getInstance().maybeUpdateLocalInfo();
AccountSession session; AccountSession session;
Bundle args=new Bundle(); Bundle args=new Bundle();
Intent intent=getIntent(); Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false); boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification"); boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){ if(fromNotification){
@@ -73,7 +55,6 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
}else{ }else{
session=AccountSessionManager.getInstance().getLastActiveAccount(); session=AccountSessionManager.getInstance().getLastActiveAccount();
} }
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID()); args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment(); Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args); fragment.setArguments(args);
@@ -97,12 +78,12 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
@Override @Override
protected void onNewIntent(Intent intent){ protected void onNewIntent(Intent intent){
super.onNewIntent(intent); super.onNewIntent(intent);
AccountSessionManager.getInstance().maybeUpdateLocalInfo(); if(intent.getBooleanExtra("fromNotification", false)){
if (intent.hasExtra("fromExternalShare")) showFragmentForExternalShare(intent.getExtras());
else if (intent.getBooleanExtra("fromNotification", false)) {
String accountID=intent.getStringExtra("accountID"); String accountID=intent.getStringExtra("accountID");
AccountSession accountSession;
try{ try{
AccountSessionManager.getInstance().getAccount(accountID); accountSession=AccountSessionManager.getInstance().getAccount(accountID);
DomainManager.getInstance().setCurrentDomain(accountSession.domain);
}catch(IllegalStateException x){ }catch(IllegalStateException x){
return; return;
} }
@@ -126,26 +107,23 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
} }
private void showFragmentForNotification(Notification notification, String accountID){ private void showFragmentForNotification(Notification notification, String accountID){
Fragment fragment;
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("_can_go_back", true);
try{ try{
notification.postprocess(); notification.postprocess();
}catch(ObjectValidationException x){ }catch(ObjectValidationException x){
Log.w("MainActivity", x); Log.w("MainActivity", x);
return; return;
} }
Bundle args = new Bundle(); if(notification.status!=null){
args.putBoolean("noTransition", true); fragment=new ThreadFragment();
UiUtils.showFragmentForNotification(this, notification, accountID, args); args.putParcelable("status", Parcels.wrap(notification.status));
} }else{
fragment=new ProfileFragment();
private void showFragmentForExternalShare(Bundle args) { args.putParcelable("profileAccount", Parcels.wrap(notification.account));
String className = args.getString("fromExternalShare"); }
Fragment fragment = switch (className) {
case "ThreadFragment" -> new ThreadFragment();
case "ProfileFragment" -> new ProfileFragment();
default -> null;
};
if (fragment == null) return;
args.putBoolean("_can_go_back", true);
fragment.setArguments(args); fragment.setArguments(args);
showFragment(fragment); showFragment(fragment);
} }
@@ -179,58 +157,25 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
(fragmentContainers.get(fragmentContainers.size() - 1)).getId() (fragmentContainers.get(fragmentContainers.size() - 1)).getId()
); );
Bundle currentArgs = currentFragment.getArguments(); Bundle currentArgs = currentFragment.getArguments();
if (fragmentContainers.size() != 1 if (this.fragmentContainers.size() == 1
|| currentArgs == null && currentArgs != null
|| !currentArgs.getBoolean("_can_go_back", false)) { && currentArgs.getBoolean("_can_go_back", false)
super.onBackPressed(); && currentArgs.containsKey("account")) {
return;
}
if (currentArgs.getBoolean("_finish_on_back", false)) {
finish();
} else if (currentArgs.containsKey("account")) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString("account", currentArgs.getString("account")); args.putString("account", currentArgs.getString("account"));
if (getIntent().getBooleanExtra("fromNotification", false)) { args.putString("tab", "notifications");
args.putString("tab", "notifications");
}
Fragment fragment=new HomeFragment(); Fragment fragment=new HomeFragment();
fragment.setArguments(args); fragment.setArguments(args);
showFragmentClearingBackStack(fragment); showFragmentClearingBackStack(fragment);
}
}
// @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 { } else {
Toast.makeText(this, R.string.permission_required, Toast.LENGTH_SHORT); super.onBackPressed();
} }
} }
public Fragment getCurrentFragment() {
for (int i = fragmentContainers.size() - 1; i >= 0; i--) {
FrameLayout fl = fragmentContainers.get(i);
if (fl.getVisibility() == View.VISIBLE) {
return getFragmentManager().findFragmentById(fl.getId());
}
}
return null;
}
@Override @Override
public void onProvideAssistContent(AssistContent assistContent) { public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(assistContent); super.onProvideAssistContent(outContent);
Fragment fragment = getCurrentFragment();
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent); outContent.setWebUri(Uri.parse(DomainManager.getInstance().getCurrentDomain()));
} }
} }

View File

@@ -61,9 +61,6 @@ public class OAuthActivity extends Activity{
@Override @Override
public void onSuccess(Token token){ public void onSuccess(Token token){
new GetOwnAccount() new GetOwnAccount()
// in case the instance (looking at pixelfed) wants to redirect to a
// website, we need to pass a context so we can launch a browser
.setContext(OAuthActivity.this)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Account account){ public void onSuccess(Account account){

View File

@@ -1,49 +0,0 @@
package org.joinmastodon.android;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class PanicResponderActivity extends Activity {
public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER";
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) {
AccountSessionManager.getInstance().getLoggedInAccounts().forEach(accountSession -> logOut(accountSession.getID()));
ExitActivity.exit(this);
}
finishAndRemoveTask();
}
private void logOut(String accountID){
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Object result){
onLoggedOut(accountID);
}
@Override
public void onError(ErrorResponse error){
onLoggedOut(accountID);
}
})
.exec(accountID);
}
private void onLoggedOut(String accountID){
AccountSessionManager.getInstance().removeAccount(accountID);
}
}

View File

@@ -1,7 +1,5 @@
package org.joinmastodon.android; package org.joinmastodon.android;
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.ALWAYS;
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.TO_OTHERS;
import static org.joinmastodon.android.GlobalUserPreferences.getPrefs; import static org.joinmastodon.android.GlobalUserPreferences.getPrefs;
import android.app.Notification; import android.app.Notification;
@@ -21,7 +19,6 @@ 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;
@@ -31,7 +28,6 @@ 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.NotificationReceivedEvent; import org.joinmastodon.android.events.NotificationReceivedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Mention;
import org.joinmastodon.android.model.NotificationAction; import org.joinmastodon.android.model.NotificationAction;
import org.joinmastodon.android.model.Preferences; import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushNotification; import org.joinmastodon.android.model.PushNotification;
@@ -41,7 +37,6 @@ import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
@@ -61,7 +56,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
private static final String ACTION_KEY_TEXT_REPLY = "ACTION_KEY_TEXT_REPLY"; private static final String ACTION_KEY_TEXT_REPLY = "ACTION_KEY_TEXT_REPLY";
private static final int SUMMARY_ID = 791; private static final int SUMMARY_ID = 791;
private static int notificationId = 0; private static int notificationId;
@Override @Override
public void onReceive(Context context, Intent intent){ public void onReceive(Context context, Intent intent){
@@ -128,16 +123,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if(intent.hasExtra("notification")){ if(intent.hasExtra("notification")){
org.joinmastodon.android.model.Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification")); org.joinmastodon.android.model.Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
String statusID = null; String statusID=notification.status.id;
String targetAccountID = null; if (statusID != null) {
if(notification.status != null){
statusID = notification.status.id;
}
if(notification.account != null){
targetAccountID = notification.account.id;
}
if (statusID != null || targetAccountID != null) {
AccountSessionManager accountSessionManager = AccountSessionManager.getInstance(); AccountSessionManager accountSessionManager = AccountSessionManager.getInstance();
Preferences preferences = accountSessionManager.getAccount(accountID).preferences; Preferences preferences = accountSessionManager.getAccount(accountID).preferences;
@@ -147,7 +134,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
case BOOST -> new SetStatusReblogged(notification.status.id, true, preferences.postingDefaultVisibility).exec(accountID); case BOOST -> new SetStatusReblogged(notification.status.id, true, preferences.postingDefaultVisibility).exec(accountID);
case UNBOOST -> new SetStatusReblogged(notification.status.id, false, preferences.postingDefaultVisibility).exec(accountID); case UNBOOST -> 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");
} }
} }
@@ -255,9 +241,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
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.UNBOOST));
} }
case FOLLOW -> {
builder.addAction(buildNotificationAction(context, id, accountID, notification, context.getString(R.string.follow_back), NotificationAction.FOLLOW_BACK));
}
} }
} }
@@ -302,64 +285,27 @@ public class PushNotificationReceiver extends BroadcastReceiver{
} }
CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY); CharSequence input = remoteInput.getCharSequence(ACTION_KEY_TEXT_REPLY);
// copied from ComposeFragment - TODO: generalize?
ArrayList<String> mentions=new ArrayList<>();
Status status = notification.status;
String ownID=AccountSessionManager.getInstance().getAccount(accountID).self.id;
if(!status.account.id.equals(ownID))
mentions.add('@'+status.account.acct);
for(Mention mention:status.mentions){
if(mention.id.equals(ownID))
continue;
String m='@'+mention.acct;
if(!mentions.contains(m))
mentions.add(m);
}
String initialText=mentions.isEmpty() ? "" : TextUtils.join(" ", mentions)+" ";
CreateStatus.Request req=new CreateStatus.Request(); CreateStatus.Request req=new CreateStatus.Request();
req.status = initialText + input.toString(); req.status = input.toString() + "\n\n" + "@" + notification.status.account.acct;
req.language = preferences.postingDefaultLanguage; req.language = notification.status.language;
req.visibility = preferences.postingDefaultVisibility; req.visibility = (notification.status.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : notification.status.visibility);
req.inReplyToId = notification.status.id; req.inReplyToId = notification.status.id;
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
if (!notification.status.spoilerText.isEmpty() &&
(GlobalUserPreferences.prefixReplies == ALWAYS
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(notification.status.account.id)))
&& !notification.status.spoilerText.startsWith("re: ")) {
req.spoilerText = "re: " + notification.status.spoilerText; req.spoilerText = "re: " + notification.status.spoilerText;
} }
new CreateStatus(req, UUID.randomUUID().toString()).setCallback(new Callback<>() { new CreateStatus(req, UUID.randomUUID().toString()).exec(accountID);
@Override
public void onSuccess(Status status) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
new Notification.Builder(context, accountID+"_"+notification.type) :
new Notification.Builder(context)
.setPriority(Notification.PRIORITY_DEFAULT)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
notification.status = status; NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Intent contentIntent=new Intent(context, MainActivity.class); Notification.Builder builder = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O ?
contentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); new Notification.Builder(context, accountID+"_"+notification.type) :
contentIntent.putExtra("fromNotification", true); new Notification.Builder(context)
contentIntent.putExtra("accountID", accountID); .setPriority(Notification.PRIORITY_DEFAULT)
contentIntent.putExtra("notification", Parcels.wrap(notification)); .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE);
Notification repliedNotification = builder.setSmallIcon(R.drawable.ic_ntf_logo) Notification repliedNotification = builder.setSmallIcon(R.drawable.ic_ntf_logo)
.setContentTitle(context.getString(R.string.sk_notification_action_replied, notification.status.account.displayName)) .setContentText(context.getString(R.string.mo_notification_action_replied, notification.status.account.getDisplayUsername()))
.setContentText(status.getStrippedText()) .build();
.setCategory(Notification.CATEGORY_SOCIAL) notificationManager.notify(accountID, notificationId, repliedNotification);
.setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
.build();
notificationManager.notify(accountID, notificationId, repliedNotification);
}
@Override
public void onError(ErrorResponse errorResponse) {
}
}).exec(accountID);
} }
} }

View File

@@ -19,6 +19,7 @@ import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.SearchResult; import org.joinmastodon.android.model.SearchResult;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.utils.StatusFilterPredicate; import org.joinmastodon.android.utils.StatusFilterPredicate;
@@ -36,7 +37,7 @@ import me.grishka.appkit.utils.WorkerThread;
public class CacheController{ public class CacheController{
private static final String TAG="CacheController"; private static final String TAG="CacheController";
private static final int DB_VERSION=4; private static final int DB_VERSION=3;
private static final WorkerThread databaseThread=new WorkerThread("databaseThread"); private static final WorkerThread databaseThread=new WorkerThread("databaseThread");
private static final Handler uiHandler=new Handler(Looper.getMainLooper()); private static final Handler uiHandler=new Handler(Looper.getMainLooper());
@@ -61,7 +62,7 @@ public class CacheController{
List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList()); List<Filter> filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(Filter.FilterContext.HOME)).collect(Collectors.toList());
if(!forceReload){ if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase(); SQLiteDatabase db=getOrOpenDatabase();
try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){ try(Cursor cursor=db.query("home_timeline", new String[]{"json", "flags"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
if(cursor.getCount()==count){ if(cursor.getCount()==count){
ArrayList<Status> result=new ArrayList<>(); ArrayList<Status> result=new ArrayList<>();
cursor.moveToFirst(); cursor.moveToFirst();
@@ -73,8 +74,10 @@ public class CacheController{
int flags=cursor.getInt(1); int flags=cursor.getInt(1);
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0); status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
newMaxID=status.id; newMaxID=status.id;
if (!new StatusFilterPredicate(filters, Filter.FilterContext.HOME).test(status)) for(Filter filter:filters){
continue outer; if(filter.matches(status))
continue outer;
}
result.add(status); result.add(status);
}while(cursor.moveToNext()); }while(cursor.moveToNext());
String _newMaxID=newMaxID; String _newMaxID=newMaxID;
@@ -89,7 +92,7 @@ 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.stream().filter(new StatusFilterPredicate(filters, Filter.FilterContext.HOME)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false)); callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
putHomeTimeline(result, maxID==null); putHomeTimeline(result, maxID==null);
} }
@@ -112,7 +115,7 @@ public class CacheController{
runOnDbThread((db)->{ runOnDbThread((db)->{
if(clear) if(clear)
db.delete("home_timeline", null, null); db.delete("home_timeline", null, null);
ContentValues values=new ContentValues(4); ContentValues values=new ContentValues(3);
for(Status s:posts){ for(Status s:posts){
values.put("id", s.id); values.put("id", s.id);
values.put("json", MastodonAPIController.gson.toJson(s)); values.put("json", MastodonAPIController.gson.toJson(s));
@@ -120,7 +123,6 @@ public class CacheController{
if(s.hasGapAfter) 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());
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE); db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
} }
}); });
@@ -135,7 +137,7 @@ public class CacheController{
if(!forceReload){ if(!forceReload){
SQLiteDatabase db=getOrOpenDatabase(); SQLiteDatabase db=getOrOpenDatabase();
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all"; String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
try(Cursor cursor=db.query(table, new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`time` DESC", count+"")){ try(Cursor cursor=db.query(table, new String[]{"json"}, maxID==null ? null : "`id`<?", maxID==null ? null : new String[]{maxID}, null, null, "`id` DESC", count+"")){
if(cursor.getCount()==count){ if(cursor.getCount()==count){
ArrayList<Notification> result=new ArrayList<>(); ArrayList<Notification> result=new ArrayList<>();
cursor.moveToFirst(); cursor.moveToFirst();
@@ -146,8 +148,10 @@ public class CacheController{
ntf.postprocess(); ntf.postprocess();
newMaxID=ntf.id; newMaxID=ntf.id;
if(ntf.status!=null){ if(ntf.status!=null){
if (!new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status)) for(Filter filter:filters){
continue outer; if(filter.matches(ntf.status))
continue outer;
}
} }
result.add(ntf); result.add(ntf);
}while(cursor.moveToNext()); }while(cursor.moveToNext());
@@ -160,13 +164,17 @@ public class CacheController{
} }
} }
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain); Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain);
new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isAkkoma()) new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.pleroma != null)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<Notification> result){ public void onSuccess(List<Notification> result){
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{ callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
if(ntf.status!=null){ if(ntf.status!=null){
return new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status); for(Filter filter:filters){
if(filter.matches(ntf.status)){
return false;
}
}
} }
return true; return true;
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false)); }).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
@@ -193,7 +201,7 @@ public class CacheController{
String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all"; String table=onlyPosts ? "notifications_posts" : onlyMentions ? "notifications_mentions" : "notifications_all";
if(clear) if(clear)
db.delete(table, null, null); db.delete(table, null, null);
ContentValues values=new ContentValues(4); ContentValues values=new ContentValues(3);
for(Notification n:notifications){ for(Notification n:notifications){
if(n.type==null){ if(n.type==null){
continue; continue;
@@ -201,7 +209,6 @@ public class CacheController{
values.put("id", n.id); values.put("id", n.id);
values.put("json", MastodonAPIController.gson.toJson(n)); values.put("json", MastodonAPIController.gson.toJson(n));
values.put("type", n.type.ordinal()); values.put("type", n.type.ordinal());
values.put("time", n.createdAt.getEpochSecond());
db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE); db.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_REPLACE);
} }
}); });
@@ -298,24 +305,21 @@ public class CacheController{
CREATE TABLE `home_timeline` ( CREATE TABLE `home_timeline` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY, `id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL, `json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0, `flags` INTEGER NOT NULL DEFAULT 0
`time` INTEGER NOT NULL
)"""); )""");
db.execSQL(""" db.execSQL("""
CREATE TABLE `notifications_all` ( CREATE TABLE `notifications_all` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY, `id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL, `json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0, `flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL, `type` INTEGER NOT NULL
`time` INTEGER NOT NULL
)"""); )""");
db.execSQL(""" db.execSQL("""
CREATE TABLE `notifications_mentions` ( CREATE TABLE `notifications_mentions` (
`id` VARCHAR(25) NOT NULL PRIMARY KEY, `id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL, `json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0, `flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL, `type` INTEGER NOT NULL
`time` INTEGER NOT NULL
)"""); )""");
createRecentSearchesTable(db); createRecentSearchesTable(db);
createPostsNotificationsTable(db); createPostsNotificationsTable(db);
@@ -323,16 +327,12 @@ public class CacheController{
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
if(oldVersion<2){ if(oldVersion==1){
createRecentSearchesTable(db); createRecentSearchesTable(db);
} }
if(oldVersion<3){ if(oldVersion==2){
// MEGALODON-SPECIFIC
createPostsNotificationsTable(db); createPostsNotificationsTable(db);
} }
if(oldVersion<4){
addTimeColumns(db);
}
} }
private void createRecentSearchesTable(SQLiteDatabase db){ private void createRecentSearchesTable(SQLiteDatabase db){
@@ -350,21 +350,9 @@ public class CacheController{
`id` VARCHAR(25) NOT NULL PRIMARY KEY, `id` VARCHAR(25) NOT NULL PRIMARY KEY,
`json` TEXT NOT NULL, `json` TEXT NOT NULL,
`flags` INTEGER NOT NULL DEFAULT 0, `flags` INTEGER NOT NULL DEFAULT 0,
`type` INTEGER NOT NULL, `type` INTEGER NOT NULL
`time` INTEGER NOT NULL
)"""); )""");
} }
private void addTimeColumns(SQLiteDatabase db){
db.execSQL("DELETE FROM `home_timeline`");
db.execSQL("DELETE FROM `notifications_all`");
db.execSQL("DELETE FROM `notifications_mentions`");
db.execSQL("DELETE FROM `notifications_posts`");
db.execSQL("ALTER TABLE `home_timeline` ADD `time` INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE `notifications_all` ADD `time` INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE `notifications_mentions` ADD `time` INTEGER NOT NULL DEFAULT 0");
db.execSQL("ALTER TABLE `notifications_posts` ADD `time` INTEGER NOT NULL DEFAULT 0");
}
} }
@FunctionalInterface @FunctionalInterface

View File

@@ -17,7 +17,6 @@ import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter; import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@@ -29,7 +28,6 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -53,9 +51,7 @@ public class MastodonAPIController{
.registerTypeAdapter(Status.class, new Status.StatusDeserializer()) .registerTypeAdapter(Status.class, new Status.StatusDeserializer())
.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().build();
.readTimeout(5, TimeUnit.MINUTES)
.build();
private AccountSession session; private AccountSession session;
private static List<String> badDomains = new ArrayList<>(); private static List<String> badDomains = new ArrayList<>();
@@ -64,7 +60,7 @@ public class MastodonAPIController{
thread.start(); thread.start();
try { try {
final BufferedReader reader = new BufferedReader(new InputStreamReader( final BufferedReader reader = new BufferedReader(new InputStreamReader(
MastodonApp.context.getAssets().open("blocks.txt") MastodonApp.context.getAssets().open("blocks.tsv")
)); ));
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
@@ -95,7 +91,7 @@ public class MastodonAPIController{
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", "MastodonAndroid/"+BuildConfig.VERSION_NAME);
String token=null; String token=null;
if(session!=null) if(session!=null)
@@ -162,11 +158,6 @@ public class MastodonAPIController{
respObj=gson.fromJson(reader, req.respClass); respObj=gson.fromJson(reader, req.respClass);
} }
}catch(JsonIOException|JsonSyntaxException x){ }catch(JsonIOException|JsonSyntaxException x){
if (req.context != null && response.body().contentType().subtype().equals("html")) {
UiUtils.launchWebBrowser(req.context, response.request().url().toString());
req.cancel();
return;
}
if(BuildConfig.DEBUG) if(BuildConfig.DEBUG)
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x); Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
req.onError(x.getLocalizedMessage(), response.code(), x); req.onError(x.getLocalizedMessage(), response.code(), x);

View File

@@ -2,7 +2,6 @@ package org.joinmastodon.android.api;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@@ -21,11 +20,9 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import me.grishka.appkit.api.APIRequest; import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -47,11 +44,10 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
TypeToken<T> respTypeToken; TypeToken<T> respTypeToken;
Call okhttpCall; Call okhttpCall;
Token token; Token token;
boolean canceled, isRemote; boolean canceled;
Map<String, String> headers; Map<String, String> headers;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
protected boolean removeUnsupportedItems; protected boolean removeUnsupportedItems;
@Nullable Context context;
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){ public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
this.path=path; this.path=path;
@@ -105,21 +101,6 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
return this; return this;
} }
public MastodonAPIRequest<T> execRemote(String domain) {
return execRemote(domain, null);
}
public MastodonAPIRequest<T> execRemote(String domain, @Nullable AccountSession remoteSession) {
this.isRemote = true;
return Optional.ofNullable(remoteSession)
.or(() -> AccountSessionManager.getInstance().getLoggedInAccounts().stream()
.filter(acc -> acc.domain.equals(domain))
.findAny())
.map(AccountSession::getID)
.map(this::exec)
.orElse(this.execNoAuth(domain));
}
public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){ public MastodonAPIRequest<T> wrapProgress(Activity activity, @StringRes int message, boolean cancelable){
return wrapProgress(activity, message, cancelable, null); return wrapProgress(activity, message, cancelable, null);
} }
@@ -183,20 +164,9 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
return this; return this;
} }
public MastodonAPIRequest<T> setContext(Context context) {
this.context = context;
return this;
}
@Nullable
public Context getContext() {
return context;
}
@CallSuper @CallSuper
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{ public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
if(respObj instanceof BaseModel){ if(respObj instanceof BaseModel){
((BaseModel) respObj).isRemote = isRemote;
((BaseModel) respObj).postprocess(); ((BaseModel) respObj).postprocess();
}else if(respObj instanceof List){ }else if(respObj instanceof List){
if(removeUnsupportedItems){ if(removeUnsupportedItems){
@@ -205,7 +175,6 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
Object item=itr.next(); Object item=itr.next();
if(item instanceof BaseModel){ if(item instanceof BaseModel){
try{ try{
((BaseModel) item).isRemote = isRemote;
((BaseModel) item).postprocess(); ((BaseModel) item).postprocess();
}catch(ObjectValidationException x){ }catch(ObjectValidationException x){
Log.w(TAG, "Removing invalid object from list", x); Log.w(TAG, "Removing invalid object from list", x);
@@ -213,20 +182,15 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
} }
} }
} }
// no idea why we're post-processing twice, but well, as long
// as upstream does it like this, i don't wanna break anything
for(Object item:((List<?>) respObj)){ for(Object item:((List<?>) respObj)){
if(item instanceof BaseModel){ if(item instanceof BaseModel){
((BaseModel) item).isRemote = isRemote;
((BaseModel) item).postprocess(); ((BaseModel) item).postprocess();
} }
} }
}else{ }else{
for(Object item:((List<?>) respObj)){ for(Object item:((List<?>) respObj)){
if(item instanceof BaseModel) { if(item instanceof BaseModel)
((BaseModel) item).isRemote = isRemote;
((BaseModel) item).postprocess(); ((BaseModel) item).postprocess();
}
} }
} }
} }

View File

@@ -46,7 +46,7 @@ public class StatusInteractionController{
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
runningFavoriteRequests.remove(status.id); runningFavoriteRequests.remove(status.id);
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1)); 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));
} }
@@ -80,7 +80,7 @@ public class StatusInteractionController{
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) E.post(new StatusCountersUpdatedEvent(result)); if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
} }

View File

@@ -4,10 +4,6 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
public class GetAccountByHandle extends MastodonAPIRequest<Account>{ public class GetAccountByHandle extends MastodonAPIRequest<Account>{
/**
* note that this method usually only returns a result if the instance already knows about an
* account - so it makes sense for looking up local users, search might be preferred otherwise
*/
public GetAccountByHandle(String acct){ public GetAccountByHandle(String acct){
super(HttpMethod.GET, "/accounts/lookup", Account.class); super(HttpMethod.GET, "/accounts/lookup", Account.class);
addQueryParameter("acct", acct); addQueryParameter("acct", acct);

View File

@@ -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<>(){});
}
}

View File

@@ -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);
}
}

View File

@@ -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<>(){});
}
}

View File

@@ -4,18 +4,16 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.ListTimeline;
public class CreateList extends MastodonAPIRequest<ListTimeline> { public class CreateList extends MastodonAPIRequest<ListTimeline> {
public CreateList(String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) { public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.POST, "/lists", ListTimeline.class); super(HttpMethod.POST, "/lists", ListTimeline.class);
Request req = new Request(); Request req = new Request();
req.title = title; req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy; req.repliesPolicy = repliesPolicy;
setRequestBody(req); setRequestBody(req);
} }
public static class Request { public static class Request {
public String title; public String title;
public boolean exclusive;
public ListTimeline.RepliesPolicy repliesPolicy; public ListTimeline.RepliesPolicy repliesPolicy;
} }
} }

View File

@@ -4,11 +4,10 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.ListTimeline;
public class UpdateList extends MastodonAPIRequest<ListTimeline> { public class UpdateList extends MastodonAPIRequest<ListTimeline> {
public UpdateList(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) { public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class); super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
CreateList.Request req = new CreateList.Request(); CreateList.Request req = new CreateList.Request();
req.title = title; req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy; req.repliesPolicy = repliesPolicy;
setRequestBody(req); setRequestBody(req);
} }

View File

@@ -1,30 +0,0 @@
package org.joinmastodon.android.api.requests.notifications;
import android.text.TextUtils;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Notification;
import java.util.List;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
private String maxID;
public PleromaMarkNotificationsRead(String maxID) {
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
this.maxID = maxID;
}
@Override
public RequestBody getRequestBody() {
MultipartBody.Builder builder=new MultipartBody.Builder()
.setType(MultipartBody.FORM);
if(!TextUtils.isEmpty(maxID))
builder.addFormDataPart("max_id", maxID);
return builder.build();
}
}

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.api.requests.statuses; package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.ScheduledStatus; import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
@@ -47,7 +46,6 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public String language; public String language;
public String quoteId; public String quoteId;
public ContentType contentType;
public static class Poll{ public static class Poll{
public ArrayList<String> options=new ArrayList<>(); public ArrayList<String> options=new ArrayList<>();

View File

@@ -2,22 +2,17 @@ package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.AllFieldsAreRequired; import org.joinmastodon.android.api.AllFieldsAreRequired;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.model.BaseModel; import org.joinmastodon.android.model.BaseModel;
import org.joinmastodon.android.model.ContentType;
public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{ public class GetStatusSourceText extends MastodonAPIRequest<GetStatusSourceText.Response>{
public GetStatusSourceText(String id){ public GetStatusSourceText(String id){
super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class); super(HttpMethod.GET, "/statuses/"+id+"/source", Response.class);
} }
@AllFieldsAreRequired
public static class Response extends BaseModel{ public static class Response extends BaseModel{
@RequiredField
public String id; public String id;
@RequiredField
public String text; public String text;
@RequiredField
public String spoilerText; public String spoilerText;
public ContentType contentType;
} }
} }

View File

@@ -1,23 +0,0 @@
package org.joinmastodon.android.api.requests.timelines;
import android.text.TextUtils;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class GetBubbleTimeline extends MastodonAPIRequest<List<Status>> {
public GetBubbleTimeline(String maxID, int limit) {
super(HttpMethod.GET, "/timelines/bubble", new TypeToken<>(){});
if(!TextUtils.isEmpty(maxID))
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
if(GlobalUserPreferences.replyVisibility != null)
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
}
}

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import android.text.TextUtils;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -21,7 +20,5 @@ public class GetPublicTimeline extends MastodonAPIRequest<List<Status>>{
addQueryParameter("max_id", maxID); addQueryParameter("max_id", maxID);
if(limit>0) if(limit>0)
addQueryParameter("limit", limit+""); addQueryParameter("limit", limit+"");
if(GlobalUserPreferences.replyVisibility != null)
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
} }
} }

View File

@@ -1,7 +1,5 @@
package org.joinmastodon.android.api.session; package org.joinmastodon.android.api.session;
import android.net.Uri;
import org.joinmastodon.android.api.CacheController; import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager; import org.joinmastodon.android.api.PushSubscriptionManager;
@@ -9,7 +7,6 @@ import org.joinmastodon.android.api.StatusInteractionController;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application; import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Markers; import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.Preferences; import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushSubscription; import org.joinmastodon.android.model.PushSubscription;
@@ -17,7 +14,6 @@ import org.joinmastodon.android.model.Token;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
public class AccountSession{ public class AccountSession{
public Token token; public Token token;
@@ -91,15 +87,4 @@ public class AccountSession{
pushSubscriptionManager=new PushSubscriptionManager(getID()); pushSubscriptionManager=new PushSubscriptionManager(getID());
return pushSubscriptionManager; return pushSubscriptionManager;
} }
public Optional<Instance> getInstance() {
return Optional.ofNullable(AccountSessionManager.getInstance().getInstanceInfo(domain));
}
public Uri getInstanceUri() {
return new Uri.Builder()
.scheme("https")
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
.build();
}
} }

View File

@@ -15,7 +15,6 @@ import android.util.Log;
import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity; import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
@@ -122,12 +121,6 @@ public class AccountSessionManager{
sessions.put(session.getID(), session); sessions.put(session.getID(), session);
lastActiveAccountID=session.getID(); lastActiveAccountID=session.getID();
writeAccountsFile(); writeAccountsFile();
// write initial instance info to file immediately to avoid sessions without instance info
InstanceInfoStorageWrapper wrapper = new InstanceInfoStorageWrapper();
wrapper.instance = instance;
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
updateMoreInstanceInfo(instance, instance.uri); updateMoreInstanceInfo(instance, instance.uri);
if(PushSubscriptionManager.arePushNotificationsAvailable()){ if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null); session.getPushSubscriptionManager().registerAccountForPush(null);
@@ -136,16 +129,14 @@ public class AccountSessionManager{
} }
public synchronized void writeAccountsFile(){ public synchronized void writeAccountsFile(){
File tmpFile = new File(MastodonApp.context.getFilesDir(), "accounts.json~"); File file=new File(MastodonApp.context.getFilesDir(), "accounts.json");
File file = new File(MastodonApp.context.getFilesDir(), "accounts.json");
try{ try{
try(FileOutputStream out=new FileOutputStream(tmpFile)){ try(FileOutputStream out=new FileOutputStream(file)){
SessionsStorageWrapper w=new SessionsStorageWrapper(); SessionsStorageWrapper w=new SessionsStorageWrapper();
w.accounts=new ArrayList<>(sessions.values()); w.accounts=new ArrayList<>(sessions.values());
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8); OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
MastodonAPIController.gson.toJson(w, writer); MastodonAPIController.gson.toJson(w, writer);
writer.flush(); writer.flush();
if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
} }
}catch(IOException x){ }catch(IOException x){
Log.e(TAG, "Error writing accounts file", x); Log.e(TAG, "Error writing accounts file", x);
@@ -171,11 +162,6 @@ public class AccountSessionManager{
return sessions.get(id); return sessions.get(id);
} }
@Nullable
public AccountSession tryGetAccount(Account account) {
return sessions.get(account.getDomainFromURL() + "_" + account.id);
}
@Nullable @Nullable
public AccountSession getLastActiveAccount(){ public AccountSession getLastActiveAccount(){
if(sessions.isEmpty() || lastActiveAccountID==null) if(sessions.isEmpty() || lastActiveAccountID==null)
@@ -203,7 +189,6 @@ public class AccountSessionManager{
AccountSession session=getAccount(id); AccountSession session=getAccount(id);
session.getCacheController().closeDatabase(); session.getCacheController().closeDatabase();
MastodonApp.context.deleteDatabase(id+".db"); MastodonApp.context.deleteDatabase(id+".db");
GlobalUserPreferences.removeAccount(id);
sessions.remove(id); sessions.remove(id);
if(lastActiveAccountID.equals(id)){ if(lastActiveAccountID.equals(id)){
if(sessions.isEmpty()) if(sessions.isEmpty())
@@ -274,35 +259,31 @@ public class AccountSessionManager{
} }
public void maybeUpdateLocalInfo(){ public void maybeUpdateLocalInfo(){
maybeUpdateLocalInfo(null);
}
public void maybeUpdateLocalInfo(AccountSession activeSession){
long now=System.currentTimeMillis(); long now=System.currentTimeMillis();
HashSet<String> domains=new HashSet<>(); HashSet<String> domains=new HashSet<>();
for(AccountSession session:sessions.values()){ for(AccountSession session:sessions.values()){
domains.add(session.domain.toLowerCase()); domains.add(session.domain.toLowerCase());
if(now-session.infoLastUpdated>24L*3600_000L || session == activeSession){ // if(now-session.infoLastUpdated>24L*3600_000L){
updateSessionPreferences(session); updateSessionPreferences(session);
updateSessionLocalInfo(session); updateSessionLocalInfo(session);
} // }
if(now-session.filtersLastUpdated>3600_000L || session == activeSession){ // if(now-session.filtersLastUpdated>3600_000L){
updateSessionWordFilters(session); updateSessionWordFilters(session);
} // }
updateSessionMarkers(session); updateSessionMarkers(session);
} }
if(loadedInstances){ if(loadedInstances){
maybeUpdateCustomEmojis(domains, activeSession != null ? activeSession.domain : null); maybeUpdateCustomEmojis(domains);
} }
} }
private void maybeUpdateCustomEmojis(Set<String> domains, String activeDomain){ private void maybeUpdateCustomEmojis(Set<String> domains){
long now=System.currentTimeMillis(); long now=System.currentTimeMillis();
for(String domain:domains){ for(String domain:domains){
Long lastUpdated=instancesLastUpdated.get(domain); // Long lastUpdated=instancesLastUpdated.get(domain);
if(lastUpdated==null || now-lastUpdated>24L*3600_000L || domain.equals(activeDomain)){ // if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
updateInstanceInfo(domain); updateInstanceInfo(domain);
} // }
} }
} }
@@ -430,9 +411,7 @@ public class AccountSessionManager{
@Override @Override
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
InstanceInfoStorageWrapper wrapper=new InstanceInfoStorageWrapper();
wrapper.instance = instance;
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, domain));
} }
}) })
.execNoAuth(domain); .execNoAuth(domain);
@@ -443,13 +422,10 @@ public class AccountSessionManager{
} }
private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){ private void writeInstanceInfoFile(InstanceInfoStorageWrapper emojis, String domain){
File file = getInstanceInfoFile(domain); try(FileOutputStream out=new FileOutputStream(getInstanceInfoFile(domain))){
File tmpFile = new File(file.getPath() + "~");
try(FileOutputStream out=new FileOutputStream(tmpFile)){
OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8); OutputStreamWriter writer=new OutputStreamWriter(out, StandardCharsets.UTF_8);
MastodonAPIController.gson.toJson(emojis, writer); MastodonAPIController.gson.toJson(emojis, writer);
writer.flush(); writer.flush();
if (!tmpFile.renameTo(file)) Log.e(TAG, "Error renaming " + tmpFile.getPath() + " to " + file.getPath());
}catch(IOException x){ }catch(IOException x){
Log.w(TAG, "Error writing instance info file for "+domain, x); Log.w(TAG, "Error writing instance info file for "+domain, x);
} }
@@ -469,7 +445,7 @@ public class AccountSessionManager{
} }
if(!loadedInstances){ if(!loadedInstances){
loadedInstances=true; loadedInstances=true;
maybeUpdateCustomEmojis(domains, null); maybeUpdateCustomEmojis(domains);
} }
} }
@@ -493,6 +469,10 @@ public class AccountSessionManager{
return instances.get(domain); return instances.get(domain);
} }
public Instance getInstanceInfoForAccount(String account) {
return AccountSessionManager.getInstance().getInstanceInfo(instance.getAccount(account).domain);
}
public void updateAccountInfo(String id, Account account){ public void updateAccountInfo(String id, Account account){
AccountSession session=getAccount(id); AccountSession session=getAccount(id);
session.self=account; session.self=account;

View File

@@ -6,12 +6,10 @@ public class ListUpdatedCreatedEvent {
public final String id; public final String id;
public final String title; public final String title;
public final ListTimeline.RepliesPolicy repliesPolicy; public final ListTimeline.RepliesPolicy repliesPolicy;
public final boolean exclusive;
public ListUpdatedCreatedEvent(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) { public ListUpdatedCreatedEvent(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
this.id = id; this.id = id;
this.title = title; this.title = title;
this.exclusive = exclusive;
this.repliesPolicy = repliesPolicy; this.repliesPolicy = repliesPolicy;
} }
} }

View File

@@ -1,6 +0,0 @@
package org.joinmastodon.android.events;
public class TakePictureRequestEvent {
public TakePictureRequestEvent(){
}
}

View File

@@ -1,9 +1,12 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.animation.TranslateAnimation;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
@@ -20,7 +23,6 @@ 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.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
@@ -62,12 +64,7 @@ public class AccountTimelineFragment extends StatusListFragment{
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return; if(getActivity()==null) return;
AccountSessionManager asm = AccountSessionManager.getInstance(); result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.ACCOUNT)).collect(Collectors.toList());
result=result.stream().filter(status -> {
// don't hide own posts in own profile
if (asm.isSelf(accountID, user) && asm.isSelf(accountID, status.account)) return true;
else return new StatusFilterPredicate(accountID, getFilterContext()).test(status);
}).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
} }
}) })
@@ -88,8 +85,7 @@ public class AccountTimelineFragment extends StatusListFragment{
} }
protected void onStatusCreated(StatusCreatedEvent ev){ protected void onStatusCreated(StatusCreatedEvent ev){
AccountSessionManager asm = AccountSessionManager.getInstance(); if(!AccountSessionManager.getInstance().isSelf(accountID, ev.status.account))
if(!asm.isSelf(accountID, ev.status.account) || !asm.isSelf(accountID, user))
return; return;
if(filter==GetAccountStatuses.Filter.PINNED) return; if(filter==GetAccountStatuses.Filter.PINNED) return;
if(filter==GetAccountStatuses.Filter.DEFAULT){ if(filter==GetAccountStatuses.Filter.DEFAULT){
@@ -97,11 +93,10 @@ public class AccountTimelineFragment extends StatusListFragment{
if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id)) if(ev.status.inReplyToAccountId!=null && !ev.status.inReplyToAccountId.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id))
return; return;
}else if(filter==GetAccountStatuses.Filter.MEDIA){ }else if(filter==GetAccountStatuses.Filter.MEDIA){
if(Optional.ofNullable(ev.status.mediaAttachments).map(List::isEmpty).orElse(true)) if(ev.status.mediaAttachments.isEmpty())
return; return;
} }
prependItems(Collections.singletonList(ev.status), true); prependItems(Collections.singletonList(ev.status), true);
if (isOnTop()) scrollToTop();
} }
protected void onStatusUnpinned(StatusUnpinnedEvent ev){ protected void onStatusUnpinned(StatusUnpinnedEvent ev){
@@ -128,19 +123,4 @@ public class AccountTimelineFragment extends StatusListFragment{
protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){ protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
// no-op // no-op
} }
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
}
@Override
public Uri getWebUri(Uri.Builder base) {
// could return different uris based on filter (e.g. media -> "/media"), but i want to
// return the remote url to the user, and i don't know whether i'd need to append
// '#media' (akkoma/pleroma) or '/media' (glitch/mastodon) since i don't know anything
// about the remote instance. so, just returning the base url to the user instead
return Uri.parse(user.url);
}
} }

View File

@@ -3,7 +3,6 @@ package org.joinmastodon.android.fragments;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@@ -94,22 +93,14 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
@Override @Override
public void onSuccess(List<Announcement> result){ public void onSuccess(List<Announcement> result){
if (getActivity() == null) return; if (getActivity() == null) return;
List<Announcement> unread = result.stream().filter(a -> !a.read).collect(toList());
// get unread items first List<Announcement> read = result.stream().filter(a -> a.read).collect(toList());
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList()); onDataLoaded(unread, true);
if (data.isEmpty()) setResult(true, null); onDataLoaded(read, false);
else unreadIDs = data.stream().map(a -> a.id).collect(toList()); if (unread.isEmpty()) setResult(true, null);
else unreadIDs = unread.stream().map(a -> a.id).collect(toList());
// append read items at the end
data.addAll(result.stream().filter(a -> a.read).collect(toList()));
onDataLoaded(data, false);
} }
}) })
.exec(accountID); .exec(accountID);
} }
@Override
public Uri getWebUri(Uri.Builder base) {
return isInstanceAkkoma() ? base.path("/announcements").build() : null;
}
} }

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.app.assist.AssistContent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
@@ -14,6 +13,7 @@ import android.text.Layout;
import android.text.StaticLayout; import android.text.StaticLayout;
import android.text.TextPaint; import android.text.TextPaint;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
@@ -21,15 +21,14 @@ 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 org.joinmastodon.android.DomainManager;
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.MainActivity;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote; import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.DisplayItemsParent;
@@ -38,7 +37,6 @@ import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.BetterItemAnimator;
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.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
@@ -51,7 +49,7 @@ 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.MediaAttachmentViewController; import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent; import org.joinmastodon.android.ui.views.MediaGridLayout;
import org.joinmastodon.android.utils.TypedObjectPool; import org.joinmastodon.android.utils.TypedObjectPool;
import java.util.ArrayList; import java.util.ArrayList;
@@ -76,7 +74,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends RecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, IsOnTop, HasFab, ProvidesAssistContent.ProvidesWebUri { public abstract class BaseStatusListFragment<T extends DisplayItemsParent> extends BaseRecyclerFragment<T> implements PhotoViewerHost, ScrollableToTop, DomainDisplay{
protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>(); protected ArrayList<StatusDisplayItem> displayItems=new ArrayList<>();
protected DisplayItemsAdapter adapter; protected DisplayItemsAdapter adapter;
protected String accountID; protected String accountID;
@@ -87,20 +85,22 @@ 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 boolean currentlyScrolling;
private final int THRESHOLD = 800;
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
if (wantsComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab); if (withComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
} }
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return false; return false;
} }
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
UiUtils.loadMaxWidth(getContext());
if(GlobalUserPreferences.disableMarquee){ if(GlobalUserPreferences.disableMarquee){
setTitleMarqueeEnabled(false); setTitleMarqueeEnabled(false);
setSubtitleMarqueeEnabled(false); setSubtitleMarqueeEnabled(false);
@@ -109,6 +109,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
setRetainInstance(true); setRetainInstance(true);
} }
@Override @Override
protected RecyclerView.Adapter getAdapter(){ protected RecyclerView.Adapter getAdapter(){
return adapter=new DisplayItemsAdapter(); return adapter=new DisplayItemsAdapter();
@@ -137,7 +139,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
displayItems.clear(); displayItems.clear();
} }
protected int prependItems(List<T> items, boolean notify){ protected void prependItems(List<T> items, boolean notify){
data.addAll(0, items); data.addAll(0, items);
int offset=0; int offset=0;
for(T s:items){ for(T s:items){
@@ -150,7 +152,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
if(notify) if(notify)
adapter.notifyItemRangeInserted(0, offset); adapter.notifyItemRangeInserted(0, offset);
return offset;
} }
protected String getMaxID(){ protected String getMaxID(){
@@ -211,7 +212,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override @Override
public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){ public boolean startPhotoViewTransition(int index, @NonNull Rect outRect, @NonNull int[] outCornerRadius){
MediaAttachmentViewController holder=findPhotoViewHolder(index); MediaAttachmentViewController holder=findPhotoViewHolder(index);
if(holder!=null && list!=null){ if(holder!=null){
transitioningHolder=holder; transitioningHolder=holder;
View view=transitioningHolder.photo; View view=transitioningHolder.photo;
int[] pos={0, 0}; int[] pos={0, 0};
@@ -277,45 +278,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}); });
} }
@Override
public @Nullable View getFab() {
if (getParentFragment() instanceof HasFab l) return l.getFab();
else return fab;
}
@Override
public void showFab() {
View fab = getFab();
if (fab == null || fab.getVisibility() == View.VISIBLE) return;
fab.setVisibility(View.VISIBLE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
fab.startAnimation(animate);
}
public boolean isScrolling() {
return currentlyScrolling;
}
@Override
public void hideFab() {
View fab = getFab();
if (fab == null || fab.getVisibility() != View.VISIBLE) return;
TranslateAnimation animate = new TranslateAnimation(
0,
0,
0,
fab.getHeight() * 2);
animate.setDuration(300);
fab.startAnimation(animate);
fab.setVisibility(View.INVISIBLE);
scrollDiff = 0;
}
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
@@ -327,34 +289,52 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(currentPhotoViewer!=null) if(currentPhotoViewer!=null)
currentPhotoViewer.offsetView(-dx, -dy); currentPhotoViewer.offsetView(-dx, -dy);
View fab = getFab(); if (fab!=null && GlobalUserPreferences.enableFabAutoHide) {
if (fab!=null && GlobalUserPreferences.autoHideFab && dy != UiUtils.SCROLL_TO_TOP_DELTA) { // This piece of code should make it so that the fab is always visible if the status list scroll view is at the item at the top
if(list.getChildAt(0).getTop() == 0){
scrollDiff= THRESHOLD +1;
}else{
if(dy > 0){
scrollDiff=0;
}
}
if (dy > 0 && fab.getVisibility() == View.VISIBLE) { if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
hideFab(); TranslateAnimation animate = new TranslateAnimation(
0,
0,
0,
fab.getHeight() * 2);
animate.setDuration(300);
// animate.setFillAfter(true);
fab.startAnimation(animate);
fab.setEnabled(false);
fab.setVisibility(View.INVISIBLE);
scrollDiff = 0;
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) { } else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
if (list.getChildAt(0).getTop() == 0 || scrollDiff > 400) { if (scrollDiff > THRESHOLD) {
showFab(); TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
// animate.setFillAfter(true);
fab.startAnimation(animate);
fab.setEnabled(true);
fab.setVisibility(View.VISIBLE);
scrollDiff = 0; scrollDiff = 0;
} else { } else {
scrollDiff += Math.abs(dy); scrollDiff += Math.abs(dy);
} }
} }
} }
} }});
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
currentlyScrolling = newState != RecyclerView.SCROLL_STATE_IDLE;
}
});
list.addItemDecoration(new StatusListItemDecoration()); list.addItemDecoration(new StatusListItemDecoration());
((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){
boolean hasDescendant = false, hasAncestor = false, isWarning = false;
int lastIndex = -1, firstIndex = -1;
list.getDecoratedBoundsWithMargins(view, outRect); list.getDecoratedBoundsWithMargins(view, outRect);
RecyclerView.ViewHolder holder=list.getChildViewHolder(view); RecyclerView.ViewHolder holder=list.getChildViewHolder(view);
if(holder instanceof StatusDisplayItem.Holder){ if(holder instanceof StatusDisplayItem.Holder){
@@ -366,54 +346,29 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
for(int i=0;i<list.getChildCount();i++){ for(int i=0;i<list.getChildCount();i++){
View child=list.getChildAt(i); View child=list.getChildAt(i);
holder=list.getChildViewHolder(child); holder=list.getChildViewHolder(child);
if(holder instanceof StatusDisplayItem.Holder<?> h){ if(holder instanceof StatusDisplayItem.Holder){
String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID(); String otherID=((StatusDisplayItem.Holder<?>) holder).getItemID();
if(otherID.equals(id)){ if(otherID.equals(id)){
if (firstIndex < 0) firstIndex = i;
lastIndex = i;
StatusDisplayItem item = h.getItem();
hasDescendant = item.hasDescendantNeighbor;
// no for direct descendants because main status (right above) is
// being displayed with an extended footer - no connected layout
hasAncestor = item.hasAncestoringNeighbor && !item.isDirectDescendant;
list.getDecoratedBoundsWithMargins(child, tmpRect); list.getDecoratedBoundsWithMargins(child, tmpRect);
outRect.left=Math.min(outRect.left, tmpRect.left); outRect.left=Math.min(outRect.left, tmpRect.left);
outRect.top=Math.min(outRect.top, tmpRect.top); outRect.top=Math.min(outRect.top, tmpRect.top);
outRect.right=Math.max(outRect.right, tmpRect.right); outRect.right=Math.max(outRect.right, tmpRect.right);
outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom); outRect.bottom=Math.max(outRect.bottom, tmpRect.bottom);
if (holder instanceof WarningFilteredStatusDisplayItem.Holder) {
isWarning = true;
}
} }
} }
} }
} }
// shifting the selection box down
// see also: FooterStatusDisplayItem#onBind (setMargins)
if (isWarning || firstIndex < 0 || lastIndex < 0 ||
!(list.getChildViewHolder(list.getChildAt(lastIndex))
instanceof FooterStatusDisplayItem.Holder)) return;
int prevIndex = firstIndex - 1, nextIndex = lastIndex + 1;
boolean prevIsWarning = prevIndex > 0 && prevIndex < list.getChildCount() &&
list.getChildViewHolder(list.getChildAt(prevIndex))
instanceof WarningFilteredStatusDisplayItem.Holder;
boolean nextIsWarning = nextIndex > 0 && nextIndex < list.getChildCount() &&
list.getChildViewHolder(list.getChildAt(nextIndex))
instanceof WarningFilteredStatusDisplayItem.Holder;
if (!prevIsWarning && hasAncestor) outRect.top += V.dp(4);
if (!nextIsWarning && hasDescendant) outRect.bottom += V.dp(4);
} }
}); });
list.setItemAnimator(new BetterItemAnimator()); list.setItemAnimator(new BetterItemAnimator());
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true); ((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
updateToolbar(); updateToolbar();
if (wantsComposeButton() && !getArguments().getBoolean("__disable_fab", false)) { if (withComposeButton()) {
fab = view.findViewById(R.id.fab);
fab.setVisibility(View.VISIBLE); fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(this::onFabClick); fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(this::onFabLongClick); fab.setOnLongClickListener(this::onFabLongClick);
} else if (fab != null) {
fab.setVisibility(View.GONE);
} }
} }
@@ -502,12 +457,26 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
} }
}else{ }else{
if(holder.getItem().status.reloadWhenClicked){
Status queryStatus = holder.getItem().status;
UiUtils.lookupStatus(getContext(), queryStatus, accountID, null, status -> {
submitPollVote(holder.getItemID(), status.poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
});
return;
}
submitPollVote(holder.getItemID(), poll.id, Collections.singletonList(poll.options.indexOf(option))); submitPollVote(holder.getItemID(), poll.id, Collections.singletonList(poll.options.indexOf(option)));
} }
} }
public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){ public void onPollVoteButtonClick(PollFooterStatusDisplayItem.Holder holder){
Poll poll=holder.getItem().poll; Poll poll=holder.getItem().poll;
if(holder.getItem().status.reloadWhenClicked){
Status queryStatus = holder.getItem().status;
UiUtils.lookupStatus(getContext(), queryStatus, accountID, null, status -> {
submitPollVote(holder.getItemID(), status.poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
});
return;
}
submitPollVote(holder.getItemID(), poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList())); submitPollVote(holder.getItemID(), poll.id, poll.selectedOptions.stream().map(opt->poll.options.indexOf(opt)).collect(Collectors.toList()));
} }
@@ -596,14 +565,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
} }
public void onImageUpdated(MediaGridStatusDisplayItem.Holder holder, int index) {
holder.rebind();
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
if(mediaGrid!=null){
adapter.notifyItemChanged(mediaGrid.getAbsoluteAdapterPosition());
}
}
public void onGapClick(GapStatusDisplayItem.Holder item){} public void onGapClick(GapStatusDisplayItem.Holder item){}
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){ public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
@@ -615,7 +576,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
warning.getItem().status.filterRevealed = true; warning.getItem().status.filterRevealed = true;
} }
@Override
public String getAccountID(){ public String getAccountID(){
return accountID; return accountID;
} }
@@ -686,8 +646,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
@Override @Override
public boolean isOnTop() { public boolean isScrolledToTop() {
return isRecyclerViewOnTop(list); return list.getChildAt(0) == null || list.getChildAt(0).getTop() == 0;
} }
protected int getListWidthForMediaLayout(){ protected int getListWidthForMediaLayout(){
@@ -736,13 +696,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
currentPhotoViewer.onPause(); currentPhotoViewer.onPause();
} }
public void onFabClick(View v){ protected void onFabClick(View v){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
Nav.go(getActivity(), ComposeFragment.class, args); Nav.go(getActivity(), ComposeFragment.class, args);
} }
public boolean onFabLongClick(View v) { protected boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID); return UiUtils.pickAccountForCompose(getActivity(), accountID);
} }
@@ -754,17 +714,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return attachmentViewsPool; return attachmentViewsPool;
} }
@Override
public void onProvideAssistContent(AssistContent assistContent) {
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
}
@Override
protected void onDataLoaded(List<T> d, boolean more) {
super.onDataLoaded(d, more);
// more available, but the page isn't even full yet? seems wrong, let's load some more
if (more && d.size() < itemsPerPage) preloader.onScrolledToLastItem();
}
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{ protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
@@ -812,7 +761,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
private int currentMediaHiddenLayoutsWidth=0; private int currentMediaHiddenLayoutsWidth=0;
{ {
dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.showDividers ? R.attr.colorPollVoted : R.attr.colorWindowBackground)); dividerPaint.setColor(UiUtils.getThemeColor(getActivity(), GlobalUserPreferences.disableDividers ? R.attr.colorWindowBackground : R.attr.colorPollVoted));
dividerPaint.setStyle(Paint.Style.STROKE); dividerPaint.setStyle(Paint.Style.STROKE);
dividerPaint.setStrokeWidth(V.dp(1)); dividerPaint.setStrokeWidth(V.dp(1));
} }
@@ -826,7 +775,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling); RecyclerView.ViewHolder siblingHolder=parent.getChildViewHolder(bottomSibling);
if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh if(holder instanceof StatusDisplayItem.Holder<?> ih && siblingHolder instanceof StatusDisplayItem.Holder<?> sh
&& (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){ && (!ih.getItemID().equals(sh.getItemID()) || sh instanceof ExtendedFooterStatusDisplayItem.Holder) && ih.getItem().getType()!=StatusDisplayItem.Type.GAP){
if (!ih.getItem().isMainStatus && ih.getItem().hasDescendantNeighbor) continue;
drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint); drawDivider(child, bottomSibling, holder, siblingHolder, parent, c, dividerPaint);
} }
} }

View File

@@ -1,11 +1,9 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses; import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -37,14 +35,4 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
}) })
.exec(accountID); .exec(accountID);
} }
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path("/bookmarks").build();
}
} }

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.*; import static android.os.ext.SdkExtensions.getExtensionVersion;
import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages; import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages;
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;
@@ -8,7 +9,6 @@ import static org.joinmastodon.android.ui.utils.UiUtils.isPhotoPickerAvailable;
import static org.joinmastodon.android.utils.MastodonLanguage.allLanguages; import static org.joinmastodon.android.utils.MastodonLanguage.allLanguages;
import static org.joinmastodon.android.utils.MastodonLanguage.defaultRecentLanguages; import static org.joinmastodon.android.utils.MastodonLanguage.defaultRecentLanguages;
import android.Manifest;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
@@ -17,7 +17,6 @@ import android.app.TimePickerDialog;
import android.content.ClipData; import android.content.ClipData;
import android.content.Context; import android.content.Context;
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.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@@ -30,6 +29,7 @@ import android.graphics.drawable.LayerDrawable;
import android.icu.text.BreakIterator; import android.icu.text.BreakIterator;
import android.media.MediaMetadataRetriever; import android.media.MediaMetadataRetriever;
import android.net.Uri; import android.net.Uri;
import android.opengl.Visibility;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
@@ -71,7 +71,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;
@@ -88,7 +87,6 @@ import org.joinmastodon.android.api.requests.statuses.GetAttachmentByID;
import org.joinmastodon.android.api.requests.statuses.UploadAttachment; import org.joinmastodon.android.api.requests.statuses.UploadAttachment;
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;
@@ -96,7 +94,6 @@ import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent; import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory; import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
@@ -115,7 +112,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.utils.TransferSpeedTracker;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ComposeEditText; import org.joinmastodon.android.ui.views.ComposeEditText;
@@ -128,9 +124,6 @@ import org.joinmastodon.android.utils.StatusTextEncoder;
import org.parceler.Parcel; import org.parceler.Parcel;
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.io.InterruptedIOException;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@@ -158,7 +151,7 @@ import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener, HasAccountID { public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener{
private static final int MEDIA_RESULT=717; private static final int MEDIA_RESULT=717;
private static final int IMAGE_DESCRIPTION_RESULT=363; private static final int IMAGE_DESCRIPTION_RESULT=363;
@@ -167,8 +160,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private static final String GLITCH_LOCAL_ONLY_SUFFIX = "👁"; private static final String GLITCH_LOCAL_ONLY_SUFFIX = "👁";
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;
public static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE); public static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
@@ -191,8 +182,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private int charCount, charLimit, trimmedCharCount; private int charCount, charLimit, trimmedCharCount;
private Button publishButton, languageButton, scheduleTimeBtn, draftsBtn; private Button publishButton, languageButton, scheduleTimeBtn, draftsBtn;
private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup; private PopupMenu languagePopup, visibilityPopup, draftOptionsPopup;
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn, scheduleDraftDismiss, contentTypeBtn; private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn, scheduleDraftDismiss;
private ImageView sensitiveIcon; private ImageView sensitiveIcon;
private ComposeMediaLayout attachmentsView; private ComposeMediaLayout attachmentsView;
private TextView replyText; private TextView replyText;
@@ -244,10 +235,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private boolean creatingView; private boolean creatingView;
private boolean ignoreSelectionChanges=false; private boolean ignoreSelectionChanges=false;
private Runnable updateUploadEtaRunnable; private Runnable updateUploadEtaRunnable;
private Uri photoUri;
private String language, encoding; private String language, encoding;
private ContentType contentType;
private MastodonLanguage.LanguageResolver languageResolver; private MastodonLanguage.LanguageResolver languageResolver;
private int navigationBarColorBefore; private int navigationBarColorBefore;
@@ -255,14 +244,11 @@ 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);
navigationBarColorBefore = getActivity().getWindow().getNavigationBarColor(); navigationBarColorBefore = getActivity().getWindow().getNavigationBarColor();
getActivity().getWindow().setNavigationBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLightest)); getActivity().getWindow().setNavigationBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorBackgroundLightest));
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
contentType = GlobalUserPreferences.accountsDefaultContentTypes.get(accountID);
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID); AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
self=session.self; self=session.self;
instanceDomain=session.domain; instanceDomain=session.domain;
@@ -280,6 +266,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Nav.finish(this); Nav.finish(this);
return; return;
} }
if(customEmojis.isEmpty()){
AccountSessionManager.getInstance().updateInstanceInfo(instanceDomain);
}
Bundle bundle = savedInstanceState != null ? savedInstanceState : getArguments(); Bundle bundle = savedInstanceState != null ? savedInstanceState : getArguments();
if (bundle.containsKey("scheduledStatus")) scheduledStatus=Parcels.unwrap(bundle.getParcelable("scheduledStatus")); if (bundle.containsKey("scheduledStatus")) scheduledStatus=Parcels.unwrap(bundle.getParcelable("scheduledStatus"));
@@ -296,7 +285,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override @Override
public void onDestroy(){ public void onDestroy(){
super.onDestroy(); super.onDestroy();
E.unregister(this);
for(DraftMediaAttachment att:attachments){ for(DraftMediaAttachment att:attachments){
if(att.isUploadingOrProcessing()) if(att.isUploadingOrProcessing())
att.cancelUpload(); att.cancelUpload();
@@ -365,7 +353,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
emojiBtn=view.findViewById(R.id.btn_emoji); emojiBtn=view.findViewById(R.id.btn_emoji);
spoilerBtn=view.findViewById(R.id.btn_spoiler); spoilerBtn=view.findViewById(R.id.btn_spoiler);
visibilityBtn=view.findViewById(R.id.btn_visibility); visibilityBtn=view.findViewById(R.id.btn_visibility);
contentTypeBtn=view.findViewById(R.id.btn_content_type);
scheduleDraftView=view.findViewById(R.id.schedule_draft_view); scheduleDraftView=view.findViewById(R.id.schedule_draft_view);
scheduleDraftText=view.findViewById(R.id.schedule_draft_text); scheduleDraftText=view.findViewById(R.id.schedule_draft_text);
scheduleDraftDismiss=view.findViewById(R.id.schedule_draft_dismiss); scheduleDraftDismiss=view.findViewById(R.id.schedule_draft_dismiss);
@@ -376,28 +363,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text_below : R.id.reply_text) view.findViewById(GlobalUserPreferences.replyLineAboveHeader ? R.id.reply_text_below : R.id.reply_text)
.setVisibility(View.GONE); .setVisibility(View.GONE);
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn); if (isPhotoPickerAvailable()) {
attachPopup.inflate(R.menu.attach); PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
if(isPhotoPickerAvailable()) attachPopup.inflate(R.menu.attach);
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 {
if (isInstancePixelfed()) pollBtn.setVisibility(View.GONE); mediaBtn.setOnClickListener(v -> openFilePicker(false));
}
pollBtn.setOnClickListener(v->togglePoll()); pollBtn.setOnClickListener(v->togglePoll());
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText)); emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
spoilerBtn.setOnClickListener(v->toggleSpoiler()); spoilerBtn.setOnClickListener(v->toggleSpoiler());
@@ -409,10 +387,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
visibilityBtn.setOnClickListener(v->visibilityPopup.show()); visibilityBtn.setOnClickListener(v->visibilityPopup.show());
visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener()); visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener());
buildContentTypePopup(contentTypeBtn);
contentTypeBtn.setOnClickListener(v->contentTypePopup.show());
contentTypeBtn.setOnTouchListener(contentTypePopup.getDragToOpenListener());
scheduleDraftDismiss.setOnClickListener(v->updateScheduledAt(null)); scheduleDraftDismiss.setOnClickListener(v->updateScheduledAt(null));
scheduleTimeBtn.setOnClickListener(v->pickScheduledDateTime()); scheduleTimeBtn.setOnClickListener(v->pickScheduledDateTime());
@@ -515,10 +489,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
} }
if (savedInstanceState != null) { if(editingStatus!=null && editingStatus.visibility!=null) {
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility"); statusVisibility=editingStatus.visibility;
} else if (editingStatus != null && editingStatus.visibility != null) {
statusVisibility = editingStatus.visibility;
} else { } else {
loadDefaultStatusVisibility(savedInstanceState); loadDefaultStatusVisibility(savedInstanceState);
} }
@@ -533,20 +505,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}).setChecked(true); }).setChecked(true);
visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly); visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly);
if (savedInstanceState != null && savedInstanceState.containsKey("contentType")) {
contentType = (ContentType) savedInstanceState.getSerializable("contentType");
} else if (getArguments().containsKey("sourceContentType")) {
try {
String val = getArguments().getString("sourceContentType");
contentType = val == null ? null : ContentType.valueOf(val);
} catch (IllegalArgumentException ignored) {}
}
int contentTypeId = ContentType.getContentTypeRes(contentType);
contentTypePopup.getMenu().findItem(contentTypeId).setChecked(true);
contentTypeBtn.setSelected(contentTypeId != R.id.content_type_null && contentTypeId != R.id.content_type_plain);
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID); autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected); autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
View autocompleteView=autocompleteViewController.getView(); View autocompleteView=autocompleteViewController.getView();
@@ -583,24 +541,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
outState.putParcelableArrayList("attachments", serializedAttachments); outState.putParcelableArrayList("attachments", serializedAttachments);
} }
outState.putSerializable("visibility", statusVisibility); outState.putSerializable("visibility", statusVisibility);
outState.putSerializable("contentType", contentType);
if (scheduledAt != null) outState.putSerializable("scheduledAt", scheduledAt); if (scheduledAt != null) outState.putSerializable("scheduledAt", scheduledAt);
if (scheduledStatus != null) outState.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus)); if (scheduledStatus != null) outState.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus));
} }
@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();
@@ -635,10 +579,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override @Override
public void afterTextChanged(Editable s){ public void afterTextChanged(Editable s){
if(s.length()==0){ if(s.length()==0)
updateCharCounter();
return; return;
}
int start=lastChangeStart; int start=lastChangeStart;
int count=lastChangeCount; int count=lastChangeCount;
// offset one char back to catch an already typed '@' or '#' or ':' // offset one char back to catch an already typed '@' or '#' or ':'
@@ -807,11 +749,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(!TextUtils.isEmpty(status.spoilerText)){ if(!TextUtils.isEmpty(status.spoilerText)){
hasSpoiler=true; hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE); spoilerEdit.setVisibility(View.VISIBLE);
if ((GlobalUserPreferences.prefixReplies == ALWAYS if(GlobalUserPreferences.prefixRepliesWithRe && !status.spoilerText.startsWith("re: ")){
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
&& !status.spoilerText.startsWith("re: ")) {
spoilerEdit.setText("re: " + status.spoilerText); spoilerEdit.setText("re: " + status.spoilerText);
} else { }else{
spoilerEdit.setText(status.spoilerText); spoilerEdit.setText(status.spoilerText);
} }
spoilerBtn.setSelected(true); spoilerBtn.setSelected(true);
@@ -923,18 +863,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null); updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
buildLanguageSelector(languageButton); buildLanguageSelector(languageButton);
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE); if (editingStatus != null && scheduledStatus == null) {
if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) {
// editing an already published post // editing an already published post
draftsBtn.setVisibility(View.GONE); draftsBtn.setVisibility(View.GONE);
} }
} }
@Override
public String getAccountID() {
return accountID;
}
private void navigateToUnsentPosts() { private void navigateToUnsentPosts() {
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -1014,17 +948,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}); });
} }
private int getContentTypeName(String id) {
return switch (id) {
case "text/plain" -> R.string.sk_content_type_plain;
case "text/html" -> R.string.sk_content_type_html;
case "text/markdown" -> R.string.sk_content_type_markdown;
case "text/bbcode" -> R.string.sk_content_type_bbcode;
case "text/x.misskeymarkdown" -> R.string.sk_content_type_mfm;
default -> throw new IllegalArgumentException("Invalid content type");
};
}
private void addBottomLanguage(Menu menu) { private void addBottomLanguage(Menu menu) {
if (menu.findItem(allLanguages.size()) == null) { if (menu.findItem(allLanguages.size()) == null) {
menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)"); menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)");
@@ -1094,8 +1017,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(att.state!=AttachmentUploadState.DONE) if(att.state!=AttachmentUploadState.DONE)
nonDoneAttachmentCount++; nonDoneAttachmentCount++;
} }
publishButton.setEnabled((!isInstancePixelfed() || attachments.size() > 0) && (trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1)); publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
sendError.setVisibility(View.GONE); // sendError.setVisibility(View.GONE);
} }
private void onCustomEmojiClick(Emoji emoji){ private void onCustomEmojiClick(Emoji emoji){
@@ -1113,7 +1036,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void onPublishClick(View v){ private void onPublishClick(View v){
publish(); if (!attachments.isEmpty()
&& statusVisibility != StatusPrivacy.DIRECT
&& !attachments.stream().allMatch(attachment -> attachment.description != null && !attachment.description.isBlank())) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.mo_no_image_desc_title)
.setMessage(R.string.mo_no_image_desc)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.publish, (dialog, i)-> publish())
.show();
} else {
publish();
}
} }
private void publishErrorCallback(ErrorResponse error) { private void publishErrorCallback(ErrorResponse error) {
@@ -1171,10 +1105,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
req.status=text; req.status=text;
req.localOnly=localOnly; req.localOnly=localOnly;
req.visibility=localOnly && instance.isAkkoma() ? StatusPrivacy.LOCAL : statusVisibility; req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility;
req.sensitive=sensitive; req.sensitive=sensitive;
req.language=language; req.language=language;
req.contentType=contentType;
req.scheduledAt = scheduledAt; req.scheduledAt = scheduledAt;
if(!attachments.isEmpty()){ if(!attachments.isEmpty()){
req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList()); req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList());
@@ -1236,7 +1169,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
sendProgress.setVisibility(View.VISIBLE); sendProgress.setVisibility(View.VISIBLE);
sendError.setVisibility(View.GONE); sendError.setVisibility(View.GONE);
Callback<Status> resCallback = new Callback<>(){ Callback<Status> resCallback=new Callback<>(){
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
maybeDeleteScheduledPost(() -> { maybeDeleteScheduledPost(() -> {
@@ -1249,17 +1182,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
E.post(new StatusCountersUpdatedEvent(replyTo)); E.post(new StatusCountersUpdatedEvent(replyTo));
} }
}else{ }else{
// pixelfed doesn't return the edited status :/ E.post(new StatusUpdatedEvent(result));
Status editedStatus = result == null ? editingStatus : result;
if (result == null) {
editedStatus.text = req.status;
editedStatus.spoilerText = req.spoilerText;
editedStatus.sensitive = req.sensitive;
editedStatus.language = req.language;
// user will have to reload to see html
editedStatus.content = req.status;
}
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);
@@ -1280,6 +1203,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
}; };
if(editingStatus!=null && !redraftStatus){ if(editingStatus!=null && !redraftStatus){
new EditStatus(req, editingStatus.id) new EditStatus(req, editingStatus.id)
.setCallback(resCallback) .setCallback(resCallback)
@@ -1461,41 +1385,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
} }
} }
if(requestCode==CAMERA_PIC_REQUEST_CODE && resultCode==Activity.RESULT_OK){
addMediaAttachment(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);
}
} }
@SuppressLint("StringFormatInvalid")
private boolean addMediaAttachment(Uri uri, String description){ private boolean addMediaAttachment(Uri uri, String description){
if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){ if(getMediaAttachmentsCount()==MAX_ATTACHMENTS){
showMediaAttachmentError(getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS)); showMediaAttachmentError(getResources().getQuantityString(R.plurals.cant_add_more_than_x_attachments, MAX_ATTACHMENTS, MAX_ATTACHMENTS));
@@ -1619,7 +1511,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private void uploadMediaAttachment(DraftMediaAttachment attachment){ private void uploadMediaAttachment(DraftMediaAttachment attachment){
if(areThereAnyUploadingAttachments()){ if(areThereAnyUploadingAttachments()){
throw new IllegalStateException("there is already an attachment being uploaded"); throw new IllegalStateException("there is already an attachment being uploaded");
} }
attachment.state=AttachmentUploadState.UPLOADING; attachment.state=AttachmentUploadState.UPLOADING;
attachment.progressBar.setVisibility(View.VISIBLE); attachment.progressBar.setVisibility(View.VISIBLE);
@@ -1818,10 +1710,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
DraftMediaAttachment att=(DraftMediaAttachment) v.getTag(); DraftMediaAttachment att=(DraftMediaAttachment) v.getTag();
if(att.serverAttachment==null) if(att.serverAttachment==null)
return; return;
editMediaDescription(att);
}
private void editMediaDescription(DraftMediaAttachment att) {
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putString("attachment", att.serverAttachment.id); args.putString("attachment", att.serverAttachment.id);
@@ -1870,24 +1758,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
pollChanged=true; pollChanged=true;
updatePublishButtonState(); updatePublishButtonState();
})); }));
option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0 ? instance.configuration.polls.maxCharactersPerOption : 50)});
int maxCharactersPerOption = 50;
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0)
maxCharactersPerOption = instance.configuration.polls.maxCharactersPerOption;
else if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0)
maxCharactersPerOption = instance.pollLimits.maxOptionChars;
option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxCharactersPerOption)});
pollOptionsView.addView(option.view); pollOptionsView.addView(option.view);
pollOptions.add(option); pollOptions.add(option);
if(pollOptions.size()==(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0 ? instance.configuration.polls.maxOptions : 4))
int maxPollOptions = 4;
if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0)
maxPollOptions = instance.configuration.polls.maxOptions;
else if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0)
maxPollOptions = instance.pollLimits.maxOptions;
if(pollOptions.size()==maxPollOptions)
addPollOptionBtn.setVisibility(View.GONE); addPollOptionBtn.setVisibility(View.GONE);
return option; return option;
} }
@@ -2042,12 +1917,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
visibilityPopup=new PopupMenu(getActivity(), v); visibilityPopup=new PopupMenu(getActivity(), v);
visibilityPopup.inflate(R.menu.compose_visibility); visibilityPopup.inflate(R.menu.compose_visibility);
Menu m=visibilityPopup.getMenu(); Menu m=visibilityPopup.getMenu();
if (isInstancePixelfed()) {
m.findItem(R.id.vis_private).setVisible(false);
}
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only); MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID); boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
if (isInstanceAkkoma()) { if (instance.pleroma != null) {
m.findItem(R.id.vis_local).setVisible(true); m.findItem(R.id.vis_local).setVisible(true);
} else if (localOnly || prefsSaysSupported) { } else if (localOnly || prefsSaysSupported) {
localOnlyItem.setVisible(true); localOnlyItem.setVisible(true);
@@ -2091,38 +1963,16 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}); });
} }
@SuppressLint("ClickableViewAccessibility")
private void buildContentTypePopup(View btn) {
contentTypePopup=new PopupMenu(getActivity(), btn);
contentTypePopup.inflate(R.menu.compose_content_type);
Menu m = contentTypePopup.getMenu();
ContentType.adaptMenuToInstance(m, instance);
if (contentType != null) m.findItem(R.id.content_type_null).setVisible(false);
contentTypePopup.setOnMenuItemClickListener(i->{
int id=i.getItemId();
if (id == R.id.content_type_null) contentType = null;
else if (id == R.id.content_type_plain) contentType = ContentType.PLAIN;
else if (id == R.id.content_type_html) contentType = ContentType.HTML;
else if (id == R.id.content_type_markdown) contentType = ContentType.MARKDOWN;
else if (id == R.id.content_type_bbcode) contentType = ContentType.BBCODE;
else if (id == R.id.content_type_misskey_markdown) contentType = ContentType.MISSKEY_MARKDOWN;
else return false;
btn.setSelected(id != R.id.content_type_null && id != R.id.content_type_plain);
i.setChecked(true);
return true;
});
if (!GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID)) {
btn.setVisibility(View.GONE);
}
}
private void loadDefaultStatusVisibility(Bundle savedInstanceState) { private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
if(replyTo != null) { if(replyTo != null) {
statusVisibility = (replyTo.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : replyTo.visibility); statusVisibility = (replyTo.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : replyTo.visibility);
} }
// A saved privacy setting from a previous compose session wins over the reply visibility
if(savedInstanceState !=null){
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
}
AccountSessionManager asm = AccountSessionManager.getInstance(); AccountSessionManager asm = AccountSessionManager.getInstance();
Preferences prefs = asm.getAccount(accountID).preferences; Preferences prefs = asm.getAccount(accountID).preferences;
if (prefs != null) { if (prefs != null) {
@@ -2271,6 +2121,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
}); });
} }
private void editMediaDescription(DraftMediaAttachment att) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("attachment", att.serverAttachment.id);
args.putParcelable("uri", att.uri);
args.putString("existingDescription", att.description);
Nav.goForResult(getActivity(), ComposeImageDescriptionFragment.class, args, IMAGE_DESCRIPTION_RESULT, this);
}
@Override @Override
public CharSequence getTitle(){ public CharSequence getTitle(){

View File

@@ -1,37 +1,26 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.view.Menu;
import android.view.MenuInflater;
import org.joinmastodon.android.DomainManager; import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.MainActivity; import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.tags.GetHashtag;
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline; import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.joinmastodon.android.utils.StatusFilterPredicate; import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
public class CustomLocalTimelineFragment extends PinnableStatusListFragment implements ProvidesAssistContent.ProvidesWebUri{ public class CustomLocalTimelineFragment extends StatusListFragment {
// private String name; // private String name;
private String domain; private String domain;
private String maxID; private String maxID;
@Override @Override
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return false; return false;
} }
@@ -67,7 +56,7 @@ public class CustomLocalTimelineFragment extends PinnableStatusListFragment impl
result.stream().forEach(status -> { result.stream().forEach(status -> {
status.account.acct += "@"+domain; status.account.acct += "@"+domain;
status.mentions.forEach(mention -> mention.id = null); status.mentions.forEach(mention -> mention.id = null);
status.isRemote = true; status.reloadWhenClicked = true;
}); });
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
@@ -82,26 +71,4 @@ public class CustomLocalTimelineFragment extends PinnableStatusListFragment impl
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
loadData(); 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 Filter.FilterContext getFilterContext() {
return null;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return Uri.parse(domain);
}
@Override
protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofCustomLocalTimeline(domain);
}
} }

View File

@@ -32,12 +32,9 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetLists; 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.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.CustomLocalTimeline; 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.Instance;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
@@ -199,7 +196,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
makeBackItem(listsMenu); makeBackItem(listsMenu);
makeBackItem(hashtagsMenu); makeBackItem(hashtagsMenu);
TimelineDefinition.getAllTimelines(accountID).forEach(tl -> addTimelineToOptions(tl, timelinesMenu)); TimelineDefinition.ALL_TIMELINES.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));
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu)); hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
@@ -225,7 +222,7 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)), false); onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES), false);
updateOptionsMenu(); updateOptionsMenu();
} }
@@ -239,6 +236,11 @@ public class EditTimelinesFragment extends RecyclerFragment<TimelineDefinition>
smoothScrollRecyclerViewToTop(list); smoothScrollRecyclerViewToTop(list);
} }
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();

View File

@@ -1,11 +1,9 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses; import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -37,16 +35,4 @@ public class FavoritedStatusListFragment extends StatusListFragment{
}) })
.exec(accountID); .exec(accountID);
} }
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return base.encodedPath(isInstanceAkkoma()
? '/' + getSession().self.username + "#favorites"
: "/favourites").build();
}
} }

View File

@@ -4,7 +4,6 @@ import android.app.Activity;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
@@ -25,7 +24,6 @@ import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton; import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.Collections; import java.util.Collections;
@@ -48,7 +46,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri { public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsListFragment.AccountWrapper> implements ScrollableToTop{
private String accountID; private String accountID;
private Map<String, Relationship> relationships=Collections.emptyMap(); private Map<String, Relationship> relationships=Collections.emptyMap();
private GetAccountRelationships relationshipsRequest; private GetAccountRelationships relationshipsRequest;
@@ -151,13 +149,8 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
} }
@Override @Override
public String getAccountID() { public boolean isScrolledToTop() {
return accountID; return list.getChildAt(0).getTop() == 0;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path(isInstanceAkkoma() ? "/friend-requests" : "/follow_requests").build();
} }
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{ private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
@@ -255,10 +248,6 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount))); followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount))); followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount))); postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
followersLabel.setVisibility(item.account.followersCount < 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);
relationship=relationships.get(item.account.id); relationship=relationships.get(item.account.id);
if(relationship == null || !relationship.followedBy){ if(relationship == null || !relationship.followedBy){
actionWrap.setVisibility(View.GONE); actionWrap.setVisibility(View.GONE);

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -15,15 +14,14 @@ import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri { public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implements ScrollableToTop {
private String nextMaxID; private String nextMaxID;
private String accountID; private String accountId;
public FollowedHashtagsFragment() { public FollowedHashtagsFragment() {
super(20); super(20);
@@ -33,7 +31,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Bundle args=getArguments(); Bundle args=getArguments();
accountID=args.getString("account"); accountId=args.getString("account");
setTitle(R.string.sk_hashtags_you_follow); setTitle(R.string.sk_hashtags_you_follow);
} }
@@ -64,7 +62,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
onDataLoaded(result, nextMaxID!=null); onDataLoaded(result, nextMaxID!=null);
} }
}) })
.exec(accountID); .exec(accountId);
} }
@Override @Override
@@ -72,20 +70,14 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
return new HashtagsAdapter(); return new HashtagsAdapter();
} }
@Override @Override
public void scrollToTop() { public void scrollToTop() {
smoothScrollRecyclerViewToTop(list); smoothScrollRecyclerViewToTop(list);
} }
@Override @Override
public String getAccountID() { public boolean isScrolledToTop() {
return accountID; return list.getChildAt(0).getTop() == 0;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return isInstanceAkkoma() ? null : base.path("/followed_tags").build();
} }
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{ private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
@@ -122,7 +114,7 @@ public class FollowedHashtagsFragment extends RecyclerFragment<Hashtag> implemen
@Override @Override
public void onClick() { public void onClick() {
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following); UiUtils.openHashtagTimeline(getActivity(), accountId, item.name, item.following);
} }
} }
} }

View File

@@ -1,27 +0,0 @@
package org.joinmastodon.android.fragments;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Instance;
import java.util.Optional;
public interface HasAccountID {
String getAccountID();
default AccountSession getSession() {
return AccountSessionManager.getInstance().getAccount(getAccountID());
}
default boolean isInstanceAkkoma() {
return getInstance().map(Instance::isAkkoma).orElse(false);
}
default boolean isInstancePixelfed() {
return getInstance().map(Instance::isPixelfed).orElse(false);
}
default Optional<Instance> getInstance() {
return getSession().getInstance();
}
}

View File

@@ -1,10 +0,0 @@
package org.joinmastodon.android.fragments;
import android.view.View;
public interface HasFab {
View getFab();
void showFab();
void hideFab();
boolean isScrolling();
}

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
import android.view.Menu; import android.view.Menu;
@@ -9,6 +8,7 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.DomainManager; import org.joinmastodon.android.DomainManager;
@@ -40,7 +40,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
private MenuItem followButton; private MenuItem followButton;
@Override @Override
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return true; return true;
} }
@@ -131,7 +131,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if (getActivity() == null) return; if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList()); result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
} }
}) })
@@ -146,12 +146,12 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
} }
@Override @Override
public boolean onFabLongClick(View v) { protected boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '); return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
} }
@Override @Override
public void onFabClick(View v){ protected void onFabClick(View v){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putString("prefilledText", '#'+hashtag+' '); args.putString("prefilledText", '#'+hashtag+' ');
@@ -162,14 +162,4 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
protected void onSetFabBottomInset(int inset){ protected void onSetFabBottomInset(int inset){
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset; ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
} }
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.PUBLIC;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
}
} }

View File

@@ -2,7 +2,6 @@ package org.joinmastodon.android.fragments;
import android.app.Fragment; import android.app.Fragment;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.assist.AssistContent;
import android.content.Intent; import android.content.Intent;
import android.graphics.Outline; import android.graphics.Outline;
import android.os.Build; import android.os.Build;
@@ -21,8 +20,6 @@ import android.widget.LinearLayout;
import androidx.annotation.IdRes; import androidx.annotation.IdRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.DomainManager; import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity; import org.joinmastodon.android.MainActivity;
@@ -43,13 +40,16 @@ import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.ui.AccountSwitcherSheet; import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TabBar; import org.joinmastodon.android.ui.views.TabBar;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels; import org.parceler.Parcels;
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.Optional;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import com.squareup.otto.Subscribe;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
@@ -63,9 +63,11 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
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 HomeFragment extends AppKitFragment implements OnBackPressedListener, ProvidesAssistContent, HasAccountID { public class HomeFragment extends AppKitFragment implements OnBackPressedListener{
private FragmentRootLinearLayout content; private FragmentRootLinearLayout content;
private HomeTabFragment homeTabFragment; private HomeTabFragment homeTabFragment;
private NotificationsFragment notificationsFragment; private NotificationsFragment notificationsFragment;
private DiscoverFragment searchFragment; private DiscoverFragment searchFragment;
private ProfileFragment profileFragment; private ProfileFragment profileFragment;
@@ -77,7 +79,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
private int currentTab=R.id.tab_home; private int currentTab=R.id.tab_home;
private String accountID; private String accountID;
private boolean isPleroma;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -85,21 +86,18 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
E.register(this); E.register(this);
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
setTitle(R.string.mo_app_name); setTitle(R.string.mo_app_name);
isPleroma = AccountSessionManager.getInstance().getAccount(accountID).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);
// TODO: clean up
if(savedInstanceState==null){ if(savedInstanceState==null){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
homeTabFragment=new HomeTabFragment(); homeTabFragment=new HomeTabFragment();
homeTabFragment.setArguments(args); homeTabFragment.setArguments(args);
args=new Bundle(args); args=new Bundle(args);
args.putBoolean("disableDiscover", isPleroma);
args.putBoolean("noAutoLoad", true); args.putBoolean("noAutoLoad", true);
searchFragment=new DiscoverFragment(); searchFragment=new DiscoverFragment();
searchFragment.setArguments(args); searchFragment.setArguments(args);
@@ -151,6 +149,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
.add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment) .add(me.grishka.appkit.R.id.fragment_wrap, profileFragment).hide(profileFragment)
.commit(); .commit();
String defaultTab=getArguments().getString("tab"); String defaultTab=getArguments().getString("tab");
if("notifications".equals(defaultTab)){ if("notifications".equals(defaultTab)){
tabBar.selectTab(R.id.tab_notifications); tabBar.selectTab(R.id.tab_notifications);
@@ -171,14 +170,18 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
@Override @Override
public void onViewStateRestored(Bundle savedInstanceState){ public void onViewStateRestored(Bundle savedInstanceState){
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
if(savedInstanceState==null) return; if(savedInstanceState==null) return;
homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment"); homeTabFragment=(HomeTabFragment) getChildFragmentManager().getFragment(savedInstanceState, "homeTabFragment");
searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment"); searchFragment=(DiscoverFragment) getChildFragmentManager().getFragment(savedInstanceState, "searchFragment");
notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment"); notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment");
profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment"); profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment");
currentTab=savedInstanceState.getInt("selectedTab"); currentTab=savedInstanceState.getInt("selectedTab");
tabBar.selectTab(currentTab);
Fragment current=fragmentForTab(currentTab); Fragment current=fragmentForTab(currentTab);
getChildFragmentManager().beginTransaction() getChildFragmentManager().beginTransaction()
.hide(homeTabFragment) .hide(homeTabFragment)
.hide(searchFragment) .hide(searchFragment)
@@ -186,12 +189,15 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
.hide(profileFragment) .hide(profileFragment)
.show(current) .show(current)
.commit(); .commit();
maybeTriggerLoading(current); maybeTriggerLoading(current);
} }
@Override @Override
public void onHiddenChanged(boolean hidden){ public void onHiddenChanged(boolean hidden){
super.onHiddenChanged(hidden); super.onHiddenChanged(hidden);
if (!hidden && fragmentForTab(currentTab) instanceof DomainDisplay display)
DomainManager.getInstance().setCurrentDomain(display.getDomain());
fragmentForTab(currentTab).onHiddenChanged(hidden); fragmentForTab(currentTab).onHiddenChanged(hidden);
} }
@@ -215,7 +221,9 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom())); super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
} }
WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0); WindowInsets topOnlyInsets=insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(), 0, 0);
homeTabFragment.onApplyWindowInsets(topOnlyInsets); homeTabFragment.onApplyWindowInsets(topOnlyInsets);
searchFragment.onApplyWindowInsets(topOnlyInsets); searchFragment.onApplyWindowInsets(topOnlyInsets);
notificationsFragment.onApplyWindowInsets(topOnlyInsets); notificationsFragment.onApplyWindowInsets(topOnlyInsets);
profileFragment.onApplyWindowInsets(topOnlyInsets); profileFragment.onApplyWindowInsets(topOnlyInsets);
@@ -234,28 +242,33 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
public void setCurrentTab(@IdRes int tab){
if(tab==currentTab)
return;
tabBar.selectTab(tab);
onTabSelected(tab);
}
private void onTabSelected(@IdRes int tab){ private void onTabSelected(@IdRes int tab){
Fragment newFragment=fragmentForTab(tab); Fragment newFragment=fragmentForTab(tab);
if(tab==currentTab){ if(tab==currentTab){
if (tab == R.id.tab_search) if(tab == R.id.tab_search){
searchFragment.onSelect(); if(newFragment instanceof ScrollableToTop scrollable)
else if(newFragment instanceof ScrollableToTop scrollable) scrollable.scrollToTop();
searchFragment.selectSearch();
return;
}
if(newFragment instanceof ScrollableToTop scrollable)
scrollable.scrollToTop(); scrollable.scrollToTop();
return; return;
} }
if(tab==currentTab && tab == R.id.tab_search){
if(newFragment instanceof ScrollableToTop scrollable)
scrollable.scrollToTop();
return;
}
if (newFragment instanceof DomainDisplay display) {
DomainManager.getInstance().setCurrentDomain(display.getDomain());
}
getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit(); getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit();
maybeTriggerLoading(newFragment); maybeTriggerLoading(newFragment);
if (newFragment instanceof HasFab fabulous && !fabulous.isScrolling()) fabulous.showFab();
currentTab=tab; currentTab=tab;
((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this); ((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this);
if (tab == R.id.tab_search && isPleroma) searchFragment.selectSearch();
} }
private void maybeTriggerLoading(Fragment newFragment){ private void maybeTriggerLoading(Fragment newFragment){
@@ -282,7 +295,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){ for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
options.add(session.self.displayName+"\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(), true, true, false, accountSession -> {
getActivity().finish();
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
}).show();
return true; return true;
} }
if(tab==R.id.tab_search){ if(tab==R.id.tab_search){
@@ -318,6 +334,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void onSaveInstanceState(Bundle outState){ public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putInt("selectedTab", currentTab); outState.putInt("selectedTab", currentTab);
if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment); if (homeTabFragment.isAdded()) getChildFragmentManager().putFragment(outState, "homeTabFragment", homeTabFragment);
if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment); if (searchFragment.isAdded()) getChildFragmentManager().putFragment(outState, "searchFragment", searchFragment);
if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment); if (notificationsFragment.isAdded()) getChildFragmentManager().putFragment(outState, "notificationsFragment", notificationsFragment);
@@ -326,10 +343,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void updateNotificationBadge() { public void updateNotificationBadge() {
AccountSession session = AccountSessionManager.getInstance().getAccount(accountID); AccountSession session = AccountSessionManager.getInstance().getAccount(accountID);
Optional<Instance> instance = session.getInstance(); Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
if (instance.isEmpty()) return; // avoiding incompatibility with akkoma if (instance == null) return;
new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance.get().isAkkoma()) new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.pleroma != null)
.setCallback(new Callback<>() { .setCallback(new Callback<>() {
@Override @Override
public void onSuccess(List<Notification> notifications) { public void onSuccess(List<Notification> notifications) {
@@ -337,6 +354,9 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
try { try {
long newestId = Long.parseLong(notifications.get(0).id); long newestId = Long.parseLong(notifications.get(0).id);
long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId); long lastSeenId = Long.parseLong(session.markers.notifications.lastReadId);
System.out.println("NEWEST: " + newestId);
System.out.println("LAST SEEN: " + lastSeenId);
setNotificationBadge(newestId > lastSeenId); setNotificationBadge(newestId > lastSeenId);
} catch (Exception ignored) { } catch (Exception ignored) {
setNotificationBadge(false); setNotificationBadge(false);
@@ -350,7 +370,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
} }
}).exec(accountID); }).exec(accountID);
} }
public void setNotificationBadge(boolean badge) { public void setNotificationBadge(boolean badge) {
notificationTabIcon.setImageResource(badge notificationTabIcon.setImageResource(badge
? R.drawable.ic_fluent_alert_28_selector_badged ? R.drawable.ic_fluent_alert_28_selector_badged
@@ -366,14 +385,4 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) { public void onAllNotificationsSeen(AllNotificationsSeenEvent allNotificationsSeenEvent) {
setNotificationBadge(false); setNotificationBadge(false);
} }
@Override
public String getAccountID() {
return accountID;
}
@Override
public void onProvideAssistContent(AssistContent assistContent) {
callFragmentToProvideAssistContent(fragmentForTab(currentTab), assistContent);
}
} }

View File

@@ -10,7 +10,6 @@ import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentTransaction; import android.app.FragmentTransaction;
import android.app.assist.AssistContent;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@@ -25,7 +24,6 @@ import android.view.ViewParent;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
@@ -48,6 +46,7 @@ import org.joinmastodon.android.events.HashtagUpdatedEvent;
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.events.SelfUpdateStateChangedEvent; import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.fragments.settings.SettingsFragment;
import org.joinmastodon.android.fragments.settings.SettingsMainFragment; import org.joinmastodon.android.fragments.settings.SettingsMainFragment;
import org.joinmastodon.android.model.Announcement; import org.joinmastodon.android.model.Announcement;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
@@ -57,7 +56,6 @@ import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.SimpleViewHolder; import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater; import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@@ -75,7 +73,7 @@ import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, HasFab, ProvidesAssistContent { public class HomeTabFragment extends MastodonToolbarFragment implements ScrollableToTop, OnBackPressedListener, DomainDisplay {
private static final int ANNOUNCEMENTS_RESULT = 654; private static final int ANNOUNCEMENTS_RESULT = 654;
private String accountID; private String accountID;
@@ -103,14 +101,13 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
private PopupMenu overflowPopup; private PopupMenu overflowPopup;
private View overflowActionView = null; private View overflowActionView = null;
private boolean announcementsBadged, settingsBadged; private boolean announcementsBadged, settingsBadged;
private ImageButton fab;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
E.register(this); E.register(this);
accountID = getArguments().getString("account"); accountID = getArguments().getString("account");
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)); timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES);
assert timelineDefinitions != null; assert timelineDefinitions != null;
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE); if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
count = timelineDefinitions.size(); count = timelineDefinitions.size();
@@ -132,10 +129,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Override @Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
FrameLayout view = new FrameLayout(getContext()); FrameLayout view = new FrameLayout(getContext());
inflater.inflate(R.layout.compose_fab, view);
fab = view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(this::onFabLongClick);
pager = new ViewPager2(getContext()); pager = new ViewPager2(getContext());
toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false); toolbarFrame = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.home_toolbar, getToolbar(), false);
@@ -143,7 +136,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putBoolean("__is_tab", true); args.putBoolean("__is_tab", true);
args.putBoolean("__disable_fab", true);
args.putBoolean("onlyPosts", true); args.putBoolean("onlyPosts", true);
for (int i = 0; i < timelineDefinitions.size(); i++) { for (int i = 0; i < timelineDefinitions.size(); i++) {
@@ -211,6 +203,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (fragments[position] instanceof BaseRecyclerFragment<?> page){ if (fragments[position] instanceof BaseRecyclerFragment<?> page){
if(!page.loaded && !page.isDataLoading()) page.loadData(); if(!page.loaded && !page.isDataLoading()) page.loadData();
} }
//update recent app list url
if (fragments[position] instanceof DomainDisplay page)
DomainManager.getInstance().setCurrentDomain(page.getDomain());
} }
}); });
@@ -295,18 +291,12 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
}).exec(accountID); }).exec(accountID);
} }
private void onFabClick(View v){ @Override
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) { public String getDomain() {
l.onFabClick(v); if (fragments[pager.getCurrentItem()] instanceof DomainDisplay page) {
} return page.getDomain();
}
private boolean onFabLongClick(View v) {
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) {
return l.onFabLongClick(v);
} else {
return false;
} }
return DomainDisplay.super.getDomain();
} }
private void addListsToOverflowMenu() { private void addListsToOverflowMenu() {
@@ -456,26 +446,9 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
updateSwitcherIcon(i); updateSwitcherIcon(i);
} }
@Override
public void showFab() {
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.showFab();
}
@Override
public void hideFab() {
if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment<?> l) l.hideFab();
}
@Override
public boolean isScrolling() {
return (fragments[pager.getCurrentItem()] instanceof HasFab fabulous)
&& fabulous.isScrolling();
}
private void updateSwitcherIcon(int i) { private void updateSwitcherIcon(int i) {
timelineIcon.setImageResource(timelines[i].getIcon().iconRes); timelineIcon.setImageResource(timelines[i].getIcon().iconRes);
timelineTitle.setText(timelines[i].getTitle(getContext())); timelineTitle.setText(timelines[i].getTitle(getContext()));
showFab();
} }
@Override @Override
@@ -498,7 +471,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
} else if ((list = listItems.get(id)) != null) { } else if ((list = listItems.get(id)) != null) {
args.putString("listID", list.id); args.putString("listID", list.id);
args.putString("listTitle", list.title); args.putString("listTitle", list.title);
args.putBoolean("listIsExclusive", list.exclusive);
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal()); if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args); Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) { } else if ((hashtag = hashtagsItems.get(id)) != null) {
@@ -511,8 +483,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Override @Override
public void scrollToTop(){ public void scrollToTop(){
if (((IsOnTop) fragments[pager.getCurrentItem()]).isOnTop() && if (((ScrollableToTop) fragments[pager.getCurrentItem()]).isScrolledToTop() &&
GlobalUserPreferences.doubleTapToSwipe && !newPostsBtnShown) { !GlobalUserPreferences.disableDoubleTapToSwipe && !newPostsBtnShown) {
int nextPage = (pager.getCurrentItem() + 1) % count; int nextPage = (pager.getCurrentItem() + 1) % count;
navigateTo(nextPage); navigateTo(nextPage);
return; return;
@@ -520,6 +492,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop(); ((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop();
} }
@Override
public boolean isScrolledToTop() {
return ((ScrollableToTop) fragments[pager.getCurrentItem()]).isScrolledToTop();
}
public void hideNewPostsButton(){ public void hideNewPostsButton(){
if(!newPostsBtnShown) if(!newPostsBtnShown)
return; return;
@@ -710,15 +687,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
return hashtagsItems.values(); return hashtagsItems.values();
} }
public ImageButton getFab() {
return fab;
}
@Override
public void onProvideAssistContent(AssistContent assistContent) {
callFragmentToProvideAssistContent(fragments[pager.getCurrentItem()], assistContent);
}
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> { private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
@NonNull @NonNull
@Override @Override

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
@@ -9,6 +8,8 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.markers.SaveMarkers; import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline; import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -37,10 +38,15 @@ public class HomeTimelineFragment extends StatusListFragment {
private String lastSavedMarkerID; private String lastSavedMarkerID;
@Override @Override
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return true; return true;
} }
@Override
public String getDomain() {
return super.getDomain() + "/home";
}
@Override @Override
public void onAttach(Activity activity){ public void onAttach(Activity activity){
super.onAttach(activity); super.onAttach(activity);
@@ -48,13 +54,12 @@ public class HomeTimelineFragment extends StatusListFragment {
loadData(); loadData();
} }
private boolean typeFilterPredicate(Status s) {
return (GlobalUserPreferences.showReplies || s.inReplyToId == null) &&
(GlobalUserPreferences.showBoosts || s.reblog == null);
}
private List<Status> filterPosts(List<Status> items) { private List<Status> filterPosts(List<Status> items) {
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList()); // This is the function I must use to solve the filters thing for real
return items.stream().filter(i ->
(GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
(GlobalUserPreferences.showBoosts || i.reblog == null)
).collect(Collectors.toList());
} }
@Override @Override
@@ -103,24 +108,24 @@ public class HomeTimelineFragment extends StatusListFragment {
@Override @Override
protected void onHidden(){ protected void onHidden(){
super.onHidden(); super.onHidden();
if(!data.isEmpty()){ // if(!data.isEmpty()){
String topPostID=displayItems.get(Math.max(0, list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset())).parentID; // String topPostID=displayItems.get(list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset()).parentID;
if(!topPostID.equals(lastSavedMarkerID)){ // if(!topPostID.equals(lastSavedMarkerID)){
lastSavedMarkerID=topPostID; // lastSavedMarkerID=topPostID;
new SaveMarkers(topPostID, null) // new SaveMarkers(topPostID, null)
.setCallback(new Callback<>(){ // .setCallback(new Callback<>(){
@Override // @Override
public void onSuccess(SaveMarkers.Response result){ // public void onSuccess(SaveMarkers.Response result){
} // }
//
@Override // @Override
public void onError(ErrorResponse error){ // public void onError(ErrorResponse error){
lastSavedMarkerID=null; // lastSavedMarkerID=null;
} // }
}) // })
.exec(accountID); // .exec(accountID);
} // }
} // }
} }
public void onStatusCreated(StatusCreatedEvent ev){ public void onStatusCreated(StatusCreatedEvent ev){
@@ -151,7 +156,7 @@ public class HomeTimelineFragment extends StatusListFragment {
result.get(result.size()-1).hasGapAfter=true; result.get(result.size()-1).hasGapAfter=true;
toAdd=result; toAdd=result;
} }
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext()); StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList()); toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
if(!toAdd.isEmpty()){ if(!toAdd.isEmpty()){
prependItems(toAdd, true); prependItems(toAdd, true);
@@ -230,11 +235,11 @@ public class HomeTimelineFragment extends StatusListFragment {
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1); List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
targetList.clear(); targetList.clear();
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1); List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext()); StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
for(Status s:result){ for(Status s:result){
if(idsBelowGap.contains(s.id)) if(idsBelowGap.contains(s.id))
break; break;
if(typeFilterPredicate(s) && filterPredicate.test(s)){ if(filterPredicate.test(s)){
targetList.addAll(buildDisplayItems(s)); targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s); insertedPosts.add(s);
} }
@@ -283,14 +288,4 @@ public class HomeTimelineFragment extends StatusListFragment {
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){ protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
return true; return true;
} }
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.HOME;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path("/").build();
}
} }

View File

@@ -1,185 +0,0 @@
package org.joinmastodon.android.fragments;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.instance.GetDomainBlocks;
import org.joinmastodon.android.fragments.onboarding.GoogleMadeMeAddThisFragment;
import org.joinmastodon.android.model.DomainBlock;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Severity;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.parceler.Parcels;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import me.grishka.appkit.views.UsableRecyclerView;
public class InstanceBlockListFragment extends LoaderFragment {
private UsableRecyclerView list;
private MergeRecyclerAdapter adapter;
private View buttonBar;
private Instance instance;
private ElevationOnScrollListener onScrollListener;
private List<DomainBlock> domainBlocks;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
loadData();
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
instance=Parcels.unwrap(getArguments().getParcelable("instance"));
setTitle(R.string.mo_instance_info_moderated_servers);
}
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragment_onboarding_rules, container, false);
list=view.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
adapter=new MergeRecyclerAdapter();
adapter.addAdapter(new ItemsAdapter());
list.setAdapter(adapter);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 0, 0, DividerItemDecoration.NOT_FIRST));
buttonBar=view.findViewById(R.id.button_bar);
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
return view;
}
@Override
protected void doLoadData() {
currentRequest= new GetDomainBlocks().setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(List<DomainBlock> result) {
domainBlocks=result;
dataLoaded();
}
}).execNoAuth(instance.uri);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
getToolbar().setElevation(0);
if(onScrollListener!=null){
onScrollListener.setViews(buttonBar, getToolbar());
}
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(Build.VERSION.SDK_INT>=27){
int inset=insets.getSystemWindowInsetBottom();
buttonBar.setPadding(0, 0, 0, inset>0 ? Math.max(inset, V.dp(36)) : 0);
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}else{
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
}
@Override
public void onRefresh(){
doLoadData();
}
private class ItemsAdapter extends RecyclerView.Adapter<ItemViewHolder>{
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new ItemViewHolder();
}
@Override
public void onBindViewHolder(@NonNull ItemViewHolder holder, int position){
holder.bind(domainBlocks.get(position));
}
@Override
public int getItemCount(){
return domainBlocks.size();
}
}
private class ItemViewHolder extends BindableViewHolder<DomainBlock>{
private final TextView instanceUri, reason;
private final ImageView severity;
public ItemViewHolder(){
super(getActivity(), R.layout.item_server_block, list);
instanceUri=findViewById(R.id.instance);
reason=findViewById(R.id.reason);
severity=findViewById(R.id.severity);
}
@SuppressLint("DefaultLocale")
@Override
public void onBind(DomainBlock item){
instanceUri.setText(item.domain);
reason.setText(item.comment);
switch (item.severity) {
case SILENCE -> {
severity.setImageDrawable(getContext().getDrawable(R.drawable.ic_fluent_speaker_mute_28_regular));
severity.setContentDescription(getContext().getString(R.string.mo_severity_silence));
}
case SUSPEND -> {
severity.setImageDrawable(getContext().getDrawable(R.drawable.ic_fluent_shield_prohibited_28_regular));
severity.setContentDescription(getContext().getString(R.string.mo_severity_suspend));
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
severity.setTooltipText(severity.getContentDescription());
}
}
}
}

View File

@@ -1,497 +0,0 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Outline;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.instance.GetExtendedDescription;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.ExtendedDescription;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.CoverImageView;
import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class InstanceInfoFragment extends LoaderFragment implements ProvidesAssistContent.ProvidesWebUri {
private Instance instance;
private String extendedDescription;
private CoverImageView cover;
private TextView uri, description, readMore;
private SwipeRefreshLayout refreshLayout;
private final CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable();
private LinearLayout textWrap;
private ScrollView scrollView, textScrollView;
private float titleTransY;
private String accountID;
private Account account;
private String targetDomain;
private final ArrayList<AccountField> fields=new ArrayList<>();
private boolean refreshing;
private boolean isExpanded = false;
public UsableRecyclerView list;
private List<AccountField> metadataListData=Collections.emptyList();
private MetadataAdapter adapter;
private ListImageLoaderWrapper imgLoader;
private int statusBarHeight;
private float textMaxHeight, textCollapsedHeight;
private LinearLayout.LayoutParams collapseParams, wrapParams;
public InstanceInfoFragment(){
super(R.layout.loader_fragment_overlay_toolbar);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
accountID=getArguments().getString("account");
account= AccountSessionManager.getInstance().getAccount(accountID).self;
targetDomain=getArguments().getString("instanceDomain");
loadData();
loadExtendedDescription();
DomainManager.getInstance().setCurrentDomain(targetDomain + "/about");
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
setHasOptionsMenu(true);
}
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View content=inflater.inflate(R.layout.fragment_instance_info, container, false);
refreshLayout=content.findViewById(R.id.refresh_layout);
scrollView=content.findViewById(R.id.scroll_view);
cover=content.findViewById(R.id.cover);
uri =content.findViewById(R.id.uri);
description=content.findViewById(R.id.description);
list=content.findViewById(R.id.metadata);
textScrollView=content.findViewById(R.id.text_scroll_view);
textWrap=content.findViewById(R.id.text_wrap);
readMore=content.findViewById(R.id.read_more);
textMaxHeight=getActivity().getResources().getDimension(R.dimen.description_max_height);
textCollapsedHeight=getActivity().getResources().getDimension(R.dimen.description_collapsed_height);
collapseParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) textCollapsedHeight);
wrapParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
cover.setForeground(coverGradient);
cover.setOnClickListener(this::onCoverClick);
readMore.setOnClickListener(this::onReadMoreClick);
refreshLayout.setOnRefreshListener(this);
if(loaded){
bindViews();
dataLoaded();
}
list.setItemAnimator(new BetterItemAnimator());
list.setDrawSelectorOnTop(true);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
imgLoader=new ListImageLoaderWrapper(getActivity(), list, new RecyclerViewDelegate(list), null);
list.setAdapter(adapter=new MetadataAdapter());
list.setClipToPadding(false);
return content;
}
@Override
protected void doLoadData(){
currentRequest=new GetInstance()
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(Instance result){
if (getActivity() == null) return;
instance = result;
bindViews();
dataLoaded();
invalidateOptionsMenu();
if(refreshing) {
refreshing = false;
refreshLayout.setRefreshing(false);
}
}
})
.execNoAuth(targetDomain);
}
private void loadExtendedDescription() {
new GetExtendedDescription()
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(ExtendedDescription result){
if (getActivity() == null || result == null || TextUtils.isEmpty(result.content)) return;
extendedDescription = result.content;
updateDescription();
collapseDescription();
}
})
.execNoAuth(targetDomain);
}
private void updateDescription() {
if (instance == null || description == null)
return;
description.setText(HtmlParser.parse(TextUtils.isEmpty(extendedDescription) ?
TextUtils.isEmpty(instance.description) ? instance.shortDescription : instance.description
: extendedDescription,
account.emojis, Collections.emptyList(), Collections.emptyList(), accountID));
description.measure(
View.MeasureSpec.makeMeasureSpec(textWrap.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
}
@Override
public void onRefresh(){
if(refreshing)
return;
refreshing=true;
doLoadData();
loadExtendedDescription();
}
@Override
public void dataLoaded(){
if(getActivity()==null)
return;
setFields(fields);
super.dataLoaded();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
updateToolbar();
scrollView.setOnScrollChangeListener(this::onScrollChanged);
titleTransY=getToolbar().getLayoutParams().height;
if(toolbarTitleView!=null){
toolbarTitleView.setTranslationY(titleTransY);
toolbarSubtitleView.setTranslationY(titleTransY);
}
RecyclerFragment.setRefreshLayoutColors(refreshLayout);
}
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
updateToolbar();
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
statusBarHeight = insets.getSystemWindowInsetTop();
if(contentView!=null){
((ViewGroup.MarginLayoutParams) getToolbar().getLayoutParams()).topMargin= statusBarHeight;
}
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
private void bindViews(){
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(instance.thumbnail, 1000, 1000));
uri.setText(instance.title);
setTitle(instance.title);
setSubtitle(targetDomain);
updateDescription();
collapseDescription();
fields.clear();
if (instance.contactAccount != null) {
AccountField admin = new AccountField();
admin.parsedName=admin.name=getContext().getString(R.string.mo_instance_admin);
admin.parsedValue=buildLinkText(instance.contactAccount.url, instance.contactAccount.getDisplayUsername() + "@" + targetDomain);
fields.add(admin);
}
if (instance.email != null) {
AccountField contact = new AccountField();
contact.parsedName=getContext().getString(R.string.mo_instance_contact);
contact.parsedValue=buildLinkText("mailto:" + instance.email, instance.email);
fields.add(contact);
}
if (instance.stats != null) {
AccountField activeUsers = new AccountField();
activeUsers.parsedName=getContext().getString(R.string.mo_instance_users);
activeUsers.parsedValue= NumberFormat.getInstance().format(instance.stats.userCount);
fields.add(activeUsers);
AccountField statusCount = new AccountField();
statusCount.parsedName=getContext().getString(R.string.mo_instance_status);
statusCount.parsedValue= NumberFormat.getInstance().format(instance.stats.statusCount);
fields.add(statusCount);
}
AccountField registration = new AccountField();
registration.parsedName=getContext().getString(R.string.mo_instance_registration);
registration.parsedValue=getContext().getString(instance.registrations ? instance.approvalRequired ? R.string.mo_instance_registration_approval : R.string.mo_instance_registration_open : R.string.instance_signup_closed);
fields.add(registration);
setFields(fields);
}
private SpannableStringBuilder buildLinkText(String link, String text) {
String value = "<span class=\"h-card\"><a href=" + link + " class=\"u-url mention\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">" + text + "</a></span>";
return HtmlParser.parse(value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
}
private void collapseDescription() {
textScrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
description.measure(
View.MeasureSpec.makeMeasureSpec(textWrap.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
readMore.setText(isExpanded ? R.string.sk_collapse : R.string.sk_expand);
description.post(() -> {
boolean tooBig = description.getMeasuredHeight() > textMaxHeight;
readMore.setVisibility(tooBig ? View.VISIBLE : View.GONE);
textScrollView.setLayoutParams(tooBig && !isExpanded ? collapseParams : wrapParams);
});
}
private void updateToolbar(){
getToolbar().setBackgroundColor(0);
if(toolbarTitleView!=null){
toolbarTitleView.setTranslationY(titleTransY);
toolbarSubtitleView.setTranslationY(titleTransY);
}
getToolbar().setOnClickListener(v->scrollToTop());
getToolbar().setNavigationContentDescription(R.string.back);
}
public void scrollToTop(){
scrollView.smoothScrollTo(0, 0);
}
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
int topBarsH=getToolbar().getHeight()+statusBarHeight;
if(scrollY>cover.getHeight()-topBarsH){
cover.setTranslationY(scrollY-(cover.getHeight()-topBarsH));
cover.setTranslationZ(V.dp(10));
cover.setTransform(cover.getHeight()/2f-topBarsH/2f, 1f);
}else{
cover.setTranslationY(0f);
cover.setTranslationZ(0f);
cover.setTransform(scrollY/2f, 1f);
}
coverGradient.setTopOffset(scrollY);
cover.invalidate();
titleTransY=getToolbar().getHeight();
if(scrollY>textWrap.getTop()-topBarsH){
titleTransY=Math.max(0f, titleTransY-(scrollY-(textWrap.getTop()-topBarsH)));
}
if(toolbarTitleView!=null){
toolbarTitleView.setTranslationY(titleTransY);
toolbarSubtitleView.setTranslationY(titleTransY);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
if (instance != null) {
inflater.inflate(R.menu.instance_info, menu);
UiUtils.enableOptionsMenuIcons(getActivity(), menu);
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, targetDomain));
}
}
@Override
protected boolean wantsToolbarMenuIconsTinted() {
return false;
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
int id=item.getItemId();
if(id==R.id.share){
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, targetDomain);
startActivity(Intent.createChooser(intent, item.getTitle()));
} else if (id==R.id.open_timeline) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("domain", targetDomain);
Nav.go(getActivity(), CustomLocalTimelineFragment.class, args);
}else if (id==R.id.rules) {
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
Nav.go(getActivity(), InstanceRulesFragment.class, args);
} else if (id==R.id.moderated_servers) {
Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance));
Nav.go(getActivity(), InstanceBlockListFragment.class, args);
}
return true;
}
@Override
public boolean wantsLightStatusBar(){
return false;
}
@Override
protected int getToolbarResource(){
return R.layout.profile_toolbar;
}
private void onReadMoreClick(View view) {
isExpanded = !isExpanded;
bindViews();
}
private void onCoverClick(View v){
Drawable drawable=cover.getDrawable();
if(drawable==null || drawable instanceof ColorDrawable)
return;
new PhotoViewer(getActivity(), Attachment.createFakeAttachments(instance.thumbnail, drawable), 0,
new SingleImagePhotoViewerListener(cover, cover, null, this, () -> {
}, () -> drawable, null, null));
}
public void setFields(ArrayList<AccountField> fields){
metadataListData=fields;
if (adapter != null) adapter.notifyDataSetChanged();
}
@Override
public String getAccountID() {
return accountID;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return Uri.parse(targetDomain);
}
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> {
public MetadataAdapter(){
super(imgLoader);
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new AboutViewHolder();
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position){
holder.bind(metadataListData.get(position));
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
return metadataListData.size();
}
@Override
public int getItemViewType(int position){
return 0;
}
}
private abstract class BaseViewHolder extends BindableViewHolder<AccountField> {
public BaseViewHolder(int layout){
super(getActivity(), layout, list);
}
}
private class AboutViewHolder extends BaseViewHolder {
private final TextView title;
private final LinkedTextView value;
public AboutViewHolder(){
super(R.layout.item_profile_about);
title=findViewById(R.id.title);
value=findViewById(R.id.value);
}
@Override
public void onBind(AccountField item){
title.setText(item.parsedName);
value.setText(item.parsedValue);
value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent));
value.setCompoundDrawables(null, null, null, null);
}
}
}

View File

@@ -1,13 +1,13 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -24,7 +24,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition; 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.ListTimelineEditor;
import org.joinmastodon.android.utils.StatusFilterPredicate; import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
@@ -36,15 +36,15 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ListTimelineFragment extends PinnableStatusListFragment { public class ListTimelineFragment extends PinnableStatusListFragment {
private String listID; private String listID;
private String listTitle; private String listTitle;
@Nullable @Nullable
private ListTimeline.RepliesPolicy repliesPolicy; private ListTimeline.RepliesPolicy repliesPolicy;
private boolean exclusive;
@Override @Override
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return true; return true;
} }
@@ -54,7 +54,6 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
Bundle args = getArguments(); Bundle args = getArguments();
listID = args.getString("listID"); listID = args.getString("listID");
listTitle = args.getString("listTitle"); listTitle = args.getString("listTitle");
exclusive = args.getBoolean("listIsExclusive");
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)]; repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
setTitle(listTitle); setTitle(listTitle);
@@ -89,8 +88,8 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (super.onOptionsItemSelected(item)) return true; if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.edit) { if (item.getItemId() == R.id.edit) {
ListEditor editor = new ListEditor(getContext()); ListTimelineEditor editor = new ListTimelineEditor(getContext());
editor.applyList(listTitle, exclusive, repliesPolicy); editor.applyList(listTitle, repliesPolicy);
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_edit_list_title) .setTitle(R.string.sk_edit_list_title)
.setIcon(R.drawable.ic_fluent_people_28_regular) .setIcon(R.drawable.ic_fluent_people_28_regular)
@@ -98,15 +97,14 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
.setPositiveButton(R.string.save, (d, which) -> { .setPositiveButton(R.string.save, (d, which) -> {
String newTitle = editor.getTitle().trim(); String newTitle = editor.getTitle().trim();
setTitle(newTitle); setTitle(newTitle);
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() { new UpdateList(listID, newTitle, 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;
exclusive = list.exclusive; E.post(new ListUpdatedCreatedEvent(listID, listTitle, repliesPolicy));
E.post(new ListUpdatedCreatedEvent(listID, listTitle, exclusive, repliesPolicy));
} }
@Override @Override
@@ -129,7 +127,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
@Override @Override
protected TimelineDefinition makeTimelineDefinition() { protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofList(listID, listTitle, exclusive); return TimelineDefinition.ofList(listID, listTitle);
} }
@Override @Override
@@ -139,7 +137,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
@Override @Override
public void onSuccess(List<Status> result) { public void onSuccess(List<Status> result) {
if (getActivity() == null) return; if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList()); result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.HOME)).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
} }
}) })
@@ -154,7 +152,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
} }
@Override @Override
public void onFabClick(View v){ protected void onFabClick(View v){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
Nav.go(getActivity(), ComposeFragment.class, args); Nav.go(getActivity(), ComposeFragment.class, args);
@@ -164,15 +162,4 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
protected void onSetFabBottomInset(int inset) { protected void onSetFabBottomInset(int inset) {
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset; ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
} }
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.HOME;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path("/lists/" + listID).build();
}
} }

View File

@@ -0,0 +1,263 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
import org.joinmastodon.android.api.requests.lists.CreateList;
import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.views.ListTimelineEditor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class ListTimelinesFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop {
private String accountId;
private String profileAccountId;
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
private final HashMap<String, Boolean> userInList = new HashMap<>();
private ListsAdapter adapter;
public ListTimelinesFragment() {
super(10);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args=getArguments();
accountId=args.getString("account");
setHasOptionsMenu(true);
E.register(this);
if(args.containsKey("profileAccount")){
profileAccountId=args.getString("profileAccount");
String profileDisplayUsername = args.getString("profileDisplayUsername");
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
} else {
setTitle(R.string.sk_your_lists);
}
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_list, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.create) {
ListTimelineEditor editor = new ListTimelineEditor(getContext());
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_create_list_title)
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
.setView(editor)
.setPositiveButton(R.string.sk_create, (d, which) ->
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
@Override
public void onSuccess(ListTimeline list) {
data.add(0, list);
adapter.notifyItemRangeInserted(0, 1);
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountId)
)
.setNegativeButton(R.string.cancel, (d, which) -> {})
.show();
}
return true;
}
private void saveListMembership(String listId, boolean isMember) {
userInList.put(listId, isMember);
List<String> accountIdList = Collections.singletonList(profileAccountId);
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
req.setCallback(new Callback<>() {
@Override
public void onSuccess(Object o) {}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountId);
}
@Override
protected void doLoadData(int offset, int count){
userInListBefore.clear();
userInList.clear();
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
.setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(List<ListTimeline> lists) {
if (getActivity() == null) return;
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
userInList.putAll(userInListBefore);
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
if (profileAccountId == null) return;
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
@Override
public void onSuccess(List<ListTimeline> allLists) {
if (getActivity() == null) return;
List<ListTimeline> newLists = new ArrayList<>();
for (ListTimeline l : allLists) {
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
if (!userInListBefore.containsKey(l.id)) {
userInListBefore.put(l.id, false);
}
}
userInList.putAll(userInListBefore);
onDataLoaded(newLists, false);
}
}).exec(accountId);
}
})
.exec(accountId);
}
@Subscribe
public void onListDeletedEvent(ListDeletedEvent event) {
for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i);
if (item.id.equals(event.id)) {
data.remove(i);
adapter.notifyItemRemoved(i);
break;
}
}
}
@Subscribe
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i);
if (item.id.equals(event.id)) {
item.title = event.title;
item.repliesPolicy = event.repliesPolicy;
adapter.notifyItemChanged(i);
break;
}
}
}
@Override
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
return adapter = new ListsAdapter();
}
@Override
public void scrollToTop() {
smoothScrollRecyclerViewToTop(list);
}
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
@NonNull
@Override
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new ListViewHolder();
}
@Override
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
holder.bind(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
}
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
private final TextView title;
private final CheckBox listToggle;
public ListViewHolder(){
super(getActivity(), R.layout.item_text, list);
title=findViewById(R.id.title);
listToggle=findViewById(R.id.list_toggle);
}
@Override
public void onBind(ListTimeline item) {
title.setText(item.title);
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
if (profileAccountId != null) {
Boolean checked = userInList.get(item.id);
listToggle.setVisibility(View.VISIBLE);
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
listToggle.setOnClickListener(this::onClickToggle);
} else {
listToggle.setVisibility(View.GONE);
}
}
private void onClickToggle(View view) {
saveListMembership(item.id, listToggle.isChecked());
}
@Override
public void onClick() {
Bundle args=new Bundle();
args.putString("account", accountId);
args.putString("listID", item.id);
args.putString("listTitle", item.title);
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
}
}
}

View File

@@ -1,274 +0,0 @@
package org.joinmastodon.android.fragments;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.lists.AddAccountsToList;
import org.joinmastodon.android.api.requests.lists.CreateList;
import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.views.ListEditor;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class ListsFragment extends RecyclerFragment<ListTimeline> implements ScrollableToTop, ProvidesAssistContent.ProvidesWebUri {
private String accountID;
private String profileAccountId;
private final HashMap<String, Boolean> userInListBefore = new HashMap<>();
private final HashMap<String, Boolean> userInList = new HashMap<>();
private ListsAdapter adapter;
public ListsFragment() {
super(10);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
accountID = args.getString("account");
setHasOptionsMenu(true);
E.register(this);
if(args.containsKey("profileAccount")){
profileAccountId=args.getString("profileAccount");
String profileDisplayUsername = args.getString("profileDisplayUsername");
setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername));
} else {
setTitle(R.string.sk_your_lists);
}
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_list, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.create) {
ListEditor editor = new ListEditor(getContext());
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_create_list_title)
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
.setView(editor)
.setPositiveButton(R.string.sk_create, (d, which) ->
new CreateList(editor.getTitle(), editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
@Override
public void onSuccess(ListTimeline list) {
data.add(0, list);
adapter.notifyItemRangeInserted(0, 1);
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.exclusive, list.repliesPolicy));
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountID)
)
.setNegativeButton(R.string.cancel, (d, which) -> {})
.show();
}
return true;
}
private void saveListMembership(String listId, boolean isMember) {
userInList.put(listId, isMember);
List<String> accountIdList = Collections.singletonList(profileAccountId);
MastodonAPIRequest<Object> req = isMember ? new AddAccountsToList(listId, accountIdList) : new RemoveAccountsFromList(listId, accountIdList);
req.setCallback(new Callback<>() {
@Override
public void onSuccess(Object o) {}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountID);
}
@Override
protected void doLoadData(int offset, int count){
userInListBefore.clear();
userInList.clear();
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
.setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(List<ListTimeline> lists) {
if (getActivity() == null) return;
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
userInList.putAll(userInListBefore);
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
if (profileAccountId == null) return;
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
@Override
public void onSuccess(List<ListTimeline> allLists) {
if (getActivity() == null) return;
List<ListTimeline> newLists = new ArrayList<>();
for (ListTimeline l : allLists) {
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
if (!userInListBefore.containsKey(l.id)) {
userInListBefore.put(l.id, false);
}
}
userInList.putAll(userInListBefore);
onDataLoaded(newLists, false);
}
}).exec(accountID);
}
})
.exec(accountID);
}
@Subscribe
public void onListDeletedEvent(ListDeletedEvent event) {
for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i);
if (item.id.equals(event.id)) {
data.remove(i);
adapter.notifyItemRemoved(i);
break;
}
}
}
@Subscribe
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
for (int i = 0; i < data.size(); i++) {
ListTimeline item = data.get(i);
if (item.id.equals(event.id)) {
item.title = event.title;
item.repliesPolicy = event.repliesPolicy;
item.exclusive = event.exclusive;
adapter.notifyItemChanged(i);
break;
}
}
}
@Override
protected RecyclerView.Adapter<ListViewHolder> getAdapter() {
return adapter = new ListsAdapter();
}
@Override
public void scrollToTop() {
smoothScrollRecyclerViewToTop(list);
}
@Override
public String getAccountID() {
return accountID;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path("/lists").build();
}
private class ListsAdapter extends RecyclerView.Adapter<ListViewHolder>{
@NonNull
@Override
public ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new ListViewHolder();
}
@Override
public void onBindViewHolder(@NonNull ListViewHolder holder, int position) {
holder.bind(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
}
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
private final TextView title;
private final CheckBox listToggle;
public ListViewHolder(){
super(getActivity(), R.layout.item_text, list);
title=findViewById(R.id.title);
listToggle=findViewById(R.id.list_toggle);
}
@Override
public void onBind(ListTimeline item) {
title.setText(item.title);
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(
item.exclusive ? R.drawable.ic_fluent_rss_24_regular : R.drawable.ic_fluent_people_24_regular
), null, null, null);
if (profileAccountId != null) {
Boolean checked = userInList.get(item.id);
listToggle.setVisibility(View.VISIBLE);
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
listToggle.setOnClickListener(this::onClickToggle);
} else {
listToggle.setVisibility(View.GONE);
}
}
private void onClickToggle(View view) {
saveListMembership(item.id, listToggle.isChecked());
}
@Override
public void onClick() {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("listID", item.id);
args.putString("listTitle", item.title);
args.putBoolean("listIsExclusive", item.exclusive);
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
}
}
}

View File

@@ -30,9 +30,4 @@ public abstract class MastodonToolbarFragment extends ToolbarFragment{
toolbar.setNavigationContentDescription(R.string.back); toolbar.setNavigationContentDescription(R.string.back);
} }
} }
@Override
protected boolean wantsToolbarMenuIconsTinted() {
return false; // else, badged icons don't work :(
}
} }

View File

@@ -2,7 +2,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.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -14,12 +13,6 @@ 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 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;
@@ -31,7 +24,12 @@ 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;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.squareup.otto.Subscribe;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -39,7 +37,7 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, ProvidesAssistContent { public class NotificationsFragment extends MastodonToolbarFragment implements ScrollableToTop, DomainDisplay{
private TabLayout tabLayout; private TabLayout tabLayout;
private ViewPager2 pager; private ViewPager2 pager;
@@ -49,6 +47,12 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
private NotificationsListFragment allNotificationsFragment, mentionsFragment, postsFragment; private NotificationsListFragment allNotificationsFragment, mentionsFragment, postsFragment;
private String accountID; private String accountID;
@Override
public String getDomain() {
return DomainDisplay.super.getDomain() + "/notifications";
}
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -103,7 +107,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
tabLayout=view.findViewById(R.id.tabbar); tabLayout=view.findViewById(R.id.tabbar);
pager=view.findViewById(R.id.pager); pager=view.findViewById(R.id.pager);
UiUtils.reduceSwipeSensitivity(pager);
tabViews=new FrameLayout[3]; tabViews=new FrameLayout[3];
for(int i=0;i<tabViews.length;i++){ for(int i=0;i<tabViews.length;i++){
@@ -121,18 +124,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
tabLayout.setTabTextSize(V.dp(16)); tabLayout.setTabTextSize(V.dp(16));
tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)); tabLayout.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorTabInactive), UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {
scrollToTop();
}
});
pager.setOffscreenPageLimit(4); pager.setOffscreenPageLimit(4);
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe); pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
@@ -154,17 +145,20 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putBoolean("__is_tab", true); args.putBoolean("__is_tab", true);
args.putBoolean("noAutoLoad", true);
allNotificationsFragment=new NotificationsListFragment(); allNotificationsFragment=new NotificationsListFragment();
allNotificationsFragment.setArguments(args); allNotificationsFragment.setArguments(args);
args=new Bundle(args); args=new Bundle(args);
args.putBoolean("onlyMentions", true); args.putBoolean("onlyMentions", true);
args.putBoolean("noAutoLoad", true);
mentionsFragment=new NotificationsListFragment(); mentionsFragment=new NotificationsListFragment();
mentionsFragment.setArguments(args); mentionsFragment.setArguments(args);
args=new Bundle(args); args=new Bundle(args);
args.putBoolean("onlyPosts", true); args.putBoolean("onlyPosts", true);
args.putBoolean("noAutoLoad", true);
postsFragment=new NotificationsListFragment(); postsFragment=new NotificationsListFragment();
postsFragment.setArguments(args); postsFragment.setArguments(args);
@@ -196,7 +190,6 @@ 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;
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty()); getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
} }
@@ -212,7 +205,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
@Override @Override
public void scrollToTop(){ public void scrollToTop(){
if (getFragmentForPage(pager.getCurrentItem()).isOnTop() && GlobalUserPreferences.doubleTapToSwipe) { if (getFragmentForPage(pager.getCurrentItem()).isScrolledToTop() && !GlobalUserPreferences.disableDoubleTapToSwipe) {
int nextPage = (pager.getCurrentItem() + 1) % tabViews.length; int nextPage = (pager.getCurrentItem() + 1) % tabViews.length;
pager.setCurrentItem(nextPage, true); pager.setCurrentItem(nextPage, true);
return; return;
@@ -220,6 +213,11 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
getFragmentForPage(pager.getCurrentItem()).scrollToTop(); getFragmentForPage(pager.getCurrentItem()).scrollToTop();
} }
@Override
public boolean isScrolledToTop() {
return getFragmentForPage(pager.getCurrentItem()).isScrolledToTop();
}
public void loadData(){ public void loadData(){
refreshFollowRequestsBadge(); refreshFollowRequestsBadge();
if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading) if(allNotificationsFragment!=null && !allNotificationsFragment.loaded && !allNotificationsFragment.dataLoading)
@@ -230,7 +228,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
protected void updateToolbar(){ protected void updateToolbar(){
super.updateToolbar(); super.updateToolbar();
getToolbar().setOutlineProvider(null); getToolbar().setOutlineProvider(null);
getToolbar().setOnClickListener(v->scrollToTop());
} }
private NotificationsListFragment getFragmentForPage(int page){ private NotificationsListFragment getFragmentForPage(int page){
@@ -242,11 +239,6 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
}; };
} }
@Override
public void onProvideAssistContent(AssistContent assistContent) {
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
}
private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{ private class DiscoverPagerAdapter extends RecyclerView.Adapter<SimpleViewHolder>{
@NonNull @NonNull
@Override @Override

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@@ -11,23 +10,18 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.markers.SaveMarkers; import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.requests.notifications.PleromaMarkNotificationsRead;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.AllNotificationsSeenEvent; import org.joinmastodon.android.events.AllNotificationsSeenEvent;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.CacheablePaginatedResponse; import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Markers;
import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
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;
@@ -56,8 +50,13 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS); private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
@Override @Override
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return false; return true;
}
@Override
public String getDomain() {
return super.getDomain() + "/notifications";
} }
@Override @Override
@@ -108,8 +107,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
case UPDATE -> getString(R.string.sk_post_edited); case UPDATE -> getString(R.string.sk_post_edited);
case SIGN_UP -> getString(R.string.sk_signed_up); case SIGN_UP -> getString(R.string.sk_signed_up);
case REPORT -> getString(R.string.sk_reported); case REPORT -> getString(R.string.sk_reported);
case REACTION, PLEROMA_EMOJI_REACTION -> case EMOJI_REACTION -> getString(R.string.sk_reacted, n.emoji);
n.emoji != null ? getString(R.string.sk_reacted_with, n.emoji) : getString(R.string.sk_reacted);
}; };
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, n.emojiUrl!=null ? HtmlParser.parseCustomEmoji(extraText, Collections.singletonList(emoji)) : extraText, n, null) : null; HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, n.emojiUrl!=null ? HtmlParser.parseCustomEmoji(extraText, Collections.singletonList(emoji)) : extraText, n, null) : null;
if(n.status!=null){ if(n.status!=null){
@@ -158,17 +156,12 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
loadRelationships(needRelationships); loadRelationships(needRelationships);
maxID=result.maxID; maxID=result.maxID;
Markers markers = AccountSessionManager.getInstance().getAccount(accountID).markers; if(offset==0 && !result.items.isEmpty() && !result.isFromCache()){
if(offset==0 && !result.items.isEmpty() && !result.isFromCache() && markers != null && markers.notifications != null){
E.post(new AllNotificationsSeenEvent()); E.post(new AllNotificationsSeenEvent());
new SaveMarkers(null, result.items.get(0).id).exec(accountID); new SaveMarkers(null, result.items.get(0).id).exec(accountID);
AccountSessionManager.getInstance().getAccount(accountID).markers AccountSessionManager.getInstance().getAccount(accountID).markers
.notifications.lastReadId = result.items.get(0).id; .notifications.lastReadId = result.items.get(0).id;
AccountSessionManager.getInstance().writeAccountsFile(); AccountSessionManager.getInstance().writeAccountsFile();
if (isInstanceAkkoma()) {
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
}
} }
} }
}); });
@@ -188,23 +181,40 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
protected void onShown(){ protected void onShown(){
super.onShown(); super.onShown();
// if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading){
// loadData(); refreshing=true;
loadData();
}
} }
@Override @Override
public void onItemClick(String id){ public void onItemClick(String id){
Notification n=getNotificationByID(id); Notification n=getNotificationByID(id);
Bundle args = new Bundle(); if(n.status!=null){
if(n.status != null && n.status.inReplyToAccountId != null && knownAccounts.containsKey(n.status.inReplyToAccountId)) Status status=n.status;
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(n.status.inReplyToAccountId))); Bundle args=new Bundle();
UiUtils.showFragmentForNotification(getContext(), n, accountID, args); args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(status));
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
Nav.go(getActivity(), ThreadFragment.class, args);
}else if(n.report != null){
String domain = AccountSessionManager.getInstance().getAccount(accountID).domain;
UiUtils.launchWebBrowser(getActivity(), "https://"+domain+"/admin/reports/"+n.report.id);
}else{
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(n.account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
} }
@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 InsetStatusItemDecoration(this));
if (getParentFragment() instanceof NotificationsFragment) fab.setVisibility(View.GONE);
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap); if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
} }
@@ -230,32 +240,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
} }
} }
// copied from StatusListFragment.EventListener (just like the method above)
// (which assumes this.data to be a list of statuses...)
@Subscribe
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
for(Notification n:data){
if (n.status == null) continue;
if(n.status.getContentStatus().id.equals(ev.id)){
n.status.getContentStatus().update(ev);
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof FooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
footer.rebind();
}else if(holder instanceof ExtendedFooterStatusDisplayItem.Holder footer && footer.getItem().status==n.status.getContentStatus()){
footer.rebind();
}
}
}
}
for(Notification n:preloadedData){
if (n.status == null) continue;
if(n.status.getContentStatus().id.equals(ev.id)){
n.status.getContentStatus().update(ev);
}
}
}
@Subscribe @Subscribe
public void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){ public void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){
if(!ev.accountID.equals(accountID) || ev.isUnfollow) if(!ev.accountID.equals(accountID) || ev.isUnfollow)
@@ -288,11 +272,4 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
displayItems.subList(index, lastIndex).clear(); displayItems.subList(index, lastIndex).clear();
adapter.notifyItemRangeRemoved(index, lastIndex-index); adapter.notifyItemRangeRemoved(index, lastIndex-index);
} }
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path(isInstanceAkkoma()
? "/users/" + getSession().self.username + "/interactions"
: "/notifications").build();
}
} }

View File

@@ -21,7 +21,7 @@ public abstract class PinnableStatusListFragment extends StatusListFragment impl
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID))); pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
} }
@Override @Override

View File

@@ -1,52 +0,0 @@
package org.joinmastodon.android.fragments;
import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status;
import org.parceler.Parcels;
import java.util.List;
import me.grishka.appkit.api.SimpleCallback;
public class PinnedPostsListFragment extends StatusListFragment{
private Account account;
public PinnedPostsListFragment() {
setListLayoutId(R.layout.recycler_fragment_no_refresh);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
setTitle(R.string.posts);
loadData();
}
@Override
protected void doLoadData(int offset, int count){
new GetAccountStatuses(account.id, null, null, 100, GetAccountStatuses.Filter.PINNED)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
onDataLoaded(result, false);
}
}).exec(accountID);
}
@Override
protected Filter.FilterContext getFilterContext() {
return Filter.FilterContext.ACCOUNT;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return Uri.parse(account.url);
}
}

View File

@@ -6,7 +6,6 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.app.assist.AssistContent;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Color; import android.graphics.Color;
@@ -20,7 +19,6 @@ import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ImageSpan; import android.text.style.ImageSpan;
import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -54,7 +52,6 @@ import org.joinmastodon.android.DomainManager;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity; import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.accounts.GetAccountByID; import org.joinmastodon.android.api.requests.accounts.GetAccountByID;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
@@ -85,7 +82,6 @@ import org.joinmastodon.android.ui.views.CoverImageView;
import org.joinmastodon.android.ui.views.LinkedTextView; import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.ui.views.NestedRecyclerScrollView; import org.joinmastodon.android.ui.views.NestedRecyclerScrollView;
import org.joinmastodon.android.ui.views.ProgressBarButton; import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -96,13 +92,9 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -123,7 +115,7 @@ import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop, HasFab, ProvidesAssistContent.ProvidesWebUri { public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
private static final int AVATAR_RESULT=722; private static final int AVATAR_RESULT=722;
private static final int COVER_RESULT=343; private static final int COVER_RESULT=343;
@@ -134,8 +126,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ProgressBarButton actionButton, notifyButton; private ProgressBarButton actionButton, notifyButton;
private ViewPager2 pager; private ViewPager2 pager;
private NestedRecyclerScrollView scrollView; private NestedRecyclerScrollView scrollView;
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, mediaFragment; private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
private PinnedPostsListFragment pinnedPostsFragment;
// private ProfileAboutFragment aboutFragment; // private ProfileAboutFragment aboutFragment;
private TabLayout tabbar; private TabLayout tabbar;
private SwipeRefreshLayout refreshLayout; private SwipeRefreshLayout refreshLayout;
@@ -152,9 +143,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
public FrameLayout noteWrap; public FrameLayout noteWrap;
public EditText noteEdit; public EditText noteEdit;
private String note; private String note;
private Account account, remoteAccount; private Account account;
private String accountID; private String accountID;
private String domain;
private Relationship relationship; private Relationship relationship;
private int statusBarHeight; private int statusBarHeight;
private boolean isOwnProfile; private boolean isOwnProfile;
@@ -168,8 +158,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private WindowInsets childInsets; private WindowInsets childInsets;
private PhotoViewer currentPhotoViewer; private PhotoViewer currentPhotoViewer;
private boolean editModeLoading; private boolean editModeLoading;
protected int scrollDiff = 0;
private int maxFields = 4; private static final int MAX_FIELDS=4;
// from ProfileAboutFragment // from ProfileAboutFragment
public UsableRecyclerView list; public UsableRecyclerView list;
@@ -190,22 +181,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
setRetainInstance(true); setRetainInstance(true);
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
domain=AccountSessionManager.getInstance().getAccount(accountID).domain; if(getArguments().containsKey("profileAccount")){
if (getArguments().containsKey("remoteAccount")) {
remoteAccount = Parcels.unwrap(getArguments().getParcelable("remoteAccount"));
if(!getArguments().getBoolean("noAutoLoad", false))
loadData();
} else if(getArguments().containsKey("profileAccount")){
account=Parcels.unwrap(getArguments().getParcelable("profileAccount")); account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
profileAccountID=account.id; profileAccountID=account.id;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account); isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
loaded=true; loaded=true;
if(!isOwnProfile) if(!isOwnProfile)
loadRelationship(); loadRelationship();
else if (isInstanceAkkoma() && getInstance().isPresent())
if(getInstance().get().pleroma != null && getInstance().get().pleroma.metadata != null && getInstance().get().pleroma.metadata.fieldsLimits != null){
maxFields = getInstance().get().pleroma.metadata.fieldsLimits.maxFields;
}
}else{ }else{
profileAccountID=getArguments().getString("profileAccountID"); profileAccountID=getArguments().getString("profileAccountID");
if(!getArguments().getBoolean("noAutoLoad", false)) if(!getArguments().getBoolean("noAutoLoad", false))
@@ -224,6 +206,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
DomainManager.getInstance().setCurrentDomain(account.url);
}
}
@Override @Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){ public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View content=inflater.inflate(R.layout.fragment_profile, container, false); View content=inflater.inflate(R.layout.fragment_profile, container, false);
@@ -242,6 +232,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followingCount=content.findViewById(R.id.following_count); followingCount=content.findViewById(R.id.following_count);
followingLabel=content.findViewById(R.id.following_label); followingLabel=content.findViewById(R.id.following_label);
followingBtn=content.findViewById(R.id.following_btn); followingBtn=content.findViewById(R.id.following_btn);
postsCount=content.findViewById(R.id.posts_count); postsCount=content.findViewById(R.id.posts_count);
postsLabel=content.findViewById(R.id.posts_label); postsLabel=content.findViewById(R.id.posts_label);
postsBtn=content.findViewById(R.id.posts_btn); postsBtn=content.findViewById(R.id.posts_btn);
@@ -281,6 +272,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
0, 0,
fab.getHeight() * 2); fab.getHeight() * 2);
animate.setDuration(300); animate.setDuration(300);
animate.setFillAfter(true);
fab.startAnimation(animate); fab.startAnimation(animate);
noteEditConfirm.setVisibility(View.VISIBLE); noteEditConfirm.setVisibility(View.VISIBLE);
@@ -295,6 +287,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
fab.getHeight() * 2, fab.getHeight() * 2,
0); 0);
animate.setDuration(300); animate.setDuration(300);
animate.setFillAfter(true);
fab.startAnimation(animate); fab.startAnimation(animate);
noteEditConfirm.animate() noteEditConfirm.animate()
@@ -393,20 +386,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followersBtn.setOnClickListener(this::onFollowersOrFollowingClick); followersBtn.setOnClickListener(this::onFollowersOrFollowingClick);
followingBtn.setOnClickListener(this::onFollowersOrFollowingClick); followingBtn.setOnClickListener(this::onFollowersOrFollowingClick);
//this currently takes up the whole username
//in the future it might need to be change to only the instance uri
username.setOnClickListener(v -> {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("instanceDomain", Uri.parse(account.url).getHost());
Nav.go(getActivity(), InstanceInfoFragment.class, args);
});
username.setOnLongClickListener(v->{ username.setOnLongClickListener(v->{
String usernameString=account.acct; String usernameString=account.acct;
if(!usernameString.contains("@")){ if(!usernameString.contains("@")){
usernameString+="@"+domain; usernameString+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
} }
UiUtils.copyText(username, '@'+usernameString); UiUtils.copyText(username, '@'+usernameString);
return true; return true;
@@ -423,32 +406,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return sizeWrapper; return sizeWrapper;
} }
private void onAccountLoaded(Account result) {
account=result;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
bindHeaderView();
dataLoaded();
if(!tabLayoutMediator.isAttached())
tabLayoutMediator.attach();
if(!isOwnProfile)
loadRelationship();
else
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
if(refreshing){
refreshing=false;
refreshLayout.setRefreshing(false);
if(postsFragment.loaded)
postsFragment.onRefresh();
if(postsWithRepliesFragment.loaded)
postsWithRepliesFragment.onRefresh();
if(pinnedPostsFragment.loaded)
pinnedPostsFragment.onRefresh();
if(mediaFragment.loaded)
mediaFragment.onRefresh();
}
V.setVisibilityAnimated(fab, View.VISIBLE);
}
public void setNote(String note){ public void setNote(String note){
this.note=note; this.note=note;
noteWrap.setVisibility(View.VISIBLE); noteWrap.setVisibility(View.VISIBLE);
@@ -462,35 +419,42 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
public void onSuccess(Relationship result) {} public void onSuccess(Relationship result) {}
@Override @Override
public void onError(ErrorResponse error) { public void onError(ErrorResponse result) {
error.showToast(getActivity()); Toast.makeText(getActivity(), getString(R.string.mo_personal_note_update_failed), Toast.LENGTH_LONG).show();
} }
}).exec(accountID); }).exec(accountID);
} }
@Override @Override
protected void doLoadData(){ protected void doLoadData(){
if (remoteAccount != null) {
UiUtils.lookupAccountHandle(getContext(), accountID, remoteAccount.getFullyQualifiedName(), (c, args) -> {
if (getContext() == null) return;
if (args == null || !args.containsKey("profileAccount")) {
Toast.makeText(getContext(), getContext().getString(
R.string.sk_error_loading_profile, domain
), Toast.LENGTH_SHORT).show();
Nav.finish(this);
return;
}
onAccountLoaded(Parcels.unwrap(args.getParcelable("profileAccount")));
});
return;
}
currentRequest=new GetAccountByID(profileAccountID) currentRequest=new GetAccountByID(profileAccountID)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(Account result){ public void onSuccess(Account result){
if (getActivity() == null) return; if (getActivity() == null) return;
onAccountLoaded(result); account=result;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
bindHeaderView();
dataLoaded();
if(!tabLayoutMediator.isAttached())
tabLayoutMediator.attach();
if(!isOwnProfile)
loadRelationship();
else
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
if(refreshing){
refreshing=false;
refreshLayout.setRefreshing(false);
if(postsFragment.loaded)
postsFragment.onRefresh();
if(postsWithRepliesFragment.loaded)
postsWithRepliesFragment.onRefresh();
if(pinnedPostsFragment.loaded)
pinnedPostsFragment.onRefresh();
if(mediaFragment.loaded)
mediaFragment.onRefresh();
}
V.setVisibilityAnimated(fab, View.VISIBLE);
} }
}) })
.exec(accountID); .exec(accountID);
@@ -516,14 +480,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(postsFragment==null){ if(postsFragment==null){
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true); postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false); postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false); mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(account));
args.putBoolean("__is_tab", true);
pinnedPostsFragment=new PinnedPostsListFragment();
pinnedPostsFragment.setArguments(args);
// aboutFragment=new ProfileAboutFragment(); // aboutFragment=new ProfileAboutFragment();
setFields(fields); setFields(fields);
} }
@@ -636,12 +594,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account); boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
String acct = ((isSelf || account.isRemote)
? account.getFullyQualifiedName()
: account.acct);
if(account.locked){ if(account.locked){
ssb=new SpannableStringBuilder("@"); ssb=new SpannableStringBuilder("@");
ssb.append(acct); ssb.append(account.acct);
if(isSelf){
ssb.append('@');
ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain);
}
ssb.append(" "); ssb.append(" ");
Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate(); Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate();
lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight()); lock.setBounds(0, 0, lock.getIntrinsicWidth(), lock.getIntrinsicHeight());
@@ -663,18 +623,17 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
username.setText(ssb); username.setText(ssb);
}else{ }else{
// noinspection SetTextI18n // noinspection SetTextI18n
username.setText('@'+acct); username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : ""));
}
CharSequence parsedBio = null;
if(account.note != null){
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
} }
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(TextUtils.isEmpty(parsedBio)){ if(TextUtils.isEmpty(parsedBio)){
bio.setVisibility(View.GONE); bio.setVisibility(View.GONE);
}else{ }else{
bio.setVisibility(View.VISIBLE); bio.setVisibility(View.VISIBLE);
bio.setText(parsedBio); bio.setText(parsedBio);
} }
followersCount.setText(UiUtils.abbreviateNumber(account.followersCount)); followersCount.setText(UiUtils.abbreviateNumber(account.followersCount));
followingCount.setText(UiUtils.abbreviateNumber(account.followingCount)); followingCount.setText(UiUtils.abbreviateNumber(account.followingCount));
postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount)); postsCount.setText(UiUtils.abbreviateNumber(account.statusesCount));
@@ -682,9 +641,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, account.followingCount))); followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, account.followingCount)));
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, account.statusesCount))); postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, account.statusesCount)));
if (account.followersCount < 0) followersBtn.setVisibility(View.GONE);
if (account.followingCount < 0) followingBtn.setVisibility(View.GONE);
UiUtils.loadCustomEmojiInTextView(name); UiUtils.loadCustomEmojiInTextView(name);
UiUtils.loadCustomEmojiInTextView(bio); UiUtils.loadCustomEmojiInTextView(bio);
@@ -739,11 +695,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return false; return false;
} }
@Override
protected boolean wantsToolbarMenuIconsTinted() {
return false;
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
if(isOwnProfile && isInEditMode){ if(isOwnProfile && isInEditMode){
@@ -778,10 +729,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
)); ));
} }
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername())); menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
if(isOwnProfile) { if(isOwnProfile)
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
return; return;
}
MenuItem mute = menu.findItem(R.id.mute); MenuItem mute = menu.findItem(R.id.mute);
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername())); mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
@@ -863,7 +812,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("profileAccount", profileAccountID); args.putString("profileAccount", profileAccountID);
args.putString("profileDisplayUsername", account.getDisplayUsername()); args.putString("profileDisplayUsername", account.getDisplayUsername());
} }
Nav.go(getActivity(), ListsFragment.class, args); Nav.go(getActivity(), ListTimelinesFragment.class, args);
}else if(id==R.id.followed_hashtags){ }else if(id==R.id.followed_hashtags){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -916,28 +865,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
// aboutFragment.setNote(relationship.note, accountID, profileAccountID); // aboutFragment.setNote(relationship.note, accountID, profileAccountID);
} }
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username)); notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
DomainManager.getInstance().setCurrentDomain(account.url);
} }
public ImageButton getFab() { public ImageButton getFab() {
return fab; return fab;
} }
@Override
public void showFab() {
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.showFab();
}
@Override
public void hideFab() {
if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.hideFab();
}
@Override
public boolean isScrolling() {
return getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous
&& fabulous.isScrolling();
}
private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){ private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){
int topBarsH=getToolbar().getHeight()+statusBarHeight; int topBarsH=getToolbar().getHeight()+statusBarHeight;
if(scrollY>avatarBorder.getTop()-topBarsH){ if(scrollY>avatarBorder.getTop()-topBarsH){
@@ -968,6 +902,36 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(currentPhotoViewer!=null){ if(currentPhotoViewer!=null){
currentPhotoViewer.offsetView(0, oldScrollY-scrollY); currentPhotoViewer.offsetView(0, oldScrollY-scrollY);
} }
if(GlobalUserPreferences.enableFabAutoHide){
int dy = scrollY - oldScrollY;
if (dy > 0 && fab.getVisibility() == View.VISIBLE) {
TranslateAnimation animate = new TranslateAnimation(
0,
0,
0,
fab.getHeight() * 2);
animate.setDuration(300);
fab.startAnimation(animate);
fab.setVisibility(View.INVISIBLE);
scrollDiff = 0;
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) {
if (scrollDiff > 400) {
fab.setVisibility(View.VISIBLE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
fab.startAnimation(animate);
scrollDiff = 0;
} else {
scrollDiff += Math.abs(dy);
}
}
}
} }
private Fragment getFragmentForPage(int page){ private Fragment getFragmentForPage(int page){
@@ -1268,6 +1232,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
scrollView.smoothScrollTo(0, 0); scrollView.smoothScrollTo(0, 0);
} }
@Override
public boolean isScrolledToTop() {
return list.getChildAt(0).getTop() == 0;
}
private void onFollowersOrFollowingClick(View v){ private void onFollowersOrFollowingClick(View v){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -1332,21 +1301,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if (adapter != null) adapter.notifyDataSetChanged(); if (adapter != null) adapter.notifyDataSetChanged();
} }
@Override
public void onProvideAssistContent(AssistContent assistContent) {
callFragmentToProvideAssistContent(getFragmentForPage(pager.getCurrentItem()), assistContent);
}
@Override
public String getAccountID() {
return accountID;
}
@Override
public Uri getWebUri(Uri.Builder base) {
return Uri.parse(account.url);
}
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter { private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter {
public MetadataAdapter(){ public MetadataAdapter(){
super(imgLoader); super(imgLoader);
@@ -1377,7 +1331,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
public int getItemCount(){ public int getItemCount(){
if(isInEditMode){ if(isInEditMode){
int size=metadataListData.size(); int size=metadataListData.size();
if(size<maxFields) if(size<MAX_FIELDS)
size++; size++;
return size; return size;
} }
@@ -1511,7 +1465,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override @Override
public void onClick(){ public void onClick(){
metadataListData.add(new AccountField()); metadataListData.add(new AccountField());
if(metadataListData.size()==maxFields){ // replace this row with new row if(metadataListData.size()==MAX_FIELDS){ // replace this row with new row
adapter.notifyItemChanged(metadataListData.size()-1); adapter.notifyItemChanged(metadataListData.size()-1);
}else{ }else{
adapter.notifyItemInserted(metadataListData.size()-1); adapter.notifyItemInserted(metadataListData.size()-1);

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.ImageButton; import android.widget.ImageButton;
@@ -33,7 +32,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
private static final int SCHEDULED_STATUS_LIST_OPENED = 161; private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
@Override @Override
protected boolean wantsComposeButton() { protected boolean withComposeButton() {
return true; return true;
} }
@@ -58,7 +57,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
} }
@Override @Override
public void onFabClick(View v) { protected void onFabClick(View v) {
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant()); args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
@@ -66,7 +65,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
} }
@Override @Override
public boolean onFabLongClick(View v) { protected boolean onFabLongClick(View v) {
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant()); args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
@@ -81,7 +80,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) { protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, null); return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, Filter.FilterContext.HOME);
} }
@Override @Override
@@ -98,8 +97,6 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
args.putString("sourceText", status.text); args.putString("sourceText", status.text);
args.putString("sourceSpoiler", status.spoilerText); args.putString("sourceSpoiler", status.spoilerText);
args.putBoolean("redraftStatus", true); args.putBoolean("redraftStatus", true);
args.putString("sourceContentType", scheduledStatus.params.contentType != null ?
scheduledStatus.params.contentType.name() : null);
setResult(true, null); setResult(true, null);
// closing this scheduled status list if another status list is opened from compose fragment // closing this scheduled status list if another status list is opened from compose fragment
@@ -183,10 +180,4 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
} }
return null; return null;
} }
@Override
public Uri getWebUri(Uri.Builder base) {
// TODO: adapt when frontends finally implement a scheduled posts list
return null;
}
} }

View File

@@ -3,11 +3,10 @@ package org.joinmastodon.android.fragments;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.utils.V;
import org.joinmastodon.android.ui.utils.UiUtils;
public interface ScrollableToTop{ public interface ScrollableToTop{
// boolean isScrolledToTop(); boolean isScrolledToTop();
void scrollToTop(); void scrollToTop();
@@ -24,7 +23,7 @@ public interface ScrollableToTop{
@Override @Override
public boolean onPreDraw(){ public boolean onPreDraw(){
list.getViewTreeObserver().removeOnPreDrawListener(this); list.getViewTreeObserver().removeOnPreDrawListener(this);
list.scrollBy(0, UiUtils.SCROLL_TO_TOP_DELTA); list.scrollBy(0, V.dp(300));
list.smoothScrollToPosition(0); list.smoothScrollToPosition(0);
return true; return true;
} }

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