Compare commits

..

5 Commits

Author SHA1 Message Date
LucasGGamerM
104423b5e8 Dunno what to do here, will leave for later 2023-01-22 15:18:17 -03:00
LucasGGamerM
521c742d1a Changing stuff back to how it was, and changing postsFragment to conversationsFragment 2023-01-22 14:47:28 -03:00
LucasGGamerM
135a98224f reverting to the old code. This is weird 2023-01-22 12:40:26 -03:00
LucasGGamerM
bed71fd9e0 Got stuck in "no such table" 2023-01-22 12:26:48 -03:00
LucasGGamerM
c695bb6aa3 Almost working, just gotta make one singular function call work 2023-01-22 12:02:50 -03:00
571 changed files with 2207 additions and 9995 deletions

View File

@@ -8,35 +8,25 @@ assignees: ''
--- ---
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
**To reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
4. See error 4. See error
**Does this happen in the official app?**
Does this issue also occur with the respective upstream release?
(Please test using the respective `upstream-xxxxxx.apk` provided in [Releases](https://github.com/sk22/megalodon/releases) or at least using the current Mastodon version from the Play Store)
> No / Yes
> In case it does, please consider filing an [upstream bug report](https://github.com/mastodon/mastodon-android/issues) instead.
> If this bug is seriously impacting your usage or you think I might want to try to fix it for Megalodon, feel free to still create this issue!
**Screenshots and screen recordings** **Screenshots and screen recordings**
If applicable, add screenshots (and screen recordings, if possible) to help explain your problem. If applicable, add screenshots (and screen recordings, if possible) to help explain your problem.
**Version** **Version**
Megalodon version: [e.g. v1.1.4+fork.#] Megalodon version: [e.g. v1.1.4+fork.#]
**Crash log** **Additional context**
- Does this issue also occur with the respective upstream release? (Please test using the respective `upstream-xxxxxx.apk` provided in [Releases](https://github.com/sk22/megalodon/releases)) No / Yes (`mastodon#…`)
> In this case, please consider filing an [upstream bug report](https://github.com/mastodon/mastodon-android/issues) instead. If this bug is seriously impacting your usage or you think I might want to try to fix it for Megalodon, feel free to still create this issue!
**Crash log**
If you know your way around Android development tools, please consider attaching a crash log, if possible. If you know your way around Android development tools, please consider attaching a crash log, if possible.

View File

@@ -5,7 +5,7 @@
> A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly wont ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer. > A fork of [megalodon](https://github.com/sk22/megalodon) which is a fork of [official Mastodon Android app](https://github.com/mastodon/mastodon-android) adding important features that are missing in the official app and possibly wont ever be implemented, such as the federated timeline, unlisted posting, bookmarks and an image description viewer.
[![Download latest release](https://img.shields.io/badge/dynamic/json?color=282C37&label=download%20apk&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2FLucasGGamerM%2Fmoshidon%2Freleases%2Flatest&style=for-the-badge)](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk) [![Download latest release](https://img.shields.io/badge/dynamic/json?color=d92aad&label=download%20apk&query=%24.tag_name&url=https%3A%2F%2Fapi.github.com%2Frepos%2FLucasGGamerM%2Fmoshidon%2Freleases%2Flatest&style=for-the-badge)](https://github.com/LucasGGamerM/moshidon/releases/latest/download/moshidon.apk)
--- ---
@@ -14,14 +14,14 @@
### **Material you theme support on Android 12+ devices!** ### **Material you theme support on Android 12+ devices!**
### **Show posts filtered with a warning!** ### **Translate button**
**Allows you to have filtered posts collapsed with a warning! As shown in the screenshots:** **Allows you to translate posts in instances with the translate feature!**
Before | After **Screenshots**
:-------------------------:|:-------------------------:
![Screenshot_20230205-100200edited](https://user-images.githubusercontent.com/71328265/216820539-20802dc5-e433-4511-b2d9-291d810e4ef2.png) | ![Screenshot_20230205-100203edited](https://user-images.githubusercontent.com/71328265/216820544-231b2966-f38f-4ec6-b555-d39c62433839.png)
![Screenshot_20221209-135457_1](https://user-images.githubusercontent.com/71328265/206753830-cdb8bc65-7732-4a6a-8bcd-bbc4ca311d19.png)
![Screenshot_20221209-135409_1](https://user-images.githubusercontent.com/71328265/206753831-7af92a48-d7a5-4780-9beb-90acef4e141b.png)
### **Color themes** ### **Color themes**
@@ -86,16 +86,9 @@ Variant with an integrated updater. If you download Moshidon from here (and not
### Features ### Features
* [Adding the ability to have filtered posts show with a warning](https://github.com/LucasGGamerM/moshidon/tree/feature/filters_again)
* [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted) * [Add “Unlisted” as a post visibility option](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/enable-unlisted)
([Pull request](https://github.com/mastodon/mastodon-android/pull/103)) ([Pull request](https://github.com/mastodon/mastodon-android/pull/103))
* Adding a useful private profile note box!* * [Add “Federation” tab and change Discover tab order](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/add-federated-timeline) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/8))
* Auto hiding the compose button on scroll!*
* Adding the hability to remind yourself to add alt text to images!*
* An indicator for if an image has alt text or not*
* Adding the ability to have drafts!*
* Also adding the ability to view announcements from your instance!*
* Adding the ability to post for local timeline only (Only on instances that support it!)*
* [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129)) * [Add image description button and viewer](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/display-alt-text) ([Pull request](https://github.com/mastodon/mastodon-android/pull/129))
* [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140)) * [Implement pinning posts and displaying pinned posts](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/pin-posts) ([Pull request](https://github.com/mastodon/mastodon-android/pull/140))
* [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21)) * [Implement deleting and re-drafting](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/delete-redraft) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/21))
@@ -117,7 +110,6 @@ Variant with an integrated updater. If you download Moshidon from here (and not
### Behavior ### Behavior
* Adding a bottom option for the publish button, allowing for easier use on larger screens!
* [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118)) * [Make back button return to the home tab before exiting the app](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/back-returns-home) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/118))
* [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113)) * [Always preserve content warnings when replying](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/always-preserve-cw) ([Closes issue](https://github.com/mastodon/mastodon-android/issues/113))
* [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182)) * [Display full image when adding image description](https://github.com/mastodon/mastodon-android/compare/master...sk22:megalodon:feature/compose-image-description-full-image) ([Pull request](https://github.com/mastodon/mastodon-android/pull/182))

View File

@@ -1,3 +0,0 @@
#!/bin/bash
find metadata -name '*.txt' -exec sed -Ei 's/^[–—─•·*]\s+/- /' {} \;

View File

@@ -16,4 +16,4 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# https://developer.android.com/topic/libraries/support-library/androidx-rn # https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX # Automatically convert third-party libraries to use AndroidX
android.enableJetifier=false android.enableJetifier=true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -9,8 +9,8 @@ android {
applicationId "org.joinmastodon.android.moshinda" applicationId "org.joinmastodon.android.moshinda"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 33
versionCode 92 versionCode 87
versionName "1.1.4+fork.92.moshinda" versionName "1.1.4+fork.87.moshinda"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW" resConfigs "ar-rSA", "be-rBY", "bn-rBD", "bs-rBA", "ca-rES", "cs-rCZ", "de-rDE", "el-rGR", "es-rES", "eu-rES", "fi-rFI", "fil-rPH", "fr-rFR", "ga-rIE", "gd-rGB", "gl-rES", "hi-rIN", "hr-rHR", "hu-rHU", "hy-rAM", "in-rID", "is-rIS", "it-rIT", "iw-rIL", "ja-rJP", "kab", "ko-rKR", "nl-rNL", "oc-rFR", "pl-rPL", "pt-rBR", "pt-rPT", "ro-rRO", "ru-rRU", "si-rLK", "sl-rSI", "sv-rSE", "th-rTH", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW"
} }
@@ -70,7 +70,6 @@ dependencies {
implementation 'com.squareup:otto:1.3.8' implementation 'com.squareup:otto:1.3.8'
implementation 'de.psdev:async-otto:1.0.3' implementation 'de.psdev:async-otto:1.0.3'
implementation 'org.parceler:parceler-api:1.1.12' implementation 'org.parceler:parceler-api:1.1.12'
implementation 'com.github.bottom-software-foundation:bottom-java:2.1.0'
annotationProcessor 'org.parceler:parceler:1.1.12' annotationProcessor 'org.parceler:parceler:1.1.12'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'

View File

@@ -14,14 +14,12 @@ import android.os.Build;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
@@ -115,70 +113,64 @@ public class GithubSelfUpdaterImpl extends GithubSelfUpdater{
private void actuallyCheckForUpdates(){ private void actuallyCheckForUpdates(){
Request req=new Request.Builder() Request req=new Request.Builder()
.url("https://api.github.com/repos/LucasGGamerM/moshidon/releases") .url("https://api.github.com/repos/LucasGGamerM/moshidon/releases/latest")
.build(); .build();
Call call=MastodonAPIController.getHttpClient().newCall(req); Call call=MastodonAPIController.getHttpClient().newCall(req);
try(Response resp=call.execute()){ try(Response resp=call.execute()){
JsonArray arr=JsonParser.parseReader(resp.body().charStream()).getAsJsonArray(); JsonObject obj=JsonParser.parseReader(resp.body().charStream()).getAsJsonObject();
for (JsonElement jsonElement : arr) { String changelog=obj.get("body").getAsString();
JsonObject obj = jsonElement.getAsJsonObject(); String tag=obj.get("tag_name").getAsString();
if (obj.get("prerelease").getAsBoolean() && !GlobalUserPreferences.enablePreReleases) continue; 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();
String tag=obj.get("tag_name").getAsString(); UpdateInfo info=new UpdateInfo();
String changelog=obj.get("body").getAsString(); info.size=size;
Pattern pattern=Pattern.compile("v?(\\d+)\\.(\\d+)\\.(\\d+)\\+fork\\.(\\d+)"); info.version=version;
Matcher matcher=pattern.matcher(tag); info.changelog=changelog;
if(!matcher.find()){ this.info=info;
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(); getPrefs().edit()
info.size=size; .putLong("apkSize", size)
info.version=version; .putString("version", version)
info.changelog=changelog; .putString("apkURL", url)
this.info=info; .putInt("checkedByBuild", BuildConfig.VERSION_CODE)
.putString("changelog", changelog)
.remove("downloadID")
.apply();
getPrefs().edit() break;
.putLong("apkSize", size)
.putString("version", version)
.putString("apkURL", url)
.putString("changelog", changelog)
.putInt("checkedByBuild", BuildConfig.VERSION_CODE)
.remove("downloadID")
.apply();
break;
}
} }
} }
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
break;
} }
getPrefs().edit().putLong("lastCheck", System.currentTimeMillis()).apply();
}catch(Exception x){ }catch(Exception x){
Log.w(TAG, "actuallyCheckForUpdates", x); Log.w(TAG, "actuallyCheckForUpdates", x);
}finally{ }finally{

View File

@@ -12,17 +12,10 @@
<permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/> <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
<application <application
android:name=".MastodonApp" android:name=".MastodonApp"
android:allowBackup="true" android:allowBackup="true"
android:label="@string/mo_app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:localeConfig="@xml/locales_config" android:localeConfig="@xml/locales_config"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@@ -83,7 +83,3 @@ mirr0r.city underage
nnia.space underage nnia.space underage
ignorelist.com malicious ignorelist.com malicious
repl.co 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
83
84
85

View File

@@ -12,6 +12,7 @@ import android.widget.Toast;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ComposeFragment; import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList; import java.util.ArrayList;
@@ -35,10 +36,13 @@ public class ExternalShareActivity extends FragmentStackActivity{
openComposeFragment(sessions.get(0).getID()); openComposeFragment(sessions.get(0).getID());
}else{ }else{
getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000)); getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
UiUtils.pickAccount(this, null, R.string.choose_account, 0, new M3AlertDialogBuilder(this)
session -> openComposeFragment(session.getID()), .setItems(sessions.stream().map(as->"@"+as.self.username+"@"+as.domain).toArray(String[]::new), (dialog, which)->{
b -> b.setOnCancelListener(d -> finish()) openComposeFragment(sessions.get(which).getID());
); })
.setTitle(R.string.choose_account)
.setOnCancelListener(dialog -> finish())
.show();
} }
} }
} }

View File

@@ -9,14 +9,10 @@ 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.TimelineDefinition;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
public class GlobalUserPreferences{ public class GlobalUserPreferences{
public static boolean playGifs; public static boolean playGifs;
@@ -25,7 +21,7 @@ public class GlobalUserPreferences{
public static boolean showReplies; public static boolean showReplies;
public static boolean showBoosts; public static boolean showBoosts;
public static boolean loadNewPosts; public static boolean loadNewPosts;
public static boolean showNewPostsButton; public static boolean showFederatedTimeline;
public static boolean showInteractionCounts; public static boolean showInteractionCounts;
public static boolean alwaysExpandContentWarnings; public static boolean alwaysExpandContentWarnings;
public static boolean disableMarquee; public static boolean disableMarquee;
@@ -37,35 +33,21 @@ public class GlobalUserPreferences{
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 showAltIndicator;
public static boolean showNoAltIndicator;
public static boolean enablePreReleases;
public static boolean prefixRepliesWithRe;
public static boolean bottomEncoding;
public static boolean collapseLongPosts;
public static boolean spectatorMode;
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 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 Set<String> accountsWithLocalOnlySupport;
public static Set<String> accountsInGlitchMode;
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;
private static SharedPreferences getPrefs(){ private static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE); return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
} }
private static <T> T fromJson(String json, Type type, T orElse) { private static <T> T fromJson(String json, Type type, T orElse) {
if (json == null) return orElse;
try { return gson.fromJson(json, type); } try { return gson.fromJson(json, type); }
catch (JsonSyntaxException ignored) { return orElse; } catch (JsonSyntaxException ignored) { return orElse; }
} }
@@ -78,8 +60,8 @@ public class GlobalUserPreferences{
showReplies=prefs.getBoolean("showReplies", true); showReplies=prefs.getBoolean("showReplies", true);
showBoosts=prefs.getBoolean("showBoosts", true); showBoosts=prefs.getBoolean("showBoosts", true);
loadNewPosts=prefs.getBoolean("loadNewPosts", true); loadNewPosts=prefs.getBoolean("loadNewPosts", true);
showNewPostsButton=prefs.getBoolean("showNewPostsButton", true);
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", true); uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", true);
showFederatedTimeline=prefs.getBoolean("showFederatedTimeline", !BuildConfig.BUILD_TYPE.equals("playRelease"));
showInteractionCounts=prefs.getBoolean("showInteractionCounts", false); showInteractionCounts=prefs.getBoolean("showInteractionCounts", false);
alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false); alwaysExpandContentWarnings=prefs.getBoolean("alwaysExpandContentWarnings", false);
disableMarquee=prefs.getBoolean("disableMarquee", false); disableMarquee=prefs.getBoolean("disableMarquee", false);
@@ -90,23 +72,10 @@ public class GlobalUserPreferences{
enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", true); enableDeleteNotifications=prefs.getBoolean("enableDeleteNotifications", true);
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);
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
prefixRepliesWithRe=prefs.getBoolean("prefixRepliesWithRe", false);
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
spectatorMode=prefs.getBoolean("spectatorMode", false);
publishButtonText=prefs.getString("publishButtonText", "");
theme=ThemePreference.values()[prefs.getInt("theme", 0)]; theme=ThemePreference.values()[prefs.getInt("theme", 0)];
recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>()); recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>());
recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>()); recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
publishButtonText=prefs.getString("publishButtonText", ""); publishButtonText=prefs.getString("publishButtonText", "");
pinnedTimelines=fromJson(prefs.getString("pinnedTimelines", null), pinnedTimelinesType, new HashMap<>());
accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>());
accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>());
try { try {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
@@ -127,7 +96,7 @@ public class GlobalUserPreferences{
.putBoolean("showReplies", showReplies) .putBoolean("showReplies", showReplies)
.putBoolean("showBoosts", showBoosts) .putBoolean("showBoosts", showBoosts)
.putBoolean("loadNewPosts", loadNewPosts) .putBoolean("loadNewPosts", loadNewPosts)
.putBoolean("showNewPostsButton", showNewPostsButton) .putBoolean("showFederatedTimeline", showFederatedTimeline)
.putBoolean("trueBlackTheme", trueBlackTheme) .putBoolean("trueBlackTheme", trueBlackTheme)
.putBoolean("showInteractionCounts", showInteractionCounts) .putBoolean("showInteractionCounts", showInteractionCounts)
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings) .putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
@@ -139,23 +108,11 @@ public class GlobalUserPreferences{
.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("showAltIndicator", showAltIndicator)
.putBoolean("showNoAltIndicator", showNoAltIndicator)
.putBoolean("enablePreReleases", enablePreReleases)
.putBoolean("prefixRepliesWithRe", prefixRepliesWithRe)
.putBoolean("collapseLongPosts", collapseLongPosts)
.putBoolean("spectatorMode", spectatorMode)
.putString("publishButtonText", publishButtonText) .putString("publishButtonText", publishButtonText)
.putBoolean("bottomEncoding", bottomEncoding)
.putInt("theme", theme.ordinal()) .putInt("theme", theme.ordinal())
.putString("color", color.name()) .putString("color", color.name())
.putString("recentLanguages", gson.toJson(recentLanguages)) .putString("recentLanguages", gson.toJson(recentLanguages))
.putString("pinnedTimelines", gson.toJson(pinnedTimelines))
.putString("recentEmojis", gson.toJson(recentEmojis)) .putString("recentEmojis", gson.toJson(recentEmojis))
.putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport)
.putStringSet("accountsInGlitchMode", accountsInGlitchMode)
.apply(); .apply();
} }

View File

@@ -40,13 +40,12 @@ public class MainActivity extends FragmentStackActivity{
AccountSession session; AccountSession session;
Bundle args=new Bundle(); Bundle args=new Bundle();
Intent intent=getIntent(); Intent intent=getIntent();
boolean fromNotification = intent.getBooleanExtra("fromNotification", false); if(intent.getBooleanExtra("fromNotification", false)){
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID"); String accountID=intent.getStringExtra("accountID");
try{ try{
session=AccountSessionManager.getInstance().getAccount(accountID); session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications"); if(!intent.hasExtra("notification"))
args.putString("tab", "notifications");
}catch(IllegalStateException x){ }catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount(); session=AccountSessionManager.getInstance().getLastActiveAccount();
} }
@@ -56,13 +55,13 @@ public class MainActivity extends FragmentStackActivity{
args.putString("account", session.getID()); args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment(); Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args); fragment.setArguments(args);
if(fromNotification && hasNotification){ showFragmentClearingBackStack(fragment);
if(intent.getBooleanExtra("fromNotification", false) && intent.hasExtra("notification")){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification")); Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID()); showFragmentForNotification(notification, session.getID());
} else if (intent.getBooleanExtra("compose", false)){ }else if(intent.getBooleanExtra("compose", false)){
showCompose(); showCompose();
} else { }else{
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission(); maybeRequestNotificationsPermission();
} }
} }
@@ -141,31 +140,4 @@ public class MainActivity extends FragmentStackActivity{
requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100); requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 100);
} }
} }
/**
* when opening app through a notification: if (thread) fragment "can go back", clear back stack
* and show home fragment. upstream's implementation doesn't require this as it opens home first
* and then immediately switches to the notification's ThreadFragment. this causes a black
* screen in megalodon, for some reason, so i'm working around this that way.
*/
@Override
public void onBackPressed() {
Fragment currentFragment = getFragmentManager().findFragmentById(
(fragmentContainers.get(fragmentContainers.size() - 1)).getId()
);
Bundle currentArgs = currentFragment.getArguments();
if (this.fragmentContainers.size() == 1
&& currentArgs != null
&& currentArgs.getBoolean("_can_go_back", false)
&& currentArgs.containsKey("account")) {
Bundle args = new Bundle();
args.putString("account", currentArgs.getString("account"));
args.putString("tab", "notifications");
Fragment fragment=new HomeFragment();
fragment.setArguments(args);
showFragmentClearingBackStack(fragment);
} else {
super.onBackPressed();
}
}
} }

View File

@@ -144,28 +144,26 @@ public class PushNotificationReceiver extends BroadcastReceiver{
builder.setContentTitle(pn.title) builder.setContentTitle(pn.title)
.setContentText(pn.body) .setContentText(pn.body)
.setContentTitle(pn.title) .setContentTitle(pn.title)
.setStyle(new Notification.BigTextStyle().bigText(pn.body)) .setStyle(new Notification.InboxStyle()
.setSmallIcon(R.drawable.ic_ntf_logo) .addLine(pn.body))
.setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT)) .setContentIntent(PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
.setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli()) .setWhen(notification==null ? System.currentTimeMillis() : notification.createdAt.toEpochMilli())
.setShowWhen(true) .setShowWhen(true)
.setCategory(Notification.CATEGORY_SOCIAL) .setCategory(Notification.CATEGORY_SOCIAL)
.setAutoCancel(true) .setAutoCancel(true)
.setColor(context.getColor(R.color.shortcut_icon_background)) .setGroup(accountID)
.setGroup(accountID); .setColor(context.getColor(R.color.shortcut_icon_background));
if(!GlobalUserPreferences.uniformNotificationIcon){
if (!GlobalUserPreferences.uniformNotificationIcon) { switch (pn.notificationType) {
builder.setSmallIcon(switch (pn.notificationType) { case FAVORITE -> builder.setSmallIcon(R.drawable.ic_fluent_star_24_filled);
case FAVORITE -> R.drawable.ic_fluent_star_24_filled; case REBLOG -> builder.setSmallIcon(R.drawable.ic_fluent_arrow_repeat_all_24_filled);
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled; case FOLLOW -> builder.setSmallIcon(R.drawable.ic_fluent_person_add_24_filled);
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled; case MENTION -> builder.setSmallIcon(R.drawable.ic_fluent_mention_24_filled);
case MENTION -> R.drawable.ic_fluent_mention_24_filled; case POLL -> builder.setSmallIcon(R.drawable.ic_fluent_poll_24_filled);
case POLL -> R.drawable.ic_fluent_poll_24_filled; default -> builder.setSmallIcon(R.drawable.ic_ntf_logo);
case STATUS -> R.drawable.ic_fluent_chat_24_filled; }
case UPDATE -> R.drawable.ic_fluent_history_24_filled; }else{
case REPORT -> R.drawable.ic_fluent_warning_24_filled; builder.setSmallIcon(R.drawable.ic_ntf_logo);
case SIGN_UP -> R.drawable.ic_fluent_person_available_24_filled;
});
} }
if(avatar!=null){ if(avatar!=null){

View File

@@ -372,7 +372,7 @@ public class PushSubscriptionManager{
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){ for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
if(session.pushSubscription==null || forceReRegister) if(session.pushSubscription==null || forceReRegister)
session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription); session.getPushSubscriptionManager().registerAccountForPush(session.pushSubscription);
else else if(session.needUpdatePushSettings)
session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription); session.getPushSubscriptionManager().updatePushSettings(session.pushSubscription);
} }
} }

View File

@@ -4,10 +4,6 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{ public class SetAccountFollowed extends MastodonAPIRequest<Relationship>{
public SetAccountFollowed(String id, boolean followed, boolean showReblogs){
this(id, followed, showReblogs, false);
}
public SetAccountFollowed(String id, boolean followed, boolean showReblogs, boolean notify){ public SetAccountFollowed(String id, boolean followed, boolean showReblogs, boolean notify){
super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class); super(HttpMethod.POST, "/accounts/"+id+"/"+(followed ? "follow" : "unfollow"), Relationship.class);
if(followed) if(followed)

View File

@@ -1,10 +0,0 @@
package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
public class GetList extends MastodonAPIRequest<ListTimeline> {
public GetList(String id) {
super(HttpMethod.GET, "/lists/" + id, ListTimeline.class);
}
}

View File

@@ -9,7 +9,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
Request r=new Request(); Request r=new Request();
r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID; r.subscription.endpoint="https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
r.data.alerts=alerts; r.data.alerts=alerts;
r.policy=policy; r.data.policy=policy;
r.subscription.keys.p256dh=encryptionKey; r.subscription.keys.p256dh=encryptionKey;
r.subscription.keys.auth=authKey; r.subscription.keys.auth=authKey;
setRequestBody(r); setRequestBody(r);
@@ -18,7 +18,6 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
private static class Request{ private static class Request{
public Subscription subscription=new Subscription(); public Subscription subscription=new Subscription();
public Data data=new Data(); public Data data=new Data();
public PushSubscription.Policy policy;
private static class Keys{ private static class Keys{
public String p256dh; public String p256dh;
@@ -32,6 +31,7 @@ public class RegisterForPushNotifications extends MastodonAPIRequest<PushSubscri
private static class Data{ private static class Data{
public PushSubscription.Alerts alerts; public PushSubscription.Alerts alerts;
public PushSubscription.Policy policy;
} }
} }
} }

View File

@@ -3,36 +3,23 @@ package org.joinmastodon.android.api.requests.notifications;
import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.PushSubscription; import org.joinmastodon.android.model.PushSubscription;
import java.io.IOException;
import okhttp3.Response;
public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{ public class UpdatePushSettings extends MastodonAPIRequest<PushSubscription>{
private final PushSubscription.Policy policy;
public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){ public UpdatePushSettings(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
super(HttpMethod.PUT, "/push/subscription", PushSubscription.class); super(HttpMethod.PUT, "/push/subscription", PushSubscription.class);
setRequestBody(new Request(alerts, policy)); setRequestBody(new Request(alerts, policy));
this.policy=policy;
}
@Override
public void validateAndPostprocessResponse(PushSubscription respObj, Response httpResponse) throws IOException{
super.validateAndPostprocessResponse(respObj, httpResponse);
respObj.policy=policy;
} }
private static class Request{ private static class Request{
public Data data=new Data(); public Data data=new Data();
public PushSubscription.Policy policy;
public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){ public Request(PushSubscription.Alerts alerts, PushSubscription.Policy policy){
this.data.alerts=alerts; this.data.alerts=alerts;
this.policy=policy; this.data.policy=policy;
} }
private static class Data{ private static class Data{
public PushSubscription.Alerts alerts; public PushSubscription.Alerts alerts;
public PushSubscription.Policy policy;
} }
} }
} }

View File

@@ -39,7 +39,6 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public Poll poll; public Poll poll;
public String inReplyToId; public String inReplyToId;
public boolean sensitive; public boolean sensitive;
public boolean localOnly;
public String spoilerText; public String spoilerText;
public StatusPrivacy visibility; public StatusPrivacy visibility;
public Instant scheduledAt; public Instant scheduledAt;

View File

@@ -0,0 +1,22 @@
package org.joinmastodon.android.api.requests.timelines;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
import java.util.List;
public class GetConversationsTimeline extends MastodonAPIRequest<List<Status>>{
public GetConversationsTimeline(String maxID, String minID, int limit, String sinceID){
super(HttpMethod.GET, "/conversations", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(minID!=null)
addQueryParameter("min_id", minID);
if(sinceID!=null)
addQueryParameter("since_id", sinceID);
if(limit>0)
addQueryParameter("limit", ""+limit);
}
}

View File

@@ -1,11 +0,0 @@
package org.joinmastodon.android.events;
public class HashtagUpdatedEvent {
public final String name;
public final boolean following;
public HashtagUpdatedEvent(String name, boolean following) {
this.name = name;
this.following = following;
}
}

View File

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

View File

@@ -1,15 +0,0 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ListTimeline;
public class ListUpdatedCreatedEvent {
public final String id;
public final String title;
public final ListTimeline.RepliesPolicy repliesPolicy;
public ListUpdatedCreatedEvent(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
this.id = id;
this.title = title;
this.repliesPolicy = repliesPolicy;
}
}

View File

@@ -15,15 +15,12 @@ import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent; import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
@@ -63,8 +60,8 @@ public class AccountTimelineFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return; if(getActivity()==null)
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.ACCOUNT)).collect(Collectors.toList()); return;
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
} }
}) })

View File

@@ -77,7 +77,12 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
public void onMarkAsRead(String id) { public void onMarkAsRead(String id) {
if (unreadIDs == null) return; if (unreadIDs == null) return;
unreadIDs.remove(id); unreadIDs.remove(id);
if (unreadIDs.isEmpty()) setResult(true, null); if (unreadIDs.size() == 0) setResult(true, null);
}
@Override
public void onDestroy() {
super.onDestroy();
} }
@Override @Override
@@ -92,13 +97,11 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Announcement> result){ public void onSuccess(List<Announcement> result){
if (getActivity() == null) return;
List<Announcement> unread = result.stream().filter(a -> !a.read).collect(toList()); List<Announcement> unread = result.stream().filter(a -> !a.read).collect(toList());
List<Announcement> read = result.stream().filter(a -> a.read).collect(toList()); List<Announcement> read = result.stream().filter(a -> a.read).collect(toList());
onDataLoaded(unread, true); onDataLoaded(unread, true);
onDataLoaded(read, false); onDataLoaded(read, false);
if (unread.isEmpty()) setResult(true, null); unreadIDs = unread.stream().map(a -> a.id).collect(toList());
else unreadIDs = unread.stream().map(a -> a.id).collect(toList());
} }
}) })
.exec(accountID); .exec(accountID);

View File

@@ -13,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.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
@@ -42,7 +43,6 @@ import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
@@ -59,8 +59,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.fragments.BaseRecyclerFragment;
@@ -77,18 +75,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected String accountID; protected String accountID;
protected PhotoViewer currentPhotoViewer; protected PhotoViewer currentPhotoViewer;
protected ImageButton fab; protected ImageButton fab;
protected int scrollDiff = 0; protected boolean isScrollingUp = false;
protected HashMap<String, Account> knownAccounts=new HashMap<>(); protected HashMap<String, Account> knownAccounts=new HashMap<>();
protected HashMap<String, Relationship> relationships=new HashMap<>(); protected HashMap<String, Relationship> relationships=new HashMap<>();
protected Rect tmpRect=new Rect(); protected Rect tmpRect=new Rect();
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
if (withComposeButton()) setListLayoutId(R.layout.recycler_fragment_with_fab);
}
protected boolean withComposeButton() {
return false;
} }
@Override @Override
@@ -102,8 +95,6 @@ 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();
@@ -288,30 +279,28 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab); fab=view.findViewById(R.id.fab);
list.addOnScrollListener(new RecyclerView.OnScrollListener(){ list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override @Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
if(currentPhotoViewer!=null) if(currentPhotoViewer!=null)
currentPhotoViewer.offsetView(-dx, -dy); currentPhotoViewer.offsetView(-dx, -dy);
if (fab!=null && GlobalUserPreferences.enableFabAutoHide) { if (fab!=null) {
if(dy > 0){ if (dy >= 0 ) {
scrollDiff = 0; if (isScrollingUp) {
} fab.setVisibility(View.INVISIBLE);
if (dy > 0 && fab.getVisibility() == View.VISIBLE) { TranslateAnimation animate = new TranslateAnimation(
TranslateAnimation animate = new TranslateAnimation( 0,
0, 0,
0, 0,
0, fab.getHeight() * 2);
fab.getHeight() * 2); animate.setDuration(300);
animate.setDuration(300); animate.setFillAfter(true);
animate.setFillAfter(true); fab.startAnimation(animate);
fab.startAnimation(animate); isScrollingUp = false;
fab.setVisibility(View.INVISIBLE); }
scrollDiff = 0; } else {
} else if (dy < 0 && fab.getVisibility() != View.VISIBLE) { if (!isScrollingUp) {
if (scrollDiff > 800) {
fab.setVisibility(View.VISIBLE); fab.setVisibility(View.VISIBLE);
TranslateAnimation animate = new TranslateAnimation( TranslateAnimation animate = new TranslateAnimation(
0, 0,
@@ -321,9 +310,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
animate.setDuration(300); animate.setDuration(300);
animate.setFillAfter(true); animate.setFillAfter(true);
fab.startAnimation(animate); fab.startAnimation(animate);
scrollDiff = 0; isScrollingUp = true;
} else {
scrollDiff += Math.abs(dy);
} }
} }
} }
@@ -362,13 +349,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
list.setItemAnimator(new BetterItemAnimator()); list.setItemAnimator(new BetterItemAnimator());
((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true); ((UsableRecyclerView) list).setIncludeMarginsInItemHitbox(true);
updateToolbar(); updateToolbar();
if (withComposeButton()) {
fab = view.findViewById(R.id.fab);
fab.setVisibility(View.VISIBLE);
fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(this::onFabLongClick);
}
} }
@Override @Override
@@ -534,7 +514,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
Status status=holder.getItem().status; Status status=holder.getItem().status;
status.spoilerRevealed=!status.spoilerRevealed; status.spoilerRevealed=!status.spoilerRevealed;
if(!TextUtils.isEmpty(status.spoilerText)){ if(!TextUtils.isEmpty(status.spoilerText)){
TextStatusDisplayItem.Holder text = findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class); TextStatusDisplayItem.Holder text=findHolderOfType(holder.getItemID(), TextStatusDisplayItem.Holder.class);
if(text!=null){ if(text!=null){
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()); adapter.notifyItemChanged(text.getAbsoluteAdapterPosition());
} }
@@ -543,23 +523,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
updateImagesSpoilerState(status, holder.getItemID()); updateImagesSpoilerState(status, holder.getItemID());
} }
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
if (holder.getItem().status.textExpandable != expandable && list != null) {
holder.getItem().status.textExpandable = expandable;
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if (header != null) header.rebind();
holder.rebind();
}
}
public void onToggleExpanded(Status status, String itemID) {
status.textExpanded = !status.textExpanded;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if (text != null) text.rebind();
if (header != null) header.rebind();
}
protected void updateImagesSpoilerState(Status status, String itemID){ protected void updateImagesSpoilerState(Status status, String itemID){
ArrayList<Integer> updatedPositions=new ArrayList<>(); ArrayList<Integer> updatedPositions=new ArrayList<>();
for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){ for(ImageStatusDisplayItem.Holder photo:(List<ImageStatusDisplayItem.Holder>)findAllHoldersOfType(itemID, ImageStatusDisplayItem.Holder.class)){
@@ -577,15 +540,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onGapClick(GapStatusDisplayItem.Holder item){} public void onGapClick(GapStatusDisplayItem.Holder item){}
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
int startPos = warning.getAbsoluteAdapterPosition();
displayItems.remove(startPos);
displayItems.addAll(startPos, warning.filteredItems);
adapter.notifyItemRangeInserted(startPos, warning.filteredItems.size() - 1);
if (startPos == 0) scrollToTop();
warning.getItem().status.filterRevealed = true;
}
public String getAccountID(){ public String getAccountID(){
return accountID; return accountID;
} }
@@ -701,16 +655,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
currentPhotoViewer.onPause(); currentPhotoViewer.onPause();
} }
protected void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), ComposeFragment.class, args);
}
protected boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID);
}
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{ protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
public DisplayItemsAdapter(){ public DisplayItemsAdapter(){

View File

@@ -25,7 +25,6 @@ public class BookmarkedStatusListFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Status> result){ public void onSuccess(HeaderPaginationList<Status> result){
if (getActivity() == null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else

View File

@@ -5,7 +5,6 @@ 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;
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;
@@ -44,7 +43,6 @@ import android.text.TextWatcher;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.util.Log; import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@@ -69,7 +67,6 @@ import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.twitter.twittertext.TwitterTextEmojiRegex; import com.twitter.twittertext.TwitterTextEmojiRegex;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
@@ -119,7 +116,6 @@ import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.ui.views.ReorderableLinearLayout; import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
import org.joinmastodon.android.ui.views.SizeListenerLinearLayout; import org.joinmastodon.android.ui.views.SizeListenerLinearLayout;
import org.joinmastodon.android.utils.MastodonLanguage; import org.joinmastodon.android.utils.MastodonLanguage;
import org.joinmastodon.android.utils.StatusTextEncoder;
import org.parceler.Parcel; import org.parceler.Parcel;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -156,21 +152,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private static final int IMAGE_DESCRIPTION_RESULT=363; private static final int IMAGE_DESCRIPTION_RESULT=363;
private static final int SCHEDULED_STATUS_OPENED_RESULT=161; private static final int SCHEDULED_STATUS_OPENED_RESULT=161;
private static final int MAX_ATTACHMENTS=4; private static final int MAX_ATTACHMENTS=4;
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 String TAG="ComposeFragment"; private static final String TAG="ComposeFragment";
public static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE); private static final Pattern MENTION_PATTERN=Pattern.compile("(^|[^\\/\\w])@(([a-z0-9_]+)@[a-z0-9\\.\\-]+[a-z0-9]+)", Pattern.CASE_INSENSITIVE);
// from https://github.com/mastodon/mastodon-ios/blob/main/Mastodon/Helper/MastodonRegex.swift // from https://github.com/mastodon/mastodon-ios/blob/main/Mastodon/Helper/MastodonRegex.swift
public static final Pattern AUTO_COMPLETE_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+)|:([a-zA-Z0-9_]+))"); private static final Pattern AUTO_COMPLETE_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+)|:([a-zA-Z0-9_]+))");
public static final Pattern HIGHLIGHT_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))"); private static final Pattern HIGHLIGHT_PATTERN=Pattern.compile("(?<!\\w)(?:@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?|#([^\\s.]+))");
@SuppressLint("NewApi") // this class actually exists on 6.0 @SuppressLint("NewApi") // this class actually exists on 6.0
private final BreakIterator breakIterator=BreakIterator.getCharacterInstance(); private final BreakIterator breakIterator=BreakIterator.getCharacterInstance();
private SizeListenerLinearLayout contentView; private SizeListenerLinearLayout contentView;
private TextView selfName, selfUsername, selfExtraText, extraText; private TextView selfName, selfUsername;
private ImageView selfAvatar; private ImageView selfAvatar;
private Account self; private Account self;
private String instanceDomain; private String instanceDomain;
@@ -219,7 +213,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private View sendingOverlay; private View sendingOverlay;
private WindowManager wm; private WindowManager wm;
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC; private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
private boolean localOnly;
private ComposeAutocompleteSpan currentAutocompleteSpan; private ComposeAutocompleteSpan currentAutocompleteSpan;
private FrameLayout mainEditTextWrap; private FrameLayout mainEditTextWrap;
private ComposeAutocompleteViewController autocompleteViewController; private ComposeAutocompleteViewController autocompleteViewController;
@@ -234,7 +227,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private boolean ignoreSelectionChanges=false; private boolean ignoreSelectionChanges=false;
private Runnable updateUploadEtaRunnable; private Runnable updateUploadEtaRunnable;
private String language, encoding; private String language;
private MastodonLanguage.LanguageResolver languageResolver; private MastodonLanguage.LanguageResolver languageResolver;
private int navigationBarColorBefore; private int navigationBarColorBefore;
@@ -254,10 +247,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain); instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain);
languageResolver=new MastodonLanguage.LanguageResolver(instance); languageResolver=new MastodonLanguage.LanguageResolver(instance);
redraftStatus=getArguments().getBoolean("redraftStatus", false); redraftStatus=getArguments().getBoolean("redraftStatus", false);
if(getArguments().containsKey("editStatus")) if(getArguments().containsKey("editStatus")){
editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus")); editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus"));
if(getArguments().containsKey("replyTo")) }
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
if(instance==null){ if(instance==null){
Nav.finish(this); Nav.finish(this);
return; return;
@@ -331,7 +323,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
selfName=view.findViewById(R.id.self_name); selfName=view.findViewById(R.id.self_name);
selfUsername=view.findViewById(R.id.self_username); selfUsername=view.findViewById(R.id.self_username);
selfAvatar=view.findViewById(R.id.self_avatar); selfAvatar=view.findViewById(R.id.self_avatar);
selfExtraText=view.findViewById(R.id.self_extra_text);
HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis); HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis);
selfUsername.setText('@'+self.username+'@'+instanceDomain); selfUsername.setText('@'+self.username+'@'+instanceDomain);
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar)); ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
@@ -357,26 +348,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
sensitiveItem=view.findViewById(R.id.sensitive_item); sensitiveItem=view.findViewById(R.id.sensitive_item);
replyText=view.findViewById(R.id.reply_text); replyText=view.findViewById(R.id.reply_text);
if (isPhotoPickerAvailable()) { mediaBtn.setOnClickListener(v->openFilePicker());
PopupMenu attachPopup = new PopupMenu(getContext(), mediaBtn);
attachPopup.inflate(R.menu.attach);
attachPopup.setOnMenuItemClickListener(i -> {
openFilePicker(i.getItemId() == R.id.media);
return true;
});
UiUtils.enablePopupMenuIcons(getContext(), attachPopup);
mediaBtn.setOnClickListener(v->attachPopup.show());
mediaBtn.setOnTouchListener(attachPopup.getDragToOpenListener());
} else {
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());
localOnly = savedInstanceState != null ? savedInstanceState.getBoolean("localOnly") :
editingStatus != null ? editingStatus.localOnly : replyTo != null && replyTo.localOnly;
buildVisibilityPopup(visibilityBtn); buildVisibilityPopup(visibilityBtn);
visibilityBtn.setOnClickListener(v->visibilityPopup.show()); visibilityBtn.setOnClickListener(v->visibilityPopup.show());
visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener()); visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener());
@@ -451,7 +426,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable()); spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable());
spoilerEdit.setBackground(spoilerBg); spoilerEdit.setBackground(spoilerBg);
if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){ if((savedInstanceState!=null && savedInstanceState.getBoolean("hasSpoiler", false)) || hasSpoiler){
hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE); spoilerEdit.setVisibility(View.VISIBLE);
spoilerBtn.setSelected(true); spoilerBtn.setSelected(true);
}else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){ }else if(editingStatus!=null && !TextUtils.isEmpty(editingStatus.spoilerText)){
@@ -495,9 +469,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
case UNLISTED -> R.id.vis_unlisted; case UNLISTED -> R.id.vis_unlisted;
case PRIVATE -> R.id.vis_followers; case PRIVATE -> R.id.vis_followers;
case DIRECT -> R.id.vis_private; case DIRECT -> R.id.vis_private;
case LOCAL -> R.id.vis_local;
}).setChecked(true); }).setChecked(true);
visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly);
autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID); autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID);
autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected); autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected);
@@ -524,7 +496,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
outState.putBoolean("pollAllowMultiple", pollAllowMultipleItem.isSelected()); outState.putBoolean("pollAllowMultiple", pollAllowMultipleItem.isSelected());
} }
outState.putBoolean("sensitive", sensitive); outState.putBoolean("sensitive", sensitive);
outState.putBoolean("localOnly", localOnly);
outState.putBoolean("hasSpoiler", hasSpoiler); outState.putBoolean("hasSpoiler", hasSpoiler);
outState.putString("language", language); outState.putString("language", language);
if(!attachments.isEmpty()){ if(!attachments.isEmpty()){
@@ -648,7 +619,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
}); });
View originalPost = view.findViewById(R.id.original_post); View originalPost = view.findViewById(R.id.original_post);
extraText = view.findViewById(R.id.extra_text);
originalPost.setVisibility(View.VISIBLE); originalPost.setVisibility(View.VISIBLE);
originalPost.setOnClickListener(v->{ originalPost.setOnClickListener(v->{
Bundle args=new Bundle(); Bundle args=new Bundle();
@@ -681,10 +651,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
view.findViewById(R.id.visibility).setVisibility(View.GONE); view.findViewById(R.id.visibility).setVisibility(View.GONE);
Drawable visibilityIcon = getActivity().getDrawable(switch(replyTo.visibility){ Drawable visibilityIcon = getActivity().getDrawable(switch(replyTo.visibility){
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular; case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular; case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled; case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
case DIRECT -> R.drawable.ic_fluent_mention_20_regular; case DIRECT -> R.drawable.ic_fluent_mention_20_regular;
case LOCAL -> R.drawable.ic_fluent_eye_20_regular;
}); });
ImageView moreBtn = view.findViewById(R.id.more); ImageView moreBtn = view.findViewById(R.id.more);
moreBtn.setImageDrawable(visibilityIcon); moreBtn.setImageDrawable(visibilityIcon);
@@ -703,14 +672,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
else view.findViewById(R.id.display_item_text).setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16))); else view.findViewById(R.id.display_item_text).setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName)); replyText.setText(getString(R.string.in_reply_to, replyTo.account.displayName));
int visibilityNameRes = switch (replyTo.visibility) { replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + UiUtils.getVisibilityText(replyTo));
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
case PRIVATE -> R.string.visibility_followers_only;
case DIRECT -> R.string.visibility_private;
case LOCAL -> R.string.sk_local_only;
};
replyText.setContentDescription(getString(R.string.in_reply_to, replyTo.account.displayName) + ". " + getString(R.string.post_visibility) + ": " + getString(visibilityNameRes));
replyText.setOnClickListener(v->{ replyText.setOnClickListener(v->{
scrollView.smoothScrollTo(0, 0); scrollView.smoothScrollTo(0, 0);
}); });
@@ -739,11 +701,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(!TextUtils.isEmpty(replyTo.spoilerText)){ if(!TextUtils.isEmpty(replyTo.spoilerText)){
hasSpoiler=true; hasSpoiler=true;
spoilerEdit.setVisibility(View.VISIBLE); spoilerEdit.setVisibility(View.VISIBLE);
if(GlobalUserPreferences.prefixRepliesWithRe && !replyTo.spoilerText.startsWith("re: ")){ spoilerEdit.setText(replyTo.spoilerText);
spoilerEdit.setText("re: " + replyTo.spoilerText);
}else{
spoilerEdit.setText(replyTo.spoilerText);
}
spoilerBtn.setSelected(true); spoilerBtn.setSelected(true);
} }
if (replyTo.language != null && !replyTo.language.isEmpty()) updateLanguage(replyTo.language); if (replyTo.language != null && !replyTo.language.isEmpty()) updateLanguage(replyTo.language);
@@ -797,7 +755,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
updateSensitive(); updateSensitive();
updateHeaders();
if(editingStatus!=null){ if(editingStatus!=null){
updateCharCounter(); updateCharCounter();
@@ -852,11 +809,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener()); draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null); updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
buildLanguageSelector(languageButton); buildLanguageSelector(languageButton);
if (editingStatus != null && scheduledStatus == null) {
// editing an already published post
draftsBtn.setVisibility(View.GONE);
}
} }
private void navigateToUnsentPosts() { private void navigateToUnsentPosts() {
@@ -880,13 +832,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void updateLanguage(MastodonLanguage loc) { private void updateLanguage(MastodonLanguage loc) {
updateLanguage(loc.getLanguage(), loc.getLanguageName(), loc.getDefaultName()); language = loc.getLanguage();
} languageButton.setText(loc.getLanguageName());
languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, loc.getDefaultName()));
private void updateLanguage(String languageTag, String languageName, String defaultName) {
language = languageTag;
languageButton.setText(languageName);
languageButton.setContentDescription(getActivity().getString(R.string.sk_post_language, defaultName));
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@@ -896,19 +844,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
btn.setOnClickListener(v->languagePopup.show()); btn.setOnClickListener(v->languagePopup.show());
Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences; Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences;
if (language != null) updateLanguage(language); updateLanguage(prefs != null && prefs.postingDefaultLanguage != null && prefs.postingDefaultLanguage.length() > 0
else updateLanguage(prefs != null && prefs.postingDefaultLanguage != null && prefs.postingDefaultLanguage.length() > 0
? languageResolver.from(prefs.postingDefaultLanguage) ? languageResolver.from(prefs.postingDefaultLanguage)
: languageResolver.getDefault()); : languageResolver.getDefault());
Menu languageMenu = languagePopup.getMenu(); Menu languageMenu = languagePopup.getMenu();
for (String recentLanguage : Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages)) { for (String recentLanguage : Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages)) {
if (recentLanguage.equals("bottom")) { MastodonLanguage l = languageResolver.from(recentLanguage);
addBottomLanguage(languageMenu); languageMenu.add(0, allLanguages.indexOf(l), Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
} else {
MastodonLanguage l = languageResolver.from(recentLanguage);
languageMenu.add(0, allLanguages.indexOf(l), Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
}
} }
SubMenu allLanguagesMenu = languageMenu.addSubMenu(R.string.sk_available_languages); SubMenu allLanguagesMenu = languageMenu.addSubMenu(R.string.sk_available_languages);
@@ -917,33 +860,13 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
allLanguagesMenu.add(0, i, Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName())); allLanguagesMenu.add(0, i, Menu.NONE, getActivity().getString(R.string.sk_language_name, l.getDefaultName(), l.getLanguageName()));
} }
if (GlobalUserPreferences.bottomEncoding) addBottomLanguage(allLanguagesMenu);
btn.setOnLongClickListener(v->{
btn.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (!GlobalUserPreferences.bottomEncoding) addBottomLanguage(allLanguagesMenu);
return false;
});
languagePopup.setOnMenuItemClickListener(i->{ languagePopup.setOnMenuItemClickListener(i->{
if (i.hasSubMenu()) return false; if (i.hasSubMenu()) return false;
if (i.getItemId() == allLanguages.size()) { updateLanguage(allLanguages.get(i.getItemId()));
updateLanguage(language, "\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48", "bottom");
encoding = "bottom";
} else {
updateLanguage(allLanguages.get(i.getItemId()));
encoding = null;
}
return true; return true;
}); });
} }
private void addBottomLanguage(Menu menu) {
if (menu.findItem(allLanguages.size()) == null) {
menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)");
}
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item){ public boolean onOptionsItemSelected(MenuItem item){
return true; return true;
@@ -973,9 +896,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
if(hasSpoiler){ if(hasSpoiler){
charCount+=spoilerEdit.length(); charCount+=spoilerEdit.length();
} }
if (localOnly && GlobalUserPreferences.accountsInGlitchMode.contains(accountID)) {
charCount -= GLITCH_LOCAL_ONLY_SUFFIX.length();
}
charCounter.setText(String.valueOf(charLimit-charCount)); charCounter.setText(String.valueOf(charLimit-charCount));
trimmedCharCount=text.toString().trim().length(); trimmedCharCount=text.toString().trim().length();
updatePublishButtonState(); updatePublishButtonState();
@@ -1008,7 +928,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
nonDoneAttachmentCount++; nonDoneAttachmentCount++;
} }
publishButton.setEnabled((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){
@@ -1028,8 +948,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
&& statusVisibility != StatusPrivacy.DIRECT && statusVisibility != StatusPrivacy.DIRECT
&& !attachments.stream().allMatch(attachment -> attachment.description != null && !attachment.description.isBlank())) { && !attachments.stream().allMatch(attachment -> attachment.description != null && !attachment.description.isBlank())) {
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.mo_no_image_desc_title) .setTitle(R.string.sk_no_image_desc_title)
.setMessage(R.string.mo_no_image_desc) .setMessage(R.string.sk_no_image_desc)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.publish, (dialog, i)-> publish()) .setPositiveButton(R.string.publish, (dialog, i)-> publish())
.show(); .show();
@@ -1076,53 +996,15 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void publish(){ private void publish(){
publish(false);
}
private void publish(boolean force){
String text=mainEditText.getText().toString(); String text=mainEditText.getText().toString();
CreateStatus.Request req=new CreateStatus.Request(); CreateStatus.Request req=new CreateStatus.Request();
if ("bottom".equals(encoding)) {
text = new StatusTextEncoder(Bottom::encode).encode(text);
req.spoilerText = "bottom-encoded emoji spam";
}
if (localOnly &&
GlobalUserPreferences.accountsInGlitchMode.contains(accountID) &&
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()) {
text += " " + GLITCH_LOCAL_ONLY_SUFFIX;
}
req.status=text; req.status=text;
req.localOnly=localOnly; req.visibility=statusVisibility;
req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility;
req.sensitive=sensitive; req.sensitive=sensitive;
req.language=language; req.language=language;
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());
Optional<DraftMediaAttachment> withoutAltText = attachments.stream().filter(a -> a.description == null || a.description.isBlank()).findFirst();
boolean isDraft = scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
if (!force && !GlobalUserPreferences.disableAltTextReminder && !isDraft && withoutAltText.isPresent()) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_alt_text_missing_title)
.setMessage(R.string.sk_alt_text_missing)
.setPositiveButton(R.string.add_alt_text, (d, w) -> editMediaDescription(withoutAltText.get()))
.setNegativeButton(R.string.sk_publish_anyway, (d, w) -> publish(true))
.show();
return;
}
}
// ask whether to publish now when editing an existing draft
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w) -> publish(true))
.setNegativeButton(R.string.publish, (d, w) -> {
updateScheduledAt(null);
publish();
})
.show();
return;
} }
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){ if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id; req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
@@ -1228,14 +1110,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
List<String> newRecentLanguages = new ArrayList<>(Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages)); List<String> newRecentLanguages = new ArrayList<>(Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages));
newRecentLanguages.remove(language); newRecentLanguages.remove(language);
newRecentLanguages.add(0, language); newRecentLanguages.add(0, language);
if (encoding != null) {
newRecentLanguages.remove(encoding);
newRecentLanguages.add(0, encoding);
}
if ("bottom".equals(encoding) && !GlobalUserPreferences.bottomEncoding) {
GlobalUserPreferences.bottomEncoding = true;
GlobalUserPreferences.save();
}
recentLanguages.put(accountID, newRecentLanguages.stream().limit(4).collect(Collectors.toList())); recentLanguages.put(accountID, newRecentLanguages.stream().limit(4).collect(Collectors.toList()));
GlobalUserPreferences.save(); GlobalUserPreferences.save();
} }
@@ -1301,14 +1175,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void confirmDiscardDraftAndFinish(){ private void confirmDiscardDraftAndFinish(){
boolean attachmentsPending = attachments.stream().anyMatch(att -> att.state != AttachmentUploadState.DONE); new M3AlertDialogBuilder(getActivity())
if (attachmentsPending) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_unfinished_attachments)
.setMessage(R.string.sk_unfinished_attachments_message)
.setPositiveButton(R.string.edit, (d, w) -> {})
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
.show();
else new M3AlertDialogBuilder(getActivity())
.setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft) .setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
.setPositiveButton(R.string.save, (d, w) -> { .setPositiveButton(R.string.save, (d, w) -> {
updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt); updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt);
@@ -1318,6 +1185,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
.show(); .show();
} }
/**
* Check to see if Android platform photopicker is available on the device\
* @return whether the device supports photopicker intents.
*/
private boolean isPhotoPickerAvailable() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return true;
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return getExtensionVersion(Build.VERSION_CODES.R) >= 2;
} else
return false;
}
/** /**
* Builds the correct intent for the device version to select media. * Builds the correct intent for the device version to select media.
@@ -1327,26 +1206,26 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
* *
* <p>For earlier versions use the built in docs ui via {@link Intent#ACTION_GET_CONTENT} * <p>For earlier versions use the built in docs ui via {@link Intent#ACTION_GET_CONTENT}
*/ */
private void openFilePicker(boolean photoPicker){ private void openFilePicker(){
Intent intent; Intent intent;
boolean usePhotoPicker=photoPicker && isPhotoPickerAvailable(); boolean usePhotoPicker = isPhotoPickerAvailable();
if(usePhotoPicker){ if (usePhotoPicker) {
intent=new Intent(MediaStore.ACTION_PICK_IMAGES); intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MAX_ATTACHMENTS-getMediaAttachmentsCount()); intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
}else{ } else {
intent=new Intent(Intent.ACTION_GET_CONTENT); intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*"); intent.setType("*/*");
} }
if(!usePhotoPicker && instance.configuration!=null && if (!usePhotoPicker && instance.configuration != null &&
instance.configuration.mediaAttachments!=null && instance.configuration.mediaAttachments != null &&
instance.configuration.mediaAttachments.supportedMimeTypes!=null && instance.configuration.mediaAttachments.supportedMimeTypes != null &&
!instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()){ !instance.configuration.mediaAttachments.supportedMimeTypes.isEmpty()) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, intent.putExtra(Intent.EXTRA_MIME_TYPES,
instance.configuration.mediaAttachments.supportedMimeTypes.toArray( instance.configuration.mediaAttachments.supportedMimeTypes.toArray(
new String[0])); new String[0]));
}else{ } else {
if(!usePhotoPicker){ if (!usePhotoPicker) {
// If photo picker is being used these are the default mimetypes. // If photo picker is being used these are the default mimetypes.
intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"}); intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*", "video/*"});
} }
@@ -1767,20 +1646,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
menu.getMenu().add(0, 2, 0, getResources().getQuantityString(R.plurals.x_minutes, 30, 30)); menu.getMenu().add(0, 2, 0, getResources().getQuantityString(R.plurals.x_minutes, 30, 30));
menu.getMenu().add(0, 3, 0, getResources().getQuantityString(R.plurals.x_hours, 1, 1)); menu.getMenu().add(0, 3, 0, getResources().getQuantityString(R.plurals.x_hours, 1, 1));
menu.getMenu().add(0, 4, 0, getResources().getQuantityString(R.plurals.x_hours, 6, 6)); menu.getMenu().add(0, 4, 0, getResources().getQuantityString(R.plurals.x_hours, 6, 6));
menu.getMenu().add(0, 5, 0, getResources().getQuantityString(R.plurals.x_hours, 12, 12)); menu.getMenu().add(0, 5, 0, getResources().getQuantityString(R.plurals.x_days, 1, 1));
menu.getMenu().add(0, 6, 0, getResources().getQuantityString(R.plurals.x_days, 1, 1)); menu.getMenu().add(0, 6, 0, getResources().getQuantityString(R.plurals.x_days, 3, 3));
menu.getMenu().add(0, 7, 0, getResources().getQuantityString(R.plurals.x_days, 3, 3)); menu.getMenu().add(0, 7, 0, getResources().getQuantityString(R.plurals.x_days, 7, 7));
menu.getMenu().add(0, 8, 0, getResources().getQuantityString(R.plurals.x_days, 7, 7));
menu.setOnMenuItemClickListener(item->{ menu.setOnMenuItemClickListener(item->{
pollDuration=switch(item.getItemId()){ pollDuration=switch(item.getItemId()){
case 1 -> 5*60; case 1 -> 5*60;
case 2 -> 30*60; case 2 -> 30*60;
case 3 -> 3600; case 3 -> 3600;
case 4 -> 6*3600; case 4 -> 6*3600;
case 5 -> 12*3600; case 5 -> 24*3600;
case 6 -> 24*3600; case 6 -> 3*24*3600;
case 7 -> 3*24*3600; case 7 -> 7*24*3600;
case 8 -> 7*24*3600;
default -> throw new IllegalStateException("Unexpected value: "+item.getItemId()); default -> throw new IllegalStateException("Unexpected value: "+item.getItemId());
}; };
pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr=item.getTitle().toString())); pollDurationView.setText(getString(R.string.compose_poll_duration, pollDurationStr=item.getTitle().toString()));
@@ -1889,33 +1766,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
return attachments.size(); return attachments.size();
} }
private void updateHeaders() {
UiUtils.setExtraTextInfo(getContext(), selfExtraText, statusVisibility, localOnly);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, replyTo.visibility, replyTo.localOnly);
}
private void buildVisibilityPopup(View v){ private void buildVisibilityPopup(View v){
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();
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
if (instance.pleroma != null) {
m.findItem(R.id.vis_local).setVisible(true);
} else if (localOnly || prefsSaysSupported) {
localOnlyItem.setVisible(true);
localOnlyItem.setChecked(localOnly);
Status status = editingStatus != null ? editingStatus : replyTo;
if (!prefsSaysSupported) {
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
if (GLITCH_LOCAL_ONLY_PATTERN.matcher(status.getStrippedText()).matches()) {
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
}
GlobalUserPreferences.save();
}
}
UiUtils.enablePopupMenuIcons(getActivity(), visibilityPopup); UiUtils.enablePopupMenuIcons(getActivity(), visibilityPopup);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) m.setGroupDividerEnabled(true); m.setGroupCheckable(0, true, true);
visibilityPopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ visibilityPopup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
@Override @Override
public boolean onMenuItemClick(MenuItem item){ public boolean onMenuItemClick(MenuItem item){
@@ -1928,44 +1784,41 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
statusVisibility=StatusPrivacy.PRIVATE; statusVisibility=StatusPrivacy.PRIVATE;
}else if(id==R.id.vis_private){ }else if(id==R.id.vis_private){
statusVisibility=StatusPrivacy.DIRECT; statusVisibility=StatusPrivacy.DIRECT;
}else if(id==R.id.vis_local){
statusVisibility=StatusPrivacy.LOCAL;
}
if (id == R.id.local_only) {
localOnly = !item.isChecked();
item.setChecked(localOnly);
} else {
item.setChecked(true);
} }
item.setChecked(true);
updateVisibilityIcon(); updateVisibilityIcon();
updateHeaders();
return true; return true;
} }
}); });
} }
private void loadDefaultStatusVisibility(Bundle savedInstanceState) { private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
if(replyTo != null) statusVisibility = replyTo.visibility; if(getArguments().containsKey("replyTo")){
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
statusVisibility = replyTo.visibility;
}
// A saved privacy setting from a previous compose session wins over the reply visibility // A saved privacy setting from a previous compose session wins over the reply visibility
if(savedInstanceState !=null){ if(savedInstanceState !=null){
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility"); statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
} }
AccountSessionManager asm = AccountSessionManager.getInstance(); Preferences prefs = AccountSessionManager.getInstance().getAccount(accountID).preferences;
Preferences prefs = asm.getAccount(accountID).preferences;
if (prefs != null) { if (prefs != null) {
// Only override the reply visibility if our preference is more private // Only override the reply visibility if our preference is more private
// (and we're not replying to ourselves, or not at all) if (prefs.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
if (prefs.postingDefaultVisibility.isLessVisibleThan(statusVisibility) && statusVisibility = switch (prefs.postingDefaultVisibility) {
(replyTo == null || !asm.isSelf(accountID, replyTo.account))) { case PUBLIC -> StatusPrivacy.PUBLIC;
statusVisibility = prefs.postingDefaultVisibility; case UNLISTED -> StatusPrivacy.UNLISTED;
case PRIVATE -> StatusPrivacy.PRIVATE;
case DIRECT -> StatusPrivacy.DIRECT;
};
} }
}
// A saved privacy setting from a previous compose session wins over all // A saved privacy setting from a previous compose session wins over all
if(savedInstanceState !=null){ if(savedInstanceState !=null){
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility"); statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
}
} }
} }
@@ -1975,10 +1828,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
visibilityBtn.setImageResource(switch(statusVisibility){ visibilityBtn.setImageResource(switch(statusVisibility){
case PUBLIC -> R.drawable.ic_fluent_earth_24_regular; case PUBLIC -> R.drawable.ic_fluent_earth_24_regular;
case UNLISTED -> R.drawable.ic_fluent_lock_open_24_regular; case UNLISTED -> R.drawable.ic_fluent_people_community_24_regular;
case PRIVATE -> R.drawable.ic_fluent_lock_closed_24_filled; case PRIVATE -> R.drawable.ic_fluent_people_checkmark_24_regular;
case DIRECT -> R.drawable.ic_fluent_mention_24_regular; case DIRECT -> R.drawable.ic_fluent_mention_24_regular;
case LOCAL -> R.drawable.ic_fluent_eye_24_regular;
}); });
} }
@@ -2100,14 +1952,6 @@ 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

@@ -0,0 +1,253 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.requests.timelines.GetConversationsTimeline;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class ConversationsTimelineFragment extends FabStatusListFragment {
private HomeTabFragment parent;
private String maxID;
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
if (getParentFragment() instanceof HomeTabFragment home) parent = home;
loadData();
}
private List<Status> filterPosts(List<Status> items) {
// Disabling this for DMs, because there are no boosts on DMs, and most of them are replies
return items.stream().filter(i ->
(i.visibility == StatusPrivacy.DIRECT)
).collect(Collectors.toList());
// return items;
}
@Override
protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance()
.getAccount(accountID).getCacheController()
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
@Override
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
if(getActivity()==null)
return;
List<Status> filteredItems = filterPosts(result.items);
onDataLoaded(filteredItems, !result.items.isEmpty());
maxID=result.maxID;
if(result.isFromCache())
loadNewPosts();
}
});
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
parent.hideNewPostsButton();
}
}
});
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad")){
if(!loaded && !dataLoading){
loadData();
}else if(!dataLoading){
loadNewPosts();
}
}
}
public void onStatusCreated(StatusCreatedEvent ev){
prependItems(Collections.singletonList(ev.status), true);
}
private void loadNewPosts(){
if (!GlobalUserPreferences.loadNewPosts) return;
dataLoading=true;
// The idea here is that we request the timeline such that if there are fewer than `limit` posts,
// we'll get the currently topmost post as last in the response. This way we know there's no gap
// between the existing and newly loaded parts of the timeline.
String sinceID=data.size()>1 ? data.get(1).id : "1";
currentRequest=new GetConversationsTimeline(null, null, 20, sinceID)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
currentRequest=null;
dataLoading=false;
result = filterPosts(result);
if(result.isEmpty() || getActivity()==null)
return;
Status last=result.get(result.size()-1);
List<Status> toAdd;
if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one
toAdd=result.subList(0, result.size()-1); // Remove the already known last post
}else{
result.get(result.size()-1).hasGapAfter=true;
toAdd=result;
}
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
if(!toAdd.isEmpty()){
prependItems(toAdd, true);
if (parent != null) parent.showNewPostsButton();
// AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomTimeline(toAdd, false);
}
}
@Override
public void onError(ErrorResponse error){
currentRequest=null;
dataLoading=false;
}
})
.exec(accountID);
}
@Override
public void onGapClick(GapStatusDisplayItem.Holder item){
if(dataLoading)
return;
item.getItem().loading=true;
V.setVisibilityAnimated(item.progress, View.VISIBLE);
V.setVisibilityAnimated(item.text, View.GONE);
GapStatusDisplayItem gap=item.getItem();
dataLoading=true;
currentRequest=new GetConversationsTimeline(item.getItemID(), null, 20, null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
currentRequest=null;
dataLoading=false;
if(getActivity()==null)
return;
int gapPos=displayItems.indexOf(gap);
if(gapPos==-1)
return;
if(result.isEmpty()){
displayItems.remove(gapPos);
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
Status gapStatus=getStatusByID(gap.parentID);
if(gapStatus!=null){
gapStatus.hasGapAfter=false;
// AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putConversationsTimeline(Collections.singletonList(gapStatus), false);
}
}else{
Set<String> idsBelowGap=new HashSet<>();
boolean belowGap=false;
int gapPostIndex=0;
for(Status s:data){
if(belowGap){
idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){
belowGap=true;
s.hasGapAfter=false;
// AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putConversationsTimeline(Collections.singletonList(s), false);
}else{
gapPostIndex++;
}
}
int endIndex=0;
for(Status s:result){
endIndex++;
if(idsBelowGap.contains(s.id))
break;
}
if(endIndex==result.size()){
result.get(result.size()-1).hasGapAfter=true;
}else{
result=result.subList(0, endIndex);
}
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
targetList.clear();
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
for(Status s:result){
if(idsBelowGap.contains(s.id))
break;
if(filterPredicate.test(s)){
targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s);
}
}
if(targetList.isEmpty()){
// oops. We didn't add new posts, but at least we know there are none.
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
}else{
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
}
// AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putConversationsTimeline(insertedPosts, false);
}
}
@Override
public void onError(ErrorResponse error){
currentRequest=null;
dataLoading=false;
gap.loading=false;
Activity a=getActivity();
if(a!=null){
error.showToast(a);
int gapPos=displayItems.indexOf(gap);
if(gapPos>=0)
adapter.notifyItemChanged(gapPos);
}
}
})
.exec(accountID);
}
@Override
public void onRefresh(){
if(currentRequest!=null){
currentRequest.cancel();
currentRequest=null;
dataLoading=false;
}
if (parent != null) parent.hideNewPostsButton();
super.onRefresh();
}
@Override
protected boolean shouldRemoveAccountPostsWhenUnfollowing(){
return true;
}
}

View File

@@ -1,352 +0,0 @@
package org.joinmastodon.android.fragments;
import static android.view.Menu.NONE;
import static org.joinmastodon.android.ui.utils.UiUtils.makeBackItem;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView;
public class EditTimelinesFragment extends BaseRecyclerFragment<TimelineDefinition> implements ScrollableToTop {
private String accountID;
private TimelinesAdapter adapter;
private final ItemTouchHelper itemTouchHelper;
private Menu optionsMenu;
private boolean updated;
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>();
private final List<ListTimeline> listTimelines = new ArrayList<>();
private final List<Hashtag> hashtags = new ArrayList<>();
public EditTimelinesFragment() {
super(10);
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ;
itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
setTitle(R.string.sk_timelines);
accountID = getArguments().getString("account");
new GetLists().setCallback(new Callback<>() {
@Override
public void onSuccess(List<ListTimeline> result) {
listTimelines.addAll(result);
updateOptionsMenu();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountID);
new GetFollowedHashtags().setCallback(new Callback<>() {
@Override
public void onSuccess(HeaderPaginationList<Hashtag> result) {
hashtags.addAll(result);
updateOptionsMenu();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
}).exec(accountID);
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading) loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
itemTouchHelper.attachToRecyclerView(list);
refreshLayout.setEnabled(false);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 0.5f, 56, 16));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
this.optionsMenu = menu;
updateOptionsMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_back) {
updateOptionsMenu();
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
return true;
}
TimelineDefinition tl = timelineByMenuItem.get(item);
if (tl != null) {
data.add(tl.copy());
adapter.notifyItemInserted(data.size());
saveTimelines();
updateOptionsMenu();
};
return true;
}
private void addTimelineToOptions(TimelineDefinition tl, Menu menu) {
if (data.contains(tl)) return;
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, tl.getTitle(getContext()));
item.setIcon(tl.getIcon().iconRes);
timelineByMenuItem.put(item, tl);
}
private void updateOptionsMenu() {
if (getActivity() == null) return;
optionsMenu.clear();
timelineByMenuItem.clear();
SubMenu menu = optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline);
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list);
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag);
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
makeBackItem(timelinesMenu);
makeBackItem(listsMenu);
makeBackItem(hashtagsMenu);
TimelineDefinition.ALL_TIMELINES.forEach(tl -> addTimelineToOptions(tl, timelinesMenu));
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu));
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu));
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0);
listsMenu.getItem().setVisible(listsMenu.size() > 0);
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0);
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
}
private void saveTimelines() {
updated = true;
GlobalUserPreferences.pinnedTimelines.put(accountID, data.size() > 0 ? data : List.of(TimelineDefinition.HOME_TIMELINE));
GlobalUserPreferences.save();
}
private void removeTimeline(int position) {
data.remove(position);
adapter.notifyItemRemoved(position);
saveTimelines();
updateOptionsMenu();
}
@Override
protected void doLoadData(int offset, int count){
onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES), false);
updateOptionsMenu();
}
@Override
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() {
return adapter = new TimelinesAdapter();
}
@Override
public void scrollToTop() {
smoothScrollRecyclerViewToTop(list);
}
@Override
public void onDestroy() {
super.onDestroy();
if (updated) UiUtils.restartApp();
}
private class TimelinesAdapter extends RecyclerView.Adapter<TimelineViewHolder>{
@NonNull
@Override
public TimelineViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new TimelineViewHolder();
}
@Override
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) {
holder.bind(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
}
private class TimelineViewHolder extends BindableViewHolder<TimelineDefinition> implements UsableRecyclerView.Clickable{
private final TextView title;
private final ImageView dragger;
public TimelineViewHolder(){
super(getActivity(), R.layout.item_text, list);
title=findViewById(R.id.title);
dragger=findViewById(R.id.dragger_thingy);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBind(TimelineDefinition item) {
title.setText(item.getTitle(getContext()));
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
dragger.setVisibility(View.VISIBLE);
dragger.setOnTouchListener((View v, MotionEvent event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
itemTouchHelper.startDrag(this);
return true;
}
return false;
});
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onClick() {
Context ctx = getContext();
LinearLayout view = (LinearLayout) getActivity().getLayoutInflater()
.inflate(R.layout.edit_timeline, (ViewGroup) itemView, false);
TextInputFrameLayout inputLayout = view.findViewById(R.id.input);
EditText editText = inputLayout.getEditText();
editText.setText(item.getCustomTitle());
editText.setHint(item.getDefaultTitle(ctx));
ImageButton btn = view.findViewById(R.id.button);
PopupMenu popup = new PopupMenu(ctx, btn);
TimelineDefinition.Icon currentIcon = item.getIcon();
btn.setImageResource(currentIcon.iconRes);
btn.setContentDescription(ctx.getString(currentIcon.nameRes));
btn.setOnTouchListener(popup.getDragToOpenListener());
btn.setOnClickListener(l -> popup.show());
Menu menu = popup.getMenu();
TimelineDefinition.Icon defaultIcon = item.getDefaultIcon();
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
if (!currentIcon.equals(defaultIcon)) {
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
}
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) {
if (icon.hidden || icon.equals(item.getIcon())) continue;
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
}
UiUtils.enablePopupMenuIcons(ctx, popup);
popup.setOnMenuItemClickListener(menuItem -> {
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()];
btn.setImageResource(icon.iconRes);
btn.setContentDescription(ctx.getString(icon.nameRes));
item.setIcon(icon);
return true;
});
new M3AlertDialogBuilder(ctx)
.setTitle(R.string.sk_edit_timeline)
.setView(view)
.setPositiveButton(R.string.save, (d, which) -> {
item.setTitle(editText.getText().toString().trim());
rebind();
saveTimelines();
})
.setNeutralButton(R.string.sk_remove, (d, which) ->
removeTimeline(getAbsoluteAdapterPosition()))
.setNegativeButton(R.string.cancel, (d, which) -> {})
.show();
btn.requestFocus();
}
}
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback {
public ItemTouchHelperCallback() {
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAbsoluteAdapterPosition();
int toPosition = target.getAbsoluteAdapterPosition();
if (Math.max(fromPosition, toPosition) >= data.size() || Math.min(fromPosition, toPosition) < 0) {
return false;
} else {
Collections.swap(data, fromPosition, toPosition);
adapter.notifyItemMoved(fromPosition, toPosition);
saveTimelines();
return true;
}
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) {
viewHolder.itemView.animate().alpha(0.65f);
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.animate().alpha(1f);
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAbsoluteAdapterPosition();
removeTimeline(position);
}
}
}

View File

@@ -0,0 +1,37 @@
package org.joinmastodon.android.fragments;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.utils.UiUtils;
import me.grishka.appkit.Nav;
public abstract class FabStatusListFragment extends StatusListFragment {
protected ImageButton fab;
public FabStatusListFragment() {
setListLayoutId(R.layout.recycler_fragment_with_fab);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
fab = view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(this::onFabLongClick);
}
protected void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), ComposeFragment.class, args);
}
protected boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID);
}
}

View File

@@ -25,7 +25,6 @@ public class FavoritedStatusListFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Status> result){ public void onSuccess(HeaderPaginationList<Status> result){
if (getActivity() == null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else

View File

@@ -80,7 +80,6 @@ public class FollowRequestsListFragment extends BaseRecyclerFragment<FollowReque
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Account> result){ public void onSuccess(HeaderPaginationList<Account> result){
if (getActivity() == null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else

View File

@@ -55,7 +55,6 @@ public class FollowedHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Hashtag> result){ public void onSuccess(HeaderPaginationList<Hashtag> result){
if (getActivity() == null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else

View File

@@ -2,7 +2,6 @@ package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.view.HapticFeedbackConstants;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@@ -11,21 +10,15 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.tags.GetHashtag; import org.joinmastodon.android.api.requests.tags.GetHashtag;
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed; import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline; import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.events.HashtagUpdatedEvent;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -33,14 +26,14 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class HashtagTimelineFragment extends PinnableStatusListFragment { public class HashtagTimelineFragment extends StatusListFragment{
private String hashtag; private String hashtag;
private boolean following; private boolean following;
private ImageButton fab;
private MenuItem followButton; private MenuItem followButton;
@Override public HashtagTimelineFragment(){
protected boolean withComposeButton() { setListLayoutId(R.layout.recycler_fragment_with_fab);
return true;
} }
@Override @Override
@@ -48,6 +41,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
super.onAttach(activity); super.onAttach(activity);
updateTitle(getArguments().getString("hashtag")); updateTitle(getArguments().getString("hashtag"));
following=getArguments().getBoolean("following", false); following=getArguments().getBoolean("following", false);
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
@@ -60,42 +54,19 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
this.following = newFollowing; this.following = newFollowing;
followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag)); followButton.setTitle(getString(newFollowing ? R.string.unfollow_user : R.string.follow_user, "#" + hashtag));
followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular); followButton.setIcon(newFollowing ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
E.post(new HashtagUpdatedEvent(hashtag, following));
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.hashtag_timeline, menu); inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
followButton = menu.findItem(R.id.follow_hashtag); followButton = menu.findItem(R.id.follow_hashtag);
updateFollowingState(following); updateFollowingState(following);
new GetHashtag(hashtag).setCallback(new Callback<>() { followButton.setOnMenuItemClickListener(i -> {
@Override
public void onSuccess(Hashtag hashtag) {
if (getActivity() == null) return;
updateTitle(hashtag.name);
updateFollowingState(hashtag.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.follow_hashtag) {
updateFollowingState(!following); updateFollowingState(!following);
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() { new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
@Override @Override
public void onSuccess(Hashtag i) { public void onSuccess(Hashtag i) {
if (getActivity() == null) return;
if (i.following == following) Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show(); if (i.following == following) Toast.makeText(getActivity(), getString(i.following ? R.string.followed_user : R.string.unfollowed_user, "#" + i.name), Toast.LENGTH_SHORT).show();
updateFollowingState(i.following); updateFollowingState(i.following);
} }
@@ -107,13 +78,20 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
} }
}).exec(accountID); }).exec(accountID);
return true; return true;
} });
return false;
}
@Override new GetHashtag(hashtag).setCallback(new Callback<>() {
protected TimelineDefinition makeTimelineDefinition() { @Override
return TimelineDefinition.ofHashtag(hashtag); public void onSuccess(Hashtag hashtag) {
updateTitle(hashtag.name);
updateFollowingState(hashtag.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
} }
@Override @Override
@@ -122,8 +100,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
} }
}) })
@@ -138,12 +114,14 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
} }
@Override @Override
protected boolean onFabLongClick(View v) { public void onViewCreated(View view, Bundle savedInstanceState){
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '); super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' '));
} }
@Override private 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+' ');

View File

@@ -16,6 +16,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import org.joinmastodon.android.PushNotificationReceiver;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -61,7 +62,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
setTitle(R.string.mo_app_name); setTitle(R.string.sk_app_name);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N) if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true); setRetainInstance(true);

View File

@@ -1,6 +1,6 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import static org.joinmastodon.android.GlobalUserPreferences.reduceMotion; import static org.joinmastodon.android.GlobalUserPreferences.showFederatedTimeline;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
@@ -41,26 +41,21 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.announcements.GetAnnouncements; import org.joinmastodon.android.api.requests.announcements.GetAnnouncements;
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.events.HashtagUpdatedEvent;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent; import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment;
import org.joinmastodon.android.fragments.discover.LocalTimelineFragment;
import org.joinmastodon.android.model.Announcement; import org.joinmastodon.android.model.Announcement;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.ListTimeline;
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 java.util.Collection; 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.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -74,12 +69,14 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
private static final int ANNOUNCEMENTS_RESULT = 654; private static final int ANNOUNCEMENTS_RESULT = 654;
private String accountID; private String accountID;
private MenuItem announcements, announcementsAction, settings, settingsAction; private MenuItem announcements;
// private ImageView toolbarLogo; // private ImageView toolbarLogo;
private Button toolbarShowNewPostsBtn; private Button toolbarShowNewPostsBtn;
private boolean newPostsBtnShown; private boolean newPostsBtnShown;
private AnimatorSet currentNewPostsAnim; private AnimatorSet currentNewPostsAnim;
private ViewPager2 pager; private ViewPager2 pager;
private final List<Fragment> fragments = new ArrayList<>();
private final List<FrameLayout> tabViews = new ArrayList<>();
private View switcher; private View switcher;
private FrameLayout toolbarFrame; private FrameLayout toolbarFrame;
private ImageView timelineIcon; private ImageView timelineIcon;
@@ -88,29 +85,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
private PopupMenu switcherPopup; private PopupMenu switcherPopup;
private final Map<Integer, ListTimeline> listItems = new HashMap<>(); private final Map<Integer, ListTimeline> listItems = new HashMap<>();
private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>(); private final Map<Integer, Hashtag> hashtagsItems = new HashMap<>();
private List<TimelineDefinition> timelineDefinitions;
private int count;
private Fragment[] fragments;
private FrameLayout[] tabViews;
private TimelineDefinition[] timelines;
private final Map<Integer, TimelineDefinition> timelinesByMenuItem = new HashMap<>();
private SubMenu hashtagsMenu, listsMenu;
private PopupMenu overflowPopup;
private View overflowActionView = null;
private boolean announcementsBadged, settingsBadged;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
E.register(this);
accountID = getArguments().getString("account"); accountID = getArguments().getString("account");
timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES);
assert timelineDefinitions != null;
if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE);
count = timelineDefinitions.size();
fragments = new Fragment[count];
tabViews = new FrameLayout[count];
timelines = new TimelineDefinition[count];
} }
@Override @Override
@@ -125,40 +104,36 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
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);
if (fragments[0] == null) { if (fragments.size() == 0) {
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("onlyPosts", true);
for (int i = 0; i < timelineDefinitions.size(); i++) { fragments.add(new HomeTimelineFragment());
TimelineDefinition tl = timelineDefinitions.get(i); fragments.add(new LocalTimelineFragment());
fragments[i] = tl.getFragment(); if (showFederatedTimeline) fragments.add(new FederatedTimelineFragment());
timelines[i] = tl; args=new Bundle(args);
} args.putBoolean("onlyPosts", true);
NotificationsListFragment postsFragment=new NotificationsListFragment();
postsFragment.setArguments(args);
fragments.add(postsFragment);
FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
for (int i = 0; i < count; i++) { for (int i = 0; i < fragments.size(); i++) {
fragments[i].setArguments(timelines[i].populateArguments(new Bundle(args))); fragments.get(i).setArguments(args);
FrameLayout tabView = new FrameLayout(getActivity()); FrameLayout tabView = new FrameLayout(getActivity());
tabView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); tabView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
tabView.setVisibility(View.GONE); tabView.setVisibility(View.GONE);
tabView.setId(i + 1); tabView.setId(i + 1);
transaction.add(i + 1, fragments[i]); transaction.add(i + 1, fragments.get(i));
view.addView(tabView); view.addView(tabView);
tabViews[i] = tabView; tabViews.add(tabView);
} }
transaction.commit(); transaction.commit();
} }
view.addView(pager, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); view.addView(pager, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
overflowActionView = UiUtils.makeOverflowActionView(getContext());
overflowPopup = new PopupMenu(getContext(), overflowActionView);
overflowPopup.setOnMenuItemClickListener(this::onOptionsItemSelected);
overflowActionView.setOnClickListener(l -> overflowPopup.show());
overflowActionView.setOnTouchListener(overflowPopup.getDragToOpenListener());
return view; return view;
} }
@@ -172,36 +147,37 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
collapsedChevron = toolbarFrame.findViewById(R.id.collapsed_chevron); collapsedChevron = toolbarFrame.findViewById(R.id.collapsed_chevron);
switcher = toolbarFrame.findViewById(R.id.switcher_btn); switcher = toolbarFrame.findViewById(R.id.switcher_btn);
switcherPopup = new PopupMenu(getContext(), switcher); switcherPopup = new PopupMenu(getContext(), switcher);
switcherPopup.inflate(R.menu.home_switcher);
switcherPopup.setOnMenuItemClickListener(this::onSwitcherItemSelected); switcherPopup.setOnMenuItemClickListener(this::onSwitcherItemSelected);
UiUtils.enablePopupMenuIcons(getContext(), switcherPopup); UiUtils.enablePopupMenuIcons(getContext(), switcherPopup);
switcher.setOnClickListener(v->switcherPopup.show()); switcher.setOnClickListener(v->{
switcher.setOnTouchListener(switcherPopup.getDragToOpenListener()); updateSwitcherMenu();
updateSwitcherMenu(); switcherPopup.show();
});
View.OnTouchListener listener = switcherPopup.getDragToOpenListener();
switcher.setOnTouchListener((v, m)-> {
updateSwitcherMenu();
return listener.onTouch(v, m);
});
UiUtils.reduceSwipeSensitivity(pager); UiUtils.reduceSwipeSensitivity(pager);
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe); pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
pager.setAdapter(new HomePagerAdapter()); pager.setAdapter(new HomePagerAdapter());
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
@Override @Override
public void onPageSelected(int position){ public void onPageSelected(int position){
if (!reduceMotion) {
// setting this here because page transformer appears to fire too late so the
// animation can appear bumpy, especially when navigating to a further-away tab
switcher.setScaleY(0.85f);
switcher.setScaleX(0.85f);
switcher.setAlpha(0.65f);
}
updateSwitcherIcon(position); updateSwitcherIcon(position);
if (!timelines[position].equals(TimelineDefinition.HOME_TIMELINE)) hideNewPostsButton(); if (position==0) return;
if (fragments[position] instanceof BaseRecyclerFragment<?> page){ hideNewPostsButton();
if (fragments.get(position) instanceof BaseRecyclerFragment<?> page){
if(!page.loaded && !page.isDataLoading()) page.loadData(); if(!page.loaded && !page.isDataLoading()) page.loadData();
} }
} }
}); });
if (!reduceMotion) { if (!GlobalUserPreferences.reduceMotion) {
pager.setPageTransformer((v, pos) -> { pager.setPageTransformer((v, pos) -> {
if (reduceMotion || tabViews[pager.getCurrentItem()] != v) return; if (tabViews.get(pager.getCurrentItem()) != v) return;
float scaleFactor = Math.max(0.85f, 1 - Math.abs(pos) * 0.06f); float scaleFactor = Math.max(0.85f, 1 - Math.abs(pos) * 0.06f);
switcher.setScaleY(scaleFactor); switcher.setScaleY(scaleFactor);
switcher.setScaleX(scaleFactor); switcher.setScaleX(scaleFactor);
@@ -211,37 +187,15 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
updateToolbarLogo(); updateToolbarLogo();
ViewTreeObserver vto = getToolbar().getViewTreeObserver();
if (vto.isAlive()) {
vto.addOnGlobalLayoutListener(() -> {
Toolbar t = getToolbar();
if (t == null) return;
int toolbarWidth = t.getWidth();
if (toolbarWidth == 0) return;
int toolbarFrameWidth = toolbarFrame.getWidth();
int padding = toolbarWidth - toolbarFrameWidth;
FrameLayout parent = ((FrameLayout) toolbarShowNewPostsBtn.getParent());
if (padding == parent.getPaddingStart()) return;
// toolbar frame goes from screen edge to beginning of right-aligned option buttons.
// centering button by applying the same space on the left
parent.setPaddingRelative(padding, 0, 0, 0);
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth - padding * 2);
switcher.setPivotX(V.dp(28)); // padding + half of icon
switcher.setPivotY(switcher.getHeight() / 2f);
});
}
if(GithubSelfUpdater.needSelfUpdating()){ if(GithubSelfUpdater.needSelfUpdating()){
E.register(this);
updateUpdateState(GithubSelfUpdater.getInstance().getState()); updateUpdateState(GithubSelfUpdater.getInstance().getState());
} }
new GetLists().setCallback(new Callback<>() { new GetLists().setCallback(new Callback<>() {
@Override @Override
public void onSuccess(List<ListTimeline> lists) { public void onSuccess(List<ListTimeline> lists) {
updateList(lists, listItems); addItemsToMap(lists, listItems);
} }
@Override @Override
@@ -253,7 +207,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
new GetFollowedHashtags().setCallback(new Callback<>() { new GetFollowedHashtags().setCallback(new Callback<>() {
@Override @Override
public void onSuccess(HeaderPaginationList<Hashtag> hashtags) { public void onSuccess(HeaderPaginationList<Hashtag> hashtags) {
updateList(hashtags, hashtagsItems); addItemsToMap(hashtags, hashtagsItems);
} }
@Override @Override
@@ -261,47 +215,6 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
error.showToast(getContext()); error.showToast(getContext());
} }
}).exec(accountID); }).exec(accountID);
new GetAnnouncements(false).setCallback(new Callback<>() {
@Override
public void onSuccess(List<Announcement> result) {
if (getActivity() == null) return;
if (result.stream().anyMatch(a -> !a.read)) {
announcementsBadged = true;
announcements.setVisible(false);
announcementsAction.setVisible(true);
}
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
private void addListsToOverflowMenu() {
Context ctx = getContext();
listsMenu.clear();
listsMenu.getItem().setVisible(listItems.size() > 0);
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(listsMenu));
listItems.forEach((id, list) -> {
MenuItem item = listsMenu.add(Menu.NONE, id, Menu.NONE, list.title);
item.setIcon(R.drawable.ic_fluent_people_24_regular);
UiUtils.insetPopupMenuIcon(ctx, item);
});
}
private void addHashtagsToOverflowMenu() {
Context ctx = getContext();
hashtagsMenu.clear();
hashtagsMenu.getItem().setVisible(hashtagsItems.size() > 0);
UiUtils.insetPopupMenuIcon(ctx, UiUtils.makeBackItem(hashtagsMenu));
hashtagsItems.forEach((id, hashtag) -> {
MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name);
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
UiUtils.insetPopupMenuIcon(ctx, item);
});
} }
public void updateToolbarLogo(){ public void updateToolbarLogo(){
@@ -316,6 +229,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
updateSwitcherIcon(pager.getCurrentItem()); updateSwitcherIcon(pager.getCurrentItem());
// toolbarLogo=new ImageView(getActivity());
// toolbarLogo.setScaleType(ImageView.ScaleType.CENTER);
// toolbarLogo.setImageResource(R.drawable.logo);
// toolbarLogo.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary)));
toolbarShowNewPostsBtn=toolbarFrame.findViewById(R.id.show_new_posts_btn); toolbarShowNewPostsBtn=toolbarFrame.findViewById(R.id.show_new_posts_btn);
toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors()); toolbarShowNewPostsBtn.setCompoundDrawableTintList(toolbarShowNewPostsBtn.getTextColors());
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N) UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn); if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N) UiUtils.fixCompoundDrawableTintOnAndroid6(toolbarShowNewPostsBtn);
@@ -336,90 +254,118 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
toolbarShowNewPostsBtn.setScaleY(.8f); toolbarShowNewPostsBtn.setScaleY(.8f);
timelineTitle.setVisibility(View.VISIBLE); timelineTitle.setVisibility(View.VISIBLE);
} }
}
private void updateOverflowMenu() { ViewTreeObserver vto = toolbar.getViewTreeObserver();
if (getActivity() == null) return; if (vto.isAlive()) {
Menu m = overflowPopup.getMenu(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
m.clear(); @Override
overflowPopup.inflate(R.menu.home_overflow); public void onGlobalLayout() {
announcements = m.findItem(R.id.announcements); Toolbar t = getToolbar();
settings = m.findItem(R.id.settings); if (t == null) return;
hashtagsMenu = m.findItem(R.id.hashtags).getSubMenu(); int toolbarWidth = t.getWidth();
listsMenu = m.findItem(R.id.lists).getSubMenu(); if (toolbarWidth == 0) return;
t.getViewTreeObserver().removeOnGlobalLayoutListener(this);
announcements.setVisible(!announcementsBadged); int toolbarFrameWidth = toolbarFrame.getWidth();
announcementsAction.setVisible(announcementsBadged); int padding = toolbarWidth - toolbarFrameWidth;
settings.setVisible(!settingsBadged); // toolbar frame goes from screen edge to beginning of right-aligned option buttons.
settingsAction.setVisible(settingsBadged); // centering button by applying the same space on the left
((FrameLayout) toolbarShowNewPostsBtn.getParent()).setPaddingRelative(padding, 0, 0, 0);
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth - padding * 2);
UiUtils.enablePopupMenuIcons(getContext(), overflowPopup); switcher.setPivotX(V.dp(28)); // padding + half of icon
switcher.setPivotY(switcher.getHeight() / 2f);
addListsToOverflowMenu(); timelineTitle.setPivotX(timelineTitle.getWidth() - V.dp(8));
addHashtagsToOverflowMenu(); timelineTitle.setPivotY(timelineTitle.getHeight() / 2f);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { });
m.setGroupDividerEnabled(true);
} }
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.home, menu); inflater.inflate(R.menu.home, menu);
announcements = menu.findItem(R.id.announcements);
menu.findItem(R.id.overflow).setActionView(overflowActionView); new GetAnnouncements(false).setCallback(new Callback<>() {
announcementsAction = menu.findItem(R.id.announcements_action); @Override
settingsAction = menu.findItem(R.id.settings_action); public void onSuccess(List<Announcement> result) {
boolean hasUnread = result.stream().anyMatch(a -> !a.read);
announcements.setIcon(hasUnread ? R.drawable.ic_announcements_24_badged : R.drawable.ic_fluent_megaphone_24_regular);
}
updateOverflowMenu(); @Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
} }
private <T> void updateList(List<T> addItems, Map<Integer, T> items) { private <T> void addItemsToMap(List<T> addItems, Map<Integer, T> items) {
if (addItems.size() == 0 || getActivity() == null) return; if (addItems.size() == 0) return;
for (int i = 0; i < addItems.size(); i++) items.put(View.generateViewId(), addItems.get(i)); for (int i = 0; i < addItems.size(); i++) items.put(View.generateViewId(), addItems.get(i));
updateOverflowMenu(); updateSwitcherMenu();
} }
private void updateSwitcherMenu() { private void updateSwitcherMenu() {
Menu switcherMenu = switcherPopup.getMenu(); Context context = getContext();
switcherMenu.clear(); switcherPopup.getMenu().findItem(R.id.federated).setVisible(showFederatedTimeline);
timelinesByMenuItem.clear();
for (TimelineDefinition tl : timelines) { if (!listItems.isEmpty()) {
int menuItemId = View.generateViewId(); MenuItem listsItem = switcherPopup.getMenu().findItem(R.id.lists);
timelinesByMenuItem.put(menuItemId, tl); listsItem.setVisible(true);
MenuItem item = switcherMenu.add(0, menuItemId, 0, tl.getTitle(getContext())); SubMenu listsMenu = listsItem.getSubMenu();
item.setIcon(tl.getIcon().iconRes); listsMenu.clear();
listItems.forEach((id, list) -> {
MenuItem item = listsMenu.add(Menu.NONE, id, Menu.NONE, list.title);
item.setIcon(R.drawable.ic_fluent_people_list_24_regular);
UiUtils.insetPopupMenuIcon(context, item);
});
} }
UiUtils.enablePopupMenuIcons(getContext(), switcherPopup); if (!hashtagsItems.isEmpty()) {
MenuItem hashtagsItem = switcherPopup.getMenu().findItem(R.id.followed_hashtags);
hashtagsItem.setVisible(true);
SubMenu hashtagsMenu = hashtagsItem.getSubMenu();
hashtagsMenu.clear();
hashtagsItems.forEach((id, hashtag) -> {
MenuItem item = hashtagsMenu.add(Menu.NONE, id, Menu.NONE, hashtag.name);
item.setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
UiUtils.insetPopupMenuIcon(context, item);
});
}
} }
private boolean onSwitcherItemSelected(MenuItem item) { private boolean onSwitcherItemSelected(MenuItem item) {
int id = item.getItemId(); int id = item.getItemId();
ListTimeline list;
Bundle args = new Bundle(); Hashtag hashtag;
args.putString("account", accountID); if (id == R.id.home) {
navigateTo(0);
if (id == R.id.menu_back) {
switcher.post(() -> switcherPopup.show());
return true; return true;
} else if (id == R.id.local) {
navigateTo(1);
return true;
} else if (id == R.id.federated) {
navigateTo(2);
return true;
} else if (id == R.id.post_notifications) {
navigateTo(showFederatedTimeline ? 3 : 2);
} else if ((list = listItems.get(id)) != null) {
Bundle args = new Bundle();
args.putString("account", accountID);
args.putString("listID", list.id);
args.putString("listTitle", list.title);
args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) {
UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag.name, hashtag.following);
} }
TimelineDefinition tl = timelinesByMenuItem.get(id);
if (tl != null) {
for (int i = 0; i < timelines.length; i++) {
if (timelines[i] == tl) {
navigateTo(i);
return true;
}
}
}
return false; return false;
} }
private void navigateTo(int i) { private void navigateTo(int i) {
navigateTo(i, !reduceMotion); navigateTo(i, !GlobalUserPreferences.reduceMotion);
} }
private void navigateTo(int i, boolean smooth) { private void navigateTo(int i, boolean smooth) {
@@ -428,43 +374,38 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
} }
private void updateSwitcherIcon(int i) { private void updateSwitcherIcon(int i) {
timelineIcon.setImageResource(timelines[i].getIcon().iconRes); // todo: refactor when implementing pinned tabs
timelineTitle.setText(timelines[i].getTitle(getContext())); if (i == (showFederatedTimeline ? 3 : 2)) {
timelineIcon.setImageResource(R.drawable.ic_fluent_alert_24_regular);
timelineTitle.setText(R.string.sk_notify_posts);
} else {
timelineIcon.setImageResource(switch (i) {
default -> R.drawable.ic_fluent_home_24_regular;
case 1 -> R.drawable.ic_fluent_people_community_24_regular;
case 2 -> R.drawable.ic_fluent_earth_24_regular;
});
timelineTitle.setText(switch (i) {
default -> R.string.sk_timeline_home;
case 1 -> R.string.sk_timeline_local;
case 2 -> R.string.sk_timeline_federated;
});
}
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item){ public boolean onOptionsItemSelected(MenuItem item){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
int id = item.getItemId(); if (item.getItemId() == R.id.settings) Nav.go(getActivity(), SettingsFragment.class, args);
ListTimeline list; if (item.getItemId() == R.id.announcements) {
Hashtag hashtag;
if (item.getItemId() == R.id.menu_back) {
getToolbar().post(() -> overflowPopup.show());
return true;
} else if (id == R.id.settings || id == R.id.settings_action) {
Nav.go(getActivity(), SettingsFragment.class, args);
} else if (id == R.id.announcements || id == R.id.announcements_action) {
Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this); Nav.goForResult(getActivity(), AnnouncementsFragment.class, args, ANNOUNCEMENTS_RESULT, this);
} else if (id == R.id.edit_timelines) {
Nav.go(getActivity(), EditTimelinesFragment.class, args);
} else if ((list = listItems.get(id)) != null) {
args.putString("listID", list.id);
args.putString("listTitle", list.title);
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) {
args.putString("hashtag", hashtag.name);
args.putBoolean("following", hashtag.following);
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
} }
return true; return true;
} }
@Override @Override
public void scrollToTop(){ public void scrollToTop(){
((ScrollableToTop) fragments[pager.getCurrentItem()]).scrollToTop(); ((ScrollableToTop) fragments.get(pager.getCurrentItem())).scrollToTop();
} }
public void hideNewPostsButton(){ public void hideNewPostsButton(){
@@ -485,7 +426,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f), ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, .8f),
ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 0f) ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 0f)
); );
set.setDuration(reduceMotion ? 0 : 300); set.setDuration(GlobalUserPreferences.reduceMotion ? 0 : 300);
set.setInterpolator(CubicBezierInterpolator.DEFAULT); set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){ set.addListener(new AnimatorListenerAdapter(){
@Override @Override
@@ -500,7 +441,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
} }
public void showNewPostsButton(){ public void showNewPostsButton(){
if(newPostsBtnShown || pager == null || !timelines[pager.getCurrentItem()].equals(TimelineDefinition.HOME_TIMELINE)) if(newPostsBtnShown || pager == null || pager.getCurrentItem() != 0)
return; return;
newPostsBtnShown=true; newPostsBtnShown=true;
if(currentNewPostsAnim!=null){ if(currentNewPostsAnim!=null){
@@ -518,7 +459,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f), ObjectAnimator.ofFloat(toolbarShowNewPostsBtn, View.SCALE_Y, 1f),
ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 1f) ObjectAnimator.ofFloat(collapsedChevron, View.ALPHA, 1f)
); );
set.setDuration(reduceMotion ? 0 : 300); set.setDuration(GlobalUserPreferences.reduceMotion ? 0 : 300);
set.setInterpolator(CubicBezierInterpolator.DEFAULT); set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){ set.addListener(new AnimatorListenerAdapter(){
@Override @Override
@@ -543,20 +484,15 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
} }
@Override @Override
public void onFragmentResult(int reqCode, boolean success, Bundle result){ public void onFragmentResult(int reqCode, boolean noMoreUnread, Bundle result){
if (reqCode == ANNOUNCEMENTS_RESULT && success) { if (reqCode == ANNOUNCEMENTS_RESULT && noMoreUnread) {
announcementsBadged = false; announcements.setIcon(R.drawable.ic_fluent_megaphone_24_regular);
announcements.setVisible(true);
announcementsAction.setVisible(false);
} }
} }
private void updateUpdateState(GithubSelfUpdater.UpdateState state){ private void updateUpdateState(GithubSelfUpdater.UpdateState state){
if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING) { if(state!=GithubSelfUpdater.UpdateState.NO_UPDATE && state!=GithubSelfUpdater.UpdateState.CHECKING)
settingsBadged = true; getToolbar().getMenu().findItem(R.id.settings).setIcon(R.drawable.ic_settings_24_badged);
settingsAction.setVisible(true);
settings.setVisible(false);
}
} }
@Subscribe @Subscribe
@@ -576,26 +512,11 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Override @Override
public void onDestroyView(){ public void onDestroyView(){
super.onDestroyView(); super.onDestroyView();
if (overflowPopup != null) {
overflowPopup.dismiss();
overflowPopup = null;
}
if (switcherPopup != null) {
switcherPopup.dismiss();
switcherPopup = null;
}
if(GithubSelfUpdater.needSelfUpdating()){ if(GithubSelfUpdater.needSelfUpdating()){
E.unregister(this); E.unregister(this);
} }
} }
@Override
protected void onShown() {
super.onShown();
Object pinnedTimelines = GlobalUserPreferences.pinnedTimelines.get(accountID);
if (pinnedTimelines != null && timelineDefinitions != pinnedTimelines) UiUtils.restartApp();
}
@Override @Override
public void onViewStateRestored(Bundle savedInstanceState) { public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
@@ -609,61 +530,12 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
outState.putInt("selectedTab", pager.getCurrentItem()); outState.putInt("selectedTab", pager.getCurrentItem());
} }
@Subscribe
public void onHashtagUpdatedEvent(HashtagUpdatedEvent event) {
handleListEvent(hashtagsItems, h -> h.name.equalsIgnoreCase(event.name), event.following, () -> {
Hashtag hashtag = new Hashtag();
hashtag.name = event.name;
hashtag.following = true;
return hashtag;
});
}
@Subscribe
public void onListDeletedEvent(ListDeletedEvent event) {
handleListEvent(listItems, l -> l.id.equals(event.id), false, null);
}
@Subscribe
public void onListUpdatedCreatedEvent(ListUpdatedCreatedEvent event) {
handleListEvent(listItems, l -> l.id.equals(event.id), true, () -> {
ListTimeline list = new ListTimeline();
list.id = event.id;
list.title = event.title;
list.repliesPolicy = event.repliesPolicy;
return list;
});
}
private <T> void handleListEvent(
Map<Integer, T> existingThings,
Predicate<T> matchExisting,
boolean shouldBeInList,
Supplier<T> makeNewThing
) {
Optional<Map.Entry<Integer, T>> existingThing = existingThings.entrySet().stream()
.filter(e -> matchExisting.test(e.getValue())).findFirst();
if (shouldBeInList) {
existingThings.put(existingThing.isPresent()
? existingThing.get().getKey() : View.generateViewId(), makeNewThing.get());
updateOverflowMenu();
} else if (existingThing.isPresent() && !shouldBeInList) {
existingThings.remove(existingThing.get().getKey());
updateOverflowMenu();
}
}
public Collection<Hashtag> getHashtags() {
return hashtagsItems.values();
}
private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> { private class HomePagerAdapter extends RecyclerView.Adapter<SimpleViewHolder> {
@NonNull @NonNull
@Override @Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
FrameLayout tabView = tabViews[viewType % getItemCount()]; FrameLayout tabView = tabViews.get(viewType % getItemCount());
ViewGroup tabParent = (ViewGroup) tabView.getParent(); ((ViewGroup)tabView.getParent()).removeView(tabView);
if (tabParent != null) tabParent.removeView(tabView);
tabView.setVisibility(View.VISIBLE); tabView.setVisibility(View.VISIBLE);
return new SimpleViewHolder(tabView); return new SimpleViewHolder(tabView);
} }
@@ -673,7 +545,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
@Override @Override
public int getItemCount(){ public int getItemCount(){
return count; return fragments.size();
} }
@Override @Override

View File

@@ -8,9 +8,6 @@ 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.timelines.GetHomeTimeline; import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
@@ -32,15 +29,9 @@ 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 HomeTimelineFragment extends StatusListFragment { public class HomeTimelineFragment extends FabStatusListFragment {
private HomeTabFragment parent; private HomeTabFragment parent;
private String maxID; private String maxID;
private String lastSavedMarkerID;
@Override
protected boolean withComposeButton() {
return true;
}
@Override @Override
public void onAttach(Activity activity){ public void onAttach(Activity activity){
@@ -50,7 +41,6 @@ public class HomeTimelineFragment extends StatusListFragment {
} }
private List<Status> filterPosts(List<Status> items) { private List<Status> filterPosts(List<Status> items) {
// This is the function I must use to solve the filters thing for real
return items.stream().filter(i -> return items.stream().filter(i ->
(GlobalUserPreferences.showReplies || i.inReplyToId == null) && (GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
(GlobalUserPreferences.showBoosts || i.reblog == null) (GlobalUserPreferences.showBoosts || i.reblog == null)
@@ -64,7 +54,8 @@ public class HomeTimelineFragment extends StatusListFragment {
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){ .getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
@Override @Override
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){ public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
if (getActivity() == null) return; if(getActivity()==null)
return;
List<Status> filteredItems = filterPosts(result.items); List<Status> filteredItems = filterPosts(result.items);
onDataLoaded(filteredItems, !result.items.isEmpty()); onDataLoaded(filteredItems, !result.items.isEmpty());
maxID=result.maxID; maxID=result.maxID;
@@ -100,29 +91,6 @@ public class HomeTimelineFragment extends StatusListFragment {
} }
} }
@Override
protected void onHidden(){
super.onHidden();
// if(!data.isEmpty()){
// String topPostID=displayItems.get(list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset()).parentID;
// if(!topPostID.equals(lastSavedMarkerID)){
// lastSavedMarkerID=topPostID;
// new SaveMarkers(topPostID, null)
// .setCallback(new Callback<>(){
// @Override
// public void onSuccess(SaveMarkers.Response result){
// }
//
// @Override
// public void onError(ErrorResponse error){
// lastSavedMarkerID=null;
// }
// })
// .exec(accountID);
// }
// }
}
public void onStatusCreated(StatusCreatedEvent ev){ public void onStatusCreated(StatusCreatedEvent ev){
prependItems(Collections.singletonList(ev.status), true); prependItems(Collections.singletonList(ev.status), true);
} }
@@ -155,7 +123,7 @@ public class HomeTimelineFragment extends StatusListFragment {
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);
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton(); if (parent != null) parent.showNewPostsButton();
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
} }
} }

View File

@@ -1,13 +0,0 @@
package org.joinmastodon.android.fragments;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
public interface IsOnTop {
boolean isOnTop();
default boolean isRecyclerViewOnTop(@Nullable RecyclerView list) {
if (list == null) return true;
return !list.canScrollVertically(-1);
}
}

View File

@@ -9,26 +9,17 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import androidx.annotation.Nullable;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetList; import org.joinmastodon.android.api.requests.lists.CreateList;
import org.joinmastodon.android.api.requests.lists.UpdateList; import org.joinmastodon.android.api.requests.lists.UpdateList;
import org.joinmastodon.android.api.requests.timelines.GetListTimeline; import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ListTimelineEditor; import org.joinmastodon.android.ui.views.ListTimelineEditor;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -37,15 +28,14 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ListTimelineFragment extends PinnableStatusListFragment { public class ListTimelineFragment extends StatusListFragment {
private String listID; private String listID;
private String listTitle; private String listTitle;
@Nullable
private ListTimeline.RepliesPolicy repliesPolicy; private ListTimeline.RepliesPolicy repliesPolicy;
private ImageButton fab;
@Override public ListTimelineFragment() {
protected boolean withComposeButton() { setListLayoutId(R.layout.recycler_fragment_with_fab);
return true;
} }
@Override @Override
@@ -58,58 +48,39 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
setTitle(listTitle); setTitle(listTitle);
setHasOptionsMenu(true); setHasOptionsMenu(true);
new GetList(listID).setCallback(new Callback<>() {
@Override
public void onSuccess(ListTimeline listTimeline) {
if (getActivity() == null) return;
// TODO: save updated info
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
repliesPolicy = listTimeline.repliesPolicy;
}
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
}
});
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.list, menu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
UiUtils.enableOptionsMenuIcons(getContext(), menu, R.id.pin); inflater.inflate(R.menu.list, menu);
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (super.onOptionsItemSelected(item)) return true; Bundle args = new Bundle();
args.putString("listID", listID);
if (item.getItemId() == R.id.edit) { if (item.getItemId() == R.id.edit) {
ListTimelineEditor editor = new ListTimelineEditor(getContext()); ListTimelineEditor editor = new ListTimelineEditor(getContext());
editor.applyList(listTitle, 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_list_28_regular)
.setView(editor) .setView(editor)
.setPositiveButton(R.string.save, (d, which) -> { .setPositiveButton(R.string.save, (d, which) -> {
String newTitle = editor.getTitle().trim(); new UpdateList(listID, editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
setTitle(newTitle);
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;
setTitle(list.title); setTitle(list.title);
listTitle = list.title; listTitle = list.title;
repliesPolicy = list.repliesPolicy; repliesPolicy = list.repliesPolicy;
E.post(new ListUpdatedCreatedEvent(listID, listTitle, repliesPolicy)); args.putString("listTitle", listTitle);
args.putInt("repliesPolicy", repliesPolicy.ordinal());
setResult(true, args);
} }
@Override @Override
public void onError(ErrorResponse error) { public void onError(ErrorResponse error) {
setTitle(listTitle);
error.showToast(getContext()); error.showToast(getContext());
} }
}).exec(accountID); }).exec(accountID);
@@ -118,30 +89,24 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
.show(); .show();
} else if (item.getItemId() == R.id.delete) { } else if (item.getItemId() == R.id.delete) {
UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> { UiUtils.confirmDeleteList(getActivity(), accountID, listID, listTitle, () -> {
E.post(new ListDeletedEvent(listID)); args.putBoolean("deleted", true);
setResult(true, args);
Nav.finish(this); Nav.finish(this);
}); });
} }
return true; return true;
} }
@Override
protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofList(listID, listTitle);
}
@Override @Override
protected void doLoadData(int offset, int count) { protected void doLoadData(int offset, int count) {
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null) currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null)
.setCallback(new SimpleCallback<>(this) { .setCallback(new SimpleCallback<>(this) {
@Override @Override
public void onSuccess(List<Status> result) { public void onSuccess(List<Status> result) {
if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.HOME)).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
} }
}) })
.exec(accountID); .exec(accountID);
} }
@Override @Override
@@ -152,7 +117,14 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
} }
@Override @Override
protected void onFabClick(View v){ public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID));
}
private 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);

View File

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

@@ -44,7 +44,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
private FrameLayout[] tabViews; private FrameLayout[] tabViews;
private TabLayoutMediator tabLayoutMediator; private TabLayoutMediator tabLayoutMediator;
private NotificationsListFragment allNotificationsFragment, mentionsFragment, postsFragment; private NotificationsListFragment allNotificationsFragment, mentionsFragment, conversationsFragment;
private String accountID; private String accountID;
@@ -102,6 +102,7 @@ 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++){
@@ -150,14 +151,14 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
mentionsFragment.setArguments(args); mentionsFragment.setArguments(args);
args=new Bundle(args); args=new Bundle(args);
args.putBoolean("onlyPosts", true); args.putBoolean("onlyConversations", true);
postsFragment=new NotificationsListFragment(); conversationsFragment =new NotificationsListFragment();
postsFragment.setArguments(args); conversationsFragment.setArguments(args);
getChildFragmentManager().beginTransaction() getChildFragmentManager().beginTransaction()
.add(R.id.notifications_all, allNotificationsFragment) .add(R.id.notifications_all, allNotificationsFragment)
.add(R.id.notifications_mentions, mentionsFragment) .add(R.id.notifications_mentions, mentionsFragment)
.add(R.id.notifications_posts, postsFragment) .add(R.id.notifications_posts, conversationsFragment)
.commit(); .commit();
} }
@@ -167,7 +168,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
tab.setText(switch(position){ tab.setText(switch(position){
case 0 -> R.string.all_notifications; case 0 -> R.string.all_notifications;
case 1 -> R.string.mentions; case 1 -> R.string.mentions;
case 2 -> R.string.posts; case 2 -> R.string.sk_conversations;
default -> throw new IllegalStateException("Unexpected value: "+position); default -> throw new IllegalStateException("Unexpected value: "+position);
}); });
tab.view.textView.setAllCaps(true); tab.view.textView.setAllCaps(true);
@@ -216,7 +217,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
return switch(page){ return switch(page){
case 0 -> allNotificationsFragment; case 0 -> allNotificationsFragment;
case 1 -> mentionsFragment; case 1 -> mentionsFragment;
case 2 -> postsFragment; case 2 -> conversationsFragment;
default -> throw new IllegalStateException("Unexpected value: "+page); default -> throw new IllegalStateException("Unexpected value: "+page);
}; };
} }

View File

@@ -2,7 +2,8 @@ package org.joinmastodon.android.fragments;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.view.Menu;
import android.view.MenuInflater;
import android.view.View; import android.view.View;
import com.squareup.otto.Subscribe; import com.squareup.otto.Subscribe;
@@ -10,23 +11,21 @@ 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.timelines.GetConversationsTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse; import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ImageStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration; import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
@@ -39,19 +38,17 @@ import java.util.stream.Stream;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.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 NotificationsListFragment extends BaseStatusListFragment<Notification>{ public class NotificationsListFragment extends BaseStatusListFragment<Notification>{
private boolean onlyMentions; private boolean onlyMentions;
private boolean onlyPosts; private boolean onlyPosts;
private String maxID;
private final DiscoverInfoBannerHelper bannerHelper = new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.POST_NOTIFICATIONS);
@Override private boolean onlyConversations;
protected boolean withComposeButton() { private String maxID;
return true;
}
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -70,6 +67,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
super.onAttach(activity); super.onAttach(activity);
onlyMentions=getArguments().getBoolean("onlyMentions", false); onlyMentions=getArguments().getBoolean("onlyMentions", false);
onlyPosts=getArguments().getBoolean("onlyPosts", false); onlyPosts=getArguments().getBoolean("onlyPosts", false);
onlyConversations=getArguments().getBoolean("onlyConversations", false);
} }
@Override @Override
@@ -82,8 +80,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Notification n){ protected List<StatusDisplayItem> buildDisplayItems(Notification n){
Account reportTarget = n.report == null ? null : n.report.targetAccount == null ? null :
n.report.targetAccount;
String extraText=switch(n.type){ String extraText=switch(n.type){
case FOLLOW -> getString(R.string.user_followed_you); case FOLLOW -> getString(R.string.user_followed_you);
case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request); case FOLLOW_REQUEST -> getString(R.string.user_sent_follow_request);
@@ -91,13 +87,10 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
case REBLOG -> getString(R.string.notification_boosted); case REBLOG -> getString(R.string.notification_boosted);
case FAVORITE -> getString(R.string.user_favorited); case FAVORITE -> getString(R.string.user_favorited);
case POLL -> getString(R.string.poll_ended); case POLL -> getString(R.string.poll_ended);
case UPDATE -> getString(R.string.sk_post_edited);
case SIGN_UP -> getString(R.string.sk_signed_up);
case REPORT -> getString(R.string.sk_reported);
}; };
HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, n.status, extraText, n, null) : null; HeaderStatusDisplayItem titleItem=extraText!=null ? new HeaderStatusDisplayItem(n.id, n.account, n.createdAt, this, accountID, null, extraText, n, null) : null;
if(n.status!=null){ if(n.status!=null){
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS); ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n);
if(titleItem!=null){ if(titleItem!=null){
for(StatusDisplayItem item:items){ for(StatusDisplayItem item:items){
if(item instanceof ImageStatusDisplayItem imgItem){ if(item instanceof ImageStatusDisplayItem imgItem){
@@ -109,13 +102,8 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
items.add(0, titleItem); items.add(0, titleItem);
return items; return items;
}else if(titleItem!=null){ }else if(titleItem!=null){
AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, AccountCardStatusDisplayItem card=new AccountCardStatusDisplayItem(n.id, this, n.account, n);
reportTarget != null ? reportTarget : n.account, n); return Arrays.asList(titleItem, card);
TextStatusDisplayItem text = n.report != null && !TextUtils.isEmpty(n.report.comment) ?
new TextStatusDisplayItem(n.id, n.report.comment, this,
Status.ofFake(n.id, n.report.comment, n.createdAt), true) :
null;
return text == null ? Arrays.asList(titleItem, card) : Arrays.asList(titleItem, text, card);
}else{ }else{
return Collections.emptyList(); return Collections.emptyList();
} }
@@ -131,27 +119,29 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance() AccountSessionManager.getInstance()
.getAccount(accountID).getCacheController() .getAccount(accountID).getCacheController()
.getNotifications(offset>0 ? maxID : null, count, onlyMentions, onlyPosts, refreshing, new SimpleCallback<>(this){ .getNotifications(offset > 0 ? maxID : null, count, onlyMentions, onlyPosts || onlyConversations, refreshing, new SimpleCallback<>(this) {
@Override @Override
public void onSuccess(PaginatedResponse<List<Notification>> result){ public void onSuccess(PaginatedResponse<List<Notification>> result) {
if (getActivity() == null) return; if (getActivity() == null)
if(refreshing) return;
relationships.clear(); if (refreshing)
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty()); relationships.clear();
Set<String> needRelationships=result.items.stream() onDataLoaded(result.items.stream().filter(n -> n.type != null || (onlyConversations &&)).collect(Collectors.toList()), !result.items.isEmpty());
.filter(ntf->ntf.status==null && !relationships.containsKey(ntf.account.id)) Set<String> needRelationships = result.items.stream()
.map(ntf->ntf.account.id) .filter(ntf -> ntf.status == null && !relationships.containsKey(ntf.account.id))
.collect(Collectors.toSet()); .map(ntf -> ntf.account.id)
loadRelationships(needRelationships); .collect(Collectors.toSet());
maxID=result.maxID; loadRelationships(needRelationships);
maxID = result.maxID;
if(offset==0 && !result.items.isEmpty()){ if (offset == 0 && !result.items.isEmpty()) {
new SaveMarkers(null, result.items.get(0).id).exec(accountID); new SaveMarkers(null, result.items.get(0).id).exec(accountID);
}
} }
} });
});
} }
@Override @Override
@@ -183,9 +173,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)) if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId))
args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId))); args.putParcelable("inReplyToAccount", Parcels.wrap(knownAccounts.get(status.inReplyToAccountId)));
Nav.go(getActivity(), ThreadFragment.class, args); 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{ }else{
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -198,8 +185,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this)); list.addItemDecoration(new InsetStatusItemDecoration(this));
if (getParentFragment() instanceof NotificationsFragment) fab.setVisibility(View.GONE);
if (onlyPosts) bannerHelper.maybeAddBanner(contentWrap);
} }
private Notification getNotificationByID(String id){ private Notification getNotificationByID(String id){

View File

@@ -1,82 +0,0 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.view.HapticFeedbackConstants;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.TimelineDefinition;
import java.util.ArrayList;
import java.util.List;
public abstract class PinnableStatusListFragment extends StatusListFragment {
protected boolean pinnedUpdated;
protected List<TimelineDefinition> pinnedTimelines;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
updatePinButton(menu.findItem(R.id.pin));
}
protected boolean isPinned() {
return pinnedTimelines.contains(makeTimelineDefinition());
}
protected void updatePinButton(MenuItem pin) {
boolean pinned = isPinned();
pin.setIcon(pinned ?
R.drawable.ic_fluent_pin_24_filled :
R.drawable.ic_fluent_pin_24_regular);
pin.setTitle(pinned ? R.string.sk_unpin_timeline : R.string.sk_pin_timeline);
}
protected abstract TimelineDefinition makeTimelineDefinition();
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.pin) {
togglePin(item);
return true;
}
return super.onOptionsItemSelected(item);
}
protected void togglePin(MenuItem pin) {
pinnedUpdated = true;
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
TimelineDefinition def = makeTimelineDefinition();
boolean pinned = isPinned();
if (pinned) pinnedTimelines.remove(def);
else pinnedTimelines.add(def);
Toast.makeText(getContext(), pinned ? R.string.sk_unpinned_timeline : R.string.sk_pinned_timeline, Toast.LENGTH_SHORT).show();
GlobalUserPreferences.pinnedTimelines.put(accountID, pinnedTimelines);
GlobalUserPreferences.save();
updatePinButton(pin);
}
protected Bundle getResultArgs() {
return new Bundle();
}
@Override
public void onDestroy() {
super.onDestroy();
Bundle resultArgs = getResultArgs();
if (pinnedUpdated) {
resultArgs.putBoolean("pinnedUpdated", true);
setResult(true, resultArgs);
}
}
}

View File

@@ -157,7 +157,7 @@ public class ProfileAboutFragment extends Fragment implements WindowInsetsAwareF
@Override @Override
public void onError(ErrorResponse result) { public void onError(ErrorResponse result) {
Toast.makeText(getActivity(), getString(R.string.mo_personal_note_update_failed), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), getString(R.string.sk_personal_note_update_failed), Toast.LENGTH_LONG).show();
} }
}).exec(accountID); }).exec(accountID);
} }

View File

@@ -8,22 +8,22 @@ import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
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.Outline; import android.graphics.Outline;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
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.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;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewOutlineProvider; import android.view.ViewOutlineProvider;
@@ -64,7 +64,6 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField; import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.SimpleViewHolder; import org.joinmastodon.android.ui.SimpleViewHolder;
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener; import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable; import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable;
@@ -73,10 +72,8 @@ 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.text.CustomEmojiSpan; import org.joinmastodon.android.ui.text.CustomEmojiSpan;
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.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.CoverImageView; import org.joinmastodon.android.ui.views.CoverImageView;
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.parceler.Parcels; import org.parceler.Parcels;
@@ -89,13 +86,6 @@ 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.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager2.widget.ViewPager2;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@@ -103,17 +93,10 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.fragments.LoaderFragment; import me.grishka.appkit.fragments.LoaderFragment;
import me.grishka.appkit.fragments.OnBackPressedListener; import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
import me.grishka.appkit.imageloader.ViewImageLoader; import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{ public class ProfileFragment extends LoaderFragment implements OnBackPressedListener, ScrollableToTop{
private static final int AVATAR_RESULT=722; private static final int AVATAR_RESULT=722;
@@ -121,24 +104,23 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ImageView avatar; private ImageView avatar;
private CoverImageView cover; private CoverImageView cover;
private View avatarBorder, nameWrap; private View avatarBorder;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel, postsCount, postsLabel; private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel, postsCount, postsLabel;
private ProgressBarButton actionButton, notifyButton; private ProgressBarButton actionButton, notifyButton;
private ViewPager2 pager; private ViewPager2 pager;
private NestedRecyclerScrollView scrollView; private NestedRecyclerScrollView scrollView;
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment; private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
// private ProfileAboutFragment aboutFragment; private ProfileAboutFragment aboutFragment;
private TabLayout tabbar; private TabLayout tabbar;
private SwipeRefreshLayout refreshLayout; private SwipeRefreshLayout refreshLayout;
private CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable(); private CoverOverlayGradientDrawable coverGradient=new CoverOverlayGradientDrawable();
private float titleTransY; private float titleTransY;
private View postsBtn, followersBtn, followingBtn, profileCounters; private View postsBtn, followersBtn, followingBtn;
private EditText nameEdit, bioEdit; private EditText nameEdit, bioEdit;
private ProgressBar actionProgress, notifyProgress; private ProgressBar actionProgress, notifyProgress;
private FrameLayout[] tabViews; private FrameLayout[] tabViews;
private TabLayoutMediator tabLayoutMediator; private TabLayoutMediator tabLayoutMediator;
private TextView followsYouView; private TextView followsYouView;
private ViewGroup rolesView;
public FrameLayout noteWrap; public FrameLayout noteWrap;
public EditText noteEdit; public EditText noteEdit;
@@ -160,16 +142,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private boolean editModeLoading; private boolean editModeLoading;
private boolean isScrollingUp = false; private boolean isScrollingUp = false;
private static final int MAX_FIELDS=4;
// from ProfileAboutFragment
public UsableRecyclerView list;
private List<AccountField> metadataListData=Collections.emptyList();
private MetadataAdapter adapter;
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
private RecyclerView.ViewHolder draggedViewHolder;
private ListImageLoaderWrapper imgLoader;
public ProfileFragment(){ public ProfileFragment(){
super(R.layout.loader_fragment_overlay_toolbar); super(R.layout.loader_fragment_overlay_toolbar);
} }
@@ -214,10 +186,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
cover=content.findViewById(R.id.cover); cover=content.findViewById(R.id.cover);
avatarBorder=content.findViewById(R.id.avatar_border); avatarBorder=content.findViewById(R.id.avatar_border);
name=content.findViewById(R.id.name); name=content.findViewById(R.id.name);
nameWrap=content.findViewById(R.id.name_wrap);
username=content.findViewById(R.id.username); username=content.findViewById(R.id.username);
bio=content.findViewById(R.id.bio); bio=content.findViewById(R.id.bio);
profileCounters=content.findViewById(R.id.profile_counters);
followersCount=content.findViewById(R.id.followers_count); followersCount=content.findViewById(R.id.followers_count);
followersLabel=content.findViewById(R.id.followers_label); followersLabel=content.findViewById(R.id.followers_label);
followersBtn=content.findViewById(R.id.followers_btn); followersBtn=content.findViewById(R.id.followers_btn);
@@ -240,8 +210,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
notifyProgress=content.findViewById(R.id.notify_progress); notifyProgress=content.findViewById(R.id.notify_progress);
fab=content.findViewById(R.id.fab); fab=content.findViewById(R.id.fab);
followsYouView=content.findViewById(R.id.follows_you); followsYouView=content.findViewById(R.id.follows_you);
list=content.findViewById(R.id.metadata);
rolesView=content.findViewById(R.id.roles);
noteEdit = content.findViewById(R.id.note_edit); noteEdit = content.findViewById(R.id.note_edit);
noteWrap = content.findViewById(R.id.note_edit_wrap); noteWrap = content.findViewById(R.id.note_edit_wrap);
@@ -257,31 +225,13 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
noteEdit.setOnFocusChangeListener((v, hasFocus) -> { noteEdit.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) { if (hasFocus) {
fab.setVisibility(View.INVISIBLE); fab.setVisibility(View.GONE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
0,
fab.getHeight() * 2);
animate.setDuration(300);
animate.setFillAfter(true);
fab.startAnimation(animate);
noteEditConfirm.setVisibility(View.VISIBLE); noteEditConfirm.setVisibility(View.VISIBLE);
noteEditConfirm.animate() noteEditConfirm.animate()
.alpha(1.0f) .alpha(1.0f)
.setDuration(700); .setDuration(700);
} else { } else {
fab.setVisibility(View.VISIBLE); fab.setVisibility(View.VISIBLE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
animate.setFillAfter(true);
fab.startAnimation(animate);
noteEditConfirm.animate() noteEditConfirm.animate()
.alpha(0.0f) .alpha(0.0f)
.setDuration(700); .setDuration(700);
@@ -308,7 +258,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
} }
}; };
tabViews=new FrameLayout[4]; tabViews=new FrameLayout[5];
for(int i=0;i<tabViews.length;i++){ for(int i=0;i<tabViews.length;i++){
FrameLayout tabView=new FrameLayout(getActivity()); FrameLayout tabView=new FrameLayout(getActivity());
tabView.setId(switch(i){ tabView.setId(switch(i){
@@ -325,7 +275,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
} }
UiUtils.reduceSwipeSensitivity(pager); UiUtils.reduceSwipeSensitivity(pager);
pager.setOffscreenPageLimit(4); pager.setOffscreenPageLimit(5);
pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe); pager.setUserInputEnabled(!GlobalUserPreferences.disableSwipe);
pager.setAdapter(new ProfilePagerAdapter()); pager.setAdapter(new ProfilePagerAdapter());
pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels; pager.getLayoutParams().height=getResources().getDisplayMetrics().heightPixels;
@@ -386,14 +336,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return true; return true;
}); });
// from ProfileAboutFragment
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 sizeWrapper; return sizeWrapper;
} }
@@ -411,7 +353,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override @Override
public void onError(ErrorResponse result) { public void onError(ErrorResponse result) {
Toast.makeText(getActivity(), getString(R.string.mo_personal_note_update_failed), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), getString(R.string.sk_personal_note_update_failed), Toast.LENGTH_LONG).show();
} }
}).exec(accountID); }).exec(accountID);
} }
@@ -422,7 +364,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(Account result){ public void onSuccess(Account result){
if (getActivity() == null) return;
account=result; account=result;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account); isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
bindHeaderView(); bindHeaderView();
@@ -445,7 +386,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(mediaFragment.loaded) if(mediaFragment.loaded)
mediaFragment.onRefresh(); mediaFragment.onRefresh();
} }
V.setVisibilityAnimated(fab, View.VISIBLE); // if(noteEdit.hasFocus()){
// V.setVisibilityAnimated(fab, View.GONE);
// }else{
// V.setVisibilityAnimated(fab, View.VISIBLE);
// }
} }
}) })
.exec(accountID); .exec(accountID);
@@ -468,8 +413,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
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); 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);
// aboutFragment=new ProfileAboutFragment(); aboutFragment=new ProfileAboutFragment();
setFields(fields); aboutFragment.setFields(fields);
} }
pager.getAdapter().notifyDataSetChanged(); pager.getAdapter().notifyDataSetChanged();
super.dataLoaded(); super.dataLoaded();
@@ -562,19 +507,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
name.setText(ssb); name.setText(ssb);
setTitle(ssb); setTitle(ssb);
if (account.roles != null && !account.roles.isEmpty()) {
rolesView.setVisibility(View.VISIBLE);
rolesView.removeAllViews();
name.setPadding(0, 0, V.dp(12), 0);
for (Account.Role role : account.roles) {
TextView roleText = new TextView(getActivity(), null, 0, R.style.role_label);
roleText.setText(role.name);
GradientDrawable bg = (GradientDrawable) roleText.getBackground().mutate();
bg.setStroke(V.dp(2), Color.parseColor(role.color));
rolesView.addView(roleText);
}
}
boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account); boolean isSelf=AccountSessionManager.getInstance().isSelf(accountID, account);
@@ -658,7 +590,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
fields.add(field); fields.add(field);
} }
setFields(fields); if(aboutFragment!=null){
aboutFragment.setFields(fields);
}
} }
private void updateToolbar(){ private void updateToolbar(){
@@ -695,7 +629,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return; return;
inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu); inflater.inflate(isOwnProfile ? R.menu.profile_own : R.menu.profile, menu);
if(isOwnProfile){ if(isOwnProfile){
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.scheduled, R.id.bookmarks, R.id.favorites); UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.manage_user_lists, R.id.bookmarks, R.id.followed_hashtags, R.id.favorites, R.id.scheduled, R.id.share);
}else{ }else{
UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags, R.id.favorites, R.id.scheduled); UiUtils.enableOptionsMenuIcons(getActivity(), menu, R.id.bookmarks, R.id.followed_hashtags, R.id.favorites, R.id.scheduled);
} }
@@ -821,7 +755,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
} }
private void updateRelationship(){ private void updateRelationship(){
if (getActivity() == null) return;
invalidateOptionsMenu(); invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE); actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE); notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
@@ -833,9 +766,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
notifyButton.setSelected(relationship.notifying); notifyButton.setSelected(relationship.notifying);
if (!isOwnProfile) { if (!isOwnProfile) {
setNote(relationship.note); setNote(relationship.note);
// 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)); if (getActivity() != null) notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
} }
public ImageButton getFab() { public ImageButton getFab() {
@@ -862,8 +795,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
coverGradient.setTopOffset(scrollY); coverGradient.setTopOffset(scrollY);
cover.invalidate(); cover.invalidate();
titleTransY=getToolbar().getHeight(); titleTransY=getToolbar().getHeight();
if(scrollY>nameWrap.getTop()-topBarsH){ if(scrollY>name.getTop()-topBarsH){
titleTransY=Math.max(0f, titleTransY-(scrollY-(nameWrap.getTop()-topBarsH))); titleTransY=Math.max(0f, titleTransY-(scrollY-(name.getTop()-topBarsH)));
} }
if(toolbarTitleView!=null){ if(toolbarTitleView!=null){
toolbarTitleView.setTranslationY(titleTransY); toolbarTitleView.setTranslationY(titleTransY);
@@ -880,7 +813,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
case 1 -> postsWithRepliesFragment; case 1 -> postsWithRepliesFragment;
case 2 -> pinnedPostsFragment; case 2 -> pinnedPostsFragment;
case 3 -> mediaFragment; case 3 -> mediaFragment;
// case 4 -> aboutFragment; case 4 -> aboutFragment;
default -> throw new IllegalStateException(); default -> throw new IllegalStateException();
}; };
} }
@@ -922,7 +855,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override @Override
public void onSuccess(Account result){ public void onSuccess(Account result){
editModeLoading=false; editModeLoading=false;
if (getActivity() == null) return; if(getActivity()==null)
return;
enterEditMode(result); enterEditMode(result);
setActionProgressVisible(false); setActionProgressVisible(false);
} }
@@ -930,7 +864,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override @Override
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
editModeLoading=false; editModeLoading=false;
if (getActivity() == null) return; if(getActivity()==null)
return;
error.showToast(getActivity()); error.showToast(getActivity());
setActionProgressVisible(false); setActionProgressVisible(false);
} }
@@ -945,12 +880,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
invalidateOptionsMenu(); invalidateOptionsMenu();
pager.setUserInputEnabled(false); pager.setUserInputEnabled(false);
actionButton.setText(R.string.done); actionButton.setText(R.string.done);
pager.setCurrentItem(4);
ArrayList<Animator> animators=new ArrayList<>(); ArrayList<Animator> animators=new ArrayList<>();
for(int i=0;i<tabViews.length-1;i++){
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, .3f));
tabbar.getTabAt(i).view.setEnabled(false);
}
Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate(); Drawable overlay=getResources().getDrawable(R.drawable.edit_avatar_overlay).mutate();
avatar.setForeground(overlay); avatar.setForeground(overlay);
animators.add(ObjectAnimator.ofInt(overlay, "alpha", 0, 255)); animators.add(ObjectAnimator.ofInt(overlay, "alpha", 0, 255));
nameWrap.setVisibility(View.GONE);
nameEdit.setVisibility(View.VISIBLE); nameEdit.setVisibility(View.VISIBLE);
nameEdit.setText(account.displayName); nameEdit.setText(account.displayName);
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams(); RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
@@ -962,9 +901,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bioEdit.setText(account.source.note); bioEdit.setText(account.source.note);
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f, 1f)); animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f, 1f));
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 0f)); animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 0f));
profileCounters.setVisibility(View.GONE);
pager.setVisibility(View.GONE); animators.add(ObjectAnimator.ofFloat(postsBtn, View.ALPHA, .3f));
tabbar.setVisibility(View.GONE); animators.add(ObjectAnimator.ofFloat(followersBtn, View.ALPHA, .3f));
animators.add(ObjectAnimator.ofFloat(followingBtn, View.ALPHA, .3f));
AnimatorSet set=new AnimatorSet(); AnimatorSet set=new AnimatorSet();
set.playTogether(animators); set.playTogether(animators);
@@ -972,12 +912,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
set.setInterpolator(CubicBezierInterpolator.DEFAULT); set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.start(); set.start();
// aboutFragment.enterEditMode(account.source.fields); aboutFragment.enterEditMode(account.source.fields);
V.setVisibilityAnimated(fab, View.GONE);
metadataListData=account.source.fields;
adapter.notifyDataSetChanged();
dragHelper.attachToRecyclerView(list);
} }
private void exitEditMode(){ private void exitEditMode(){
@@ -988,14 +923,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
invalidateOptionsMenu(); invalidateOptionsMenu();
ArrayList<Animator> animators=new ArrayList<>(); ArrayList<Animator> animators=new ArrayList<>();
actionButton.setText(R.string.edit_profile); actionButton.setText(R.string.edit_profile);
for(int i=0;i<tabViews.length-1;i++){
animators.add(ObjectAnimator.ofFloat(tabbar.getTabAt(i).view, View.ALPHA, 1f));
}
animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0)); animators.add(ObjectAnimator.ofInt(avatar.getForeground(), "alpha", 0));
animators.add(ObjectAnimator.ofFloat(nameEdit, View.ALPHA, 0f)); animators.add(ObjectAnimator.ofFloat(nameEdit, View.ALPHA, 0f));
animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f)); animators.add(ObjectAnimator.ofFloat(bioEdit, View.ALPHA, 0f));
animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 1f)); animators.add(ObjectAnimator.ofFloat(bio, View.ALPHA, 1f));
profileCounters.setVisibility(View.VISIBLE); animators.add(ObjectAnimator.ofFloat(postsBtn, View.ALPHA, 1f));
pager.setVisibility(View.VISIBLE); animators.add(ObjectAnimator.ofFloat(followersBtn, View.ALPHA, 1f));
tabbar.setVisibility(View.VISIBLE); animators.add(ObjectAnimator.ofFloat(followingBtn, View.ALPHA, 1f));
V.setVisibilityAnimated(nameWrap, View.VISIBLE);
AnimatorSet set=new AnimatorSet(); AnimatorSet set=new AnimatorSet();
set.playTogether(animators); set.playTogether(animators);
@@ -1004,21 +941,20 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
set.addListener(new AnimatorListenerAdapter(){ set.addListener(new AnimatorListenerAdapter(){
@Override @Override
public void onAnimationEnd(Animator animation){ public void onAnimationEnd(Animator animation){
for(int i=0;i<tabViews.length-1;i++){
tabbar.getTabAt(i).view.setEnabled(true);
}
pager.setUserInputEnabled(true); pager.setUserInputEnabled(true);
nameEdit.setVisibility(View.GONE); nameEdit.setVisibility(View.GONE);
bioEdit.setVisibility(View.GONE); bioEdit.setVisibility(View.GONE);
RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams(); RelativeLayout.LayoutParams lp=(RelativeLayout.LayoutParams) username.getLayoutParams();
lp.addRule(RelativeLayout.BELOW, R.id.name_wrap); lp.addRule(RelativeLayout.BELOW, R.id.name);
username.getParent().requestLayout(); username.getParent().requestLayout();
avatar.setForeground(null); avatar.setForeground(null);
scrollToTop();
} }
}); });
set.start(); set.start();
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
V.setVisibilityAnimated(fab, View.VISIBLE);
bindHeaderView(); bindHeaderView();
} }
@@ -1026,13 +962,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(!isInEditMode) if(!isInEditMode)
throw new IllegalStateException(); throw new IllegalStateException();
setActionProgressVisible(true); setActionProgressVisible(true);
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), editNewAvatar, editNewCover, metadataListData) new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), editNewAvatar, editNewCover, aboutFragment.getFields())
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Account result){ public void onSuccess(Account result){
account=result; account=result;
AccountSessionManager.getInstance().updateAccountInfo(accountID, account); AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
if (getActivity() == null) return;
exitEditMode(); exitEditMode();
setActionProgressVisible(false); setActionProgressVisible(false);
} }
@@ -1200,227 +1135,4 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return position; return position;
} }
} }
// from ProfileAboutFragment
public void setFields(ArrayList<AccountField> fields){
metadataListData=fields;
if (isInEditMode) {
isInEditMode=false;
dragHelper.attachToRecyclerView(null);
}
if (adapter != null) adapter.notifyDataSetChanged();
}
private class MetadataAdapter extends UsableRecyclerView.Adapter<BaseViewHolder> implements ImageLoaderRecyclerAdapter {
public MetadataAdapter(){
super(imgLoader);
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return switch(viewType){
case 0 -> new AboutViewHolder();
case 1 -> new EditableAboutViewHolder();
case 2 -> new AddRowViewHolder();
default -> throw new IllegalStateException("Unexpected value: "+viewType);
};
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position){
if(position<metadataListData.size()){
holder.bind(metadataListData.get(position));
}else{
holder.bind(null);
}
super.onBindViewHolder(holder, position);
}
@Override
public int getItemCount(){
if(isInEditMode){
int size=metadataListData.size();
if(size<MAX_FIELDS)
size++;
return size;
}
return metadataListData.size();
}
@Override
public int getItemViewType(int position){
if(isInEditMode){
return position==metadataListData.size() ? 2 : 1;
}
return 0;
}
@Override
public int getImageCountForItem(int position){
return isInEditMode || metadataListData.get(position).emojiRequests==null
? 0 : metadataListData.get(position).emojiRequests.size();
}
@Override
public ImageLoaderRequest getImageRequest(int position, int image){
return metadataListData.get(position).emojiRequests.get(image);
}
}
private abstract class BaseViewHolder extends BindableViewHolder<AccountField> {
public BaseViewHolder(int layout){
super(getActivity(), layout, list);
}
}
private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder {
private TextView title;
private 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);
if(item.verifiedAt!=null){
int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63;
value.setTextColor(textColor);
value.setLinkTextColor(textColor);
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_24_regular, getActivity().getTheme()).mutate();
check.setTint(textColor);
value.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, check, null);
}else{
value.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorPrimary));
value.setLinkTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.colorAccent));
value.setCompoundDrawables(null, null, null, null);
}
}
@Override
public void setImage(int index, Drawable image){
CustomEmojiSpan span=index>=item.nameEmojis.length ? item.valueEmojis[index-item.nameEmojis.length] : item.nameEmojis[index];
span.setDrawable(image);
title.invalidate();
value.invalidate();
}
@Override
public void clearImage(int index){
setImage(index, null);
}
}
private class EditableAboutViewHolder extends BaseViewHolder {
private EditText title;
private EditText value;
public EditableAboutViewHolder(){
super(R.layout.item_profile_about_editable);
title=findViewById(R.id.title);
value=findViewById(R.id.value);
findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
dragHelper.startDrag(this);
return true;
});
title.addTextChangedListener(new SimpleTextWatcher(e->item.name=e.toString()));
value.addTextChangedListener(new SimpleTextWatcher(e->item.value=e.toString()));
findViewById(R.id.remove_row_btn).setOnClickListener(this::onRemoveRowClick);
}
@Override
public void onBind(AccountField item){
title.setText(item.name);
value.setText(item.value);
}
private void onRemoveRowClick(View v){
int pos=getAbsoluteAdapterPosition();
metadataListData.remove(pos);
adapter.notifyItemRemoved(pos);
for(int i=0;i<list.getChildCount();i++){
BaseViewHolder vh=(BaseViewHolder) list.getChildViewHolder(list.getChildAt(i));
vh.rebind();
}
}
}
private class AddRowViewHolder extends BaseViewHolder implements UsableRecyclerView.Clickable{
public AddRowViewHolder(){
super(R.layout.item_profile_about_add_row);
}
@Override
public void onClick(){
metadataListData.add(new AccountField());
if(metadataListData.size()==MAX_FIELDS){ // replace this row with new row
adapter.notifyItemChanged(metadataListData.size()-1);
}else{
adapter.notifyItemInserted(metadataListData.size()-1);
rebind();
}
}
@Override
public void onBind(AccountField item) {}
}
private class ReorderCallback extends ItemTouchHelper.SimpleCallback{
public ReorderCallback(){
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target){
if(target instanceof AddRowViewHolder)
return false;
int fromPosition=viewHolder.getAbsoluteAdapterPosition();
int toPosition=target.getAbsoluteAdapterPosition();
if (fromPosition<toPosition) {
for (int i=fromPosition;i<toPosition;i++) {
Collections.swap(metadataListData, i, i+1);
}
} else {
for (int i=fromPosition;i>toPosition;i--) {
Collections.swap(metadataListData, i, i-1);
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
((BindableViewHolder)viewHolder).rebind();
((BindableViewHolder)target).rebind();
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction){
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
super.onSelectedChanged(viewHolder, actionState);
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
viewHolder.itemView.setTag(R.id.item_touch_helper_previous_elevation, viewHolder.itemView.getElevation()); // prevents the default behavior of changing elevation in onDraw()
viewHolder.itemView.animate().translationZ(V.dp(1)).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
draggedViewHolder=viewHolder;
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.animate().translationZ(0).setDuration(100).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
draggedViewHolder=null;
}
@Override
public boolean isLongPressDragEnabled(){
return false;
}
}
} }

View File

@@ -13,7 +13,6 @@ import org.joinmastodon.android.api.requests.statuses.CreateStatus;
import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses; import org.joinmastodon.android.api.requests.statuses.GetScheduledStatuses;
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.model.Filter;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.ScheduledStatus; import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -29,11 +28,11 @@ import me.grishka.appkit.api.SimpleCallback;
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> { public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
private String nextMaxID; private String nextMaxID;
private ImageButton fab;
private static final int SCHEDULED_STATUS_LIST_OPENED = 161; private static final int SCHEDULED_STATUS_LIST_OPENED = 161;
@Override public ScheduledStatusListFragment() {
protected boolean withComposeButton() { setListLayoutId(R.layout.recycler_fragment_with_fab);
return true;
} }
@Override @Override
@@ -56,31 +55,21 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
loadData(); loadData();
} }
@Override
protected void onFabClick(View v) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
Nav.go(getActivity(), ComposeFragment.class, args);
}
@Override
protected boolean onFabLongClick(View v) {
Bundle args=new Bundle();
args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
return UiUtils.pickAccountForCompose(getActivity(), accountID, 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);
fab=view.findViewById(R.id.fab);
Bundle args=new Bundle();
args.putString("account", accountID);
args.putSerializable("scheduledAt", CreateStatus.getDraftInstant());
fab.setOnClickListener(v -> Nav.go(getActivity(), ComposeFragment.class, args));
fab.setOnLongClickListener(v -> UiUtils.pickAccountForCompose(getActivity(), accountID, args));
if (getArguments().getBoolean("hide_fab", false)) fab.setVisibility(View.GONE); if (getArguments().getBoolean("hide_fab", false)) fab.setVisibility(View.GONE);
} }
@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, Filter.FilterContext.HOME); return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true);
} }
@Override @Override
@@ -120,7 +109,6 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else
nextMaxID=null; nextMaxID=null;
if (getActivity() == null) return;
onDataLoaded(result, nextMaxID!=null); onDataLoaded(result, nextMaxID!=null);
} }
}) })

View File

@@ -9,8 +9,6 @@ import android.graphics.Canvas;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.util.LruCache;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -45,18 +43,17 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager; import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken; import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken;
import org.joinmastodon.android.api.session.AccountActivationInfo;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent; import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment; import org.joinmastodon.android.fragments.onboarding.InstanceRulesFragment;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.fragments.onboarding.AccountActivationFragment;
import org.joinmastodon.android.model.PushNotification; import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.PushSubscription; import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import org.joinmastodon.android.updater.GithubSelfUpdater; import org.joinmastodon.android.updater.GithubSelfUpdater;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -68,6 +65,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@@ -81,7 +79,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
private ArrayList<Item> items=new ArrayList<>(); private ArrayList<Item> items=new ArrayList<>();
private ThemeItem themeItem; private ThemeItem themeItem;
private NotificationPolicyItem notificationPolicyItem; private NotificationPolicyItem notificationPolicyItem;
private SwitchItem showNewPostsButtonItem, glitchModeItem;
private String accountID; private String accountID;
private boolean needUpdateNotificationSettings; private boolean needUpdateNotificationSettings;
private boolean needAppRestart; private boolean needAppRestart;
@@ -127,8 +124,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.save(); GlobalUserPreferences.save();
needAppRestart=true; needAppRestart=true;
})); }));
items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{ items.add(new ButtonItem(R.string.sk_settings_color_palette, R.drawable.ic_fluent_color_24_regular, b->{
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL); PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.color_palettes); popupMenu.inflate(R.menu.color_palettes);
@@ -145,14 +140,14 @@ public class SettingsFragment extends MastodonToolbarFragment{
case BROWN -> R.string.sk_color_palette_brown; case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red; case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow; case YELLOW -> R.string.sk_color_palette_yellow;
case NORD -> R.string.mo_color_palette_nord; case NORD -> R.string.sk_color_palette_nord;
}); });
})); }));
items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> { items.add(new ButtonItem(R.string.sk_settings_publish_button_text, R.drawable.ic_fluent_send_24_regular, b-> {
updatePublishText(b); updatePublishText(b);
if (GlobalUserPreferences.relocatePublishButton) { if (GlobalUserPreferences.relocatePublishButton) {
b.setOnClickListener(l -> { b.setOnClickListener(l -> {
Toast.makeText(getActivity(), R.string.mo_disable_relocate_publish_button_to_enable_customization, Toast.makeText(getActivity(), R.string.sk_disable_relocate_publish_button_to_enable_customization,
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
}); });
} else { } else {
@@ -182,7 +177,13 @@ public class SettingsFragment extends MastodonToolbarFragment{
});} });}
})); }));
items.add(new HeaderItem(R.string.settings_behavior)); items.add(new HeaderItem(R.string.settings_behavior));
items.add(new SwitchItem(R.string.sk_settings_show_federated_timeline, R.drawable.ic_fluent_earth_24_regular, GlobalUserPreferences.showFederatedTimeline, i->{
GlobalUserPreferences.showFederatedTimeline=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{ items.add(new SwitchItem(R.string.settings_gif, R.drawable.ic_fluent_gif_24_regular, GlobalUserPreferences.playGifs, i->{
GlobalUserPreferences.playGifs=i.checked; GlobalUserPreferences.playGifs=i.checked;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
@@ -205,30 +206,27 @@ public class SettingsFragment extends MastodonToolbarFragment{
needAppRestart=true; needAppRestart=true;
})); }));
// items.add(new SwitchItem(R.string.sk_settings_show_differentiated_notification_icons, R.drawable.ic_ntf_logo, GlobalUserPreferences.showUniformPushNoticationIcons, this::onNotificationStyleChanged)); // items.add(new SwitchItem(R.string.sk_settings_show_differentiated_notification_icons, R.drawable.ic_ntf_logo, GlobalUserPreferences.showUniformPushNoticationIcons, this::onNotificationStyleChanged));
items.add(new SwitchItem(R.string.mo_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{ items.add(new SwitchItem(R.string.sk_disable_dividers, R.drawable.ic_fluent_timeline_24_regular, GlobalUserPreferences.disableDividers, i->{
GlobalUserPreferences.disableDividers=i.checked; GlobalUserPreferences.disableDividers=i.checked;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
needAppRestart=true; needAppRestart=true;
})); }));
items.add(new SwitchItem(R.string.mo_hide_compose_button_while_scrolling_setting, R.drawable.ic_fluent_edit_24_regular, GlobalUserPreferences.enableFabAutoHide, i->{ // items.add(new SwitchItem(R.string.sk_enable_delete_notifications, R.drawable.ic_fluent_delete_24_regular, GlobalUserPreferences.enableDeleteNotifications, i->{
GlobalUserPreferences.enableFabAutoHide =i.checked; // GlobalUserPreferences.enableDeleteNotifications=i.checked;
GlobalUserPreferences.save(); // GlobalUserPreferences.save();
needAppRestart=true; // needAppRestart=true;
})); // }));
items.add(new SwitchItem(R.string.mo_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{ items.add(new SwitchItem(R.string.sk_relocate_publish_button, R.drawable.ic_fluent_arrow_autofit_down_24_regular, GlobalUserPreferences.relocatePublishButton, i->{
GlobalUserPreferences.relocatePublishButton=i.checked; GlobalUserPreferences.relocatePublishButton=i.checked;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
})); }));
items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{ // items.add(new SwitchItem(R.string.sk_settings_hide_translate_in_timeline, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
GlobalUserPreferences.keepOnlyLatestNotification=i.checked; // GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
GlobalUserPreferences.save(); // GlobalUserPreferences.save();
})); // needAppRestart=true;
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{ // }));
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
GlobalUserPreferences.save();
}));
items.add(new HeaderItem(R.string.sk_timelines)); items.add(new HeaderItem(R.string.home_timeline));
items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{ items.add(new SwitchItem(R.string.sk_settings_show_replies, R.drawable.ic_fluent_chat_multiple_24_regular, GlobalUserPreferences.showReplies, i->{
GlobalUserPreferences.showReplies=i.checked; GlobalUserPreferences.showReplies=i.checked;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
@@ -237,58 +235,23 @@ public class SettingsFragment extends MastodonToolbarFragment{
GlobalUserPreferences.showBoosts=i.checked; GlobalUserPreferences.showBoosts=i.checked;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
})); }));
items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{ items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.loadNewPosts, i->{
GlobalUserPreferences.loadNewPosts=i.checked; GlobalUserPreferences.loadNewPosts=i.checked;
showNewPostsButtonItem.enabled = i.checked;
if (!i.checked) {
GlobalUserPreferences.showNewPostsButton = false;
showNewPostsButtonItem.checked = false;
}
if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsButtonItem)) instanceof SwitchViewHolder svh) svh.rebind();
GlobalUserPreferences.save(); GlobalUserPreferences.save();
})); }));
items.add(showNewPostsButtonItem = new SwitchItem(R.string.sk_settings_show_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{
GlobalUserPreferences.showNewPostsButton=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_show_alt_indicator, R.drawable.ic_fluent_scan_text_24_regular, GlobalUserPreferences.showAltIndicator, i->{
GlobalUserPreferences.showAltIndicator=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
items.add(new SwitchItem(R.string.sk_settings_show_no_alt_indicator, R.drawable.ic_fluent_important_24_regular, GlobalUserPreferences.showNoAltIndicator, i->{
GlobalUserPreferences.showNoAltIndicator=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_filled, GlobalUserPreferences.collapseLongPosts, i->{
GlobalUserPreferences.collapseLongPosts=i.checked;
GlobalUserPreferences.save();
}));
items.add(new SwitchItem(R.string.sk_spectator_mode, R.drawable.ic_fluent_eye_24_regular, GlobalUserPreferences.spectatorMode, i->{
GlobalUserPreferences.spectatorMode=i.checked;
GlobalUserPreferences.save();
needAppRestart=true;
}));
// items.add(new SwitchItem(R.string.sk_settings_translate_only_opened, R.drawable.ic_fluent_translate_24_regular, GlobalUserPreferences.translateButtonOpenedOnly, i->{
// GlobalUserPreferences.translateButtonOpenedOnly=i.checked;
// GlobalUserPreferences.save();
// needAppRestart=true;
// }));
items.add(new HeaderItem(R.string.settings_notifications)); items.add(new HeaderItem(R.string.settings_notifications));
items.add(notificationPolicyItem=new NotificationPolicyItem()); items.add(notificationPolicyItem=new NotificationPolicyItem());
PushSubscription pushSubscription=getPushSubscription(); PushSubscription pushSubscription=getPushSubscription();
boolean switchEnabled=pushSubscription.policy!=PushSubscription.Policy.NONE; items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i->onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked)));
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i->onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked)));
items.add(new SwitchItem(R.string.notify_favorites, R.drawable.ic_fluent_star_24_regular, pushSubscription.alerts.favourite, i->onNotificationsChanged(PushNotification.Type.FAVORITE, i.checked), switchEnabled)); items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i->onNotificationsChanged(PushNotification.Type.REBLOG, i.checked)));
items.add(new SwitchItem(R.string.notify_follow, R.drawable.ic_fluent_person_add_24_regular, pushSubscription.alerts.follow, i->onNotificationsChanged(PushNotification.Type.FOLLOW, i.checked), switchEnabled)); items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked)));
items.add(new SwitchItem(R.string.notify_reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, pushSubscription.alerts.reblog, i->onNotificationsChanged(PushNotification.Type.REBLOG, i.checked), switchEnabled)); items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_alert_24_regular, pushSubscription.alerts.status, i->onNotificationsChanged(PushNotification.Type.STATUS, i.checked)));
items.add(new SwitchItem(R.string.notify_mention, R.drawable.ic_fluent_mention_24_regular, pushSubscription.alerts.mention, i->onNotificationsChanged(PushNotification.Type.MENTION, i.checked), switchEnabled)); items.add(new SwitchItem(R.string.sk_settings_single_notification, R.drawable.ic_fluent_convert_range_24_regular, GlobalUserPreferences.keepOnlyLatestNotification, i->{
items.add(new SwitchItem(R.string.sk_notify_posts, R.drawable.ic_fluent_chat_24_regular, pushSubscription.alerts.status, i->onNotificationsChanged(PushNotification.Type.STATUS, i.checked), switchEnabled)); GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
items.add(new SwitchItem(R.string.sk_notify_update, R.drawable.ic_fluent_history_24_regular, pushSubscription.alerts.update, i->onNotificationsChanged(PushNotification.Type.UPDATE, i.checked), switchEnabled)); GlobalUserPreferences.save();
items.add(new SwitchItem(R.string.sk_notify_poll_results, R.drawable.ic_fluent_poll_24_regular, pushSubscription.alerts.poll, i->onNotificationsChanged(PushNotification.Type.POLL, i.checked), switchEnabled)); }));
items.add(new HeaderItem(R.string.settings_account)); items.add(new HeaderItem(R.string.settings_account));
items.add(new TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular)); items.add(new TextItem(R.string.sk_settings_profile, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/settings/profile"), R.drawable.ic_fluent_open_24_regular));
@@ -306,35 +269,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular)); items.add(new TextItem(R.string.settings_tos, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular)); items.add(new TextItem(R.string.settings_privacy_policy, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular)); items.add(new TextItem(R.string.log_out, this::confirmLogOut, R.drawable.ic_fluent_sign_out_24_regular));
if (!TextUtils.isEmpty(instance.version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, instance.version)));
items.add(new HeaderItem(R.string.sk_instance_features));
items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{
glitchModeItem.enabled = i.checked;
if (i.checked) {
GlobalUserPreferences.accountsWithLocalOnlySupport.add(accountID);
if (instance.pleroma == null) GlobalUserPreferences.accountsInGlitchMode.add(accountID);
} else {
GlobalUserPreferences.accountsWithLocalOnlySupport.remove(accountID);
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
}
glitchModeItem.checked = GlobalUserPreferences.accountsInGlitchMode.contains(accountID);
if (list.findViewHolderForAdapterPosition(items.indexOf(glitchModeItem)) instanceof SwitchViewHolder svh) svh.rebind();
GlobalUserPreferences.save();
}));
items.add(new SmallTextItem(getString(R.string.sk_settings_local_only_explanation)));
items.add(glitchModeItem = new SwitchItem(R.string.sk_settings_glitch_instance, 0, GlobalUserPreferences.accountsInGlitchMode.contains(accountID), i->{
if (i.checked) {
GlobalUserPreferences.accountsInGlitchMode.add(accountID);
} else {
GlobalUserPreferences.accountsInGlitchMode.remove(accountID);
}
GlobalUserPreferences.save();
}));
glitchModeItem.enabled = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
items.add(new SmallTextItem(getString(R.string.sk_settings_glitch_mode_explanation)));
boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled; boolean translationAvailable = instance.v2 != null && instance.v2.configuration.translation != null && instance.v2.configuration.translation.enabled;
items.add(new SmallTextItem(getString(translationAvailable ? items.add(new SmallTextItem(getString(translationAvailable ?
R.string.sk_settings_translation_availability_note_available : R.string.sk_settings_translation_availability_note_available :
@@ -342,46 +276,27 @@ public class SettingsFragment extends MastodonToolbarFragment{
items.add(new HeaderItem(R.string.sk_settings_about)); items.add(new HeaderItem(R.string.sk_settings_about));
// items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sk22/megalodon"), R.drawable.ic_fluent_open_24_regular));
// items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://ko-fi.com/xsk22"), R.drawable.ic_fluent_heart_24_regular));
if (GithubSelfUpdater.needSelfUpdating()) { if (GithubSelfUpdater.needSelfUpdating()) {
checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates); checkForUpdateItem = new TextItem(R.string.sk_check_for_update, GithubSelfUpdater.getInstance()::checkForUpdates);
items.add(checkForUpdateItem); items.add(checkForUpdateItem);
items.add(new SwitchItem(R.string.sk_updater_enable_pre_releases, 0, GlobalUserPreferences.enablePreReleases, i->{
GlobalUserPreferences.enablePreReleases=i.checked;
GlobalUserPreferences.save();
}));
} }
items.add(new TextItem(R.string.sk_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.mo_settings_contribute, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"), R.drawable.ic_fluent_open_24_regular));
items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular)); items.add(new TextItem(R.string.sk_settings_donate, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/sponsors/LucasGGamerM"), R.drawable.ic_fluent_heart_24_regular));
// items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache));
LruCache<?, ?> cache = imageCache == null ? null : imageCache.getLruCache(); clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), imageCache.getDiskCache().size(), true), this::clearImageCache, 0);
clearImageCacheItem = new TextItem(R.string.settings_clear_cache, UiUtils.formatFileSize(getContext(), cache != null ? cache.size() : 0, true), this::clearImageCache, 0);
items.add(clearImageCacheItem); items.add(clearImageCacheItem);
items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{ items.add(new TextItem(R.string.sk_clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.sk_clear_recent_languages, R.string.sk_confirm_clear_recent_languages, R.string.clear, ()->{
GlobalUserPreferences.recentLanguages.remove(accountID); GlobalUserPreferences.recentLanguages.remove(accountID);
GlobalUserPreferences.save(); GlobalUserPreferences.save();
}))); })));
items.add(new TextItem(R.string.sk_clear_recent_emoji, ()-> {
items.add(new TextItem(R.string.mo_clear_recent_emoji, ()-> {
GlobalUserPreferences.recentEmojis.clear(); GlobalUserPreferences.recentEmojis.clear();
GlobalUserPreferences.save(); GlobalUserPreferences.save();
})); }));
// items.add(new TextItem(R.string.log_out, this::confirmLogOut)); // items.add(new TextItem(R.string.log_out, this::confirmLogOut));
if(BuildConfig.DEBUG){
items.add(new RedHeaderItem("Debug options"));
items.add(new TextItem("Test e-mail confirmation flow", ()->{
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
sess.activated=false;
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
Bundle args=new Bundle();
args.putString("account", accountID);
args.putBoolean("debug", true);
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
}));
}
items.add(new FooterItem(getString(R.string.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE))); items.add(new FooterItem(getString(R.string.sk_settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)));
} }
@@ -435,7 +350,11 @@ public class SettingsFragment extends MastodonToolbarFragment{
if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){ if(needUpdateNotificationSettings && PushSubscriptionManager.arePushNotificationsAvailable()){
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription); AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().updatePushSettings(pushSubscription);
} }
if(needAppRestart) UiUtils.restartApp(); if(needAppRestart){
Intent intent = Intent.makeRestartActivityTask(MastodonApp.context.getPackageManager().getLaunchIntentForPackage(MastodonApp.context.getPackageName()).getComponent());
MastodonApp.context.startActivity(intent);
Runtime.getRuntime().exit(0);
}
} }
@Override @Override
@@ -539,10 +458,8 @@ public class SettingsFragment extends MastodonToolbarFragment{
case FAVORITE -> subscription.alerts.favourite=enabled; case FAVORITE -> subscription.alerts.favourite=enabled;
case FOLLOW -> subscription.alerts.follow=enabled; case FOLLOW -> subscription.alerts.follow=enabled;
case REBLOG -> subscription.alerts.reblog=enabled; case REBLOG -> subscription.alerts.reblog=enabled;
case MENTION -> subscription.alerts.mention=enabled; case MENTION -> subscription.alerts.mention=subscription.alerts.poll=enabled;
case POLL -> subscription.alerts.poll=enabled;
case STATUS -> subscription.alerts.status=enabled; case STATUS -> subscription.alerts.status=enabled;
case UPDATE -> subscription.alerts.update=enabled;
} }
needUpdateNotificationSettings=true; needUpdateNotificationSettings=true;
} }
@@ -567,13 +484,9 @@ public class SettingsFragment extends MastodonToolbarFragment{
list.getAdapter().notifyItemChanged(index); list.getAdapter().notifyItemChanged(index);
} }
if((prevPolicy==PushSubscription.Policy.NONE)!=(policy==PushSubscription.Policy.NONE)){ if((prevPolicy==PushSubscription.Policy.NONE)!=(policy==PushSubscription.Policy.NONE)){
boolean newState=policy!=PushSubscription.Policy.NONE;
for(PushNotification.Type value : PushNotification.Type.values()){
onNotificationsChanged(value, newState);
}
index++; index++;
while(items.get(index) instanceof SwitchItem si){ while(items.get(index) instanceof SwitchItem si){
si.enabled=si.checked=newState; si.enabled=si.checked=policy!=PushSubscription.Policy.NONE;
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index); RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index);
if(holder!=null) if(holder!=null)
((BindableViewHolder<?>)holder).rebind(); ((BindableViewHolder<?>)holder).rebind();
@@ -613,7 +526,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
} }
private void onLoggedOut(){ private void onLoggedOut(){
if (getActivity() == null) return;
AccountSessionManager.getInstance().removeAccount(accountID); AccountSessionManager.getInstance().removeAccount(accountID);
getActivity().finish(); getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class); Intent intent=new Intent(getActivity(), MainActivity.class);
@@ -667,7 +579,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
this.text=getString(text); this.text=getString(text);
} }
public HeaderItem(String text){ public HeaderItem(String text) {
this.text=text; this.text=text;
} }
@@ -691,7 +603,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
this.onChanged=onChanged; this.onChanged=onChanged;
} }
public SwitchItem(@StringRes int text, @DrawableRes int icon, boolean checked, Consumer<SwitchItem> onChanged, boolean enabled){ public SwitchItem(@StringRes int text, int icon, boolean checked, Consumer<SwitchItem> onChanged, boolean enabled){
this.text=getString(text); this.text=getString(text);
this.icon=icon; this.icon=icon;
this.checked=checked; this.checked=checked;
@@ -785,11 +697,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
this.secondaryText = secondaryText; this.secondaryText = secondaryText;
} }
public TextItem(String text, Runnable onClick){
this.text=text;
this.onClick=onClick;
}
@Override @Override
public int getViewType(){ public int getViewType(){
return 4; return 4;
@@ -802,10 +709,6 @@ public class SettingsFragment extends MastodonToolbarFragment{
super(text); super(text);
} }
public RedHeaderItem(String text){
super(text);
}
@Override @Override
public int getViewType(){ public int getViewType(){
return 5; return 5;
@@ -899,12 +802,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
@Override @Override
public void onBind(SwitchItem item){ public void onBind(SwitchItem item){
text.setText(item.text); text.setText(item.text);
if (item.icon == 0) { icon.setImageResource(item.icon);
icon.setVisibility(View.GONE);
} else {
icon.setVisibility(View.VISIBLE);
icon.setImageResource(item.icon);
}
checkbox.setChecked(item.checked && item.enabled); checkbox.setChecked(item.checked && item.enabled);
checkbox.setEnabled(item.enabled); checkbox.setEnabled(item.enabled);
} }
@@ -1079,19 +977,21 @@ public class SettingsFragment extends MastodonToolbarFragment{
private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> { private class SmallTextViewHolder extends BindableViewHolder<SmallTextItem> {
private final TextView text; private final TextView text;
;
public SmallTextViewHolder(){ public SmallTextViewHolder(){
super(getActivity(), R.layout.item_settings_text, list); super(getActivity(), R.layout.item_settings_text, list);
text = itemView.findViewById(R.id.text); text = itemView.findViewById(R.id.text);
text.setTextColor(UiUtils.getThemeColor(getActivity(), android.R.attr.textColorSecondary));
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
text.setPaddingRelative(text.getPaddingStart(), 0, text.getPaddingEnd(), text.getPaddingBottom());
} }
@Override @Override
public void onBind(SmallTextItem item){ public void onBind(SmallTextItem item){
text.setText(item.text); text.setText(item.text);
TypedValue val = new TypedValue();
getContext().getTheme().resolveAttribute(android.R.attr.textColorSecondary, val, true);
text.setTextColor(getResources().getColor(val.resourceId, getContext().getTheme()));
text.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
} }
} }
@@ -1146,10 +1046,10 @@ public class SettingsFragment extends MastodonToolbarFragment{
if (state == GithubSelfUpdater.UpdateState.CHECKING) return; if (state == GithubSelfUpdater.UpdateState.CHECKING) return;
GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo(); GithubSelfUpdater.UpdateInfo info=updater.getUpdateInfo();
if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){ if(state!=GithubSelfUpdater.UpdateState.DOWNLOADED){
text.setText(getString(R.string.mo_update_available, info.version)); text.setText(getString(R.string.sk_update_available, info.version));
button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false))); button.setText(getString(R.string.download_update, UiUtils.formatFileSize(getActivity(), info.size, false)));
}else{ }else{
text.setText(getString(R.string.mo_update_ready, info.version)); text.setText(getString(R.string.sk_update_ready, info.version));
button.setText(R.string.install_update); button.setText(R.string.install_update);
} }
if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){ if(state==GithubSelfUpdater.UpdateState.DOWNLOADING){

View File

@@ -173,7 +173,16 @@ public class SplashFragment extends AppKitFragment{
TextView title=new TextView(getActivity()); TextView title=new TextView(getActivity());
title.setTextAppearance(R.style.m3_headline_medium); title.setTextAppearance(R.style.m3_headline_medium);
title.setText(switch(page){ title.setText(switch(page){
case 0 -> getString(R.string.welcome_page1_title); case 0 -> {
String src=getString(R.string.welcome_page1_title);
SpannableString ss=new SpannableString(src);
int start=src.indexOf("{logo}");
if(start!=-1){
LogoSpan span=new LogoSpan(getResources().getDrawable(R.drawable.splash_logo, getActivity().getTheme()));
ss.setSpan(span, start, start+6, 0);
}
yield ss;
}
case 1 -> getString(R.string.welcome_page2_title); case 1 -> getString(R.string.welcome_page2_title);
case 2 -> getString(R.string.welcome_page3_title); case 2 -> getString(R.string.welcome_page3_title);
default -> throw new IllegalStateException("Unexpected value: "+page); default -> throw new IllegalStateException("Unexpected value: "+page);
@@ -195,4 +204,26 @@ public class SplashFragment extends AppKitFragment{
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
} }
} }
private class LogoSpan extends ReplacementSpan{
private final Drawable drawable;
private LogoSpan(Drawable drawable){
this.drawable=drawable;
}
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
return drawable.getIntrinsicWidth();
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
canvas.save();
canvas.translate(x, y-V.dp(20));
drawable.draw(canvas);
canvas.restore();
}
}
} }

View File

@@ -6,7 +6,6 @@ import android.view.View;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory; import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
@@ -47,7 +46,6 @@ public class StatusEditHistoryFragment extends StatusListFragment{
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed()); Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
if (getActivity() == null) return;
onDataLoaded(result, false); onDataLoaded(result, false);
} }
}) })
@@ -56,7 +54,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, Filter.FilterContext.HOME); List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
int idx=data.indexOf(s); int idx=data.indexOf(s);
if(idx>=0){ if(idx>=0){
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault())); String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
@@ -141,8 +139,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
action=getString(R.string.edit_multiple_changed); action=getString(R.string.edit_multiple_changed);
} }
} }
String sep = getString(R.string.sk_separator); items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" · "+date, Collections.emptyList(), 0, null, null));
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
} }
return items; return items;
} }

View File

@@ -6,14 +6,12 @@ import android.os.Bundle;
import com.squareup.otto.Subscribe; import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
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.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent; import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent; import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
@@ -32,9 +30,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
protected EventListener eventListener=new EventListener(); protected EventListener eventListener=new EventListener();
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
boolean addFooter = !GlobalUserPreferences.spectatorMode || return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true, null);
(this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id));
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, Filter.FilterContext.HOME);
} }
@Override @Override
@@ -60,7 +56,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
Status status=getContentStatusByID(id); Status status=getContentStatusByID(id);
if(status==null) if(status==null)
return; return;
status.filterRevealed = true;
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(status)); args.putParcelable("status", Parcels.wrap(status));

View File

@@ -26,7 +26,7 @@ import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
public class ThreadFragment extends StatusListFragment{ public class ThreadFragment extends StatusListFragment{
protected Status mainStatus; private Status mainStatus;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -61,7 +61,8 @@ public class ThreadFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(StatusContext result){ public void onSuccess(StatusContext result){
if (getActivity() == null) return; if(getActivity()==null)
return;
if(refreshing){ if(refreshing){
data.clear(); data.clear();
displayItems.clear(); displayItems.clear();
@@ -125,14 +126,4 @@ public class ThreadFragment extends StatusListFragment{
public boolean isItemEnabled(String id){ public boolean isItemEnabled(String id){
return !id.equals(mainStatus.id); return !id.equals(mainStatus.id);
} }
@Override
public boolean wantsLightStatusBar(){
return !UiUtils.isDarkTheme();
}
@Override
public boolean wantsLightNavigationBar(){
return !UiUtils.isDarkTheme();
}
} }

View File

@@ -101,7 +101,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
for(Relationship rel:result){ for(Relationship rel:result){
relationships.put(rel.id, rel); relationships.put(rel.id, rel);
} }
if (getActivity() == null) return;
if(list==null) if(list==null)
return; return;
for(int i=0;i<list.getChildCount();i++){ for(int i=0;i<list.getChildCount();i++){
@@ -129,8 +128,7 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
// list.setPadding(0, V.dp(16), 0, V.dp(16)); // list.setPadding(0, V.dp(16), 0, V.dp(16));
list.setClipToPadding(false); list.setClipToPadding(false);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 72, 16));
Math.round(16f + 56f * getResources().getConfiguration().fontScale), 16));
updateToolbar(); updateToolbar();
} }
@@ -372,7 +370,6 @@ public abstract class BaseAccountListFragment extends BaseRecyclerFragment<BaseA
@Override @Override
public void onSuccess(Relationship result){ public void onSuccess(Relationship result){
relationships.put(AccountViewHolder.this.item.account.id, result); relationships.put(AccountViewHolder.this.item.account.id, result);
if (getActivity() == null) return;
bindRelationship(); bindRelationship();
} }

View File

@@ -23,7 +23,6 @@ public abstract class PaginatedAccountListFragment extends BaseAccountListFragme
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else
nextMaxID=null; nextMaxID=null;
if (getActivity() == null) return;
onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null); onDataLoaded(result.stream().map(AccountItem::new).collect(Collectors.toList()), nextMaxID!=null);
} }
}) })

View File

@@ -15,7 +15,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions; import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ScrollableToTop; import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
@@ -49,7 +48,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop, IsOnTop { public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccountsFragment.AccountWrapper> implements ScrollableToTop{
private String accountID; private String accountID;
private Map<String, Relationship> relationships=Collections.emptyMap(); private Map<String, Relationship> relationships=Collections.emptyMap();
private GetAccountRelationships relationshipsRequest; private GetAccountRelationships relationshipsRequest;
@@ -74,7 +73,6 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<FollowSuggestion> result){ public void onSuccess(List<FollowSuggestion> result){
if (getActivity() == null) return;
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false); onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
loadRelationships(); loadRelationships();
} }
@@ -109,7 +107,6 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
public void onSuccess(List<Relationship> result){ public void onSuccess(List<Relationship> result){
relationshipsRequest=null; relationshipsRequest=null;
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity())); relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
if (getActivity() == null) return;
if(list==null) if(list==null)
return; return;
for(int i=0;i<list.getChildCount();i++){ for(int i=0;i<list.getChildCount();i++){
@@ -140,11 +137,6 @@ public class DiscoverAccountsFragment extends BaseRecyclerFragment<DiscoverAccou
smoothScrollRecyclerViewToTop(list); smoothScrollRecyclerViewToTop(list);
} }
@Override
public boolean isOnTop() {
return isRecyclerViewOnTop(list);
}
private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{ private class AccountsAdapter extends UsableRecyclerView.Adapter<AccountViewHolder> implements ImageLoaderRecyclerAdapter{
public AccountsAdapter(){ public AccountsAdapter(){

View File

@@ -62,7 +62,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
private String accountID; private String accountID;
private Runnable searchDebouncer=this::onSearchChangedDebounced; private Runnable searchDebouncer=this::onSearchChangedDebounced;
// private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline; private final boolean noFederated = !GlobalUserPreferences.showFederatedTimeline;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -324,7 +324,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
} }
private Fragment getFragmentForPage(int page){ private Fragment getFragmentForPage(int page){
// if (noFederated && page > 0) page++; if (noFederated && page > 0) page++;
// return switch(page){ // return switch(page){
// case 0 -> localTimelineFragment; // case 0 -> localTimelineFragment;

View File

@@ -10,7 +10,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.trends.GetTrendingLinks; import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ScrollableToTop; import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Card; import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
@@ -35,7 +34,7 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop, IsOnTop { public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements ScrollableToTop{
private String accountID; private String accountID;
private List<ImageLoaderRequest> imageRequests=Collections.emptyList(); private List<ImageLoaderRequest> imageRequests=Collections.emptyList();
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS); private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS);
@@ -59,7 +58,6 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
imageRequests=result.stream() imageRequests=result.stream()
.map(card->TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(150), V.dp(150))) .map(card->TextUtils.isEmpty(card.image) ? null : new UrlImageLoaderRequest(card.image, V.dp(150), V.dp(150)))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (getActivity() == null) return;
onDataLoaded(result, false); onDataLoaded(result, false);
} }
}) })
@@ -83,11 +81,6 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<Card> implements
smoothScrollRecyclerViewToTop(list); smoothScrollRecyclerViewToTop(list);
} }
@Override
public boolean isOnTop() {
return isRecyclerViewOnTop(list);
}
private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{ private class LinksAdapter extends UsableRecyclerView.Adapter<LinkViewHolder> implements ImageLoaderRecyclerAdapter{
public LinksAdapter(){ public LinksAdapter(){
super(imgLoader); super(imgLoader);

View File

@@ -4,19 +4,15 @@ import android.os.Bundle;
import android.view.View; import android.view.View;
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses; import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.StatusListFragment; import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper; import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop { public class DiscoverPostsFragment extends StatusListFragment{
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS); private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_POSTS);
@Override @Override
@@ -25,8 +21,6 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
} }
}).exec(accountID); }).exec(accountID);
@@ -37,9 +31,4 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
bannerHelper.maybeAddBanner(contentWrap); bannerHelper.maybeAddBanner(contentWrap);
} }
@Override
public boolean isOnTop() {
return isRecyclerViewOnTop(list);
}
} }

View File

@@ -5,6 +5,7 @@ import android.view.View;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline; import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
import org.joinmastodon.android.fragments.FabStatusListFragment;
import org.joinmastodon.android.fragments.StatusListFragment; import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -16,16 +17,10 @@ import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
public class FederatedTimelineFragment extends StatusListFragment { public class FederatedTimelineFragment extends FabStatusListFragment {
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE); private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.FEDERATED_TIMELINE);
private String maxID; private String maxID;
@Override
protected boolean withComposeButton() {
return true;
}
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count) currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count)
@@ -34,9 +29,7 @@ public class FederatedTimelineFragment extends StatusListFragment {
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(!result.isEmpty()) if(!result.isEmpty())
maxID=result.get(result.size()-1).id; maxID=result.get(result.size()-1).id;
if (getActivity() == null) return; onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty());
} }
}) })
.exec(accountID); .exec(accountID);
@@ -45,6 +38,6 @@ public class FederatedTimelineFragment extends StatusListFragment {
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
// bannerHelper.maybeAddBanner(contentWrap); bannerHelper.maybeAddBanner(contentWrap);
} }
} }

View File

@@ -3,7 +3,9 @@ package org.joinmastodon.android.fragments.discover;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline; import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
import org.joinmastodon.android.fragments.FabStatusListFragment;
import org.joinmastodon.android.fragments.StatusListFragment; import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -15,16 +17,10 @@ import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
public class LocalTimelineFragment extends StatusListFragment { public class LocalTimelineFragment extends FabStatusListFragment {
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE); private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.LOCAL_TIMELINE);
private String maxID; private String maxID;
@Override
protected boolean withComposeButton() {
return true;
}
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count) currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count)
@@ -33,9 +29,7 @@ public class LocalTimelineFragment extends StatusListFragment {
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(!result.isEmpty()) if(!result.isEmpty())
maxID=result.get(result.size()-1).id; maxID=result.get(result.size()-1).id;
if (getActivity() == null) return; onDataLoaded(result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList()), !result.isEmpty());
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty());
} }
}) })
.exec(accountID); .exec(accountID);

View File

@@ -14,7 +14,6 @@ import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.SearchResult; import org.joinmastodon.android.model.SearchResult;
import org.joinmastodon.android.model.SearchResults; import org.joinmastodon.android.model.SearchResults;
@@ -63,7 +62,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N) if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true); setRetainInstance(true);
loadData(); loadData();
resetEmptyText();
} }
@Override @Override
@@ -72,16 +70,12 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
imm=activity.getSystemService(InputMethodManager.class); imm=activity.getSystemService(InputMethodManager.class);
} }
private void resetEmptyText() {
setEmptyText(R.string.sk_recent_searches_placeholder);
}
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){ protected List<StatusDisplayItem> buildDisplayItems(SearchResult s){
return switch(s.type){ return switch(s.type){
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account)); case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag)); case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null, Filter.FilterContext.PUBLIC); case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null);
}; };
} }
@@ -125,8 +119,6 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
if (getActivity() == null) return;
resetEmptyText();
if(isInRecentMode()){ if(isInRecentMode()){
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{ AccountSessionManager.getInstance().getAccount(accountID).getCacheController().getRecentSearches(sr->{
if(getActivity()==null) if(getActivity()==null)
@@ -136,13 +128,11 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
onDataLoaded(sr, false); onDataLoaded(sr, false);
}); });
}else{ }else{
setEmptyText(R.string.sk_searching);
progressVisibilityListener.onProgressVisibilityChanged(true); progressVisibilityListener.onProgressVisibilityChanged(true);
currentRequest=new GetSearchResults(currentQuery, null, true) currentRequest=new GetSearchResults(currentQuery, null, true)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(SearchResults result){ public void onSuccess(SearchResults result){
setEmptyText(R.string.sk_no_results);
ArrayList<SearchResult> results=new ArrayList<>(); ArrayList<SearchResult> results=new ArrayList<>();
if(result.accounts!=null){ if(result.accounts!=null){
for(Account acc:result.accounts) for(Account acc:result.accounts)
@@ -158,13 +148,11 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
} }
prevDisplayItems=new ArrayList<>(displayItems); prevDisplayItems=new ArrayList<>(displayItems);
unfilteredResults=results; unfilteredResults=results;
if (getActivity() == null) return;
onDataLoaded(filterSearchResults(results), false); onDataLoaded(filterSearchResults(results), false);
} }
@Override @Override
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
resetEmptyText();
currentRequest=null; currentRequest=null;
Activity a=getActivity(); Activity a=getActivity();
if(a==null) if(a==null)

View File

@@ -7,7 +7,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags; import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ScrollableToTop; import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
@@ -24,7 +23,7 @@ import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop, IsOnTop { public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> implements ScrollableToTop{
private String accountID; private String accountID;
private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS); private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_HASHTAGS);
@@ -44,7 +43,6 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Hashtag> result){ public void onSuccess(List<Hashtag> result){
if (getActivity() == null) return;
onDataLoaded(result, false); onDataLoaded(result, false);
} }
}) })
@@ -68,11 +66,6 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
smoothScrollRecyclerViewToTop(list); smoothScrollRecyclerViewToTop(list);
} }
@Override
public boolean isOnTop() {
return isRecyclerViewOnTop(list);
}
private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{ private class HashtagsAdapter extends RecyclerView.Adapter<HashtagViewHolder>{
@NonNull @NonNull
@Override @Override

View File

@@ -193,24 +193,30 @@ public class AccountActivationFragment extends ToolbarFragment{
mgr.removeAccount(accountID); mgr.removeAccount(accountID);
mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null); mgr.addAccount(mgr.getInstanceInfo(session.domain), session.token, result, session.app, null);
String newID=mgr.getLastActiveAccountID(); String newID=mgr.getLastActiveAccountID();
accountID=newID; Bundle args=new Bundle();
if((session.self.avatar!=null || session.self.displayName!=null) && !getArguments().getBoolean("debug")){ args.putString("account", newID);
new UpdateAccountCredentials(session.self.displayName, "", (File)null, null, Collections.emptyList()) if(session.self.avatar!=null || session.self.displayName!=null){
File avaFile=session.self.avatar!=null ? new File(session.self.avatar) : null;
new UpdateAccountCredentials(session.self.displayName, "", avaFile, null, Collections.emptyList())
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Account result){ public void onSuccess(Account result){
if(avaFile!=null)
avaFile.delete();
mgr.updateAccountInfo(newID, result); mgr.updateAccountInfo(newID, result);
proceed(); Nav.goClearingStack(getActivity(), HomeFragment.class, args);
} }
@Override @Override
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
proceed(); if(avaFile!=null)
avaFile.delete();
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
} }
}) })
.exec(newID); .exec(newID);
}else{ }else{
proceed(); Nav.goClearingStack(getActivity(), HomeFragment.class, args);
} }
} }
@@ -243,11 +249,4 @@ public class AccountActivationFragment extends ToolbarFragment{
super.onDestroyView(); super.onDestroyView();
resendBtn.removeCallbacks(resendTimer); resendBtn.removeCallbacks(resendTimer);
} }
private void proceed(){
Bundle args=new Bundle();
args.putString("account", accountID);
// Nav.goClearingStack(getActivity(), HomeFragment.class, args);
Nav.goClearingStack(getActivity(), OnboardingProfileSetupFragment.class, args);
}
} }

View File

@@ -140,8 +140,8 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
headerView.findViewById(R.id.separator).setVisibility(View.GONE); headerView.findViewById(R.id.separator).setVisibility(View.GONE);
headerView.findViewById(R.id.timestamp).setVisibility(View.GONE); headerView.findViewById(R.id.timestamp).setVisibility(View.GONE);
headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE); headerView.findViewById(R.id.unread_indicator).setVisibility(View.GONE);
((TextView) headerView.findViewById(R.id.username)).setText(R.string.mo_app_username); ((TextView) headerView.findViewById(R.id.username)).setText(R.string.sk_app_username);
((TextView) headerView.findViewById(R.id.name)).setText(R.string.mo_app_name); ((TextView) headerView.findViewById(R.id.name)).setText(R.string.sk_app_name);
((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher)); ((ImageView) headerView.findViewById(R.id.avatar)).setImageDrawable(getActivity().getDrawable(R.mipmap.ic_launcher));
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this); ((FragmentStackActivity) getActivity()).invalidateSystemBarColors(this);

View File

@@ -5,7 +5,6 @@ import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -20,7 +19,6 @@ import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -44,7 +42,6 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.Callback; import okhttp3.Callback;
@@ -61,9 +58,6 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
private ArrayList<Item> items=new ArrayList<>(); private ArrayList<Item> items=new ArrayList<>();
private Call currentRequest; private Call currentRequest;
private ItemsAdapter itemsAdapter; private ItemsAdapter itemsAdapter;
private ElevationOnScrollListener onScrollListener;
private static final int SIGNUP_REQUEST=722;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -78,7 +72,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground)); setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
instance=Parcels.unwrap(getArguments().getParcelable("instance")); instance=Parcels.unwrap(getArguments().getParcelable("instance"));
items.add(new Item("Mastodon for Android Privacy Policy", getString(R.string.privacy_policy_explanation), "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png")); items.add(new Item("Mastodon for Android Privacy Policy", "joinmastodon.org", "https://joinmastodon.org/android/privacy", "https://joinmastodon.org/favicon-32x32.png"));
loadServerPrivacyPolicy(); loadServerPrivacyPolicy();
} }
@@ -99,24 +93,18 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
list.setLayoutManager(new LinearLayoutManager(getActivity())); list.setLayoutManager(new LinearLayoutManager(getActivity()));
View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false); View headerView=inflater.inflate(R.layout.item_list_header_simple, list, false);
TextView text=headerView.findViewById(R.id.text); TextView text=headerView.findViewById(R.id.text);
text.setText(getString(R.string.privacy_policy_subtitle, instance.uri)); text.setText(R.string.privacy_policy_subtitle);
adapter=new MergeRecyclerAdapter(); adapter=new MergeRecyclerAdapter();
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
adapter.addAdapter(itemsAdapter=new ItemsAdapter()); adapter.addAdapter(itemsAdapter=new ItemsAdapter());
list.setAdapter(adapter); list.setAdapter(adapter);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
btn=view.findViewById(R.id.btn_next); btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick()); btn.setOnClickListener(v->onButtonClick());
buttonBar=view.findViewById(R.id.button_bar); buttonBar=view.findViewById(R.id.button_bar);
Button backBtn=view.findViewById(R.id.btn_back);
backBtn.setText(getString(R.string.server_policy_disagree, instance.uri));
backBtn.setOnClickListener(v->{
setResult(false, null);
Nav.finish(this);
});
return view; return view;
} }
@@ -125,32 +113,19 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
} }
@Override // @Override
protected void onUpdateToolbar(){ protected void onUpdateToolbar(){
super.onUpdateToolbar(); // super.onUpdateToolbar();
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel); getToolbar().setBackground(null);
getToolbar().setElevation(0); getToolbar().setElevation(0);
if(onScrollListener!=null){
onScrollListener.setViews(buttonBar, getToolbar());
}
} }
protected void onButtonClick(){ protected void onButtonClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance)); args.putParcelable("instance", Parcels.wrap(instance));
Nav.goForResult(getActivity(), SignupFragment.class, args, SIGNUP_REQUEST, this); Nav.go(getActivity(), SignupFragment.class, args);
}
@Override
public void onFragmentResult(int reqCode, boolean success, Bundle result){
super.onFragmentResult(reqCode, success, result);
if(reqCode==SIGNUP_REQUEST && !success){
setResult(false, null);
Nav.finish(this);
}
} }
@Override @Override
@@ -183,7 +158,7 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
if(!response.isSuccessful()) if(!response.isSuccessful())
return; return;
Document doc=Jsoup.parse(Objects.requireNonNull(body).byteStream(), Objects.requireNonNull(body.contentType()).charset(StandardCharsets.UTF_8).name(), req.url().toString()); Document doc=Jsoup.parse(Objects.requireNonNull(body).byteStream(), Objects.requireNonNull(body.contentType()).charset(StandardCharsets.UTF_8).name(), req.url().toString());
final Item item=new Item(doc.title(), null, instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico"); final Item item=new Item(doc.title(), instance.uri, req.url().toString(), "https://"+instance.uri+"/favicon.ico");
Activity activity=getActivity(); Activity activity=getActivity();
if(activity!=null){ if(activity!=null){
activity.runOnUiThread(()->{ activity.runOnUiThread(()->{
@@ -217,23 +192,16 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{ private class ItemViewHolder extends BindableViewHolder<Item> implements UsableRecyclerView.Clickable{
private final TextView title; private final TextView title;
private final TextView subtitle;
public ItemViewHolder(){ public ItemViewHolder(){
super(getActivity(), R.layout.item_privacy_policy_link, list); super(getActivity(), R.layout.item_privacy_policy_link, list);
title=findViewById(R.id.title); title=findViewById(R.id.title);
subtitle=findViewById(R.id.subtitle); title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
} }
@Override @Override
public void onBind(Item item){ public void onBind(Item item){
title.setText(item.title); title.setText(item.title);
if(TextUtils.isEmpty(item.subtitle)){
subtitle.setVisibility(View.GONE);
}else{
subtitle.setVisibility(View.VISIBLE);
subtitle.setText(item.subtitle);
}
} }
@Override @Override
@@ -243,11 +211,10 @@ public class GoogleMadeMeAddThisFragment extends ToolbarFragment{
} }
private static class Item{ private static class Item{
public String title, subtitle, domain, url, faviconUrl; public String title, domain, url, faviconUrl;
public Item(String title, String subtitle, String domain, String url, String faviconUrl){ public Item(String title, String domain, String url, String faviconUrl){
this.title=title; this.title=title;
this.subtitle=subtitle;
this.domain=domain; this.domain=domain;
this.url=url; this.url=url;
this.faviconUrl=faviconUrl; this.faviconUrl=faviconUrl;

View File

@@ -92,7 +92,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){ protected boolean onSearchEnterPressed(TextView v, int actionId, KeyEvent event){
if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN) if(event!=null && event.getAction()!=KeyEvent.ACTION_DOWN)
return true; return true;
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim(); currentSearchQuery=searchEdit.getText().toString().toLowerCase();
updateFilteredList(); updateFilteredList();
searchEdit.removeCallbacks(searchDebouncer); searchEdit.removeCallbacks(searchDebouncer);
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery)); Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
@@ -106,7 +106,7 @@ abstract class InstanceCatalogFragment extends BaseRecyclerFragment<CatalogInsta
} }
protected void onSearchChangedDebounced(){ protected void onSearchChangedDebounced(){
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim(); currentSearchQuery=searchEdit.getText().toString().toLowerCase();
updateFilteredList(); updateFilteredList();
loadInstanceInfo(currentSearchQuery, false); loadInstanceInfo(currentSearchQuery, false);
} }

View File

@@ -1,8 +1,14 @@
package org.joinmastodon.android.fragments.onboarding; package org.joinmastodon.android.fragments.onboarding;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
@@ -14,6 +20,7 @@ import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.HorizontalScrollView; import android.widget.HorizontalScrollView;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.RadioButton; import android.widget.RadioButton;
@@ -31,7 +38,6 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
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.FilterChipView; import org.joinmastodon.android.ui.views.FilterChipView;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
@@ -50,7 +56,11 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.OnBackPressedListener; import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
@@ -70,7 +80,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
private LinearLayout filtersWrap; private LinearLayout filtersWrap;
private HorizontalScrollView filtersScroll; private HorizontalScrollView filtersScroll;
private ImageButton backBtn, clearSearchBtn; private ImageButton backBtn, clearSearchBtn;
private View focusThing;
private FilterChipView categoryGeneral, categorySpecialInterests; private FilterChipView categoryGeneral, categorySpecialInterests;
private List<FilterChipView> regionalFilters; private List<FilterChipView> regionalFilters;
@@ -205,7 +214,47 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
setStatusBarColor(0); setStatusBarColor(0);
topBar=view.findViewById(R.id.top_bar); topBar=view.findViewById(R.id.top_bar);
list.addOnScrollListener(new ElevationOnScrollListener(null, topBar, buttonBar)); LayerDrawable topBg=(LayerDrawable) topBar.getBackground().mutate();
topBar.setBackground(topBg);
Drawable topOverlay=topBg.findDrawableByLayerId(R.id.color_overlay);
topOverlay.setAlpha(0);
LayerDrawable btmBg=(LayerDrawable) buttonBar.getBackground().mutate();
buttonBar.setBackground(btmBg);
Drawable btmOverlay=btmBg.findDrawableByLayerId(R.id.color_overlay);
btmOverlay.setAlpha(0);
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
private boolean isAtTop=true;
private Animator currentPanelsAnim;
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
boolean newAtTop=recyclerView.getChildCount()==0 || (recyclerView.getChildAdapterPosition(recyclerView.getChildAt(0))==0 && recyclerView.getChildAt(0).getTop()==recyclerView.getPaddingTop());
if(newAtTop!=isAtTop){
isAtTop=newAtTop;
if(currentPanelsAnim!=null)
currentPanelsAnim.cancel();
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofInt(topOverlay, "alpha", isAtTop ? 0 : 20),
ObjectAnimator.ofInt(btmOverlay, "alpha", isAtTop ? 0 : 20),
ObjectAnimator.ofFloat(topBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3)),
ObjectAnimator.ofFloat(buttonBar, View.TRANSLATION_Z, isAtTop ? 0 : V.dp(3))
);
set.setDuration(150);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
currentPanelsAnim=null;
}
});
set.start();
currentPanelsAnim=set;
}
}
});
searchEdit=view.findViewById(R.id.search_edit); searchEdit=view.findViewById(R.id.search_edit);
searchEdit.setOnEditorActionListener(this::onSearchEnterPressed); searchEdit.setOnEditorActionListener(this::onSearchEnterPressed);
@@ -236,13 +285,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
FilterChipView langFilter=new FilterChipView(getActivity()); FilterChipView langFilter=new FilterChipView(getActivity());
langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18); langFilter.setDrawableEnd(R.drawable.ic_baseline_arrow_drop_down_18);
if(currentLanguage==null){ langFilter.setText(R.string.server_filter_any_language);
langFilter.setText(R.string.server_filter_any_language);
}else{
Locale locale=Locale.forLanguageTag(currentLanguage);
langFilter.setText(locale.getDisplayLanguage(locale));
langFilter.setSelected(true);
}
langFilterMenu=new PopupMenu(getContext(), langFilter); langFilterMenu=new PopupMenu(getContext(), langFilter);
langFilter.setOnTouchListener(langFilterMenu.getDragToOpenListener()); langFilter.setOnTouchListener(langFilterMenu.getDragToOpenListener());
langFilter.setOnClickListener(v->langFilterMenu.show()); langFilter.setOnClickListener(v->langFilterMenu.show());
@@ -258,12 +301,8 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
speedFilterMenu.getMenu().add(0, 2, 0, R.string.server_filter_manual_review); speedFilterMenu.getMenu().add(0, 2, 0, R.string.server_filter_manual_review);
speedFilter.setOnTouchListener(speedFilterMenu.getDragToOpenListener()); speedFilter.setOnTouchListener(speedFilterMenu.getDragToOpenListener());
speedFilter.setOnClickListener(v->speedFilterMenu.show()); speedFilter.setOnClickListener(v->speedFilterMenu.show());
speedFilter.setText(switch(currentSignupSpeedFilter){ speedFilter.setText(R.string.server_filter_instant_signup);
case ANY -> R.string.server_filter_any_signup_speed; speedFilter.setSelected(true);
case INSTANT -> R.string.server_filter_instant_signup;
case REVIEWED -> R.string.server_filter_manual_review;
});
speedFilter.setSelected(currentSignupSpeedFilter!=SignupSpeedFilter.ANY);
filtersWrap.addView(speedFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); filtersWrap.addView(speedFilter, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
speedFilterMenu.setOnMenuItemClickListener(item->{ speedFilterMenu.setOnMenuItemClickListener(item->{
@@ -289,13 +328,11 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
categoryGeneral.setText(R.string.category_general); categoryGeneral.setText(R.string.category_general);
categoryGeneral.setTag(CategoryChoice.GENERAL); categoryGeneral.setTag(CategoryChoice.GENERAL);
categoryGeneral.setOnClickListener(this::onCategoryFilterClick); categoryGeneral.setOnClickListener(this::onCategoryFilterClick);
categoryGeneral.setSelected(categoryChoice==CategoryChoice.GENERAL);
filtersWrap.addView(categoryGeneral, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); filtersWrap.addView(categoryGeneral, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
categorySpecialInterests=new FilterChipView(getActivity()); categorySpecialInterests=new FilterChipView(getActivity());
categorySpecialInterests.setText(R.string.category_special_interests); categorySpecialInterests.setText(R.string.category_special_interests);
categorySpecialInterests.setTag(CategoryChoice.SPECIAL); categorySpecialInterests.setTag(CategoryChoice.SPECIAL);
categorySpecialInterests.setOnClickListener(this::onCategoryFilterClick); categorySpecialInterests.setOnClickListener(this::onCategoryFilterClick);
categorySpecialInterests.setSelected(categoryChoice==CategoryChoice.SPECIAL);
filtersWrap.addView(categorySpecialInterests, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); filtersWrap.addView(categorySpecialInterests, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
regionalFilters=Arrays.stream(CatalogInstance.Region.values()).map(r->{ regionalFilters=Arrays.stream(CatalogInstance.Region.values()).map(r->{
@@ -314,11 +351,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
filtersWrap.addView(fv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); filtersWrap.addView(fv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return fv; return fv;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
focusThing=view.findViewById(R.id.focus_thing);
focusThing.requestFocus();
view.findViewById(R.id.btn_random_instance).setOnClickListener(this::onPickRandomInstanceClick);
nextButton.setEnabled(chosenInstance!=null);
} }
private void onRegionFilterClick(View v){ private void onRegionFilterClick(View v){
@@ -349,6 +381,22 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
updateFilteredList(); updateFilteredList();
} }
@Override
protected void onNextClick(View v){
if(chosenInstance==null){
String lang=Locale.getDefault().getLanguage();
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
if(instances.isEmpty()){
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
}
if(instances.isEmpty()){
return;
}
chosenInstance=instances.get(new Random().nextInt(instances.size()));
}
super.onNextClick(v);
}
@Override @Override
protected void proceedWithAuthOrSignup(Instance instance){ protected void proceedWithAuthOrSignup(Instance instance){
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0); getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0);
@@ -365,19 +413,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
Nav.go(getActivity(), InstanceRulesFragment.class, args); Nav.go(getActivity(), InstanceRulesFragment.class, args);
} }
private void onPickRandomInstanceClick(View v){
String lang=Locale.getDefault().getLanguage();
List<CatalogInstance> instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general"))) && (lang.equals(ci.language) || (ci.languages!=null && ci.languages.contains(lang)))).collect(Collectors.toList());
if(instances.isEmpty()){
instances=data.stream().filter(ci->!ci.approvalRequired && ("general".equals(ci.category) || (ci.categories!=null && ci.categories.contains("general")))).collect(Collectors.toList());
}
if(instances.isEmpty()){
return;
}
chosenInstance=instances.get(new Random().nextInt(instances.size()));
onNextClick(v);
}
// private String getEmojiForCategory(String category){ // private String getEmojiForCategory(String category){
// return switch(category){ // return switch(category){
// case "all" -> "💬"; // case "all" -> "💬";
@@ -515,7 +550,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0)); searchEdit.setCompoundDrawableTintList(ColorStateList.valueOf(0));
}else{ }else{
filtersScroll.setVisibility(View.VISIBLE); filtersScroll.setVisibility(View.VISIBLE);
focusThing.requestFocus(); searchEdit.clearFocus();
searchEdit.setText(""); searchEdit.setText("");
lp.addRule(RelativeLayout.END_OF, R.id.btn_back); lp.addRule(RelativeLayout.END_OF, R.id.btn_back);
getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0); getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
@@ -527,16 +562,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
updateFilteredList(); updateFilteredList();
} }
@Override private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder> implements ImageLoaderRecyclerAdapter{
protected void onShown(){
super.onShown();
if(!searchQueryMode){
// Prevent search view automatically getting focused when the user returns to this fragment
focusThing.requestFocus();
}
}
private class InstancesAdapter extends UsableRecyclerView.Adapter<InstanceCatalogSignupFragment.InstanceViewHolder>{
public InstancesAdapter(){ public InstancesAdapter(){
super(imgLoader); super(imgLoader);
} }
@@ -562,11 +588,22 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
public int getItemViewType(int position){ public int getItemViewType(int position){
return -1; return -1;
} }
@Override
public int getImageCountForItem(int position){
return filteredData.get(position).thumbnailRequest!=null ? 1 : 0;
}
@Override
public ImageLoaderRequest getImageRequest(int position, int image){
return filteredData.get(position).thumbnailRequest;
}
} }
private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable{ private class InstanceViewHolder extends BindableViewHolder<CatalogInstance> implements UsableRecyclerView.DisableableClickable, ImageLoaderViewHolder{
private final TextView title, description; private final TextView title, description;
private final RadioButton radioButton; private final RadioButton radioButton;
private final ImageView thumbnail;
private boolean enabled; private boolean enabled;
public InstanceViewHolder(){ public InstanceViewHolder(){
@@ -574,12 +611,15 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
title=findViewById(R.id.title); title=findViewById(R.id.title);
description=findViewById(R.id.description); description=findViewById(R.id.description);
radioButton=findViewById(R.id.radiobtn); radioButton=findViewById(R.id.radiobtn);
thumbnail=findViewById(R.id.image);
} }
@Override @Override
public void onBind(CatalogInstance item){ public void onBind(CatalogInstance item){
title.setText(item.normalizedDomain); title.setText(item.normalizedDomain);
radioButton.setChecked(chosenInstance==item); radioButton.setChecked(chosenInstance==item);
if(item.thumbnailRequest==null)
thumbnail.setImageDrawable(null);
Instance realInstance=instancesCache.get(item.normalizedDomain); Instance realInstance=instancesCache.get(item.normalizedDomain);
float alpha; float alpha;
if(realInstance!=null && !realInstance.registrations){ if(realInstance!=null && !realInstance.registrations){
@@ -594,6 +634,7 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
title.setAlpha(alpha); title.setAlpha(alpha);
description.setAlpha(alpha); description.setAlpha(alpha);
radioButton.setAlpha(alpha); radioButton.setAlpha(alpha);
thumbnail.setAlpha(alpha);
} }
@Override @Override
@@ -616,9 +657,6 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
adapter.notifyItemChanged(idx); adapter.notifyItemChanged(idx);
} }
} }
if(!nextButton.isEnabled()){
nextButton.setEnabled(true);
}
radioButton.setChecked(true); radioButton.setChecked(true);
if(chosenInstance==null) if(chosenInstance==null)
nextButton.setEnabled(true); nextButton.setEnabled(true);
@@ -626,6 +664,16 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
loadInstanceInfo(chosenInstance.domain, false); loadInstanceInfo(chosenInstance.domain, false);
} }
@Override
public void setImage(int index, Drawable image){
thumbnail.setImageDrawable(image);
}
@Override
public void clearImage(int index){
setImage(index, null);
}
@Override @Override
public boolean isEnabled(){ public boolean isEnabled(){
return enabled; return enabled;
@@ -647,5 +695,4 @@ public class InstanceCatalogSignupFragment extends InstanceCatalogFragment imple
return (this==GENERAL)==isGeneral; return (this==GENERAL)==isGeneral;
} }
} }
} }

View File

@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments.onboarding;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.res.Resources;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -17,7 +18,6 @@ import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.ui.DividerItemDecoration; import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.parceler.Parcels; import org.parceler.Parcels;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -29,7 +29,6 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter; import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class InstanceRulesFragment extends ToolbarFragment{ public class InstanceRulesFragment extends ToolbarFragment{
@@ -38,9 +37,6 @@ public class InstanceRulesFragment extends ToolbarFragment{
private Button btn; private Button btn;
private View buttonBar; private View buttonBar;
private Instance instance; private Instance instance;
private ElevationOnScrollListener onScrollListener;
private static final int RULES_REQUEST=376;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -70,47 +66,33 @@ public class InstanceRulesFragment extends ToolbarFragment{
adapter.addAdapter(new SingleViewRecyclerAdapter(headerView)); adapter.addAdapter(new SingleViewRecyclerAdapter(headerView));
adapter.addAdapter(new ItemsAdapter()); adapter.addAdapter(new ItemsAdapter());
list.setAdapter(adapter); list.setAdapter(adapter);
list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorM3SurfaceVariant, 1, 56, 0, DividerItemDecoration.NOT_FIRST)); list.addItemDecoration(new DividerItemDecoration(getActivity(), R.attr.colorPollVoted, 1, 56, 0, DividerItemDecoration.NOT_FIRST));
btn=view.findViewById(R.id.btn_next); btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick()); btn.setOnClickListener(v->onButtonClick());
buttonBar=view.findViewById(R.id.button_bar); buttonBar=view.findViewById(R.id.button_bar);
view.findViewById(R.id.btn_back).setOnClickListener(v->Nav.finish(this));
return view; return view;
} }
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); // setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); // view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
} }
@Override // @Override
protected void onUpdateToolbar(){ protected void onUpdateToolbar(){
super.onUpdateToolbar(); // super.onUpdateToolbar();
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel); getToolbar().setBackground(null);
getToolbar().setElevation(0); getToolbar().setElevation(0);
if(onScrollListener!=null){
onScrollListener.setViews(buttonBar, getToolbar());
}
} }
protected void onButtonClick(){ protected void onButtonClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putParcelable("instance", Parcels.wrap(instance)); args.putParcelable("instance", Parcels.wrap(instance));
Nav.goForResult(getActivity(), GoogleMadeMeAddThisFragment.class, args, RULES_REQUEST, this); Nav.go(getActivity(), GoogleMadeMeAddThisFragment.class, args);
}
@Override
public void onFragmentResult(int reqCode, boolean success, Bundle result){
super.onFragmentResult(reqCode, success, result);
if(reqCode==RULES_REQUEST && !success){
Nav.finish(this);
}
} }
@Override @Override

View File

@@ -1,350 +0,0 @@
package org.joinmastodon.android.fragments.onboarding;
import android.app.ProgressDialog;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowSuggestions;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.FollowSuggestion;
import org.joinmastodon.android.model.ParsedAccount;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
import me.grishka.appkit.views.UsableRecyclerView;
public class OnboardingFollowSuggestionsFragment extends BaseRecyclerFragment<ParsedAccount>{
private String accountID;
private Map<String, Relationship> relationships=Collections.emptyMap();
private GetAccountRelationships relationshipsRequest;
private View buttonBar;
private ElevationOnScrollListener onScrollListener;
private int numRunningFollowRequests=0;
public OnboardingFollowSuggestionsFragment(){
super(R.layout.fragment_onboarding_follow_suggestions, 40);
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
setTitle(R.string.popular_on_mastodon);
accountID=getArguments().getString("account");
loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
buttonBar=view.findViewById(R.id.button_bar);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
list.addOnScrollListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
view.findViewById(R.id.btn_next).setOnClickListener(UiUtils.rateLimitedClickListener(this::onFollowAllClick));
view.findViewById(R.id.btn_skip).setOnClickListener(UiUtils.rateLimitedClickListener(v->proceed()));
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel);
getToolbar().setElevation(0);
if(onScrollListener!=null){
onScrollListener.setViews(buttonBar, getToolbar());
}
}
@Override
protected void doLoadData(int offset, int count){
new GetFollowSuggestions(40)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<FollowSuggestion> result){
onDataLoaded(result.stream().map(fs->new ParsedAccount(fs.account, accountID)).collect(Collectors.toList()), false);
loadRelationships();
}
})
.exec(accountID);
}
private void loadRelationships(){
relationships=Collections.emptyMap();
relationshipsRequest=new GetAccountRelationships(data.stream().map(fs->fs.account.id).collect(Collectors.toList()));
relationshipsRequest.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Relationship> result){
relationshipsRequest=null;
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
if(list==null)
return;
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof SuggestionViewHolder svh)
svh.rebind();
}
}
@Override
public void onError(ErrorResponse error){
relationshipsRequest=null;
}
}).exec(accountID);
}
@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(), 0));
}else{
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
}
@Override
protected RecyclerView.Adapter getAdapter(){
return new SuggestionsAdapter();
}
private void onFollowAllClick(View v){
if(!loaded || relationships.isEmpty())
return;
if(data.isEmpty()){
proceed();
return;
}
ArrayList<String> accountIdsToFollow=new ArrayList<>();
for(ParsedAccount acc:data){
Relationship rel=relationships.get(acc.account.id);
if(rel==null)
continue;
if(rel.canFollow())
accountIdsToFollow.add(acc.account.id);
}
final ProgressDialog progress=new ProgressDialog(getActivity());
progress.setIndeterminate(false);
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progress.setMax(accountIdsToFollow.size());
progress.setCancelable(false);
progress.setMessage(getString(R.string.sending_follows));
progress.show();
for(int i=0;i<Math.min(accountIdsToFollow.size(), 5);i++){ // Send up to 5 requests in parallel
followNextAccount(accountIdsToFollow, progress);
}
}
private void followNextAccount(ArrayList<String> accountIdsToFollow, ProgressDialog progress){
if(accountIdsToFollow.isEmpty()){
if(numRunningFollowRequests==0){
progress.dismiss();
proceed();
}
return;
}
numRunningFollowRequests++;
String id=accountIdsToFollow.remove(0);
new SetAccountFollowed(id, true, true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
numRunningFollowRequests--;
progress.setProgress(progress.getMax()-accountIdsToFollow.size()-numRunningFollowRequests);
followNextAccount(accountIdsToFollow, progress);
}
@Override
public void onError(ErrorResponse error){
numRunningFollowRequests--;
progress.setProgress(progress.getMax()-accountIdsToFollow.size()-numRunningFollowRequests);
followNextAccount(accountIdsToFollow, progress);
}
})
.exec(accountID);
}
private void proceed(){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), HomeFragment.class, args);
getActivity().getWindow().getDecorView().postDelayed(()->Nav.finish(this), 500);
}
@Override
protected boolean canGoBack(){
return true;
}
@Override
public void onToolbarNavigationClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
}
private class SuggestionsAdapter extends UsableRecyclerView.Adapter<SuggestionViewHolder> implements ImageLoaderRecyclerAdapter{
public SuggestionsAdapter(){
super(imgLoader);
}
@NonNull
@Override
public SuggestionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
return new SuggestionViewHolder();
}
@Override
public int getItemCount(){
return data.size();
}
@Override
public void onBindViewHolder(SuggestionViewHolder holder, int position){
holder.bind(data.get(position));
super.onBindViewHolder(holder, position);
}
@Override
public int getImageCountForItem(int position){
return data.get(position).emojiHelper.getImageCount()+1;
}
@Override
public ImageLoaderRequest getImageRequest(int position, int image){
ParsedAccount account=data.get(position);
if(image==0)
return account.avatarRequest;
return account.emojiHelper.getImageRequest(image-1);
}
}
private class SuggestionViewHolder extends BindableViewHolder<ParsedAccount> implements ImageLoaderViewHolder, UsableRecyclerView.Clickable{
private final TextView name, username, bio;
private final ImageView avatar;
private final ProgressBarButton actionButton;
private final ProgressBar actionProgress;
private final View actionWrap;
private Relationship relationship;
public SuggestionViewHolder(){
super(getActivity(), R.layout.item_user_row_m3, list);
name=findViewById(R.id.name);
username=findViewById(R.id.username);
bio=findViewById(R.id.bio);
avatar=findViewById(R.id.avatar);
actionButton=findViewById(R.id.action_btn);
actionProgress=findViewById(R.id.action_progress);
actionWrap=findViewById(R.id.action_btn_wrap);
avatar.setOutlineProvider(OutlineProviders.roundedRect(10));
avatar.setClipToOutline(true);
actionButton.setOnClickListener(UiUtils.rateLimitedClickListener(this::onActionButtonClick));
}
@Override
public void onBind(ParsedAccount item){
name.setText(item.parsedName);
username.setText(item.account.getDisplayUsername());
if(TextUtils.isEmpty(item.parsedBio)){
bio.setVisibility(View.GONE);
}else{
bio.setVisibility(View.VISIBLE);
bio.setText(item.parsedBio);
}
relationship=relationships.get(item.account.id);
if(relationship==null){
actionWrap.setVisibility(View.GONE);
}else{
actionWrap.setVisibility(View.VISIBLE);
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
}
}
@Override
public void setImage(int index, Drawable image){
if(index==0){
avatar.setImageDrawable(image);
}else{
item.emojiHelper.setImageDrawable(index-1, image);
name.invalidate();
bio.invalidate();
}
if(image instanceof Animatable a && !a.isRunning())
a.start();
}
@Override
public void clearImage(int index){
setImage(index, null);
}
@Override
public void onClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("profileAccount", Parcels.wrap(item.account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
private void onActionButtonClick(View v){
itemView.setHasTransientState(true);
UiUtils.performAccountAction(getActivity(), item.account, accountID, relationship, actionButton, this::setActionProgressVisible, rel->{
itemView.setHasTransientState(false);
relationships.put(item.account.id, rel);
rebind();
});
}
private void setActionProgressVisible(boolean visible){
actionButton.setTextVisible(!visible);
actionProgress.setVisibility(visible ? View.VISIBLE : View.GONE);
actionButton.setClickable(!visible);
}
}
}

View File

@@ -1,237 +0,0 @@
package org.joinmastodon.android.fragments.onboarding;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ScrollView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ReorderableLinearLayout;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import java.util.ArrayList;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.FragmentRootLinearLayout;
public class OnboardingProfileSetupFragment extends ToolbarFragment implements ReorderableLinearLayout.OnDragListener{
private Button btn;
private View buttonBar;
private String accountID;
private ElevationOnScrollListener onScrollListener;
private ScrollView scroller;
private EditText nameEdit, bioEdit;
private ImageView avaImage, coverImage;
private Button addRow;
private ReorderableLinearLayout profileFieldsLayout;
private Uri avatarUri, coverUri;
private static final int AVATAR_RESULT=348;
private static final int COVER_RESULT=183;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
setNavigationBarColor(UiUtils.getThemeColor(activity, R.attr.colorWindowBackground));
accountID=getArguments().getString("account");
setTitle(R.string.profile_setup);
}
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view=inflater.inflate(R.layout.fragment_onboarding_profile_setup, container, false);
scroller=view.findViewById(R.id.scroller);
nameEdit=view.findViewById(R.id.display_name);
bioEdit=view.findViewById(R.id.bio);
avaImage=view.findViewById(R.id.avatar);
coverImage=view.findViewById(R.id.header);
addRow=view.findViewById(R.id.add_row);
profileFieldsLayout=view.findViewById(R.id.profile_fields);
btn=view.findViewById(R.id.btn_next);
btn.setOnClickListener(v->onButtonClick());
buttonBar=view.findViewById(R.id.button_bar);
avaImage.setOutlineProvider(OutlineProviders.roundedRect(24));
avaImage.setClipToOutline(true);
Account account=AccountSessionManager.getInstance().getAccount(accountID).self;
if(savedInstanceState==null){
nameEdit.setText(account.displayName);
makeFieldsRow();
}else{
ArrayList<String> fieldTitles=savedInstanceState.getStringArrayList("fieldTitles");
ArrayList<String> fieldValues=savedInstanceState.getStringArrayList("fieldValues");
for(int i=0;i<fieldTitles.size();i++){
View row=makeFieldsRow();
EditText title=row.findViewById(R.id.title);
EditText content=row.findViewById(R.id.content);
title.setText(fieldTitles.get(i));
content.setText(fieldValues.get(i));
}
if(fieldTitles.size()==4)
addRow.setVisibility(View.GONE);
}
addRow.setOnClickListener(v->{
makeFieldsRow();
if(profileFieldsLayout.getChildCount()==4){
addRow.setVisibility(View.GONE);
}
});
profileFieldsLayout.setDragListener(this);
avaImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), AVATAR_RESULT));
coverImage.setOnClickListener(v->startActivityForResult(UiUtils.getMediaPickerIntent(new String[]{"image/*"}, 1), COVER_RESULT));
return view;
}
@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));
scroller.setOnScrollChangeListener(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());
}
}
protected void onButtonClick(){
ArrayList<AccountField> fields=new ArrayList<>();
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
View row=profileFieldsLayout.getChildAt(i);
EditText title=row.findViewById(R.id.title);
EditText content=row.findViewById(R.id.content);
AccountField fld=new AccountField();
fld.name=title.getText().toString();
fld.value=content.getText().toString();
fields.add(fld);
}
new UpdateAccountCredentials(nameEdit.getText().toString(), bioEdit.getText().toString(), avatarUri, coverUri, fields)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
AccountSessionManager.getInstance().updateAccountInfo(accountID, result);
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), OnboardingFollowSuggestionsFragment.class, args);
getActivity().getWindow().getDecorView().postDelayed(()->Nav.finish(OnboardingProfileSetupFragment.this), 500);
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.saving, true)
.exec(accountID);
}
@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(), 0));
}else{
super.onApplyWindowInsets(insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(), insets.getSystemWindowInsetRight(), insets.getSystemWindowInsetBottom()));
}
}
private View makeFieldsRow(){
View view=LayoutInflater.from(getActivity()).inflate(R.layout.onboarding_profile_field, profileFieldsLayout, false);
profileFieldsLayout.addView(view);
view.findViewById(R.id.dragger_thingy).setOnLongClickListener(v->{
profileFieldsLayout.startDragging(view);
return true;
});
return view;
}
@Override
public void onSwapItems(int oldIndex, int newIndex){}
@Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
ArrayList<String> fieldTitles=new ArrayList<>(), fieldValues=new ArrayList<>();
for(int i=0;i<profileFieldsLayout.getChildCount();i++){
View row=profileFieldsLayout.getChildAt(i);
EditText title=row.findViewById(R.id.title);
EditText content=row.findViewById(R.id.content);
fieldTitles.add(title.getText().toString());
fieldValues.add(content.getText().toString());
}
outState.putStringArrayList("fieldTitles", fieldTitles);
outState.putStringArrayList("fieldValues", fieldValues);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
if(resultCode!=Activity.RESULT_OK)
return;
ImageView img;
Uri uri=data.getData();
int size;
if(requestCode==AVATAR_RESULT){
img=avaImage;
avatarUri=uri;
size=V.dp(100);
}else{
img=coverImage;
coverUri=uri;
size=V.dp(1000);
}
img.setForeground(null);
ViewImageLoader.load(img, null, new UrlImageLoaderRequest(uri, size, size));
}
@Override
protected boolean canGoBack(){
return true;
}
@Override
public void onToolbarNavigationClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.goClearingStack(getActivity(), HomeFragment.class, args);
}
}

View File

@@ -1,18 +1,14 @@
package org.joinmastodon.android.fragments.onboarding; package org.joinmastodon.android.fragments.onboarding;
import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.graphics.Typeface; import android.content.Intent;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.Html;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.text.style.TypefaceSpan; import android.util.Log;
import android.text.style.URLSpan;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -20,9 +16,11 @@ import android.view.ViewTreeObserver;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.MastodonDetailedErrorResponse; import org.joinmastodon.android.api.MastodonDetailedErrorResponse;
import org.joinmastodon.android.api.requests.accounts.RegisterAccount; import org.joinmastodon.android.api.requests.accounts.RegisterAccount;
import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp; import org.joinmastodon.android.api.requests.oauth.CreateOAuthApp;
@@ -33,22 +31,18 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application; import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Token; import org.joinmastodon.android.model.Token;
import org.joinmastodon.android.ui.text.LinkSpan;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher; import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout; import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
import org.joinmastodon.android.utils.ElevationOnScrollListener;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.NodeVisitor;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -57,10 +51,12 @@ import me.grishka.appkit.api.APIRequest;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.ToolbarFragment; import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.imageloader.ViewImageLoader;
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;
public class SignupFragment extends ToolbarFragment{ public class SignupFragment extends ToolbarFragment{
private static final int AVATAR_RESULT=198;
private static final String TAG="SignupFragment"; private static final String TAG="SignupFragment";
private Instance instance; private Instance instance;
@@ -77,7 +73,6 @@ public class SignupFragment extends ToolbarFragment{
private boolean submitAfterGettingToken; private boolean submitAfterGettingToken;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
private HashSet<EditText> errorFields=new HashSet<>(); private HashSet<EditText> errorFields=new HashSet<>();
private ElevationOnScrollListener onScrollListener;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -150,22 +145,19 @@ public class SignupFragment extends ToolbarFragment{
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); setStatusBarColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background)); view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorM3Background));
view.findViewById(R.id.scroller).setOnScrollChangeListener(onScrollListener=new ElevationOnScrollListener((FragmentRootLinearLayout) view, buttonBar, getToolbar()));
} }
@Override // @Override
protected void onUpdateToolbar(){ protected void onUpdateToolbar(){
super.onUpdateToolbar(); // super.onUpdateToolbar();
getToolbar().setBackgroundResource(R.drawable.bg_onboarding_panel); getToolbar().setBackground(null);
getToolbar().setElevation(0); getToolbar().setElevation(0);
if(onScrollListener!=null){
onScrollListener.setViews(buttonBar, getToolbar());
}
} }
private void onButtonClick(){ private void onButtonClick(){
if(!password.getText().toString().equals(passwordConfirm.getText().toString())){ if(!password.getText().toString().equals(passwordConfirm.getText().toString())){
passwordConfirmWrap.setErrorState(getString(R.string.signup_passwords_dont_match)); passwordConfirm.setError(getString(R.string.signup_passwords_dont_match));
passwordConfirmWrap.setErrorState();
return; return;
} }
showProgressDialog(); showProgressDialog();
@@ -220,22 +212,8 @@ public class SignupFragment extends ToolbarFragment{
anyFieldsSkipped=true; anyFieldsSkipped=true;
continue; continue;
} }
List<MastodonDetailedErrorResponse.FieldError> errors=Objects.requireNonNull(fieldErrors.get(fieldName)); field.setError(fieldErrors.get(fieldName).stream().map(err->err.description).collect(Collectors.joining("\n")));
if(errors.size()==1){ getFieldWrapByName(fieldName).setErrorState();
getFieldWrapByName(fieldName).setErrorState(getErrorDescription(errors.get(0), fieldName));
}else{
SpannableStringBuilder ssb=new SpannableStringBuilder();
boolean firstErr=true;
for(MastodonDetailedErrorResponse.FieldError err:errors){
if(firstErr){
firstErr=false;
}else{
ssb.append('\n');
}
ssb.append(getErrorDescription(err, fieldName));
}
getFieldWrapByName(fieldName).setErrorState(getErrorDescription(errors.get(0), fieldName));
}
errorFields.add(field); errorFields.add(field);
if(first){ if(first){
first=false; first=false;
@@ -253,40 +231,6 @@ public class SignupFragment extends ToolbarFragment{
.exec(instance.uri, apiToken); .exec(instance.uri, apiToken);
} }
private CharSequence getErrorDescription(MastodonDetailedErrorResponse.FieldError error, String fieldName){
return switch(fieldName){
case "email" -> switch(error.error){
case "ERR_BLOCKED" -> {
String emailAddr=email.getText().toString();
String s=getResources().getString(R.string.signup_email_domain_blocked, TextUtils.htmlEncode(instance.uri), TextUtils.htmlEncode(emailAddr.substring(emailAddr.lastIndexOf('@')+1)));
SpannableStringBuilder ssb=new SpannableStringBuilder();
Jsoup.parseBodyFragment(s).body().traverse(new NodeVisitor(){
private int spanStart;
@Override
public void head(Node node, int depth){
if(node instanceof TextNode tn){
ssb.append(tn.text());
}else if(node instanceof Element){
spanStart=ssb.length();
}
}
@Override
public void tail(Node node, int depth){
if(node instanceof Element){
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.setSpan(new TypefaceSpan("sans-serif-medium"), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
});
yield ssb;
}
default -> error.description;
};
default -> error.description;
};
}
private EditText getFieldByName(String name){ private EditText getFieldByName(String name){
return switch(name){ return switch(name){
case "email" -> email; case "email" -> email;
@@ -379,11 +323,6 @@ public class SignupFragment extends ToolbarFragment{
} }
} }
private void onGoBackLinkClick(LinkSpan span){
setResult(false, null);
Nav.finish(this);
}
private class ErrorClearingListener implements TextWatcher{ private class ErrorClearingListener implements TextWatcher{
public final EditText editText; public final EditText editText;

View File

@@ -21,7 +21,6 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.events.FinishReportFragmentsEvent; import org.joinmastodon.android.events.FinishReportFragmentsEvent;
import org.joinmastodon.android.fragments.StatusListFragment; import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper; import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
@@ -90,7 +89,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if (getActivity() == null) return;
onDataLoaded(result, !result.isEmpty()); onDataLoaded(result, !result.isEmpty());
} }
}) })
@@ -239,7 +237,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, Filter.FilterContext.HOME); List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
for(StatusDisplayItem item:items){ for(StatusDisplayItem item:items){
if(item instanceof ImageStatusDisplayItem isdi){ if(item instanceof ImageStatusDisplayItem isdi){
isdi.horizontalInset=V.dp(40+32); isdi.horizontalInset=V.dp(40+32);

View File

@@ -9,7 +9,6 @@ import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import com.squareup.otto.Subscribe; import com.squareup.otto.Subscribe;
@@ -29,17 +28,15 @@ import java.util.ArrayList;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.fragments.ToolbarFragment;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ReportCommentFragment extends MastodonToolbarFragment{ public class ReportCommentFragment extends MastodonToolbarFragment{
private String accountID; private String accountID;
private Account reportAccount; private Account reportAccount;
private Button btn; private Button btn;
private View buttonBar, forwardReportItem; private View buttonBar;
private TextView forwardReportText;
private Switch forwardReportSwitch;
private EditText commentEdit; private EditText commentEdit;
private boolean forwardReport;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
@@ -80,17 +77,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
view.findViewById(R.id.btn_back).setOnClickListener(this::onButtonClick); view.findViewById(R.id.btn_back).setOnClickListener(this::onButtonClick);
buttonBar=view.findViewById(R.id.button_bar); buttonBar=view.findViewById(R.id.button_bar);
commentEdit=view.findViewById(R.id.text); commentEdit=view.findViewById(R.id.text);
forwardReportSwitch = view.findViewById(R.id.forward_report_switch);
forwardReportItem = view.findViewById(R.id.forward_report);
forwardReportText = view.findViewById(R.id.forward_report_text);
String domain = reportAccount.getDomain();
if (domain == null) {
forwardReportItem.setVisibility(View.GONE);
} else {
forwardReportItem.setOnClickListener(this::onForwardReportClick);
forwardReportText.setText(getActivity().getString(R.string.sk_forward_report_to, domain));
forwardReportSwitch.setChecked(forwardReport = true);
}
return view; return view;
} }
@@ -115,7 +102,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
ReportReason reason=ReportReason.valueOf(getArguments().getString("reason")); ReportReason reason=ReportReason.valueOf(getArguments().getString("reason"));
ArrayList<String> statusIDs=getArguments().getStringArrayList("statusIDs"); ArrayList<String> statusIDs=getArguments().getStringArrayList("statusIDs");
ArrayList<String> ruleIDs=getArguments().getStringArrayList("ruleIDs"); ArrayList<String> ruleIDs=getArguments().getStringArrayList("ruleIDs");
new SendReport(reportAccount.id, reason, statusIDs, ruleIDs, v.getId()==R.id.btn_back ? null : commentEdit.getText().toString(), forwardReport) new SendReport(reportAccount.id, reason, statusIDs, ruleIDs, v.getId()==R.id.btn_back ? null : commentEdit.getText().toString(), true)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(Object result){ public void onSuccess(Object result){
@@ -136,11 +123,6 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
.exec(accountID); .exec(accountID);
} }
private void onForwardReportClick(View v) {
forwardReport = !forwardReport;
forwardReportSwitch.setChecked(forwardReport);
}
@Subscribe @Subscribe
public void onFinishReportFragments(FinishReportFragmentsEvent ev){ public void onFinishReportFragments(FinishReportFragmentsEvent ev){
if(ev.reportAccountID.equals(reportAccount.id)) if(ev.reportAccountID.equals(reportAccount.id))

View File

@@ -133,14 +133,6 @@ public class Account extends BaseModel{
*/ */
public Instant muteExpiresAt; public Instant muteExpiresAt;
public List<Role> roles;
@Parcel
public static class Role {
public String name;
/** #rrggbb */
public String color;
}
@Override @Override
public void postprocess() throws ObjectValidationException{ public void postprocess() throws ObjectValidationException{

View File

@@ -42,9 +42,17 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
} }
public Status toStatus() { public Status toStatus() {
Status s = Status.ofFake(id, content, publishedAt); Status s = new Status();
s.id = id;
s.mediaAttachments = List.of();
s.createdAt = startsAt != null ? startsAt : publishedAt; s.createdAt = startsAt != null ? startsAt : publishedAt;
if (updatedAt != null) s.editedAt = updatedAt; if (updatedAt != null) s.editedAt = updatedAt;
s.content = s.text = content;
s.spoilerText = "";
s.visibility = StatusPrivacy.PUBLIC;
s.mentions = List.of();
s.tags = List.of();
s.emojis = List.of();
return s; return s;
} }

View File

@@ -47,26 +47,26 @@ public class Attachment extends BaseModel{
public int getWidth(){ public int getWidth(){
if(meta==null) if(meta==null)
return 1920; return 0;
if(meta.width>0) if(meta.width>0)
return meta.width; return meta.width;
if(meta.original!=null && meta.original.width>0) if(meta.original!=null && meta.original.width>0)
return meta.original.width; return meta.original.width;
if(meta.small!=null && meta.small.width>0) if(meta.small!=null && meta.small.width>0)
return meta.small.width; return meta.small.width;
return 1920; return 0;
} }
public int getHeight(){ public int getHeight(){
if(meta==null) if(meta==null)
return 1080; return 0;
if(meta.height>0) if(meta.height>0)
return meta.height; return meta.height;
if(meta.original!=null && meta.original.height>0) if(meta.original!=null && meta.original.height>0)
return meta.original.height; return meta.original.height;
if(meta.small!=null && meta.small.height>0) if(meta.small!=null && meta.small.height>0)
return meta.small.height; return meta.small.height;
return 1080; return 0;
} }
public double getDuration(){ public double getDuration(){

View File

@@ -18,8 +18,6 @@ public class Filter extends BaseModel{
@RequiredField @RequiredField
public String id; public String id;
@RequiredField @RequiredField
public String title;
@RequiredField
public String phrase; public String phrase;
public transient EnumSet<FilterContext> context=EnumSet.noneOf(FilterContext.class); public transient EnumSet<FilterContext> context=EnumSet.noneOf(FilterContext.class);
public Instant expiresAt; public Instant expiresAt;
@@ -52,7 +50,6 @@ public class Filter extends BaseModel{
else else
pattern=Pattern.compile(Pattern.quote(phrase), Pattern.CASE_INSENSITIVE); pattern=Pattern.compile(Pattern.quote(phrase), Pattern.CASE_INSENSITIVE);
} }
if (title == null) title = phrase;
return pattern.matcher(text).find(); return pattern.matcher(text).find();
} }
@@ -64,7 +61,6 @@ public class Filter extends BaseModel{
public String toString(){ public String toString(){
return "Filter{"+ return "Filter{"+
"id='"+id+'\''+ "id='"+id+'\''+
", title='"+title+'\''+
", phrase='"+phrase+'\''+ ", phrase='"+phrase+'\''+
", context="+context+ ", context="+context+
", expiresAt="+expiresAt+ ", expiresAt="+expiresAt+
@@ -81,9 +77,7 @@ public class Filter extends BaseModel{
@SerializedName("public") @SerializedName("public")
PUBLIC, PUBLIC,
@SerializedName("thread") @SerializedName("thread")
THREAD, THREAD
@SerializedName("account")
ACCOUNT
} }
public enum FilterAction{ public enum FilterAction{

View File

@@ -45,7 +45,7 @@ public class Instance extends BaseModel{
@RequiredField @RequiredField
public String version; public String version;
/** /**
* Primary languages of the website and its staff. * Primary langauges of the website and its staff.
*/ */
// @RequiredField // @RequiredField
public List<String> languages; public List<String> languages;
@@ -84,8 +84,6 @@ public class Instance extends BaseModel{
public V2 v2; public V2 v2;
public Pleroma pleroma;
@Override @Override
public void postprocess() throws ObjectValidationException{ public void postprocess() throws ObjectValidationException{
super.postprocess(); super.postprocess();
@@ -195,9 +193,4 @@ public class Instance extends BaseModel{
public boolean enabled; public boolean enabled;
} }
} }
@Parcel
public static class Pleroma extends BaseModel {
// metadata etc
}
} }

View File

@@ -1,7 +1,5 @@
package org.joinmastodon.android.model; package org.joinmastodon.android.model;
import androidx.annotation.NonNull;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.joinmastodon.android.api.RequiredField; import org.joinmastodon.android.api.RequiredField;
@@ -13,9 +11,9 @@ public class ListTimeline extends BaseModel {
public String id; public String id;
@RequiredField @RequiredField
public String title; public String title;
@RequiredField
public RepliesPolicy repliesPolicy; public RepliesPolicy repliesPolicy;
@NonNull
@Override @Override
public String toString() { public String toString() {
return "List{" + return "List{" +

View File

@@ -18,8 +18,8 @@ public class Notification extends BaseModel implements DisplayItemsParent{
public Instant createdAt; public Instant createdAt;
@RequiredField @RequiredField
public Account account; public Account account;
public Status status; public Status status;
public Report report;
@Override @Override
public void postprocess() throws ObjectValidationException{ public void postprocess() throws ObjectValidationException{
@@ -48,19 +48,6 @@ public class Notification extends BaseModel implements DisplayItemsParent{
@SerializedName("poll") @SerializedName("poll")
POLL, POLL,
@SerializedName("status") @SerializedName("status")
STATUS, STATUS
@SerializedName("update")
UPDATE,
@SerializedName("admin.sign_up")
SIGN_UP,
@SerializedName("admin.report")
REPORT
}
@Parcel
public static class Report {
public String id;
public String comment;
public Account targetAccount;
} }
} }

View File

@@ -1,33 +0,0 @@
package org.joinmastodon.android.model;
import android.text.SpannableStringBuilder;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import java.util.Collections;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
public class ParsedAccount{
public Account account;
public CharSequence parsedName, parsedBio;
public CustomEmojiHelper emojiHelper;
public ImageLoaderRequest avatarRequest;
public ParsedAccount(Account account, String accountID){
this.account=account;
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
emojiHelper=new CustomEmojiHelper();
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
ssb.append(parsedBio);
emojiHelper.setText(ssb);
avatarRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(40), V.dp(40));
}
}

View File

@@ -45,13 +45,7 @@ public class PushNotification extends BaseModel{
@SerializedName("poll") @SerializedName("poll")
POLL(R.string.notification_type_poll), POLL(R.string.notification_type_poll),
@SerializedName("status") @SerializedName("status")
STATUS(R.string.sk_notification_type_status), STATUS(R.string.sk_notification_type_status);
@SerializedName("update")
UPDATE(R.string.sk_notification_type_update),
@SerializedName("admin.sign_up")
SIGN_UP(R.string.sk_sign_ups),
@SerializedName("admin.report")
REPORT(R.string.sk_new_reports);
@StringRes @StringRes
public final int localizedName; public final int localizedName;

View File

@@ -23,7 +23,6 @@ public class PushSubscription extends BaseModel implements Cloneable{
", endpoint='"+endpoint+'\''+ ", endpoint='"+endpoint+'\''+
", alerts="+alerts+ ", alerts="+alerts+
", serverKey='"+serverKey+'\''+ ", serverKey='"+serverKey+'\''+
", policy="+policy+
'}'; '}';
} }
@@ -45,19 +44,10 @@ public class PushSubscription extends BaseModel implements Cloneable{
public boolean mention; public boolean mention;
public boolean poll; public boolean poll;
public boolean status; public boolean status;
public boolean update;
// set to true here because i didn't add any items for those to the settings
// (so i don't have to determine whether the user is an admin to show the items or not, and
// admins can still disable those through the android notifications settings)
@SerializedName("admin.sign_up")
public boolean adminSignUp = true;
@SerializedName("admin.report")
public boolean adminReport = true;
public static Alerts ofAll(){ public static Alerts ofAll(){
Alerts alerts=new Alerts(); Alerts alerts=new Alerts();
alerts.follow=alerts.favourite=alerts.reblog=alerts.mention=alerts.poll=alerts.status=alerts.update=true; alerts.follow=alerts.favourite=alerts.reblog=alerts.mention=alerts.poll=alerts.status=true;
return alerts; return alerts;
} }
@@ -70,9 +60,6 @@ public class PushSubscription extends BaseModel implements Cloneable{
", mention="+mention+ ", mention="+mention+
", poll="+poll+ ", poll="+poll+
", status="+status+ ", status="+status+
", update="+update+
", adminSignUp="+adminSignUp+
", adminReport="+adminReport+
'}'; '}';
} }

View File

@@ -18,10 +18,6 @@ public class Relationship extends BaseModel{
public boolean blockedBy; public boolean blockedBy;
public String note; public String note;
public boolean canFollow(){
return !(following || blocking || blockedBy || domainBlocking);
}
@Override @Override
public String toString(){ public String toString(){
return "Relationship{"+ return "Relationship{"+

View File

@@ -62,13 +62,19 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
} }
public Status toStatus() { public Status toStatus() {
Status s = Status.ofFake(id, params.text, scheduledAt); Status s = new Status();
s.id = id;
s.mediaAttachments = mediaAttachments; s.mediaAttachments = mediaAttachments;
s.createdAt = scheduledAt;
s.inReplyToId = params.inReplyToId > 0 ? "" + params.inReplyToId : null; s.inReplyToId = params.inReplyToId > 0 ? "" + params.inReplyToId : null;
s.content = s.text = params.text;
s.spoilerText = params.spoilerText; s.spoilerText = params.spoilerText;
s.visibility = params.visibility; s.visibility = params.visibility;
s.language = params.language; s.language = params.language;
s.sensitive = params.sensitive; s.sensitive = params.sensitive;
s.mentions = List.of();
s.tags = List.of();
s.emojis = List.of();
if (params.poll != null) s.poll = params.poll.toPoll(); if (params.poll != null) s.poll = params.poll.toPoll();
return s; return s;
} }

View File

@@ -50,7 +50,6 @@ public class Status extends BaseModel implements DisplayItemsParent{
public Card card; public Card card;
public String language; public String language;
public String text; public String text;
public boolean localOnly;
public boolean favourited; public boolean favourited;
public boolean reblogged; public boolean reblogged;
@@ -58,9 +57,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
public boolean bookmarked; public boolean bookmarked;
public boolean pinned; public boolean pinned;
public transient boolean filterRevealed;
public transient boolean spoilerRevealed; public transient boolean spoilerRevealed;
public transient boolean textExpanded, textExpandable;
public transient boolean hasGapAfter; public transient boolean hasGapAfter;
private transient String strippedText; private transient String strippedText;
@@ -86,7 +83,6 @@ public class Status extends BaseModel implements DisplayItemsParent{
reblog.postprocess(); reblog.postprocess();
spoilerRevealed=GlobalUserPreferences.alwaysExpandContentWarnings || !sensitive; spoilerRevealed=GlobalUserPreferences.alwaysExpandContentWarnings || !sensitive;
if (visibility.equals(StatusPrivacy.LOCAL)) localOnly = true;
} }
@Override @Override
@@ -148,19 +144,4 @@ public class Status extends BaseModel implements DisplayItemsParent{
strippedText=HtmlParser.strip(content); strippedText=HtmlParser.strip(content);
return strippedText; return strippedText;
} }
public static Status ofFake(String id, String text, Instant createdAt) {
Status s = new Status();
s.id = id;
s.mediaAttachments = List.of();
s.createdAt = createdAt;
s.content = s.text = text;
s.spoilerText = "";
s.visibility = StatusPrivacy.PUBLIC;
s.mentions = List.of();
s.tags = List.of();
s.emojis = List.of();
s.filtered = List.of();
return s;
}
} }

View File

@@ -10,9 +10,7 @@ public enum StatusPrivacy{
@SerializedName("private") @SerializedName("private")
PRIVATE(2), PRIVATE(2),
@SerializedName("direct") @SerializedName("direct")
DIRECT(3), DIRECT(3);
@SerializedName("local")
LOCAL(4); // akkoma
private int privacy; private int privacy;

View File

@@ -1,252 +0,0 @@
package org.joinmastodon.android.model;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTimelineFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment;
import org.joinmastodon.android.fragments.NotificationsListFragment;
import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment;
import org.joinmastodon.android.fragments.discover.LocalTimelineFragment;
import java.util.List;
import java.util.Objects;
public class TimelineDefinition {
private TimelineType type;
private String title;
private @Nullable Icon icon;
private @Nullable String listId;
private @Nullable String listTitle;
private @Nullable String hashtagName;
public static TimelineDefinition ofList(String listId, String listTitle) {
TimelineDefinition def = new TimelineDefinition(TimelineType.LIST);
def.listId = listId;
def.listTitle = listTitle;
return def;
}
public static TimelineDefinition ofList(ListTimeline list) {
return ofList(list.id, list.title);
}
public static TimelineDefinition ofHashtag(String hashtag) {
TimelineDefinition def = new TimelineDefinition(TimelineType.HASHTAG);
def.hashtagName = hashtag;
return def;
}
public static TimelineDefinition ofHashtag(Hashtag hashtag) {
return ofHashtag(hashtag.name);
}
@SuppressWarnings("unused")
public TimelineDefinition() {}
public TimelineDefinition(TimelineType type) {
this.type = type;
}
public String getTitle(Context ctx) {
return title != null ? title : getDefaultTitle(ctx);
}
public String getCustomTitle() {
return title;
}
public void setTitle(String title) {
this.title = title == null || title.isBlank() ? null : title;
}
public String getDefaultTitle(Context ctx) {
return switch (type) {
case HOME -> ctx.getString(R.string.sk_timeline_home);
case LOCAL -> ctx.getString(R.string.sk_timeline_local);
case FEDERATED -> ctx.getString(R.string.sk_timeline_federated);
case POST_NOTIFICATIONS -> ctx.getString(R.string.sk_timeline_posts);
case LIST -> listTitle;
case HASHTAG -> hashtagName;
};
}
public Icon getDefaultIcon() {
return switch (type) {
case HOME -> Icon.HOME;
case LOCAL -> Icon.LOCAL;
case FEDERATED -> Icon.FEDERATED;
case POST_NOTIFICATIONS -> Icon.POST_NOTIFICATIONS;
case LIST -> Icon.LIST;
case HASHTAG -> Icon.HASHTAG;
};
}
public Fragment getFragment() {
return switch (type) {
case HOME -> new HomeTimelineFragment();
case LOCAL -> new LocalTimelineFragment();
case FEDERATED -> new FederatedTimelineFragment();
case LIST -> new ListTimelineFragment();
case HASHTAG -> new HashtagTimelineFragment();
case POST_NOTIFICATIONS -> new NotificationsListFragment();
};
}
@Nullable
public Icon getIcon() {
return icon == null ? getDefaultIcon() : icon;
}
public void setIcon(@Nullable Icon icon) {
this.icon = icon;
}
public TimelineType getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TimelineDefinition that = (TimelineDefinition) o;
if (type != that.type) return false;
if (type == TimelineType.LIST) return Objects.equals(listId, that.listId);
if (type == TimelineType.HASHTAG) return Objects.equals(hashtagName.toLowerCase(), that.hashtagName.toLowerCase());
return true;
}
@Override
public int hashCode() {
int result = type.ordinal();
result = 31 * result + (listId != null ? listId.hashCode() : 0);
result = 31 * result + (hashtagName.toLowerCase() != null ? hashtagName.toLowerCase().hashCode() : 0);
return result;
}
public TimelineDefinition copy() {
TimelineDefinition def = new TimelineDefinition(type);
def.title = title;
def.listId = listId;
def.listTitle = listTitle;
def.hashtagName = hashtagName;
def.icon = icon == null ? null : Icon.values()[icon.ordinal()];
return def;
}
public Bundle populateArguments(Bundle args) {
if (type == TimelineType.LIST) {
args.putString("listTitle", title);
args.putString("listID", listId);
} else if (type == TimelineType.HASHTAG) {
args.putString("hashtag", hashtagName);
}
return args;
}
public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG }
public enum Icon {
HEART(R.drawable.ic_fluent_heart_24_regular, R.string.sk_icon_heart),
STAR(R.drawable.ic_fluent_star_24_regular, R.string.sk_icon_star),
PEOPLE(R.drawable.ic_fluent_people_24_regular, R.string.sk_icon_people),
CITY(R.drawable.ic_fluent_city_24_regular, R.string.sk_icon_city),
IMAGE(R.drawable.ic_fluent_image_24_regular, R.string.sk_icon_image),
NEWS(R.drawable.ic_fluent_news_24_regular, R.string.sk_icon_news),
COLOR_PALETTE(R.drawable.ic_fluent_color_24_regular, R.string.sk_icon_color_palette),
CAT(R.drawable.ic_fluent_animal_cat_24_regular, R.string.sk_icon_cat),
DOG(R.drawable.ic_fluent_animal_dog_24_regular, R.string.sk_icon_dog),
RABBIT(R.drawable.ic_fluent_animal_rabbit_24_regular, R.string.sk_icon_rabbit),
TURTLE(R.drawable.ic_fluent_animal_turtle_24_regular, R.string.sk_icon_turtle),
ACADEMIC_CAP(R.drawable.ic_fluent_hat_graduation_24_regular, R.string.sk_icon_academic_cap),
BOT(R.drawable.ic_fluent_bot_24_regular, R.string.sk_icon_bot),
IMPORTANT(R.drawable.ic_fluent_important_24_regular, R.string.sk_icon_important),
PIN(R.drawable.ic_fluent_pin_24_regular, R.string.sk_icon_pin),
SHIELD(R.drawable.ic_fluent_shield_24_regular, R.string.sk_icon_shield),
CHAT(R.drawable.ic_fluent_chat_multiple_24_regular, R.string.sk_icon_chat),
TAG(R.drawable.ic_fluent_tag_24_regular, R.string.sk_icon_tag),
TRAIN(R.drawable.ic_fluent_vehicle_subway_24_regular, R.string.sk_icon_train),
BICYCLE(R.drawable.ic_fluent_vehicle_bicycle_24_regular, R.string.sk_icon_bicycle),
MAP(R.drawable.ic_fluent_map_24_regular, R.string.sk_icon_map),
BACKPACK(R.drawable.ic_fluent_backpack_24_regular, R.string.sk_icon_backpack),
BRIEFCASE(R.drawable.ic_fluent_briefcase_24_regular, R.string.sk_icon_briefcase),
BOOK(R.drawable.ic_fluent_book_open_24_regular, R.string.sk_icon_book),
LANGUAGE(R.drawable.ic_fluent_local_language_24_regular, R.string.sk_icon_language),
WEATHER(R.drawable.ic_fluent_weather_rain_showers_day_24_regular, R.string.sk_icon_weather),
APERTURE(R.drawable.ic_fluent_scan_24_regular, R.string.sk_icon_aperture),
MUSIC(R.drawable.ic_fluent_music_note_2_24_regular, R.string.sk_icon_music),
LOCATION(R.drawable.ic_fluent_location_24_regular, R.string.sk_icon_location),
GLOBE(R.drawable.ic_fluent_globe_24_regular, R.string.sk_icon_globe),
MEGAPHONE(R.drawable.ic_fluent_megaphone_loud_24_regular, R.string.sk_icon_megaphone),
MICROPHONE(R.drawable.ic_fluent_mic_24_regular, R.string.sk_icon_microphone),
MICROSCOPE(R.drawable.ic_fluent_microscope_24_regular, R.string.sk_icon_microscope),
STETHOSCOPE(R.drawable.ic_fluent_stethoscope_24_regular, R.string.sk_icon_stethoscope),
KEYBOARD(R.drawable.ic_fluent_midi_24_regular, R.string.sk_icon_keyboard),
COFFEE(R.drawable.ic_fluent_drink_coffee_24_regular, R.string.sk_icon_coffee),
CLAPPER_BOARD(R.drawable.ic_fluent_movies_and_tv_24_regular, R.string.sk_icon_clapper_board),
LAUGH(R.drawable.ic_fluent_emoji_laugh_24_regular, R.string.sk_icon_laugh),
BALLOON(R.drawable.ic_fluent_balloon_24_regular, R.string.sk_icon_balloon),
PI(R.drawable.ic_fluent_pi_24_regular, R.string.sk_icon_pi),
MATH_FORMULA(R.drawable.ic_fluent_math_formula_24_regular, R.string.sk_icon_math_formula),
GAMES(R.drawable.ic_fluent_games_24_regular, R.string.sk_icon_games),
CODE(R.drawable.ic_fluent_code_24_regular, R.string.sk_icon_code),
BUG(R.drawable.ic_fluent_bug_24_regular, R.string.sk_icon_bug),
LIGHT_BULB(R.drawable.ic_fluent_lightbulb_24_regular, R.string.sk_icon_light_bulb),
FIRE(R.drawable.ic_fluent_fire_24_regular, R.string.sk_icon_fire),
LEAVES(R.drawable.ic_fluent_leaf_three_24_regular, R.string.sk_icon_leaves),
SPORT(R.drawable.ic_fluent_sport_24_regular, R.string.sk_icon_sport),
HEALTH(R.drawable.ic_fluent_heart_pulse_24_regular, R.string.sk_icon_health),
PIZZA(R.drawable.ic_fluent_food_pizza_24_regular, R.string.sk_icon_pizza),
GAVEL(R.drawable.ic_fluent_gavel_24_regular, R.string.sk_icon_gavel),
GAUGE(R.drawable.ic_fluent_gauge_24_regular, R.string.sk_icon_gauge),
HEADPHONES(R.drawable.ic_fluent_headphones_sound_wave_24_regular, R.string.sk_icon_headphones),
HUMAN(R.drawable.ic_fluent_accessibility_24_regular, R.string.sk_icon_human),
HOME(R.drawable.ic_fluent_home_24_regular, R.string.sk_timeline_home, true),
LOCAL(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
FEDERATED(R.drawable.ic_fluent_earth_24_regular, R.string.sk_timeline_federated, true),
POST_NOTIFICATIONS(R.drawable.ic_fluent_chat_24_regular, R.string.sk_timeline_posts, true),
LIST(R.drawable.ic_fluent_people_24_regular, R.string.sk_list, true),
HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true);
public final int iconRes, nameRes;
public final boolean hidden;
Icon(@DrawableRes int iconRes, @StringRes int nameRes) {
this(iconRes, nameRes, false);
}
Icon(@DrawableRes int iconRes, @StringRes int nameRes, boolean hidden) {
this.iconRes = iconRes;
this.nameRes = nameRes;
this.hidden = hidden;
}
}
public static final TimelineDefinition HOME_TIMELINE = new TimelineDefinition(TimelineType.HOME);
public static final TimelineDefinition LOCAL_TIMELINE = new TimelineDefinition(TimelineType.LOCAL);
public static final TimelineDefinition FEDERATED_TIMELINE = new TimelineDefinition(TimelineType.FEDERATED);
public static final TimelineDefinition POSTS_TIMELINE = new TimelineDefinition(TimelineType.POST_NOTIFICATIONS);
public static final List<TimelineDefinition> DEFAULT_TIMELINES = BuildConfig.BUILD_TYPE.equals("playRelease")
? List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy())
: List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy(), FEDERATED_TIMELINE.copy());
public static final List<TimelineDefinition> ALL_TIMELINES = List.of(
HOME_TIMELINE.copy(),
LOCAL_TIMELINE.copy(),
FEDERATED_TIMELINE.copy(),
POSTS_TIMELINE.copy()
);
}

View File

@@ -99,7 +99,7 @@ public class CustomEmojiPopupKeyboard extends PopupKeyboard{
Optional<Emoji> element = allAvailableEmojis.stream().filter(e -> e.shortcode.equals(emojiCode)).findFirst(); Optional<Emoji> element = allAvailableEmojis.stream().filter(e -> e.shortcode.equals(emojiCode)).findFirst();
element.ifPresent(recentEmojiList::add); element.ifPresent(recentEmojiList::add);
} }
emojis.add(0, new EmojiCategory(activity.getString(R.string.mo_emoji_recent), recentEmojiList)); emojis.add(0, new EmojiCategory(activity.getString(R.string.sk_emoji_recent), recentEmojiList));
} }
for(EmojiCategory category:emojis) for(EmojiCategory category:emojis)

View File

@@ -105,7 +105,6 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{
}else{ }else{
seekBar.setEnabled(false); seekBar.setEnabled(false);
} }
} }
private void onPlayPauseClick(View v){ private void onPlayPauseClick(View v){

View File

@@ -2,6 +2,7 @@ package org.joinmastodon.android.ui.displayitems;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
@@ -96,11 +97,15 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
visibility.setImageResource(switch (s.visibility) { visibility.setImageResource(switch (s.visibility) {
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular; case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular; case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled; case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
case DIRECT -> R.drawable.ic_fluent_mention_20_regular; case DIRECT -> R.drawable.ic_fluent_mention_24_regular;
case LOCAL -> R.drawable.ic_fluent_eye_20_regular;
}); });
visibility.setContentDescription(UiUtils.getVisibilityText(s));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
visibility.setTooltipText(visibility.getContentDescription());
}
} }
@Override @Override

View File

@@ -151,9 +151,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
boost.setSelected(item.status.reblogged); boost.setSelected(item.status.reblogged);
favorite.setSelected(item.status.favourited); favorite.setSelected(item.status.favourited);
bookmark.setSelected(item.status.bookmarked); bookmark.setSelected(item.status.bookmarked);
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id))); || (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
} }
private void bindButton(TextView btn, long count){ private void bindButton(TextView btn, long count){
@@ -254,8 +253,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
Drawable checkMark = ctx.getDrawable(R.drawable.ic_fluent_checkmark_circle_20_regular); Drawable checkMark = ctx.getDrawable(R.drawable.ic_fluent_checkmark_circle_20_regular);
Drawable publicDrawable = ctx.getDrawable(R.drawable.ic_fluent_earth_24_regular); Drawable publicDrawable = ctx.getDrawable(R.drawable.ic_fluent_earth_24_regular);
Drawable unlistedDrawable = ctx.getDrawable(R.drawable.ic_fluent_lock_open_24_regular); Drawable unlistedDrawable = ctx.getDrawable(R.drawable.ic_fluent_people_community_24_regular);
Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_lock_closed_24_regular); Drawable followersDrawable = ctx.getDrawable(R.drawable.ic_fluent_people_checkmark_24_regular);
StatusPrivacy defaultVisibility = session.preferences != null ? session.preferences.postingDefaultVisibility : null; StatusPrivacy defaultVisibility = session.preferences != null ? session.preferences.postingDefaultVisibility : null;
// e.g. post visibility is unlisted, but default is public // e.g. post visibility is unlisted, but default is public

View File

@@ -23,8 +23,6 @@ import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.StringRes;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
@@ -35,7 +33,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.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ComposeFragment; import org.joinmastodon.android.fragments.ComposeFragment;
import org.joinmastodon.android.fragments.ListTimelinesFragment;
import org.joinmastodon.android.fragments.NotificationsListFragment; import org.joinmastodon.android.fragments.NotificationsListFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.fragments.ThreadFragment;
@@ -47,7 +44,6 @@ import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
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.ui.text.HtmlParser; 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;
@@ -57,7 +53,6 @@ import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle; import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -142,8 +137,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView name, username, timestamp, extraText, separator; private final TextView name, username, timestamp, extraText, separator;
private final View collapseBtn; private final ImageView avatar, more, visibility, deleteNotification, unreadIndicator, botIcon;
private final ImageView avatar, more, visibility, deleteNotification, unreadIndicator, collapseBtnIcon,botIcon;
private final PopupMenu optionsMenu; private final PopupMenu optionsMenu;
private Relationship relationship; private Relationship relationship;
private APIRequest<?> currentRelationshipRequest; private APIRequest<?> currentRelationshipRequest;
@@ -166,8 +160,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
visibility=findViewById(R.id.visibility); visibility=findViewById(R.id.visibility);
deleteNotification=findViewById(R.id.delete_notification); deleteNotification=findViewById(R.id.delete_notification);
unreadIndicator=findViewById(R.id.unread_indicator); unreadIndicator=findViewById(R.id.unread_indicator);
collapseBtn=findViewById(R.id.collapse_btn);
collapseBtnIcon=findViewById(R.id.collapse_btn_icon);
botIcon=findViewById(R.id.bot_icon); botIcon=findViewById(R.id.bot_icon);
extraText=findViewById(R.id.extra_text); extraText=findViewById(R.id.extra_text);
avatar.setOnClickListener(this::onAvaClick); avatar.setOnClickListener(this::onAvaClick);
@@ -180,12 +172,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
fragment.removeNotification(item.notification); fragment.removeNotification(item.notification);
} }
})); }));
collapseBtn.setOnClickListener(l -> item.parentFragment.onToggleExpanded(item.status, getItemID()));
optionsMenu=new PopupMenu(activity, more); optionsMenu=new PopupMenu(activity, more);
optionsMenu.inflate(R.menu.post); optionsMenu.inflate(R.menu.post);
optionsMenu.setOnMenuItemClickListener(menuItem->{ optionsMenu.setOnMenuItemClickListener(menuItem->{
Account account=item.user; Account account=item.user;
int id=menuItem.getItemId(); int id=menuItem.getItemId();
@@ -194,8 +183,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
final Bundle args=new Bundle(); final Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID()); args.putString("account", item.parentFragment.getAccountID());
args.putParcelable("editStatus", Parcels.wrap(item.status)); args.putParcelable("editStatus", Parcels.wrap(item.status));
boolean redraft = id==R.id.delete_and_redraft; if (id==R.id.delete_and_redraft) {
if (redraft) {
args.putBoolean("redraftStatus", true); args.putBoolean("redraftStatus", true);
if (item.parentFragment instanceof ThreadFragment thread && !thread.isItemEnabled(item.status.id)) { if (item.parentFragment instanceof ThreadFragment thread && !thread.isItemEnabled(item.status.id)) {
// ("enabled" = clickable; opened status is not clickable) // ("enabled" = clickable; opened status is not clickable)
@@ -203,7 +191,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
args.putBoolean("navigateToStatus", true); args.putBoolean("navigateToStatus", true);
} }
} }
if(!redraft && TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){ if(TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}else if(item.scheduledStatus!=null){ }else if(item.scheduledStatus!=null){
args.putString("sourceText", item.status.text); args.putString("sourceText", item.status.text);
@@ -218,7 +206,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public void onSuccess(GetStatusSourceText.Response result){ public void onSuccess(GetStatusSourceText.Response result){
args.putString("sourceText", result.text); args.putString("sourceText", result.text);
args.putString("sourceSpoiler", result.spoilerText); args.putString("sourceSpoiler", result.spoilerText);
if (redraft) { if (id==R.id.delete_and_redraft) {
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{ UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}, true); }, true);
@@ -276,36 +264,12 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{}); UiUtils.confirmToggleBlockDomain(activity, item.parentFragment.getAccountID(), account.getDomain(), relationship!=null && relationship.domainBlocking, ()->{});
}else if(id==R.id.bookmark){ }else if(id==R.id.bookmark){
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked); AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
}else if(id==R.id.manage_user_lists){
final Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putString("profileAccount", account.id);
args.putString("profileDisplayUsername", account.getDisplayUsername());
Nav.go(item.parentFragment.getActivity(), ListTimelinesFragment.class, args);
}
if(!item.status.filterRevealed){
this.itemView.setVisibility(View.GONE);
ViewGroup.LayoutParams params = this.itemView.getLayoutParams();
params.height = 0;
params.width = 0;
this.itemView.setLayoutParams(params);
// item.parentFragment.notifyItemsChanged(this.getAbsoluteAdapterPosition());
} }
return true; return true;
}); });
UiUtils.enablePopupMenuIcons(activity, optionsMenu); UiUtils.enablePopupMenuIcons(activity, optionsMenu);
} }
// public void setFilteredShown(){
// this.itemView.setVisibility(View.VISIBLE);
// params = this.itemView.getLayoutParams();
// params.height = 0;
// params.width = 0;
// this.itemView.setLayoutParams(params);
// }
private void populateAccountsMenu(Menu menu) { private void populateAccountsMenu(Menu menu) {
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts(); List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
sessions.stream().filter(s -> !s.getID().equals(item.accountID)).forEach(s -> { sessions.stream().filter(s -> !s.getID().equals(item.accountID)).forEach(s -> {
@@ -325,6 +289,20 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
botIcon.setColorFilter(username.getCurrentTextColor()); botIcon.setColorFilter(username.getCurrentTextColor());
separator.setVisibility(View.VISIBLE); separator.setVisibility(View.VISIBLE);
// if(item.user.bot){
// SpannableStringBuilder ssb = new SpannableStringBuilder();
// ssb.append('@'+item.user.acct);
// ssb.append(" ");
// Drawable botIcon=username.getResources().getDrawable(R.drawable.ic_bot, itemView.getContext().getTheme()).mutate();
// botIcon.setBounds(0, 0, botIcon.getIntrinsicWidth(), botIcon.getIntrinsicHeight());
// botIcon.setTint(username.getCurrentTextColor());
// ssb.append(itemView.getContext().getString(R.string.manually_approves_followers), new ImageSpan(botIcon, ImageSpan.ALIGN_BASELINE), 0);
// username.setPaddingRelative(0,0,16,0);
// username.setText(ssb);
// }
// username.setCompoundDrawablesWithIntrinsicBounds(item.user.bot ? R.drawable.ic_fluent_bot_24_filled : 0, 0, 0, 0);
if (item.scheduledStatus!=null) if (item.scheduledStatus!=null)
if (item.scheduledStatus.scheduledAt.isAfter(CreateStatus.DRAFTS_AFTER_INSTANT)) { if (item.scheduledStatus.scheduledAt.isAfter(CreateStatus.DRAFTS_AFTER_INSTANT)) {
timestamp.setText(R.string.sk_draft); timestamp.setText(R.string.sk_draft);
@@ -332,7 +310,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault()); DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.getDefault());
timestamp.setText(item.scheduledStatus.scheduledAt.atZone(ZoneId.systemDefault()).format(formatter)); timestamp.setText(item.scheduledStatus.scheduledAt.atZone(ZoneId.systemDefault()).format(formatter));
} }
else if ((!item.inset || item.status==null || item.status.editedAt==null) && item.createdAt != null) else if ((item.status==null || item.status.editedAt==null) && item.createdAt != null)
timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt)); timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt));
else if (item.status != null && item.status.editedAt != null) else if (item.status != null && item.status.editedAt != null)
timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt))); timestamp.setText(item.parentFragment.getString(R.string.edited_timestamp, UiUtils.formatRelativeTimestamp(itemView.getContext(), item.status.editedAt)));
@@ -351,15 +329,12 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
} }
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0); itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
if(TextUtils.isEmpty(item.extraText)){ if(TextUtils.isEmpty(item.extraText)){
if (item.status != null) { extraText.setVisibility(View.GONE);
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), extraText, item.status.visibility, item.status.localOnly);
}
}else{ }else{
extraText.setVisibility(View.VISIBLE); extraText.setVisibility(View.VISIBLE);
extraText.setText(item.extraText); extraText.setText(item.extraText);
} }
more.setVisibility(item.inset || (item.notification != null && item.notification.report != null) more.setVisibility(item.inset ? View.GONE : View.VISIBLE);
? View.GONE : View.VISIBLE);
avatar.setClickable(!item.inset); avatar.setClickable(!item.inset);
avatar.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct)); avatar.setContentDescription(item.parentFragment.getString(R.string.avatar_description, item.user.acct));
if(currentRelationshipRequest!=null){ if(currentRelationshipRequest!=null){
@@ -387,7 +362,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public void onSuccess(Object o) { public void onSuccess(Object o) {
item.consumeReadAnnouncement.accept(item.announcement.id); item.consumeReadAnnouncement.accept(item.announcement.id);
item.announcement.read = true; item.announcement.read = true;
if (item.parentFragment.getActivity() == null) return;
rebind(); rebind();
} }
@@ -405,17 +379,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
more.setContentDescription(desc); more.setContentDescription(desc);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) more.setTooltipText(desc); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) more.setTooltipText(desc);
if (item.status == null || !item.status.textExpandable) {
collapseBtn.setVisibility(View.GONE);
} else {
String collapseText = item.parentFragment.getString(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
collapseBtn.setVisibility(item.status.textExpandable ? View.VISIBLE : View.GONE);
collapseBtn.setContentDescription(collapseText);
if (GlobalUserPreferences.reduceMotion) collapseBtnIcon.setScaleY(item.status.textExpanded ? -1 : 1);
else collapseBtnIcon.animate().scaleY(item.status.textExpanded ? -1 : 1).start();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) collapseBtn.setTooltipText(collapseText);
}
} }
@Override @Override
@@ -471,7 +434,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
} }
private void updateOptionsMenu(){ private void updateOptionsMenu(){
if (item.parentFragment.getActivity() == null) return;
if (item.announcement != null) return; if (item.announcement != null) return;
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1; boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
Menu menu=optionsMenu.getMenu(); Menu menu=optionsMenu.getMenu();
@@ -502,7 +464,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
MenuItem block=menu.findItem(R.id.block); MenuItem block=menu.findItem(R.id.block);
MenuItem report=menu.findItem(R.id.report); MenuItem report=menu.findItem(R.id.report);
MenuItem follow=menu.findItem(R.id.follow); MenuItem follow=menu.findItem(R.id.follow);
MenuItem manageUserLists = menu.findItem(R.id.manage_user_lists);
MenuItem bookmark=menu.findItem(R.id.bookmark); MenuItem bookmark=menu.findItem(R.id.bookmark);
bookmark.setVisible(false); bookmark.setVisible(false);
/* disabled in megalodon: add/remove bookmark is already available through status footer /* disabled in megalodon: add/remove bookmark is already available through status footer
@@ -519,7 +480,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
report.setVisible(false); report.setVisible(false);
follow.setVisible(false); follow.setVisible(false);
blockDomain.setVisible(false); blockDomain.setVisible(false);
manageUserLists.setVisible(false);
}else{ }else{
mute.setVisible(true); mute.setVisible(true);
block.setVisible(true); block.setVisible(true);
@@ -540,8 +500,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
boolean following = relationship!=null && relationship.following; boolean following = relationship!=null && relationship.following;
follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, account.getShortUsername())); follow.setTitle(item.parentFragment.getString(following ? R.string.unfollow_user : R.string.follow_user, account.getShortUsername()));
follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular); follow.setIcon(following ? R.drawable.ic_fluent_person_delete_24_regular : R.drawable.ic_fluent_person_add_24_regular);
manageUserLists.setVisible(relationship != null && relationship.following);
manageUserLists.setTitle(item.parentFragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow); UiUtils.insetPopupMenuIcon(item.parentFragment.getContext(), follow);
} }
} }

View File

@@ -1,21 +1,12 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity; import android.app.Activity;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
@@ -28,7 +19,6 @@ import org.joinmastodon.android.ui.views.ImageAttachmentFrameLayout;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
public abstract class ImageStatusDisplayItem extends StatusDisplayItem{ public abstract class ImageStatusDisplayItem extends StatusDisplayItem{
public final int index; public final int index;
@@ -66,35 +56,11 @@ public abstract class ImageStatusDisplayItem extends StatusDisplayItem{
private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable(); private BlurhashCrossfadeDrawable crossfadeDrawable=new BlurhashCrossfadeDrawable();
private boolean didClear; private boolean didClear;
private AnimatorSet currentAnim;
private final FrameLayout altTextWrapper;
private final TextView altTextButton;
private final ImageView noAltTextButton;
private final View altTextScroller;
private final ImageButton altTextClose;
private final TextView altText, noAltText;
private View altOrNoAltButton;
private boolean altTextShown;
public Holder(Activity activity, @LayoutRes int layout, ViewGroup parent){ public Holder(Activity activity, @LayoutRes int layout, ViewGroup parent){
super(activity, layout, parent); super(activity, layout, parent);
photo=findViewById(R.id.photo); photo=findViewById(R.id.photo);
photo.setOnClickListener(this::onViewClick); photo.setOnClickListener(this::onViewClick);
this.layout=(ImageAttachmentFrameLayout)itemView; this.layout=(ImageAttachmentFrameLayout)itemView;
altTextWrapper=findViewById(R.id.alt_text_wrapper);
altTextButton=findViewById(R.id.alt_button);
noAltTextButton=findViewById(R.id.no_alt_button);
altTextScroller=findViewById(R.id.alt_text_scroller);
altTextClose=findViewById(R.id.alt_text_close);
altText=findViewById(R.id.alt_text);
noAltText=findViewById(R.id.no_alt_text);
altTextButton.setOnClickListener(this::onShowHideClick);
noAltTextButton.setOnClickListener(this::onShowHideClick);
altTextClose.setOnClickListener(this::onShowHideClick);
// altTextScroller.setNestedScrollingEnabled(true);
} }
@Override @Override
@@ -107,111 +73,6 @@ public abstract class ImageStatusDisplayItem extends StatusDisplayItem{
photo.setImageDrawable(crossfadeDrawable); photo.setImageDrawable(crossfadeDrawable);
photo.setContentDescription(TextUtils.isEmpty(item.attachment.description) ? item.parentFragment.getString(R.string.media_no_description) : item.attachment.description); photo.setContentDescription(TextUtils.isEmpty(item.attachment.description) ? item.parentFragment.getString(R.string.media_no_description) : item.attachment.description);
didClear=false; didClear=false;
if (currentAnim != null) currentAnim.cancel();
boolean altTextMissing = TextUtils.isEmpty(item.attachment.description);
altOrNoAltButton = altTextMissing ? noAltTextButton : altTextButton;
altTextShown=false;
altTextScroller.setVisibility(View.GONE);
altTextClose.setVisibility(View.GONE);
altTextButton.setVisibility(View.VISIBLE);
noAltTextButton.setVisibility(View.VISIBLE);
altTextButton.setAlpha(1f);
noAltTextButton.setAlpha(1f);
altTextWrapper.setVisibility(View.VISIBLE);
if (altTextMissing){
if (GlobalUserPreferences.showNoAltIndicator) {
noAltTextButton.setVisibility(View.VISIBLE);
noAltText.setVisibility(View.VISIBLE);
altTextWrapper.setBackgroundResource(R.drawable.bg_image_no_alt_overlay);
altTextButton.setVisibility(View.GONE);
altText.setVisibility(View.GONE);
} else {
altTextWrapper.setVisibility(View.GONE);
}
}else{
if (GlobalUserPreferences.showAltIndicator) {
noAltTextButton.setVisibility(View.GONE);
noAltText.setVisibility(View.GONE);
altTextWrapper.setBackgroundResource(R.drawable.bg_image_alt_overlay);
altTextButton.setVisibility(View.VISIBLE);
altTextButton.setText(R.string.sk_alt_button);
altText.setVisibility(View.VISIBLE);
altText.setText(item.attachment.description);
altText.setPadding(0, 0, 0, 0);
} else {
altTextWrapper.setVisibility(View.GONE);
}
}
}
private void onShowHideClick(View v){
boolean show=v.getId()==R.id.alt_button || v.getId()==R.id.no_alt_button;
if(altTextShown==show)
return;
if(currentAnim!=null)
currentAnim.cancel();
altTextShown=show;
if(show){
altTextScroller.setVisibility(View.VISIBLE);
altTextClose.setVisibility(View.VISIBLE);
}else{
altOrNoAltButton.setVisibility(View.VISIBLE);
// Hide these views temporarily so FrameLayout measures correctly
altTextScroller.setVisibility(View.GONE);
altTextClose.setVisibility(View.GONE);
}
// This is the current size...
int prevLeft=altTextWrapper.getLeft();
int prevRight=altTextWrapper.getRight();
int prevTop=altTextWrapper.getTop();
altTextWrapper.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){
altTextWrapper.getViewTreeObserver().removeOnPreDrawListener(this);
// ...and this is after the layout pass, right now the FrameLayout has its final size, but we animate that change
if(!show){
// Show these views again so they're visible for the duration of the animation.
// No one would notice they were missing during measure/layout.
altTextScroller.setVisibility(View.VISIBLE);
altTextClose.setVisibility(View.VISIBLE);
}
AnimatorSet set=new AnimatorSet();
set.playTogether(
ObjectAnimator.ofInt(altTextWrapper, "left", prevLeft, altTextWrapper.getLeft()),
ObjectAnimator.ofInt(altTextWrapper, "right", prevRight, altTextWrapper.getRight()),
ObjectAnimator.ofInt(altTextWrapper, "top", prevTop, altTextWrapper.getTop()),
ObjectAnimator.ofFloat(altOrNoAltButton, View.ALPHA, show ? 1f : 0f, show ? 0f : 1f),
ObjectAnimator.ofFloat(altTextScroller, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f),
ObjectAnimator.ofFloat(altTextClose, View.ALPHA, show ? 0f : 1f, show ? 1f : 0f)
);
set.setDuration(300);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
if(show){
altOrNoAltButton.setVisibility(View.GONE);
}else{
altTextScroller.setVisibility(View.GONE);
altTextClose.setVisibility(View.GONE);
}
currentAnim=null;
}
});
set.start();
currentAnim=set;
return true;
}
});
} }
@Override @Override

View File

@@ -73,7 +73,6 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
photo.setImageDrawable(null); photo.setImageDrawable(null);
if(item.imgRequest!=null){ if(item.imgRequest!=null){
crossfadeDrawable.setSize(card.width, card.height);
if (card.width > 0) { if (card.width > 0) {
// akkoma servers don't provide width and height // akkoma servers don't provide width and height
crossfadeDrawable.setSize(card.width, card.height); crossfadeDrawable.setSize(card.width, card.height);
@@ -105,4 +104,3 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
} }
} }
} }

View File

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

View File

@@ -39,11 +39,10 @@ public class PollFooterStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(PollFooterStatusDisplayItem item){ public void onBind(PollFooterStatusDisplayItem item){
String text=item.parentFragment.getResources().getQuantityString(R.plurals.x_voters, item.poll.votersCount, item.poll.votersCount); String text=item.parentFragment.getResources().getQuantityString(R.plurals.x_voters, item.poll.votersCount, item.poll.votersCount);
String sep=item.parentFragment.getString(R.string.sk_separator);
if(item.poll.expiresAt!=null && !item.poll.isExpired()){ if(item.poll.expiresAt!=null && !item.poll.isExpired()){
text+=" "+sep+" "+UiUtils.formatTimeLeft(itemView.getContext(), item.poll.expiresAt); text+=" · "+UiUtils.formatTimeLeft(itemView.getContext(), item.poll.expiresAt);
}else if(item.poll.isExpired()){ }else if(item.poll.isExpired()){
text+=" "+sep+" "+item.parentFragment.getString(R.string.poll_closed); text+=" · "+item.parentFragment.getString(R.string.poll_closed);
} }
this.text.setText(text); this.text.setText(text);
button.setVisibility(item.poll.isExpired() || item.poll.voted || (!item.poll.multiple && !GlobalUserPreferences.voteButtonForSingleChoice) ? View.GONE : View.VISIBLE); button.setVisibility(item.poll.isExpired() || item.poll.voted || (!item.poll.multiple && !GlobalUserPreferences.voteButtonForSingleChoice) ? View.GONE : View.VISIBLE);

View File

@@ -55,8 +55,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
this.visibility = visibility; this.visibility = visibility;
this.iconEnd = visibility != null ? switch (visibility) { this.iconEnd = visibility != null ? switch (visibility) {
case PUBLIC -> R.drawable.ic_fluent_earth_20_regular; case PUBLIC -> R.drawable.ic_fluent_earth_20_regular;
case UNLISTED -> R.drawable.ic_fluent_lock_open_20_regular; case UNLISTED -> R.drawable.ic_fluent_people_community_20_regular;
case PRIVATE -> R.drawable.ic_fluent_lock_closed_20_filled; case PRIVATE -> R.drawable.ic_fluent_people_checkmark_20_regular;
default -> 0; default -> 0;
} : 0; } : 0;
} }
@@ -100,7 +100,6 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")"); if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")");
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N) if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
UiUtils.fixCompoundDrawableTintOnAndroid6(text); UiUtils.fixCompoundDrawableTintOnAndroid6(text);
} }
@Override @Override

View File

@@ -8,34 +8,25 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
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.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTabFragment;
import org.joinmastodon.android.fragments.HomeTimelineFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Poll;
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.ui.PhotoLayoutHelper; import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
@@ -82,39 +73,21 @@ public abstract class StatusDisplayItem{
case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent); case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent);
case GAP -> new GapStatusDisplayItem.Holder(activity, parent); case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent); case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent);
case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent);
}; };
} }
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification){ public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification){
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, Filter.FilterContext.HOME); return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false);
} }
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, Filter.FilterContext filterContext){ public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate){
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, filterContext);
}
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate){
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, disableTranslate, Filter.FilterContext.HOME);
}
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
String parentID=parentObject.getID(); String parentID=parentObject.getID();
ArrayList<StatusDisplayItem> items=new ArrayList<>(); ArrayList<StatusDisplayItem> items=new ArrayList<>();
Status statusForContent=status.getContentStatus(); Status statusForContent=status.getContentStatus();
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null; ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null;
List<Filter> filters = AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream()
.filter(f -> f.context.contains(filterContext)).collect(Collectors.toList());
StatusFilterPredicate filterPredicate = new StatusFilterPredicate(filters);
if(!statusForContent.filterRevealed){
statusForContent.filterRevealed = filterPredicate.testWithWarning(status);
}
if(status.reblog!=null){ if(status.reblog!=null){
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account); boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{ items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{
@@ -127,32 +100,12 @@ public abstract class StatusDisplayItem{
args.putParcelable("profileAccount", Parcels.wrap(account)); args.putParcelable("profileAccount", Parcels.wrap(account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args); Nav.go(fragment.getActivity(), ProfileFragment.class, args);
})); }));
} else if (
!(status.tags.isEmpty() ||
fragment instanceof HashtagTimelineFragment ||
fragment instanceof ListTimelineFragment
) && fragment.getParentFragment() instanceof HomeTabFragment home
) {
home.getHashtags().stream()
.filter(followed -> status.tags.stream()
.anyMatch(hashtag -> followed.name.equalsIgnoreCase(hashtag.name)))
.findAny()
// post contains a hashtag the user is following
.ifPresent(hashtag -> items.add(new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, hashtag.name, List.of(),
R.drawable.ic_fluent_number_symbol_20_filled, null,
i -> {
args.putString("hashtag", hashtag.name);
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
}
)));
} }
HeaderStatusDisplayItem header; HeaderStatusDisplayItem header;
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus)); items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus));
if(!TextUtils.isEmpty(statusForContent.content)){ if(!TextUtils.isEmpty(statusForContent.content))
items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate)); items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate));
} else else
header.needBottomPadding=true; header.needBottomPadding=true;
List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList()); List<Attachment> imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList());
if(!imageAttachments.isEmpty()){ if(!imageAttachments.isEmpty()){
@@ -184,23 +137,14 @@ public abstract class StatusDisplayItem{
} }
if(addFooter){ if(addFooter){
items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID)); items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID));
if(status.hasGapAfter && !(fragment instanceof ThreadFragment)){ if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
items.add(new GapStatusDisplayItem(parentID, fragment)); items.add(new GapStatusDisplayItem(parentID, fragment));
}
} }
int i=1; int i=1;
for(StatusDisplayItem item:items){ for(StatusDisplayItem item:items){
item.inset=inset; item.inset=inset;
item.index=i++; item.index=i++;
} }
if (!statusForContent.filterRevealed) {
return new ArrayList<>(List.of(
new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items)
));
}
return items; return items;
} }
@@ -227,7 +171,6 @@ public abstract class StatusDisplayItem{
ACCOUNT, ACCOUNT,
HASHTAG, HASHTAG,
GAP, GAP,
WARNING,
EXTENDED_FOOTER EXTENDED_FOOTER
} }

View File

@@ -3,19 +3,15 @@ package org.joinmastodon.android.ui.displayitems;
import android.app.Activity; import android.app.Activity;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Button; import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus; import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
@@ -23,15 +19,12 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.TranslatedStatus; import org.joinmastodon.android.model.TranslatedStatus;
import org.joinmastodon.android.ui.text.HtmlParser; 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.views.LinkedTextView; import org.joinmastodon.android.ui.views.LinkedTextView;
import org.joinmastodon.android.utils.StatusTextEncoder;
import java.util.regex.Pattern;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@@ -51,7 +44,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public boolean translated = false; public boolean translated = false;
public TranslatedStatus translation = null; public TranslatedStatus translation = null;
private AccountSession session; private AccountSession session;
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){ public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
super(parentID, parentFragment); super(parentID, parentFragment);
@@ -89,14 +81,10 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text; private final LinkedTextView text;
private final LinearLayout spoilerHeader; private final LinearLayout spoilerHeader;
private final TextView spoilerTitle, spoilerTitleInline, translateInfo, readMore; private final TextView spoilerTitle, spoilerTitleInline, translateInfo;
private final View spoilerOverlay, borderTop, borderBottom, textWrap, translateWrap, translateProgress, spaceBelowText; private final View spoilerOverlay, borderTop, borderBottom, textWrap, translateWrap, translateProgress;
private final int backgroundColor, borderColor; private final Drawable backgroundColor, borderColor;
private final Button translateButton; private final Button translateButton;
private final ScrollView textScrollView;
private final float textMaxHeight, textCollapsedHeight;
private final LinearLayout.LayoutParams collapseParams, wrapParams;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent); super(activity, R.layout.display_item_text, parent);
@@ -113,33 +101,28 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
translateInfo=findViewById(R.id.translate_info); translateInfo=findViewById(R.id.translate_info);
translateProgress=findViewById(R.id.translate_progress); translateProgress=findViewById(R.id.translate_progress);
itemView.setOnClickListener(v->item.parentFragment.onRevealSpoilerClick(this)); itemView.setOnClickListener(v->item.parentFragment.onRevealSpoilerClick(this));
backgroundColor=UiUtils.getThemeColor(activity, R.attr.colorBackgroundLight);
borderColor=UiUtils.getThemeColor(activity, R.attr.colorPollVoted); TypedValue outValue=new TypedValue();
textScrollView=findViewById(R.id.text_scroll_view); activity.getTheme().resolveAttribute(R.attr.colorBackgroundLight, outValue, true);
readMore=findViewById(R.id.read_more); backgroundColor=activity.getDrawable(outValue.resourceId);
spaceBelowText=findViewById(R.id.space_below_text); // activity.getTheme().resolveAttribute(R.attr.colorBackgroundLightest, outValue, true);
textMaxHeight=activity.getResources().getDimension(R.dimen.text_max_height); // backgroundColorInset=activity.getDrawable(outValue.resourceId);
textCollapsedHeight=activity.getResources().getDimension(R.dimen.text_collapsed_height); activity.getTheme().resolveAttribute(R.attr.colorPollVoted, outValue, true);
collapseParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) textCollapsedHeight); borderColor=activity.getDrawable(outValue.resourceId);
wrapParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
readMore.setOnClickListener(v -> item.parentFragment.onToggleExpanded(item.status, getItemID()));
} }
@Override @Override
public void onBind(TextStatusDisplayItem item){ public void onBind(TextStatusDisplayItem item){
text.setText(item.translated text.setText(item.translated
? HtmlParser.parse(item.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID()) ? HtmlParser.parse(item.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
: item.text); : item.text);
text.setTextIsSelectable(item.textSelectable); text.setTextIsSelectable(item.textSelectable);
if (item.textSelectable) {
textScrollView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
}
spoilerTitleInline.setTextIsSelectable(item.textSelectable); spoilerTitleInline.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
spoilerTitleInline.setBackgroundColor(item.inset ? 0 : backgroundColor); spoilerTitleInline.setBackground(item.inset ? null : backgroundColor);
spoilerTitleInline.setPadding(spoilerTitleInline.getPaddingLeft(), item.inset ? 0 : V.dp(14), spoilerTitleInline.getPaddingRight(), item.inset ? 0 : V.dp(14)); spoilerTitleInline.setPadding(spoilerTitleInline.getPaddingLeft(), item.inset ? 0 : V.dp(14), spoilerTitleInline.getPaddingRight(), item.inset ? 0 : V.dp(14));
borderTop.setBackgroundColor(item.inset ? 0 : borderColor); borderTop.setBackground(item.inset ? null : borderColor);
borderBottom.setBackgroundColor(item.inset ? 0 : borderColor); borderBottom.setBackground(item.inset ? null : borderColor);
if(!TextUtils.isEmpty(item.status.spoilerText)){ if(!TextUtils.isEmpty(item.status.spoilerText)){
spoilerTitle.setText(item.parsedSpoilerText); spoilerTitle.setText(item.parsedSpoilerText);
spoilerTitleInline.setText(item.parsedSpoilerText); spoilerTitleInline.setText(item.parsedSpoilerText);
@@ -162,34 +145,18 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
} }
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(item.session.domain); Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(item.session.domain);
boolean translateEnabled = !item.disableTranslate && instanceInfo != null && boolean translateEnabled = !item.disableTranslate && instanceInfo.v2 != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null && instanceInfo.v2.configuration.translation != null && instanceInfo.v2.configuration.translation.enabled;
instanceInfo.v2.configuration.translation.enabled;
boolean isBottomText = BOTTOM_TEXT_PATTERN.matcher(item.status.getStrippedText()).find(); translateWrap.setVisibility(translateEnabled &&
boolean translateVisible = (isBottomText || ( !item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
translateEnabled && item.status.language != null &&
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) && (item.session.preferences == null || !item.status.language.equalsIgnoreCase(item.session.preferences.postingDefaultLanguage))
item.status.language != null && ? View.VISIBLE : View.GONE);
(item.session.preferences == null || !item.status.language.equalsIgnoreCase(item.session.preferences.postingDefaultLanguage))));
// && (!GlobalUserPreferences.translateButtonOpenedOnly || item.textSelectable);
translateWrap.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post); translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translated ? itemView.getResources().getString(R.string.sk_translated_using, isBottomText ? "bottom-java" : item.translation.provider) : ""); translateInfo.setText(item.translated ? itemView.getResources().getString(R.string.sk_translated_using, item.translation.provider) : "");
translateButton.setOnClickListener(v->{ translateButton.setOnClickListener(v->{
if (item.translation == null) { if (item.translation == null) {
if (isBottomText) {
try {
item.translation = new TranslatedStatus();
item.translation.content = new StatusTextEncoder(Bottom::decode).decode(item.status.getStrippedText(), BOTTOM_TEXT_PATTERN);
item.translated = true;
} catch (TranslationError err) {
item.translation = null;
Toast.makeText(itemView.getContext(), err.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
rebind();
return;
}
translateProgress.setVisibility(View.VISIBLE); translateProgress.setVisibility(View.VISIBLE);
translateButton.setClickable(false); translateButton.setClickable(false);
translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start(); translateButton.animate().alpha(0.5f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
@@ -198,7 +165,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public void onSuccess(TranslatedStatus translatedStatus) { public void onSuccess(TranslatedStatus translatedStatus) {
item.translation = translatedStatus; item.translation = translatedStatus;
item.translated = true; item.translated = true;
if (item.parentFragment.getActivity() == null) return;
translateProgress.setVisibility(View.GONE); translateProgress.setVisibility(View.GONE);
translateButton.setClickable(true); translateButton.setClickable(true);
translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start(); translateButton.animate().alpha(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(50).start();
@@ -218,26 +184,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
rebind(); rebind();
} }
}); });
readMore.setText(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
spaceBelowText.setVisibility(translateVisible ? View.VISIBLE : View.GONE);
if (!GlobalUserPreferences.collapseLongPosts) {
textScrollView.setLayoutParams(wrapParams);
readMore.setVisibility(View.GONE);
}
if (GlobalUserPreferences.collapseLongPosts) text.post(() -> {
boolean tooBig = text.getMeasuredHeight() > textMaxHeight;
boolean inTimeline = !item.textSelectable;
boolean hasSpoiler = !TextUtils.isEmpty(item.status.spoilerText);
boolean expandable = inTimeline && tooBig && !hasSpoiler;
item.parentFragment.onEnableExpandable(this, expandable);
});
readMore.setVisibility(item.status.textExpandable && !item.status.textExpanded ? View.VISIBLE : View.GONE);
textScrollView.setLayoutParams(item.status.textExpandable && !item.status.textExpanded ? collapseParams : wrapParams);
if (item.status.textExpandable && !translateVisible) spaceBelowText.setVisibility(View.VISIBLE);
} }
@Override @Override

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