Compare commits

..

1 Commits

Author SHA1 Message Date
LucasGGamerM
f6ea0404ef Revert "fix: allows for editing timeline badges again. fixes sk22#800 (#804)"
This reverts commit f14df2bb0f.
2023-09-03 18:26:55 -03:00
416 changed files with 3353 additions and 8280 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 33
versionCode 107
versionName "2.1.6+fork.107"
versionCode 98
versionName "2.0.3+fork.98"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
}
@@ -70,7 +70,7 @@ dependencies {
implementation 'me.grishka.litex:viewpager:1.0.0'
implementation 'me.grishka.litex:viewpager2:1.0.0'
implementation 'me.grishka.litex:palette:1.0.0'
implementation 'me.grishka.appkit:appkit:1.2.14'
implementation 'me.grishka.appkit:appkit:1.2.9'
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'com.squareup:otto:1.3.8'

View File

@@ -1,43 +1,56 @@
13bells.com
1611.social
4aem.com
5dollah.click
adachi.party
adtension.com
anime.website
annihilation.social
anon-kenkai.com
asbestos.cafe
bae.st
bajax.us
banepo.st
baraag.net
bassam.social
battlepenguin.video
beefyboys.win
beepboop.ga
berserker.town
bikeshed.party
boks.moe
boymoder.biz
brainsoap.net
breastmilk.club
brighteon.social
cachapa.xyz
canary.fedinuke.example.com
catgirl.life
bungle.online
cawfee.club
childlove.space
clew.lol
clubcyberia.co
collapsitarian.io
comfyboy.club
contrapointsfan.club
crucible.world
cum.camp
cum.salon
darknight-coffee.org
decayable.ink
dembased.xyz
desupost.soy
detroitriotcity.com
djsumdog.com
eatthebugs.social
eientei.org
elementality.org
eveningzoo.club
firedragonstudios.com
firefaithfellowship.com
fluf.club
foxfam.club
freak.university
freeatlantis.com
freedomstrike.org
freesoftwareextremist.com
freespeech.group
freespeechextremist.com
freetalklive.com
froth.zone
fulltermprivacy.com
gameliberty.club
gearlandia.haus
genderheretics.xyz
@@ -46,34 +59,42 @@ gleasonator.com
glee.li
glindr.org
goyim.app
h5q.net
goyslop.cafe
haeder.net
handholding.io
hitchhiker.social
hunk.city
iddqd.social
intkos.link
justicewarrior.social
kawa-kun.com
kitsunemimi.club
kiwifarms.cc
kompost.cz
kurosawa.moe
kyaruc.moe
leafposter.club
leftychan.net
lewdieheaven.com
liberdon.com
ligma.pro
lolicon.rocks
lolison.network
lolison.top
lovingexpressions.net
mahodou.moe
makemysarcophagus.com
maladaptive.art
marsey.moe
masochi.st
mastinator.com
merovingian.club
midwaytrades.com
mirr0r.city
morale.ch
moa.st
mouse.services
mugicha.club
narrativerry.xyz
natehiggers.online
neckbeard.xyz
needs.vodka
neenster.org
nicecrew.digital
@@ -82,18 +103,18 @@ noagendasocial.com
noagendasocial.nl
noagendatube.com
nobodyhasthe.biz
norwoodzero.net
nyanide.com
nukem.biz
obo.sh
onionfarms.org
pawlicker.com
pawoo.net
pedo.school
peervideo.club
piazza.today
pibvt.net
pieville.net
pisskey.io
plagu.ee
pmth.us
poa.st
poast.org
poast.tv
@@ -102,18 +123,17 @@ prospeech.space
quodverum.com
r18.social
rakket.app
rapemeat.express
rapemeat.solutions
rayci.st
rdrama.cc
rebelbase.site
retardedniggers.forsale
rojogato.com
ryona.agency
sad.cab
schwartzwelt.xyz
seal.cafe
shaw.app
shigusegubu.club
shitpost.cloud
shortstacksran.ch
shota.house
silliness.observer
skinheads.eu
skinheads.io
@@ -128,20 +148,23 @@ sneed.social
sonichu.com
spinster.xyz
springbo.cc
starnix.network
strelizia.net
syspxl.xyz
tastingtraffic.net
teci.world
theapex.social
thechimp.zone
thenobody.club
thepostearthdestination.com
tkammer.de
trumpislovetrumpis.life
truthsocial.co.in
usualsuspects.lol
urchan.org
varishangout.net
vtuberfan.social
whinge.house
whinge.town
wideboys.org
wolfgirl.bar
xn--p1abe3d.xn--80asehdb
yggdrasil.social
youjo.love
zztails.gay

View File

@@ -266,7 +266,7 @@ public class AudioPlayerService extends Service{
private void updateNotification(boolean dismissable, boolean removeNotification){
Notification.Builder bldr=new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_ntf_logo)
.setContentTitle(status.account.getDisplayName())
.setContentTitle(status.account.displayName)
.setContentText(HtmlParser.strip(status.content))
.setOngoing(!dismissable)
.setShowWhen(false)
@@ -281,7 +281,7 @@ public class AudioPlayerService extends Service{
if(playerReady){
boolean isPlaying=player.isPlaying();
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_fluent_pause_24_filled : R.drawable.ic_fluent_play_24_filled),
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_pause_24 : R.drawable.ic_play_24),
getString(isPlaying ? R.string.pause : R.string.play),
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
.build());

View File

@@ -32,6 +32,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
UiUtils.setUserPreferredTheme(this);
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
Optional<String> text = Optional.ofNullable(getIntent().getStringExtra(Intent.EXTRA_TEXT));
Optional<Pair<String, Optional<String>>> fediHandle = text.flatMap(UiUtils::parseFediverseHandle);
boolean isFediUrl = text.map(UiUtils::looksLikeFediverseUrl).orElse(false);

View File

@@ -1,17 +1,17 @@
package org.joinmastodon.android;
import static org.joinmastodon.android.api.MastodonAPIController.gson;
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.MATERIAL3;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.annotation.StringRes;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.ContentType;
@@ -51,17 +51,15 @@ public class GlobalUserPreferences{
public static boolean collapseLongPosts;
public static boolean spectatorMode;
public static boolean autoHideFab;
public static boolean compactReblogReplyLine;
public static boolean allowRemoteLoading;
public static boolean forwardReportDefault;
public static AutoRevealMode autoRevealEqualSpoilers;
public static ColorPreference color;
public static boolean disableM3PillActiveIndicator;
public static boolean showNavigationLabels;
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
public static boolean overlayMedia;
public static boolean showSuicideHelp;
public static boolean underlinedLinks;
public static ColorPreference color;
public static boolean likeIcon;
private static SharedPreferences getPrefs(){
return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE);
@@ -111,6 +109,7 @@ public class GlobalUserPreferences{
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
spectatorMode=prefs.getBoolean("spectatorMode", false);
autoHideFab=prefs.getBoolean("autoHideFab", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
@@ -120,10 +119,6 @@ public class GlobalUserPreferences{
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
overlayMedia=prefs.getBoolean("overlayMedia", false);
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
underlinedLinks=prefs.getBoolean("underlinedLinks", true);
color=ColorPreference.valueOf(prefs.getString("color", MATERIAL3.name()));
likeIcon=prefs.getBoolean("likeIcon", false);
if (prefs.contains("prefixRepliesWithRe")) {
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
@@ -134,11 +129,14 @@ public class GlobalUserPreferences{
.apply();
}
int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE);
if(migrationLevel < 61)
migrateToUpstreamVersion61();
if(migrationLevel < BuildConfig.VERSION_CODE)
prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply();
try {
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
} catch (IllegalArgumentException|ClassCastException ignored) {
// invalid color name or color was previously saved as integer
color=ColorPreference.PINK;
}
if(prefs.getInt("migrationLevel", 0) < 61) migrateToUpstreamVersion61();
}
public static void save(){
@@ -168,6 +166,8 @@ public class GlobalUserPreferences{
.putBoolean("collapseLongPosts", collapseLongPosts)
.putBoolean("spectatorMode", spectatorMode)
.putBoolean("autoHideFab", autoHideFab)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putString("color", color.name())
.putBoolean("allowRemoteLoading", allowRemoteLoading)
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
.putBoolean("forwardReportDefault", forwardReportDefault)
@@ -177,34 +177,9 @@ public class GlobalUserPreferences{
.putBoolean("displayPronounsInThreads", displayPronounsInThreads)
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
.putBoolean("overlayMedia", overlayMedia)
.putBoolean("showSuicideHelp", showSuicideHelp)
.putBoolean("underlinedLinks", underlinedLinks)
.putString("color", color.name())
.putBoolean("likeIcon", likeIcon)
.apply();
}
public enum ThemePreference{
AUTO,
LIGHT,
DARK
}
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
//region preferences migrations
private static void migrateToUpstreamVersion61(){
Log.d(TAG, "Migrating preferences to upstream version 61!!");
@@ -251,8 +226,49 @@ public class GlobalUserPreferences{
localPrefs.save();
}
prefs.edit().putInt("migrationLevel", 61).apply();
}
//endregion
public enum ColorPreference{
MATERIAL3,
PINK,
PURPLE,
GREEN,
BLUE,
BROWN,
RED,
YELLOW;
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
};
}
}
public enum ThemePreference{
AUTO,
LIGHT,
DARK
}
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
}

View File

@@ -39,12 +39,54 @@ import me.grishka.appkit.api.ErrorResponse;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState){
AccountSession session=getCurrentSession();
UiUtils.setUserPreferredTheme(this, session);
UiUtils.setUserPreferredTheme(this);
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
restartHomeFragment();
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID());
} else if (intent.getBooleanExtra("compose", false)){
showCompose();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
} else {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
}
}
if(GithubSelfUpdater.needSelfUpdating()){
@@ -101,7 +143,7 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
}
public void openSearchQuery(String q, String accountID, int progressText, boolean fromSearch){
new GetSearchResults(q, null, true, null, 0, 0)
new GetSearchResults(q, null, true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
@@ -217,81 +259,4 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
Fragment fragment = getCurrentFragment();
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
}
public AccountSession getCurrentSession(){
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
return AccountSessionManager.getInstance()
.getAccount(intent.getStringExtra("account"));
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
return session;
}
public void restartActivity(){
finish();
startActivity(new Intent(this, MainActivity.class));
}
public void restartHomeFragment(){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment());
}else{
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
AccountSessionManager.getInstance()
.setLastActiveAccountID(intent.getStringExtra("account"));
AccountSessionManager.getInstance().maybeUpdateLocalInfo(
AccountSessionManager.getInstance().getLastActiveAccount());
showFragmentForExternalShare(intent.getExtras());
return;
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
AccountSessionManager.getInstance().maybeUpdateLocalInfo(session);
args.putString("account", session.getID());
Fragment fragment=session.activated ? new HomeFragment() : new AccountActivationFragment();
fragment.setArguments(args);
if(fromNotification && hasNotification){
Notification notification=Parcels.unwrap(intent.getParcelableExtra("notification"));
showFragmentForNotification(notification, session.getID());
} else if (intent.getBooleanExtra("compose", false)){
showCompose();
} else if (Intent.ACTION_VIEW.equals(intent.getAction())){
handleURL(intent.getData(), null);
} else {
showFragmentClearingBackStack(fragment);
maybeRequestNotificationsPermission();
}
}
}
}

View File

@@ -14,6 +14,7 @@ import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.Log;
@@ -32,6 +33,7 @@ import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushNotification;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
@@ -174,8 +176,6 @@ public class PushNotificationReceiver extends BroadcastReceiver{
List<NotificationChannel> channels=Arrays.stream(PushNotification.Type.values())
.map(type->{
NotificationChannel channel=new NotificationChannel(accountID+"_"+type, context.getString(type.localizedName), NotificationManager.IMPORTANCE_DEFAULT);
channel.setLightColor(context.getColor(R.color.primary_700));
channel.enableLights(true);
channel.setGroup(accountID);
return channel;
})
@@ -205,12 +205,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
.setShowWhen(true)
.setCategory(Notification.CATEGORY_SOCIAL)
.setAutoCancel(true)
.setLights(UiUtils.getThemeColor(context, android.R.attr.colorAccent), 500, 1000)
.setColor(UiUtils.getThemeColor(context, android.R.attr.colorAccent));
if (!GlobalUserPreferences.uniformNotificationIcon) {
builder.setSmallIcon(switch (pn.notificationType) {
case FAVORITE -> GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled;
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
case MENTION -> R.drawable.ic_fluent_mention_24_filled;
@@ -322,8 +321,8 @@ public class PushNotificationReceiver extends BroadcastReceiver{
CreateStatus.Request req=new CreateStatus.Request();
req.status = initialText + input.toString();
req.language = notification.status.language;
req.visibility = notification.status.visibility;
req.language = preferences.postingDefaultLanguage;
req.visibility = preferences.postingDefaultVisibility;
req.inReplyToId = notification.status.id;
if (notification.status.hasSpoiler() &&

View File

@@ -26,7 +26,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import me.grishka.appkit.api.Callback;
@@ -70,11 +69,12 @@ public class CacheController{
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
status.postprocess();
int flags=cursor.getInt(1);
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0) ? status.id : null;
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
newMaxID=status.id;
result.add(status);
}while(cursor.moveToNext());
String _newMaxID=newMaxID;
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
return;
}
@@ -86,7 +86,9 @@ public class CacheController{
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
callback.onSuccess(new CacheablePaginatedResponse<>(result, result.isEmpty() ? null : result.get(result.size()-1).id, false));
ArrayList<Status> filtered=new ArrayList<>(result);
AccountSessionManager.get(accountID).filterStatuses(filtered, FilterContext.HOME);
callback.onSuccess(new CacheablePaginatedResponse<>(filtered, result.isEmpty() ? null : result.get(result.size()-1).id, false));
putHomeTimeline(result, maxID==null);
}
@@ -114,14 +116,12 @@ public class CacheController{
values.put("id", s.id);
values.put("json", MastodonAPIController.gson.toJson(s));
int flags=0;
if(Objects.equals(s.hasGapAfter, s.id))
if(s.hasGapAfter)
flags|=POST_FLAG_GAP_AFTER;
values.put("flags", flags);
values.put("time", s.createdAt.getEpochSecond());
db.insertWithOnConflict("home_timeline", null, values, SQLiteDatabase.CONFLICT_REPLACE);
}
if(!clear)
db.delete("home_timeline", "`id` NOT IN (SELECT `id` FROM `home_timeline` ORDER BY `time` DESC LIMIT ?)", new String[]{"100"});
});
}

View File

@@ -53,7 +53,9 @@ public class MastodonAPIController{
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
.create();
private static WorkerThread thread=new WorkerThread("MastodonAPIController");
private static OkHttpClient httpClient=new OkHttpClient.Builder().build();
private static OkHttpClient httpClient=new OkHttpClient.Builder()
.readTimeout(5, TimeUnit.MINUTES)
.build();
private AccountSession session;
private static List<String> badDomains = new ArrayList<>();
@@ -111,13 +113,13 @@ public class MastodonAPIController{
}
Request hreq=builder.build();
OkHttpClient client=req.timeout>0
? httpClient.newBuilder().readTimeout(req.timeout, TimeUnit.MILLISECONDS).build()
: httpClient;
Call call=client.newCall(hreq);
Call call=httpClient.newCall(hreq);
synchronized(req){
req.okhttpCall=call;
}
if(req.timeout>0){
call.timeout().timeout(req.timeout, TimeUnit.MILLISECONDS);
}
if(BuildConfig.DEBUG)
Log.d(TAG, "["+(session==null ? "no-auth" : session.getID())+"] Sending request: "+hreq);

View File

@@ -153,9 +153,8 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
headers.put(key, value);
}
public MastodonAPIRequest<T> setTimeout(long timeout){
protected void setTimeout(long timeout){
this.timeout=timeout;
return this;
}
protected String getPathPrefix(){

View File

@@ -130,11 +130,12 @@ public class PushSubscriptionManager{
return;
}
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/";
String endpoint = "https://app.joinmastodon.org/relay-to/fcm/"+deviceToken+"/"+accountID;
registerAccountForPush(subscription, endpoint);
}
public void registerAccountForPush(PushSubscription subscription, String endpoint){
MastodonAPIController.runInBackground(()->{
Log.d(TAG, "registerAccountForPush: started for "+accountID);
String encodedPublicKey, encodedAuthKey, pushAccountID;
@@ -163,13 +164,7 @@ public class PushSubscriptionManager{
Log.e(TAG, "registerAccountForPush: error generating encryption key", e);
return;
}
//work-around for adding the randomAccountId
String newEndpoint = endpoint;
if (endpoint.startsWith("https://app.joinmastodon.org/relay-to/fcm/"))
newEndpoint += pushAccountID;
new RegisterForPushNotifications(newEndpoint,
new RegisterForPushNotifications(endpoint,
encodedPublicKey,
encodedAuthKey,
subscription==null ? PushSubscription.Alerts.ofAll() : subscription.alerts,

View File

@@ -7,10 +7,7 @@ import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
@@ -51,7 +48,7 @@ public class StatusInteractionController{
runningFavoriteRequests.remove(status.id);
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
cb.accept(result);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
}
@Override
@@ -60,13 +57,13 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context);
status.favourited=!favorited;
cb.accept(status);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningFavoriteRequests.put(status.id, req);
status.favourited=favorited;
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
@@ -81,15 +78,11 @@ public class StatusInteractionController{
.setCallback(new Callback<>(){
@Override
public void onSuccess(Status reblog){
Status result=reblog.getContentStatus();
Status result = reblog.getContentStatus();
runningReblogRequests.remove(status.id);
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
cb.accept(result);
if(updateCounters){
E.post(new StatusCountersUpdatedEvent(result));
if(reblogged) E.post(new StatusCreatedEvent(reblog, accountID));
else E.post(new ReblogDeletedEvent(status.id, accountID));
}
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
}
@Override
@@ -98,13 +91,13 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context);
status.reblogged=!reblogged;
cb.accept(status);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningReblogRequests.put(status.id, req);
status.reblogged=reblogged;
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
public void setBookmarked(Status status, boolean bookmarked){
@@ -125,7 +118,7 @@ public class StatusInteractionController{
public void onSuccess(Status result){
runningBookmarkRequests.remove(status.id);
cb.accept(result);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
}
@Override
@@ -134,12 +127,12 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context);
status.bookmarked=!bookmarked;
cb.accept(status);
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningBookmarkRequests.put(status.id, req);
status.bookmarked=bookmarked;
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
}

View File

@@ -1,16 +0,0 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetAccountBlocks extends HeaderPaginationRequest<Account>{
public GetAccountBlocks(String maxID, int limit){
super(HttpMethod.GET, "/blocks", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
}
}

View File

@@ -1,16 +0,0 @@
package org.joinmastodon.android.api.requests.accounts;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.model.Account;
public class GetAccountMutes extends HeaderPaginationRequest<Account>{
public GetAccountMutes(String maxID, int limit){
super(HttpMethod.GET, "/mutes/", new TypeToken<>(){});
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(limit>0)
addQueryParameter("limit", limit+"");
}
}

View File

@@ -6,19 +6,18 @@ import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.StatusPrivacy;
public class UpdateAccountCredentialsPreferences extends MastodonAPIRequest<Account>{
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable, Boolean indexable){
public UpdateAccountCredentialsPreferences(Preferences preferences, Boolean locked, Boolean discoverable){
super(HttpMethod.PATCH, "/accounts/update_credentials", Account.class);
setRequestBody(new Request(locked, discoverable, indexable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
setRequestBody(new Request(locked, discoverable, new RequestSource(preferences.postingDefaultVisibility, preferences.postingDefaultLanguage)));
}
private static class Request{
public Boolean locked, discoverable, indexable;
public Boolean locked, discoverable;
public RequestSource source;
public Request(Boolean locked, Boolean discoverable, Boolean indexable, RequestSource source){
public Request(Boolean locked, Boolean discoverable, RequestSource source){
this.locked=locked;
this.discoverable=discoverable;
this.indexable=indexable;
this.source=source;
}
}

View File

@@ -6,9 +6,6 @@ import org.joinmastodon.android.model.FilterContext;
import java.util.EnumSet;
import java.util.List;
import androidx.annotation.Keep;
@Keep
class FilterRequest{
public String title;
public EnumSet<FilterContext> context;

View File

@@ -13,7 +13,7 @@ import okhttp3.MultipartBody;
import okhttp3.RequestBody;
public class PleromaMarkNotificationsRead extends MastodonAPIRequest<List<Notification>> {
private final String maxID;
private String maxID;
public PleromaMarkNotificationsRead(String maxID) {
super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){});
this.maxID = maxID;

View File

@@ -4,19 +4,13 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.SearchResults;
public class GetSearchResults extends MastodonAPIRequest<SearchResults>{
public GetSearchResults(String query, Type type, boolean resolve, String maxID, int offset, int count){
public GetSearchResults(String query, Type type, boolean resolve){
super(HttpMethod.GET, "/search", SearchResults.class);
addQueryParameter("q", query);
if(type!=null)
addQueryParameter("type", type.name().toLowerCase());
if(resolve)
addQueryParameter("resolve", "true");
if(maxID!=null)
addQueryParameter("max_id", maxID);
if(offset>0)
addQueryParameter("offset", String.valueOf(offset));
if(count>0)
addQueryParameter("limit", String.valueOf(count));
}
public GetSearchResults limit(int limit){

View File

@@ -1,13 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.model.TranslatedStatus;
import java.util.Map;
public class TranslateStatus extends MastodonAPIRequest<Translation>{
public TranslateStatus(String id, String lang){
super(HttpMethod.POST, "/statuses/"+id+"/translate", Translation.class);
setRequestBody(Map.of("lang", lang));
}
public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> {
public TranslateStatus(String id) {
super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class);
setRequestBody(new Object());
}
}

View File

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

View File

@@ -1,11 +0,0 @@
package org.joinmastodon.android.api.requests.tags;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Hashtag;
public class SetTagFollowed extends MastodonAPIRequest<Hashtag>{
public SetTagFollowed(String tag, boolean followed){
super(HttpMethod.POST, "/tags/"+tag+(followed ? "/follow" : "/unfollow"), Hashtag.class);
setRequestBody(new Object());
}
}

View File

@@ -6,14 +6,9 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
import android.content.SharedPreferences;
import androidx.annotation.StringRes;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.TimelineDefinition;
import java.lang.reflect.Type;
@@ -41,14 +36,12 @@ public class AccountLocalPreferences{
public String publishButtonText;
public String timelineReplyVisibility; // akkoma-only
public boolean keepOnlyLatestNotification;
public boolean emojiReactionsEnabled;
public ShowEmojiReactions showEmojiReactions;
public ColorPreference color;
public ArrayList<Emoji> recentCustomEmoji;
private final static Type recentLanguagesType=new TypeToken<ArrayList<String>>() {}.getType();
private final static Type timelinesType=new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
private final static Type recentCustomEmojiType=new TypeToken<ArrayList<Emoji>>() {}.getType();
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
public AccountLocalPreferences(SharedPreferences prefs, AccountSession session){
this.prefs=prefs;
@@ -73,8 +66,6 @@ public class AccountLocalPreferences{
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
}
public long getNotificationsPauseEndTime(){
@@ -85,10 +76,6 @@ public class AccountLocalPreferences{
prefs.edit().putLong("notificationsPauseTime", time).apply();
}
public ColorPreference getCurrentColor(){
return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3;
}
public void save(){
prefs.edit()
.putBoolean("interactionCounts", showInteractionCounts)
@@ -112,35 +99,9 @@ public class AccountLocalPreferences{
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
.putString("showEmojiReactions", showEmojiReactions.name())
.putString("color", color!=null ? color.name() : null)
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))
.apply();
}
public enum ColorPreference{
MATERIAL3,
PINK,
PURPLE,
GREEN,
BLUE,
BROWN,
RED,
YELLOW;
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
};
}
}
public enum ShowEmojiReactions{
HIDE_EMPTY,
ONLY_OPENED,

View File

@@ -40,6 +40,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -218,7 +219,7 @@ public class AccountSession{
public void savePreferencesIfPending(){
if(preferencesNeedSaving){
new UpdateAccountCredentialsPreferences(preferences, self.locked, self.discoverable, self.source.indexable)
new UpdateAccountCredentialsPreferences(preferences, null, null)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Account result){
@@ -254,75 +255,52 @@ public class AccountSession{
filterStatusContainingObjects(objects, extractor, context, null);
}
private boolean statusIsOnOwnProfile(Status s, Account profile){
return self != null && profile != null && s.account != null
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
}
private boolean isFilteredType(Status s){
return (!localPreferences.showReplies && s.inReplyToId != null)
|| (!localPreferences.showBoosts && s.reblog != null);
}
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
Predicate<Status> statusIsOnOwnProfile = (s) -> self != null && profile != null && s.account != null
&& Objects.equals(self.id, profile.id) && Objects.equals(self.id, s.account.id);
if(getLocalPreferences().serverSideFiltersSupported){
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
objects.removeIf(o->{
Status s=extractor.apply(o);
if(s==null)
return false;
if(s.filtered==null)
return false;
// don't hide own posts in own profile
if (statusIsOnOwnProfile.test(s))
return false;
for(FilterResult filter:s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;
}
return false;
});
return;
}
if(wordFilters==null)
return;
for(T obj:objects){
Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){
localPreferences.serverSideFiltersSupported=true;
localPreferences.save();
break;
getLocalPreferences().serverSideFiltersSupported=true;
getLocalPreferences().save();
return;
}
}
List<T> removeUs=new ArrayList<>();
for(int i=0; i<objects.size(); i++){
T o=objects.get(i);
if(filterStatusContainingObject(o, extractor, context, profile)){
Status s=extractor.apply(o);
removeUs.add(o);
if(s!=null && s.hasGapAfter!=null && i>0){
// oops, we're about to remove an item that has a gap after...
// gotta find the previous status that's not also about to be removed
for(int j=i-1; j>=0; j--){
T p=objects.get(j);
Status prev=extractor.apply(objects.get(j));
if(prev!=null && !removeUs.contains(p)){
prev.hasGapAfter=s.hasGapAfter;
break;
}
}
}
}
}
objects.removeAll(removeUs);
}
public <T> boolean filterStatusContainingObject(T object, Function<T, Status> extractor, FilterContext context, Account profile){
Status s=extractor.apply(object);
if(s==null)
return false;
// don't hide own posts in own profile
if(statusIsOnOwnProfile(s, profile))
return false;
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
return true;
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
if(localPreferences.serverSideFiltersSupported){
for(FilterResult filter : s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;
}
}else if(wordFilters!=null){
for(LegacyFilter filter : wordFilters){
objects.removeIf(o->{
Status s=extractor.apply(o);
if(s==null)
return false;
// don't hide own posts in own profile
if (statusIsOnOwnProfile.test(s))
return false;
for(LegacyFilter filter:wordFilters){
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
return true;
}
}
return false;
}
public void updateAccountInfo(){
AccountSessionManager.getInstance().updateSessionLocalInfo(this);
return false;
});
}
public Optional<Instance> getInstance() {
@@ -335,10 +313,4 @@ public class AccountSession{
.authority(getInstance().map(i -> i.normalizedUri).orElse(domain))
.build();
}
public String getDefaultAvatarUrl() {
return getInstance()
.map(instance->"https://"+domain+(instance.isAkkoma() ? "/images/avi.png" : "/avatars/original/missing.png"))
.orElse("");
}
}

View File

@@ -303,7 +303,8 @@ public class AccountSessionManager{
}
}
/*package*/ void updateSessionLocalInfo(AccountSession session){
private void updateSessionLocalInfo(AccountSession session){
new GetOwnAccount()
.setCallback(new Callback<>(){
@Override

View File

@@ -1,11 +0,0 @@
package org.joinmastodon.android.events;
public class ReblogDeletedEvent{
public final String statusID;
public final String accountID;
public ReblogDeletedEvent(String statusID, String accountID){
this.statusID=statusID;
this.accountID=accountID;
}
}

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ScheduledStatus;
public class ScheduledStatusDeletedEvent{
public final String id;
public final String accountID;

View File

@@ -9,6 +9,5 @@ public class StatusCreatedEvent{
public StatusCreatedEvent(Status status, String accountID){
this.status=status;
this.accountID=accountID;
status.fromStatusCreated=true;
}
}

View File

@@ -9,16 +9,19 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUnpinnedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback;
@@ -52,14 +55,15 @@ public class AccountTimelineFragment extends StatusListFragment{
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetAccountStatuses(user.id, getMaxID(), null, count, filter)
currentRequest=new GetAccountStatuses(user.id, offset>0 ? getMaxID() : null, null, count, filter)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
boolean more=applyMaxID(result);
AccountSessionManager asm = AccountSessionManager.getInstance();
boolean empty=result.isEmpty();
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext(), user);
onDataLoaded(result, more);
onDataLoaded(result, !empty);
}
})
.exec(accountID);

View File

@@ -97,7 +97,7 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Announcement> result){
if(getActivity()==null) return;
if (getActivity() == null) return;
// get unread items first
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList());

View File

@@ -9,6 +9,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -21,7 +22,6 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account;
@@ -29,10 +29,9 @@ import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
@@ -56,7 +55,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -88,7 +86,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected Rect tmpRect=new Rect();
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
protected boolean currentlyScrolling;
protected String maxID;
public BaseStatusListFragment(){
super(20);
@@ -155,8 +152,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
protected String getMaxID(){
if(refreshing) return null;
if(maxID!=null) return maxID;
if(!preloadedData.isEmpty())
return preloadedData.get(preloadedData.size()-1).getID();
else if(!data.isEmpty())
@@ -165,12 +160,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null;
}
protected boolean applyMaxID(List<Status> result){
boolean empty=result.isEmpty();
if(!empty) maxID=result.get(result.size()-1).id;
return !empty;
}
protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
protected abstract void addAccountToKnown(T s);
@@ -557,21 +546,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
Status status = holder.getItem().status;
if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false);
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
if(mediaGrid!=null){
if(!status.sensitiveRevealed) mediaGrid.revealSensitive();
MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
if (mediaGrid != null) {
if (!status.sensitiveRevealed) mediaGrid.revealSensitive();
else mediaGrid.hideSensitive();
}else{
status.sensitiveRevealed=false;
notifyItemChangedAfter(holder.getItem(), MediaGridStatusDisplayItem.class);
} else {
// media grid's methods normally change the status' state - we still want to be able
// to do this if the media grid is not bound, tho - so, doing it ourselves here
status.sensitiveRevealed = !status.sensitiveRevealed;
}
holder.rebind();
}
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true);
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header != null) header.rebind();
}
protected void toggleSpoiler(Status status, String itemID){
@@ -580,8 +569,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
status.sensitiveRevealed = false;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null) spoiler.rebind();
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
if(spoiler!=null)
spoiler.rebind();
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
int index=displayItems.indexOf(spoilerItem);
@@ -593,32 +582,42 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
}
notifyItemChanged(itemID, TextStatusDisplayItem.class);
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null)
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.rebind();
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
if(header!=null)
header.rebind();
list.invalidateItemDecorations();
}
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
Status s=holder.getItem().status;
if(s.textExpandable!=expandable && list!=null) {
s.textExpandable=expandable;
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.bindCollapseButton();
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();
}
}
public void onToggleExpanded(Status status, String itemID) {
status.textExpanded = !status.textExpanded;
notifyItemChanged(itemID, TextStatusDisplayItem.class);
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.animateExpandToggle();
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
if (text != null) text.rebind();
if (header != null) header.rebind();
}
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
public void updateEmojiReactions(Status status, String itemID){
EmojiReactionsStatusDisplayItem.Holder reactions=findHolderOfType(itemID, EmojiReactionsStatusDisplayItem.Holder.class);
if(reactions != null){
reactions.getItem().status.reactions.clear();
reactions.getItem().status.reactions.addAll(status.reactions);
reactions.rebind();
}
}
public void onGapClick(GapStatusDisplayItem.Holder item){}
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
int startPos = warning.getAbsoluteAdapterPosition();
@@ -674,61 +673,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null;
}
/**
* Use this as a fallback if findHolderOfType fails to find the ViewHolder.
* It might still be bound but off-screen and therefore not a child of the RecyclerView -
* resulting in the ViewHolder displaying an outdated state once scrolled back into view.
*/
protected <I extends StatusDisplayItem> int notifyItemChanged(String id, Class<I> type){
boolean encounteredParent=false;
for(int i=0; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
boolean idEquals=id.equals(item.parentID);
if(!encounteredParent && idEquals) encounteredParent=true; // reached top of the parent
else if(encounteredParent && !idEquals) break; // passed by bottom of the parent. man muss ja wissen wann schluss is
if(idEquals && type.isInstance(item)){
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedAfter(StatusDisplayItem afterThis, Class<I> type){
int startIndex=displayItems.indexOf(afterThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem");
String parentID=afterThis.parentID;
for(int i=startIndex; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class<I> type){
int startIndex=displayItems.indexOf(beforeThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem");
String parentID=beforeThis.parentID;
for(int i=startIndex; i>=0; i--){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
@Nullable
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
for(int i=0; i<list.getChildCount(); i++){
for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
return type.cast(holder);
@@ -825,67 +772,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
}
public void togglePostTranslation(Status status, String itemID){
switch(status.translationState){
case LOADING -> {
return;
}
case SHOWN -> {
status.translationState=Status.TranslationState.HIDDEN;
}
case HIDDEN -> {
if(status.translation!=null){
status.translationState=Status.TranslationState.SHOWN;
}else{
status.translationState=Status.TranslationState.LOADING;
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
.setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()==null)
return;
status.translation=result;
status.translationState=Status.TranslationState.SHOWN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
status.translationState=Status.TranslationState.HIDDEN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error)
.setMessage(R.string.translation_failed)
.setPositiveButton(R.string.ok, null)
.show();
}
})
.exec(accountID);
}
}
}
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
}
public void rebuildAllDisplayItems(){
displayItems.clear();
for(T item:data){
@@ -901,7 +787,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(getContext()==null) return;
super.onDataLoaded(d, more);
// more available, but the page isn't even full yet? seems wrong, let's load some more
if(more && data.size() < itemsPerPage){
if(more && d.size() < itemsPerPage){
preloader.onScrolledToLastItem();
}
}

View File

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

View File

@@ -110,11 +110,11 @@ import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -150,7 +150,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public LinearLayout mainLayout;
private SizeListenerLinearLayout contentView;
private TextView selfName, selfUsername, selfExtraText, extraText;
private TextView selfName, selfUsername, selfExtraText, extraText, pronouns;
private ImageView selfAvatar;
private Account self;
private String instanceDomain;
@@ -161,7 +161,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private int charCount, charLimit, trimmedCharCount;
private Button publishButton, languageButton, scheduleTimeBtn;
private PopupMenu contentTypePopup, visibilityPopup, draftOptionsPopup;
private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup;
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn;
private View sensitiveBtn;
private TextView replyText;
@@ -293,7 +293,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
creatingView=true;
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), accountID, customEmojis, instanceDomain);
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), customEmojis, instanceDomain);
emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){
@Override
public void onEmojiSelected(Emoji emoji){
@@ -326,7 +326,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
selfUsername=view.findViewById(R.id.self_username);
selfAvatar=view.findViewById(R.id.self_avatar);
selfExtraText=view.findViewById(R.id.self_extra_text);
HtmlParser.setTextWithCustomEmoji(selfName, self.getDisplayName(), self.emojis);
HtmlParser.setTextWithCustomEmoji(selfName, self.displayName, self.emojis);
selfUsername.setText('@'+self.username+'@'+instanceDomain);
if(self.avatar!=null)
ViewImageLoader.load(selfAvatar, null, new UrlImageLoaderRequest(self.avatar));
@@ -626,6 +626,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
});
View originalPost=view.findViewById(R.id.original_post);
extraText=view.findViewById(R.id.extra_text);
pronouns=view.findViewById(R.id.pronouns);
originalPost.setVisibility(View.VISIBLE);
originalPost.setOnClickListener(v->{
Bundle args=new Bundle();
@@ -665,7 +666,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
moreBtn.setBackground(null);
TextView name = view.findViewById(R.id.name);
name.setText(HtmlParser.parseCustomEmoji(status.account.getDisplayName(), status.account.emojis));
name.setText(HtmlParser.parseCustomEmoji(status.account.displayName, status.account.emojis));
UiUtils.loadCustomEmojiInTextView(name);
String time = status==null || status.editedAt==null
@@ -699,7 +700,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(16)));
}
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.getDisplayName()));
replyText.setText(getString(quote!=null? R.string.sk_quoting_user : R.string.in_reply_to, status.account.displayName));
int visibilityNameRes = switch (status.visibility) {
case PUBLIC -> R.string.visibility_public;
case UNLISTED -> R.string.sk_visibility_unlisted;
@@ -707,7 +708,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
case DIRECT -> R.string.visibility_private;
case LOCAL -> R.string.sk_local_only;
};
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.getDisplayName()) + ", " + getString(visibilityNameRes));
replyText.setContentDescription(getString(R.string.in_reply_to, status.account.displayName) + ", " + getString(visibilityNameRes));
replyText.setOnClickListener(v->{
scrollView.smoothScrollTo(0, 0);
});
@@ -796,7 +797,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
@@ -826,35 +826,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
publishButton = wrap.findViewById(R.id.publish_btn);
languageButton = wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
if(!getLocalPrefs().bottomEncoding){
getLocalPrefs().bottomEncoding=true;
getLocalPrefs().save();
}
return false;
});
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
publishButton.setOnClickListener(v->{
Consumer<Boolean> draftCheckComplete=(isDraft)->{
if(GlobalUserPreferences.altTextReminders && !isDraft) checkAltTextsAndPublish();
else publish();
};
boolean isAlreadyDraft=scheduledAt!=null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
if(editingStatus!=null && scheduledAt!=null && isAlreadyDraft) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w)->draftCheckComplete.accept(isAlreadyDraft))
.setNegativeButton(R.string.publish, (d, w)->{
updateScheduledAt(null);
draftCheckComplete.accept(false);
})
.show();
}else{
draftCheckComplete.accept(isAlreadyDraft);
}
publishButton.setOnClickListener(v -> {
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
checkAltTextsAndPublish();
else
publish();
});
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
@@ -866,8 +843,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
? languageResolver.fromOrFallback(prefs.postingDefaultLanguage)
: languageResolver.getDefault());
if(isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
if(isInstancePixelfed() || (editingStatus!=null && !redraftStatus)) {
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) {
// editing an already published post
draftsBtn.setVisibility(View.GONE);
}
@@ -991,7 +968,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override
protected int getNavigationIconDrawableResource(){
return R.drawable.ic_fluent_dismiss_24_regular;
return R.drawable.ic_baseline_close_24;
}
@Override
@@ -1067,16 +1044,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void actuallyPublish(){
actuallyPublish(false);
}
private void actuallyPublish(boolean force){
String text=mainEditText.getText().toString();
CreateStatus.Request req=new CreateStatus.Request();
if("bottom".equals(postLang.encoding)){
text=new StatusTextEncoder(Bottom::encode).encode(text);
req.spoilerText="bottom-encoded emoji spam";
if ("bottom".equals(postLang.encoding)) {
text = new StatusTextEncoder(Bottom::encode).encode(text);
req.spoilerText = "bottom-encoded emoji spam";
}
if(localOnly &&
if (localOnly &&
AccountSessionManager.get(accountID).getLocalPreferences().glitchInstance &&
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()){
text+=" "+GLITCH_LOCAL_ONLY_SUFFIX;
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()) {
text += " " + GLITCH_LOCAL_ONLY_SUFFIX;
}
req.status=text;
req.localOnly=localOnly;
@@ -1090,6 +1070,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.mediaAttributes=mediaViewController.getAttachmentAttributes();
}
}
// ask whether to publish now when editing an existing draft
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w) -> actuallyPublish(true))
.setNegativeButton(R.string.publish, (d, w) -> {
updateScheduledAt(null);
actuallyPublish();
})
.show();
return;
}
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
}
@@ -1284,20 +1277,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void confirmDiscardDraftAndFinish(){
boolean attachmentsPending=mediaViewController.areAnyAttachmentsNotDone();
if(attachmentsPending) new M3AlertDialogBuilder(getActivity())
boolean attachmentsPending = mediaViewController.areAnyAttachmentsNotDone();
if (attachmentsPending) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_unfinished_attachments)
.setMessage(R.string.sk_unfinished_attachments_message)
.setPositiveButton(R.string.ok, (d, w)->{})
.setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
.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)
.setPositiveButton(R.string.save, (d, w)->{
updateScheduledAt(scheduledAt==null ? getDraftInstant() : scheduledAt);
.setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
.setPositiveButton(R.string.save, (d, w) -> {
updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt);
publish();
})
.setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
.show();
}
@@ -1315,8 +1308,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
boolean usePhotoPicker=photoPicker && UiUtils.isPhotoPickerAvailable();
if(usePhotoPicker){
intent=new Intent(MediaStore.ACTION_PICK_IMAGES);
if(mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount()>1)
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, mediaViewController.getMaxAttachments()-mediaViewController.getMediaAttachmentsCount());
}else{
intent=new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
@@ -1454,8 +1446,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void updateHeaders() {
UiUtils.setExtraTextInfo(getContext(), selfExtraText, false, false, localOnly, null);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
UiUtils.setExtraTextInfo(getContext(), selfExtraText, null, false, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, pronouns, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
}
private void buildVisibilityPopup(View v){

View File

@@ -26,12 +26,12 @@ import android.widget.ImageView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.utils.ColorPalette;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FixedAspectRatioImageView;
import java.util.Collections;
@@ -54,17 +54,16 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment");
setHasOptionsMenu(true);
}
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment");
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().getCurrentColor())
.apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
ColorPalette.palettes.get(GlobalUserPreferences.color).apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK);
setTitle(R.string.add_alt_text);
}

View File

@@ -169,7 +169,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
}
private void updateOptionsMenu() {
if(getActivity()==null) return;
if (getActivity() == null) return;
optionsMenu.clear();
timelineByMenuItem.clear();
@@ -239,21 +239,16 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) {
if (tags == null || tags.isEmpty()) return false;
editText.setText(tags);
editText.chipifyAllUnterminatedTokens();
editText.setText(String.join(",", tags));
editText.chipifyAllUnterminatedTokens();
return true;
}
private NachoTextView prepareChipTextView(NachoTextView nacho) {
//Ill Be Back
nacho.setChipTerminators(
Map.of(
',', BEHAVIOR_CHIPIFY_ALL,
'\n', BEHAVIOR_CHIPIFY_ALL,
' ', BEHAVIOR_CHIPIFY_ALL,
';', BEHAVIOR_CHIPIFY_ALL
)
);
nacho.addChipTerminator(',', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator('\n', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator(' ', BEHAVIOR_CHIPIFY_ALL);
nacho.addChipTerminator(';', BEHAVIOR_CHIPIFY_ALL);
nacho.enableEditChipOnTouch(true, true);
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens());
return nacho;
@@ -349,7 +344,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
mainHashtag = name;
name = null;
}
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) {
if (TextUtils.isEmpty(mainHashtag)) {
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(null);
return;

View File

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

View File

@@ -45,8 +45,8 @@ public class FeaturedHashtagsListFragment extends BaseStatusListFragment<Hashtag
}
@Override
public void onItemClick(String id){
UiUtils.openHashtagTimeline(getActivity(), accountID, Objects.requireNonNull(findItemOfType(id, HashtagStatusDisplayItem.class)).tag);
public void onItemClick(String hashtag){
UiUtils.openHashtagTimeline(getActivity(), accountID, hashtag, data.stream().filter(h -> Objects.equals(h.name, hashtag)).findAny().map(h -> h.following).orElse(null));
}
@Override

View File

@@ -17,7 +17,6 @@ import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Relationship;
@@ -82,7 +81,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(HeaderPaginationList<Account> result){
if(getActivity()==null) return;
if (getActivity() == null) return;
if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else
@@ -270,7 +269,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id);
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account);
UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(relationship==null || !relationship.followedBy){
actionWrap.setVisibility(View.GONE);
@@ -358,16 +357,15 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
public AccountWrapper(Account account){
this.account=account;
avaRequest=new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.get(getAccountID()).getDefaultAvatarUrl() : account.avatar,
V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.avatar))
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.header))
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(account.emojis.isEmpty()){
parsedName= account.getDisplayName();
parsedName=account.displayName;
}else{
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis);
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
}
}

View File

@@ -56,7 +56,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(HeaderPaginationList<Hashtag> result){
if(getActivity()==null) return;
if (getActivity() == null) return;
if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else
@@ -121,7 +121,7 @@ public class FollowedHashtagsFragment extends MastodonRecyclerFragment<Hashtag>
@Override
public void onClick() {
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name);
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
}
}
}

View File

@@ -1,65 +1,46 @@
package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.view.HapticFeedbackConstants;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.tags.GetTag;
import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
import org.joinmastodon.android.api.requests.tags.GetHashtag;
import org.joinmastodon.android.api.requests.tags.SetHashtagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.HashtagUpdatedEvent;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.text.SpacerSpan;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton;
import org.parceler.Parcels;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List;
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.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V;
public class HashtagTimelineFragment extends PinnableStatusListFragment{
private Hashtag hashtag;
private String hashtagName;
private TextView headerTitle, headerSubtitle;
private ProgressBarButton followButton;
private ProgressBar followProgress;
private MenuItem followMenuItem, pinMenuItem;
private boolean followRequestRunning;
private boolean toolbarContentVisible;
public class HashtagTimelineFragment extends PinnableStatusListFragment {
private String hashtag;
private List<String> any;
private List<String> all;
private List<String> none;
private boolean following;
private boolean localOnly;
private Menu optionsMenu;
private MenuInflater optionsMenuInflater;
private MenuItem followButton;
@Override
protected boolean wantsComposeButton() {
@@ -69,36 +50,89 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
@Override
public void onAttach(Activity activity){
super.onAttach(activity);
updateTitle(getArguments().getString("hashtag"));
following=getArguments().getBoolean("following", false);
localOnly=getArguments().getBoolean("localOnly", false);
any=getArguments().getStringArrayList("any");
all=getArguments().getStringArrayList("all");
none=getArguments().getStringArrayList("none");
if(getArguments().containsKey("hashtag")){
hashtag=Parcels.unwrap(getArguments().getParcelable("hashtag"));
hashtagName=hashtag.name;
}else{
hashtagName=getArguments().getString("hashtagName");
}
setTitle('#'+hashtagName);
setHasOptionsMenu(true);
}
private void updateTitle(String hashtagName) {
hashtag = hashtagName;
setTitle('#'+hashtag);
}
private void updateFollowingState(boolean newFollowing) {
this.following = newFollowing;
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);
E.post(new HashtagUpdatedEvent(hashtag, following));
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
followButton = menu.findItem(R.id.follow_hashtag);
updateFollowingState(following);
new GetHashtag(hashtag).setCallback(new Callback<>() {
@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);
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
new SetHashtagFollowed(hashtag, following).setCallback(new Callback<>() {
@Override
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();
updateFollowingState(i.following);
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
updateFollowingState(!following);
}
}).exec(accountID);
return true;
}
return false;
}
@Override
protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofHashtag(hashtagName);
return TimelineDefinition.ofHashtag(hashtag);
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetHashtagTimeline(hashtagName, getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetHashtagTimeline(hashtag, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
boolean more=applyMaxID(result);
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty());
}
})
.exec(accountID);
@@ -112,40 +146,15 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
}
@Override
public void loadData(){
reloadTag();
super.loadData();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setOnClickListener(this::onFabClick);
if(getParentFragment() instanceof HomeTabFragment) return;
list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
View topChild=recyclerView.getChildAt(0);
int firstChildPos=recyclerView.getChildAdapterPosition(topChild);
float newAlpha=firstChildPos>0 ? 1f : Math.min(1f, -topChild.getTop()/(float)headerTitle.getHeight());
toolbarTitleView.setAlpha(newAlpha);
boolean newToolbarVisibility=newAlpha>0.5f;
if(newToolbarVisibility!=toolbarContentVisible){
toolbarContentVisible=newToolbarVisibility;
createOptionsMenu();
}
}
});
public boolean onFabLongClick(View v) {
return UiUtils.pickAccountForCompose(getActivity(), accountID, '#'+hashtag+' ');
}
@Override
public void onFabClick(View v){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("prefilledText", '#'+hashtagName+' ');
args.putString("prefilledText", '#'+hashtag+' ');
Nav.go(getActivity(), ComposeFragment.class, args);
}
@@ -161,202 +170,6 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
@Override
public Uri getWebUri(Uri.Builder base) {
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags/") + hashtag).build();
}
@Override
protected RecyclerView.Adapter getAdapter(){
View header=getActivity().getLayoutInflater().inflate(R.layout.header_hashtag_timeline, list, false);
headerTitle=header.findViewById(R.id.title);
headerSubtitle=header.findViewById(R.id.subtitle);
followButton=header.findViewById(R.id.profile_action_btn);
followProgress=header.findViewById(R.id.action_progress);
headerTitle.setText("#"+hashtagName);
followButton.setVisibility(View.GONE);
followButton.setOnClickListener(v->{
if(hashtag==null)
return;
setFollowed(!hashtag.following);
});
followButton.setOnLongClickListener(v->{
if(hashtag==null) return false;
UiUtils.pickAccount(getActivity(), accountID, R.string.sk_follow_as, R.drawable.ic_fluent_person_add_28_regular, session -> {
new SetTagFollowed(hashtagName, true).setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag hashtag) {
Toast.makeText(
getActivity(),
getString(R.string.sk_followed_as, session.self.getShortUsername()),
Toast.LENGTH_SHORT
).show();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(session.getID());
}, null);
return true;
});
updateHeader();
MergeRecyclerAdapter mergeAdapter=new MergeRecyclerAdapter();
if(!(getParentFragment() instanceof HomeTabFragment)){
mergeAdapter.addAdapter(new SingleViewRecyclerAdapter(header));
}
mergeAdapter.addAdapter(super.getAdapter());
return mergeAdapter;
}
@Override
protected int getMainAdapterOffset(){
return 1;
}
private void createOptionsMenu(){
optionsMenu.clear();
optionsMenuInflater.inflate(R.menu.hashtag_timeline, optionsMenu);
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
pinMenuItem=optionsMenu.findItem(R.id.pin);
followMenuItem.setVisible(toolbarContentVisible);
pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
super.updatePinButton(pinMenuItem);
if(toolbarContentVisible){
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu);
}else{
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.pin);
}
}
@Override
public void updatePinButton(MenuItem pin){
super.updatePinButton(pin);
if(toolbarContentVisible) UiUtils.insetPopupMenuIcon(getContext(), pin);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.hashtag_timeline, menu);
super.onCreateOptionsMenu(menu, inflater);
optionsMenu=menu;
optionsMenuInflater=inflater;
createOptionsMenu();
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.follow_hashtag && hashtag!=null) {
setFollowed(!hashtag.following);
}
return true;
}
@Override
protected void onUpdateToolbar(){
super.onUpdateToolbar();
toolbarTitleView.setAlpha(toolbarContentVisible ? 1f : 0f);
createOptionsMenu();
}
private void updateHeader(){
if(hashtag==null || getActivity()==null)
return;
if(hashtag.history!=null && !hashtag.history.isEmpty()){
int weekPosts=hashtag.history.stream().mapToInt(h->h.uses).sum();
int todayPosts=hashtag.history.get(0).uses;
int numAccounts=hashtag.history.stream().mapToInt(h->h.accounts).sum();
int hSpace=V.dp(8);
SpannableStringBuilder ssb=new SpannableStringBuilder();
ssb.append(getResources().getQuantityString(R.plurals.x_posts, weekPosts, weekPosts));
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append('·');
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append(getResources().getQuantityString(R.plurals.x_participants, numAccounts, numAccounts));
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append('·');
ssb.append(" ", new SpacerSpan(hSpace, 0), 0);
ssb.append(getResources().getQuantityString(R.plurals.x_posts_today, todayPosts, todayPosts));
headerSubtitle.setText(ssb);
}
int styleRes;
followButton.setVisibility(View.VISIBLE);
if(hashtag.following){
followButton.setText(R.string.button_following);
styleRes=R.style.Widget_Mastodon_M3_Button_Tonal;
}else{
followButton.setText(R.string.button_follow);
styleRes=R.style.Widget_Mastodon_M3_Button_Filled;
}
TypedArray ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.background});
followButton.setBackground(ta.getDrawable(0));
ta.recycle();
ta=followButton.getContext().obtainStyledAttributes(styleRes, new int[]{android.R.attr.textColor});
followButton.setTextColor(ta.getColorStateList(0));
followProgress.setIndeterminateTintList(ta.getColorStateList(0));
ta.recycle();
followButton.setTextVisible(true);
followProgress.setVisibility(View.GONE);
if(followMenuItem!=null){
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), followMenuItem);
}
}
private void reloadTag(){
new GetTag(hashtagName)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
hashtag=result;
updateHeader();
}
@Override
public void onError(ErrorResponse error){
}
})
.exec(accountID);
}
private void setFollowed(boolean followed){
if(followRequestRunning)
return;
getToolbar().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
followButton.setTextVisible(false);
followProgress.setVisibility(View.VISIBLE);
followRequestRunning=true;
new SetTagFollowed(hashtagName, followed)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Hashtag result){
if(getActivity()==null)
return;
hashtag=result;
updateHeader();
followRequestRunning=false;
}
@Override
public void onError(ErrorResponse error){
if(getActivity()==null)
return;
if(error instanceof MastodonErrorResponse er && "Duplicate record".equals(er.error)){
hashtag.following=true;
}else{
error.showToast(getActivity());
}
updateHeader();
followRequestRunning=false;
}
})
.exec(accountID);
return base.path((isInstanceAkkoma() ? "/tag/" : "/tags") + hashtag).build();
}
}

View File

@@ -32,6 +32,7 @@ import org.joinmastodon.android.events.NotificationsMarkerUpdatedEvent;
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
import org.joinmastodon.android.fragments.discover.DiscoverFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.PaginatedResponse;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
@@ -70,12 +71,14 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
private TextView notificationsBadge;
private String accountID;
private boolean isAkkoma;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
setTitle(R.string.sk_app_name);
isAkkoma = getInstance().map(Instance::isAkkoma).orElse(false);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
@@ -86,6 +89,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
homeTabFragment=new HomeTabFragment();
homeTabFragment.setArguments(args);
args=new Bundle(args);
args.putBoolean("disableDiscover", isAkkoma);
args.putBoolean("noAutoLoad", true);
discoverFragment=new DiscoverFragment();
discoverFragment.setArguments(args);
@@ -180,7 +184,6 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
});
}
}
tabBar.selectTab(currentTab);
return content;
}
@@ -292,7 +295,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene
if(tab==R.id.tab_profile){
ArrayList<String> options=new ArrayList<>();
for(AccountSession session:AccountSessionManager.getInstance().getLoggedInAccounts()){
options.add(session.self.getDisplayName()+"\n("+session.self.username+"@"+session.domain+")");
options.add(session.self.displayName+"\n("+session.self.username+"@"+session.domain+")");
}
new AccountSwitcherSheet(getActivity(), this).show();
return true;

View File

@@ -233,25 +233,21 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
ViewTreeObserver vto = getToolbar().getViewTreeObserver();
if (vto.isAlive()) {
vto.addOnGlobalLayoutListener(()->{
Toolbar t=getToolbar();
if(t==null) return;
int toolbarWidth=t.getWidth();
if(toolbarWidth==0) return;
vto.addOnGlobalLayoutListener(() -> {
Toolbar t = getToolbar();
if (t == null) return;
int toolbarWidth = t.getWidth();
if (toolbarWidth == 0) return;
int toolbarFrameWidth=toolbarFrame.getWidth();
int actionsWidth=toolbarWidth-toolbarFrameWidth;
// margin (4) + padding (12) + icon (24) + margin (8) + chevron (16) + padding (12)
int switcherWidth=V.dp(76);
FrameLayout parent=((FrameLayout) toolbarShowNewPostsBtn.getParent());
if(actionsWidth==parent.getPaddingStart()) return;
int paddingMax=Math.max(actionsWidth, switcherWidth);
int paddingEnd=(Math.max(0, switcherWidth-actionsWidth));
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(paddingMax, 0, paddingEnd, 0);
toolbarShowNewPostsBtn.setMaxWidth(toolbarWidth-paddingMax*2);
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);
@@ -291,7 +287,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
new GetAnnouncements(false).setCallback(new Callback<>() {
@Override
public void onSuccess(List<Announcement> result) {
if(getActivity()==null) return;
if (getActivity() == null) return;
if (result.stream().anyMatch(a -> !a.read)) {
announcementsBadged = true;
announcements.setVisible(false);
@@ -385,7 +381,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
}
private void updateOverflowMenu() {
if(getActivity()==null) return;
if (getActivity() == null) return;
Menu m = overflowPopup.getMenu();
m.clear();
overflowPopup.inflate(R.menu.home_overflow);
@@ -404,8 +400,9 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
addListsToOverflowMenu();
addHashtagsToOverflowMenu();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
m.setGroupDividerEnabled(true);
}
}
@Override
@@ -527,7 +524,9 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) {
UiUtils.openHashtagTimeline(getContext(), accountID, hashtag);
args.putString("hashtag", hashtag.name);
args.putBoolean("following", hashtag.following);
Nav.go(getActivity(), HashtagTimelineFragment.class, args);
}
return true;
}

View File

@@ -11,26 +11,27 @@ import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
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 HomeTimelineFragment extends StatusListFragment {
private HomeTabFragment parent;
@@ -49,6 +50,16 @@ public class HomeTimelineFragment extends StatusListFragment {
loadData();
}
private boolean typeFilterPredicate(Status s) {
AccountLocalPreferences lp=getLocalPrefs();
return (lp.showReplies || s.inReplyToId == null) &&
(lp.showBoosts || s.reblog == null);
}
private List<Status> filterPosts(List<Status> items) {
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
}
@Override
protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance()
@@ -56,12 +67,11 @@ public class HomeTimelineFragment extends StatusListFragment {
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
@Override
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
if(getActivity()==null) return;
boolean empty=result.items.isEmpty();
if (getActivity() == null) return;
List<Status> filteredItems = filterPosts(result.items);
maxID=result.maxID;
AccountSessionManager.get(accountID).filterStatuses(result.items, getFilterContext());
onDataLoaded(result.items, !empty);
if(result.isFromCache() && GlobalUserPreferences.loadNewPosts)
onDataLoaded(filteredItems, !result.items.isEmpty());
if(result.isFromCache())
loadNewPosts();
}
});
@@ -74,7 +84,7 @@ public class HomeTimelineFragment extends StatusListFragment {
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()){
if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
parent.hideNewPostsButton();
}
}
@@ -87,7 +97,7 @@ public class HomeTimelineFragment extends StatusListFragment {
if(!getArguments().getBoolean("noAutoLoad")){
if(!loaded && !dataLoading){
loadData();
}else if(!dataLoading && GlobalUserPreferences.loadNewPosts){
}else if(!dataLoading){
loadNewPosts();
}
}
@@ -117,46 +127,38 @@ public class HomeTimelineFragment extends StatusListFragment {
}
public void onStatusCreated(Status status){
if(status.reblog!=null) return;
prependItems(Collections.singletonList(status), true);
}
private void loadNewPosts(){
if (!GlobalUserPreferences.loadNewPosts) return;
dataLoading=true;
// we only care about the data that was actually retrieved from the timeline api since
// user-created statuses are probably in the wrong position
List<Status> dataFromTimeline=data.stream().filter(s->!s.fromStatusCreated).collect(Collectors.toList());
// The idea here is that we request the timeline such that if there are fewer than `limit` posts,
// 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=dataFromTimeline.size()>1 ? dataFromTimeline.get(1).id : "1";
String sinceID=data.size()>1 ? data.get(1).id : "1";
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
currentRequest=null;
dataLoading=false;
refreshDone();
result = filterPosts(result);
if(result.isEmpty() || getActivity()==null)
return;
Status last=result.get(result.size()-1);
List<Status> toAdd;
if(!dataFromTimeline.isEmpty() && last.id.equals(dataFromTimeline.get(0).id)){ // This part intersects with the existing one
toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post
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{
last.hasGapAfter=last.id;
result.get(result.size()-1).hasGapAfter=true;
toAdd=result;
}
if(!toAdd.isEmpty())
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(new ArrayList<>(toAdd), false);
// removing statuses that come up as duplicates (hopefully only posts and boosts that were locally created
// and thus were already prepended to the timeline earlier)
List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList());
toAdd.removeIf(s->existingIds.contains(s.getID()));
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
if(!toAdd.isEmpty()){
prependItems(toAdd, true);
if(parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
}
}
@@ -164,7 +166,6 @@ public class HomeTimelineFragment extends StatusListFragment {
public void onError(ErrorResponse error){
currentRequest=null;
dataLoading=false;
refreshDone();
}
})
.exec(accountID);
@@ -175,23 +176,15 @@ public class HomeTimelineFragment extends StatusListFragment {
}
@Override
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){
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();
gap.loading=true;
dataLoading=true;
String maxID=null;
String minID=null;
if (downwards) {
maxID=item.getItem().getMaxID();
} else {
int gapPos=displayItems.indexOf(gap);
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
minID=nextItem.parentID;
}
currentRequest=new GetHomeTimeline(maxID, minID, 20, null, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
@@ -202,107 +195,61 @@ public class HomeTimelineFragment extends StatusListFragment {
int gapPos=displayItems.indexOf(gap);
if(gapPos==-1)
return;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
if(result.isEmpty()){
displayItems.remove(gapPos);
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
Status gapStatus=getStatusByID(gap.parentID);
if(gapStatus!=null){
gapStatus.hasGapAfter=null;
gapStatus.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
}
}else{
if(downwards) {
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=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{
gapPostIndex++;
}
}
int endIndex=0;
for(Status s:result){
endIndex++;
if(idsBelowGap.contains(s.id))
break;
}
if(endIndex==result.size()){
Status last=result.get(result.size()-1);
last.hasGapAfter=last.id;
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().putHomeTimeline(Collections.singletonList(s), false);
}else{
result=result.subList(0, endIndex);
gapPostIndex++;
}
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
targetList.clear();
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
for(Status s:result){
if(idsBelowGap.contains(s.id))
break;
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().putHomeTimeline(insertedPosts, false);
} else {
String aboveGapID = gap.parentID;
int gapPostIndex = 0;
for (;gapPostIndex<data.size();gapPostIndex++){
if (Objects.equals(aboveGapID, data.get(gapPostIndex).id)) {
break;
}
}
// find if there's an overlap between the new data and the current data
int indexOfGapInResponse = 0;
for (;indexOfGapInResponse<result.size();indexOfGapInResponse++){
if (Objects.equals(aboveGapID, result.get(indexOfGapInResponse).id)) {
break;
}
}
// there is an overlap between new and current data
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
if(indexOfGapInResponse<result.size()){
result=result.subList(indexOfGapInResponse+1,result.size());
Optional<Status> gapStatus=data.stream()
.filter(s->Objects.equals(s.id, gap.parentID))
.findFirst();
if (gapStatus.isPresent()) {
gapStatus.get().hasGapAfter=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
}
targetList.clear();
} else {
gap.loading=false;
}
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
for(Status s:result){
targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s);
}
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, FilterContext.HOME);
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);
}
list.scrollToPosition(getMainAdapterOffset()+gapPos+targetList.size());
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
}
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, getFilterContext());
for(Status s:result){
if(idsBelowGap.contains(s.id))
break;
if(typeFilterPredicate(s) && filterPredicate.test(s)){
targetList.addAll(buildDisplayItems(s));
insertedPosts.add(s);
}
}
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
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().putHomeTimeline(insertedPosts, false);
}
}
@@ -331,8 +278,8 @@ public class HomeTimelineFragment extends StatusListFragment {
currentRequest=null;
dataLoading=false;
}
if(parent!=null) parent.hideNewPostsButton();
loadNewPosts();
if (parent != null) parent.hideNewPostsButton();
super.onRefresh();
}
@Override

View File

@@ -16,7 +16,6 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.lists.GetList;
import org.joinmastodon.android.api.requests.lists.UpdateList;
import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.FilterContext;
@@ -26,8 +25,10 @@ import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ListEditor;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
@@ -62,7 +63,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
new GetList(listID).setCallback(new Callback<>() {
@Override
public void onSuccess(ListTimeline listTimeline) {
if(getActivity()==null) return;
if (getActivity() == null) return;
// TODO: save updated info
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
@@ -100,7 +101,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
@Override
public void onSuccess(ListTimeline list) {
if(getActivity()==null) return;
if (getActivity() == null) return;
setTitle(list.title);
listTitle = list.title;
repliesPolicy = list.repliesPolicy;
@@ -133,14 +134,13 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
@Override
protected void doLoadData(int offset, int count) {
currentRequest=new GetListTimeline(listID, getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(List<Status> result) {
if(getActivity()==null) return;
boolean more=applyMaxID(result);
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty());
}
})
.exec(accountID);

View File

@@ -140,7 +140,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
.setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(List<ListTimeline> lists) {
if(getActivity()==null) return;
if (getActivity() == null) return;
for (ListTimeline l : lists) userInListBefore.put(l.id, true);
userInList.putAll(userInListBefore);
if (profileAccountId == null || !lists.isEmpty()) onDataLoaded(lists, false);
@@ -149,7 +149,7 @@ public class ListsFragment extends MastodonRecyclerFragment<ListTimeline> implem
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListsFragment.this) {
@Override
public void onSuccess(List<ListTimeline> allLists) {
if(getActivity()==null) return;
if (getActivity() == null) return;
List<ListTimeline> newLists = new ArrayList<>();
for (ListTimeline l : allLists) {
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);

View File

@@ -254,7 +254,7 @@ public class NotificationsFragment extends MastodonToolbarFragment implements Sc
new GetFollowRequests(null, 1).setCallback(new Callback<>() {
@Override
public void onSuccess(HeaderPaginationList<Account> accounts) {
if(getActivity()==null) return;
if (getActivity() == null) return;
getToolbar().getMenu().findItem(R.id.follow_requests).setVisible(!accounts.isEmpty());
}

View File

@@ -96,7 +96,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
if (n.type == Notification.Type.FOLLOW_REQUEST) {
ArrayList<StatusDisplayItem> items = new ArrayList<>();
items.add(titleItem);
items.add(new AccountCardStatusDisplayItem(n.id, this, accountID, n.account, n));
items.add(new AccountCardStatusDisplayItem(n.id, this, n.account, n));
return items;
}
if(n.status!=null){
@@ -134,7 +134,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
return;
maxID=result.maxID;
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
if(bannerHelper!=null) bannerHelper.onBannerBecameVisible();
reloadingFromCache=false;
if (getParentFragment() instanceof NotificationsFragment nf) {
nf.updateMarkAllReadButton();
@@ -238,7 +237,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
continue;
Status contentStatus=ntf.status.getContentStatus();
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
updatePoll(ntf.id, contentStatus, ev.poll);
updatePoll(ntf.id, ntf.status, ev.poll);
}
}
}

View File

@@ -5,7 +5,6 @@ import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
@@ -36,8 +35,6 @@ public class PinnedPostsListFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, false);
}
}).exec(accountID);

View File

@@ -23,6 +23,7 @@ import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.transition.ChangeBounds;
import android.transition.Fade;
import android.transition.TransitionManager;
@@ -59,10 +60,8 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.account_list.BlockedAccountsListFragment;
import org.joinmastodon.android.fragments.account_list.FollowerListFragment;
import org.joinmastodon.android.fragments.account_list.FollowingListFragment;
import org.joinmastodon.android.fragments.account_list.MutedAccountsListFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
import org.joinmastodon.android.model.Account;
@@ -133,7 +132,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ImageView avatar;
private CoverImageView cover;
private View avatarBorder;
private View usernameWrap;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
private ImageView lockIcon, botIcon;
private ProgressBarButton actionButton, notifyButton;
@@ -234,7 +232,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
cover=content.findViewById(R.id.cover);
avatarBorder=content.findViewById(R.id.avatar_border);
name=content.findViewById(R.id.name);
usernameWrap=content.findViewById(R.id.username_wrap);
username=content.findViewById(R.id.username);
lockIcon=content.findViewById(R.id.lock_icon);
botIcon=content.findViewById(R.id.bot_icon);
@@ -307,12 +304,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
tabbar.setTabTextColors(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurfaceVariant), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
tabbar.setTabTextSize(V.dp(14));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, (tab, position)->tab.setText(switch(position){
case 0 -> R.string.profile_featured;
case 1 -> R.string.profile_timeline;
case 2 -> R.string.profile_about;
default -> throw new IllegalStateException();
}));
tabLayoutMediator=new TabLayoutMediator(tabbar, pager, new TabLayoutMediator.TabConfigurationStrategy(){
@Override
public void onConfigureTab(@NonNull TabLayout.Tab tab, int position){
@@ -326,19 +317,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if (position == 4) tab.view.setVisibility(View.GONE);
}
});
tabbar.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
@Override
public void onTabSelected(TabLayout.Tab tab){}
@Override
public void onTabUnselected(TabLayout.Tab tab){}
@Override
public void onTabReselected(TabLayout.Tab tab){
if(getFragmentForPage(tab.getPosition()) instanceof ScrollableToTop stt)
stt.scrollToTop();
}
});
cover.setOutlineProvider(new ViewOutlineProvider(){
@Override
@@ -399,7 +377,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
});
content.findViewById(R.id.username_wrap).setOnLongClickListener(v->{
username.setOnLongClickListener(v->{
String usernameString=account.acct;
if(!usernameString.contains("@")){
usernameString+="@"+domain;
@@ -482,7 +460,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(Account result){
if(getActivity()==null) return;
if (getActivity() == null) return;
onAccountLoaded(result);
}
})
@@ -621,14 +599,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@SuppressLint("SetTextI18n")
private void bindHeaderView(){
setTitle(account.getDisplayName());
setTitle(account.displayName);
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() :
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
V.dp(100), V.dp(100)));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(100), V.dp(100)));
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000));
SpannableStringBuilder ssb=new SpannableStringBuilder(account.getDisplayName());
SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName);
if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
HtmlParser.parseCustomEmoji(ssb, account.emojis);
name.setText(ssb);
@@ -755,21 +730,20 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
MenuItem openWithAccounts = menu.findItem(R.id.open_with_account);
openWithAccounts.setVisible(hasMultipleAccounts);
SubMenu accountsMenu=openWithAccounts.getSubMenu();
if(hasMultipleAccounts){
SubMenu accountsMenu = openWithAccounts.getSubMenu();
if (hasMultipleAccounts) {
accountsMenu.clear();
UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL(
getActivity(), s.getID(), account.url, false
));
}
menu.findItem(R.id.share).setTitle(R.string.share_user);
if(isOwnProfile) {
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
return;
}
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
MenuItem mute=menu.findItem(R.id.mute);
MenuItem mute = menu.findItem(R.id.mute);
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), mute);
@@ -777,22 +751,19 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
if (relationship.following) {
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
hideBoosts.setVisible(true);
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
} else {
hideBoosts.setVisible(false);
}
MenuItem blockDomain=menu.findItem(R.id.block_domain);
if(!account.isLocal()){
blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
blockDomain.setVisible(true);
}else{
blockDomain.setVisible(false);
menu.findItem(R.id.hide_boosts).setVisible(false);
}
if(!account.isLocal())
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
else
menu.findItem(R.id.block_domain).setVisible(false);
}
@Override
@@ -804,11 +775,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
intent.putExtra(Intent.EXTRA_TEXT, account.url);
startActivity(Intent.createChooser(intent, item.getTitle()));
}else if(id==R.id.mute){
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
confirmToggleMuted();
}else if(id==R.id.block){
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
confirmToggleBlocked();
}else if(id==R.id.soft_block){
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
confirmSoftBlockUser();
}else if(id==R.id.report){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -853,16 +824,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
args.putString("profileDisplayUsername", account.getDisplayUsername());
}
Nav.go(getActivity(), ListsFragment.class, args);
}else if(id==R.id.muted_accounts){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), MutedAccountsListFragment.class, args);
}else if(id==R.id.blocked_accounts){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), BlockedAccountsListFragment.class, args);
}else if(id==R.id.followed_hashtags){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -898,7 +859,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
private void updateRelationship(){
if(getActivity()==null) return;
if (getActivity() == null) return;
invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
@@ -1105,7 +1066,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
updateMetadataHeight();
Toolbar toolbar=getToolbar();
Drawable close=getToolbarContext().getDrawable(R.drawable.ic_fluent_dismiss_24_regular).mutate();
Drawable close=getToolbarContext().getDrawable(R.drawable.ic_baseline_close_24).mutate();
close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant));
toolbar.setNavigationIcon(close);
toolbar.setNavigationContentDescription(R.string.discard);
@@ -1120,7 +1081,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
name.setVisibility(View.GONE);
rolesView.setVisibility(View.GONE);
usernameWrap.setVisibility(View.GONE);
username.setVisibility(View.GONE);
bio.setVisibility(View.GONE);
countersLayout.setVisibility(View.GONE);
@@ -1169,7 +1130,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bioEditWrap.setVisibility(View.GONE);
name.setVisibility(View.VISIBLE);
rolesView.setVisibility(View.VISIBLE);
usernameWrap.setVisibility(View.VISIBLE);
username.setVisibility(View.VISIBLE);
bio.setVisibility(View.VISIBLE);
countersLayout.setVisibility(View.VISIBLE);
refreshLayout.setEnabled(true);
@@ -1181,7 +1142,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
V.setVisibilityAnimated(fab, View.VISIBLE);
bindHeaderView();
V.setVisibilityAnimated(fab, View.VISIBLE);
}
private void saveAndExitEditMode(){
@@ -1196,7 +1156,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
savingEdits=false;
account=result;
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
if(getActivity()==null) return;
if (getActivity() == null) return;
exitEditMode();
setActionProgressVisible(false);
}
@@ -1211,6 +1171,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.exec(accountID);
}
private void confirmToggleMuted(){
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
}
private void confirmToggleBlocked(){
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
}
private void confirmSoftBlockUser(){
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
}
private void updateRelationship(Relationship r){
relationship=r;
updateRelationship();
@@ -1257,7 +1229,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
if(ava==null)
return;
int radius=V.dp(25);
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : account.avatar, ava), 0,
currentPhotoViewer=new PhotoViewer(getActivity(), createFakeAttachments(account.avatar, ava), 0,
new SingleImagePhotoViewerListener(avatar, avatarBorder, new int[]{radius, radius, radius, radius}, this, ()->currentPhotoViewer=null, ()->ava, null, null));
}
}
@@ -1329,7 +1301,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=new FrameLayout(parent.getContext());
FrameLayout view=tabViews[viewType];
if (view.getParent() != null) ((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@@ -1337,13 +1311,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
Fragment fragment=getFragmentForPage(position);
FrameLayout fragmentView=tabViews[position];
fragmentView.setVisibility(View.VISIBLE);
if(fragmentView.getParent() instanceof ViewGroup parent)
parent.removeView(fragmentView);
((FrameLayout)holder.itemView).addView(fragmentView);
if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(fragmentView.getId(), fragment).commit();
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){

View File

@@ -2,11 +2,8 @@ package org.joinmastodon.android.fragments;
import android.app.Activity;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.ImageButton;
import com.squareup.otto.Subscribe;
@@ -29,7 +26,6 @@ import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class ScheduledStatusListFragment extends BaseStatusListFragment<ScheduledStatus> {
private String nextMaxID;
@@ -119,15 +115,6 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
}
}
@Override
protected void onShown(){
super.onShown();
// because, for some reason, when navigating back from compose fragment,
// match_parent would otherwise be incorrect (leaving a gap for the keyboard
// where there is none)
list.post(list::requestLayout);
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
@@ -138,7 +125,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else
nextMaxID=null;
if(getActivity()==null) return;
if (getActivity() == null) return;
onDataLoaded(result, nextMaxID!=null);
}
})
@@ -199,21 +186,6 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
return null;
}
@Override
public void onApplyWindowInsets(WindowInsets insets){
if(contentView!=null){
if(Build.VERSION.SDK_INT>=29 && insets.getTappableElementInsets().bottom==0){
int insetBottom=insets.getSystemWindowInsetBottom();
((ViewGroup.MarginLayoutParams) list.getLayoutParams()).bottomMargin=insetBottom;
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16)+insetBottom;
insets=insets.inset(0, 0, 0, insetBottom);
}else{
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(16);
}
}
super.onApplyWindowInsets(insets);
}
@Override
public Uri getWebUri(Uri.Builder base) {
// TODO: adapt when frontends finally implement a scheduled posts list

View File

@@ -47,12 +47,13 @@ public class SplashFragment extends AppKitFragment{
private ProgressBarButton defaultServerButton;
private ProgressBar defaultServerProgress;
private String chosenDefaultServer=DEFAULT_SERVER;
private boolean loadingDefaultServer, loadedDefaultServer;
private boolean loadingDefaultServer;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
motionEffect=new InterpolatingMotionEffect(MastodonApp.context);
loadAndChooseDefaultServer();
}
@Nullable
@@ -100,8 +101,6 @@ public class SplashFragment extends AppKitFragment{
});
}
});
if(!loadedDefaultServer && !loadingDefaultServer)
loadAndChooseDefaultServer();
return contentView;
}
@@ -240,7 +239,6 @@ public class SplashFragment extends AppKitFragment{
private void setChosenDefaultServer(String domain){
chosenDefaultServer=domain;
loadingDefaultServer=false;
loadedDefaultServer=true;
if(defaultServerButton!=null && getActivity()!=null){
defaultServerButton.setTextVisible(true);
defaultServerProgress.setVisibility(View.GONE);

View File

@@ -9,7 +9,6 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
@@ -48,8 +47,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
if (getActivity() == null) return;
onDataLoaded(result, false);
}
})
@@ -144,8 +143,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
}
}
String sep = getString(R.string.sk_separator);
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s));
items.add(1, new DummyStatusDisplayItem(s.id, this));
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
}
return items;
}

View File

@@ -9,12 +9,10 @@ import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
@@ -30,12 +28,7 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -49,12 +42,10 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id);
int flags = 0;
AccountLocalPreferences lp=getLocalPrefs();
if(GlobalUserPreferences.spectatorMode)
if (GlobalUserPreferences.spectatorMode)
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
if(!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
if (!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS;
if(GlobalUserPreferences.translateButtonOpenedOnly)
flags |= StatusDisplayItem.FLAG_NO_TRANSLATE;
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags);
}
@@ -149,12 +140,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
}
}
public Status getContentStatusByID(String id){
protected Status getContentStatusByID(String id){
Status s=getStatusByID(id);
return s==null ? null : s.getContentStatus();
}
public Status getStatusByID(String id){
protected Status getStatusByID(String id){
for(Status s:data){
if(s.id.equals(id)){
return s;
@@ -181,71 +172,41 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
}
}
private boolean removeStatusDisplayItems(String parentID, int firstIndex, int ancestorFirstIndex, int ancestorLastIndex){
// did we find an ancestor that is also the status' neighbor?
if(ancestorFirstIndex>=0 && ancestorLastIndex==firstIndex-1){
// update ancestor to have no descendant anymore
displayItems.subList(ancestorFirstIndex, ancestorLastIndex+1).forEach(i->i.hasDescendantNeighbor=false);
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex-ancestorFirstIndex+1);
}
if(firstIndex==-1) return false;
int lastIndex=firstIndex;
while(lastIndex<displayItems.size()){
if(!displayItems.get(lastIndex).parentID.equals(parentID)) break;
lastIndex++;
}
int count=lastIndex-firstIndex;
displayItems.subList(firstIndex, lastIndex).clear();
adapter.notifyItemRangeRemoved(firstIndex, count);
return true;
}
protected void removeStatus(Status status){
final AccountSessionManager asm=AccountSessionManager.getInstance();
final CacheController cache=AccountSessionManager.get(accountID).getCacheController();
final boolean unReblogging=status.reblog!=null && asm.isSelf(accountID, status.account);
final Predicate<Status> isToBeRemovedReblog=item->item!=null && item.reblog!=null
&& item.reblog.id.equals(status.reblog.id)
&& asm.isSelf(accountID, item.account);
final BiPredicate<String, Supplier<String>> isToBeRemovedContent=(parentId, contentIdSupplier)->
parentId.equals(status.id) || contentIdSupplier.get().equals(status.id);
int ancestorFirstIndex=-1, ancestorLastIndex=-1;
data.remove(status);
preloadedData.remove(status);
int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
for(int i=0;i<displayItems.size();i++){
StatusDisplayItem item=displayItems.get(i);
// we found a status that the to-be-removed status replies to!
// storing indices to maybe update its display items
if(item.parentID.equals(status.inReplyToId)){
if(ancestorFirstIndex==-1) ancestorFirstIndex=i;
ancestorLastIndex=i;
StatusDisplayItem item = displayItems.get(i);
if(status.id.equals(item.parentID)){
index=i;
break;
}
// if we're un-reblogging, we compare the reblogged status's id with the current status's
if(unReblogging
? isToBeRemovedReblog.test(getStatusByID(item.parentID))
: isToBeRemovedContent.test(item.parentID, item::getContentStatusID)){
// if statuses are removed from index i, the next iteration should be on the same index again
if(removeStatusDisplayItems(item.parentID, i, ancestorFirstIndex, ancestorLastIndex)) i--;
// resetting in case we find another occurrence of the same status that also has ancestors
// (we won't - unless the timeline is being especially weird)
ancestorFirstIndex=-1; ancestorLastIndex=-1;
if (item.parentID.equals(status.inReplyToId)) {
if (ancestorFirstIndex == -1) ancestorFirstIndex = i;
ancestorLastIndex = i;
}
}
Consumer<List<Status>> removeStatusFromData=(list)->{
Iterator<Status> it=list.iterator();
while(it.hasNext()){
Status s=it.next();
if(unReblogging
? isToBeRemovedReblog.test(s)
: isToBeRemovedContent.test(s.id, s::getContentStatusID)){
it.remove();
cache.deleteStatus(s.id);
}
// did we find an ancestor that is also the status' neighbor?
if (ancestorFirstIndex >= 0 && ancestorLastIndex == index - 1) {
for (int i = ancestorFirstIndex; i <= ancestorLastIndex; i++) {
StatusDisplayItem item = displayItems.get(i);
// update ancestor to have no descendant anymore
if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
}
};
removeStatusFromData.accept(data);
removeStatusFromData.accept(preloadedData);
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
}
if(index==-1)
return;
int lastIndex;
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
if(!displayItems.get(lastIndex).parentID.equals(status.id))
break;
}
displayItems.subList(index, lastIndex).clear();
adapter.notifyItemRangeRemoved(index, lastIndex-index);
}
@Override
@@ -314,22 +275,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
removeStatus(status);
}
@Subscribe
public void onReblogDeleted(ReblogDeletedEvent ev){
AccountSessionManager asm=AccountSessionManager.getInstance();
if(!ev.accountID.equals(accountID))
return;
for(Status item : data){
boolean itemIsOwnReblog=item.reblog!=null
&& item.getContentStatusID().equals(ev.statusID)
&& asm.isSelf(accountID, item.account);
if(itemIsOwnReblog){
removeStatus(item);
break;
}
}
}
@Subscribe
public void onStatusCreated(StatusCreatedEvent ev){
if(!ev.accountID.equals(accountID))

View File

@@ -13,8 +13,8 @@ import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext;
@@ -24,13 +24,13 @@ import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.ArrayDeque;
@@ -64,7 +64,7 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus));
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.displayName), mainStatus.account.emojis));
transitionFinished = getArguments().getBoolean("noTransition", false);
}
@@ -105,12 +105,6 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
text.textSelectable=true;
else if(item instanceof FooterStatusDisplayItem footer)
footer.hideCounts=true;
else if(item instanceof SpoilerStatusDisplayItem spoiler){
for(StatusDisplayItem subItem:spoiler.contentItems){
if(subItem instanceof TextStatusDisplayItem text)
text.textSelectable=true;
}
}
}
}
@@ -194,8 +188,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
// TODO: figure out how this code works
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
filterStatuses(result.descendants);
filterStatuses(result.ancestors);
result.descendants=filterStatuses(result.descendants);
result.ancestors=filterStatuses(result.ancestors);
restoreStatusStates(result.descendants, oldData);
restoreStatusStates(result.ancestors, oldData);
@@ -331,8 +325,11 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
.collect(Collectors.toList());
}
private void filterStatuses(List<Status> statuses){
AccountSessionManager.get(accountID).filterStatuses(statuses, getFilterContext());
private List<Status> filterStatuses(List<Status> statuses){
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext());
return statuses.stream()
.filter(statusFilterPredicate)
.collect(Collectors.toList());
}
@Override

View File

@@ -83,7 +83,7 @@ public abstract class BaseAccountListFragment extends MastodonRecyclerFragment<A
for(Relationship rel:result){
relationships.put(rel.id, rel);
}
if(getActivity()==null) return;
if (getActivity() == null) return;
if(list==null)
return;
for(int i=0;i<list.getChildCount();i++){

View File

@@ -1,36 +0,0 @@
package org.joinmastodon.android.fragments.account_list;
import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountBlocks;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
public class BlockedAccountsListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.sk_blocked_accounts);
}
@Override
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetAccountBlocks(maxID, count);
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
}
@Override
public Uri getWebUri(Uri.Builder base) {
return super.getWebUri(base).buildUpon()
.appendPath("/blocks").build();
}
}

View File

@@ -48,7 +48,7 @@ public class ComposeAccountSearchFragment extends BaseAccountListFragment{
@Override
protected void doLoadData(int offset, int count){
refreshing=true;
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false, null, 0, 0)
currentRequest=new GetSearchResults(currentQuery, GetSearchResults.Type.ACCOUNTS, false)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(SearchResults result){

View File

@@ -1,36 +0,0 @@
package org.joinmastodon.android.fragments.account_list;
import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.HeaderPaginationRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountMutes;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
public class MutedAccountsListFragment extends AccountRelatedAccountListFragment{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.sk_muted_accounts);
}
@Override
public HeaderPaginationRequest<Account> onCreateRequest(String maxID, int count){
return new GetAccountMutes(maxID, count);
}
@Override
protected void onConfigureViewHolder(AccountViewHolder holder){
super.onConfigureViewHolder(holder);
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
}
@Override
public Uri getWebUri(Uri.Builder base) {
return super.getWebUri(base).buildUpon()
.appendPath("/mutes").build();
}
}

View File

@@ -133,7 +133,7 @@ public abstract class PaginatedAccountListFragment<T> extends BaseAccountListFra
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else
nextMaxID=null;
if(getActivity()==null) return;
if (getActivity() == null) return;
List<AccountViewModel> items = result.stream()
.filter(a -> d.size() > 1000 || d.stream()
.noneMatch(i -> i.account.url.equals(a.url)))

View File

@@ -6,19 +6,21 @@ import android.os.Bundle;
import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
public class BubbleTimelineFragment extends StatusListFragment {
private DiscoverInfoBannerHelper bannerHelper;
private String maxID;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -34,15 +36,15 @@ public class BubbleTimelineFragment extends StatusListFragment {
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetBubbleTimeline(getMaxID(), count, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetBubbleTimeline(refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
boolean more=applyMaxID(result);
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
bannerHelper.onBannerBecameVisible();
if(!result.isEmpty())
maxID=result.get(result.size()-1).id;
if (getActivity() == null) return;
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
onDataLoaded(result, !result.isEmpty());
}
})
.exec(accountID);

View File

@@ -4,7 +4,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -17,7 +16,6 @@ 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.session.AccountSessionManager;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.MastodonRecyclerFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
@@ -66,8 +64,6 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
}
@Override
@@ -80,7 +76,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<FollowSuggestion> result){
if(getActivity()==null) return;
if (getActivity() == null) return;
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
loadRelationships();
}
@@ -115,7 +111,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public void onSuccess(List<Relationship> result){
relationshipsRequest=null;
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
if(getActivity()==null) return;
if (getActivity() == null) return;
if(list==null)
return;
for(int i=0;i<list.getChildCount();i++){
@@ -261,7 +257,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id);
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account);
UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(relationship==null){
actionWrap.setVisibility(View.GONE);
@@ -323,16 +319,15 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public AccountWrapper(Account account){
this.account=account;
avaRequest=new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? AccountSessionManager.getInstance().getAccount(accountID).getDefaultAvatarUrl() : account.avatar,
V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.avatar))
avaRequest=new UrlImageLoaderRequest(account.avatar, V.dp(50), V.dp(50));
if(!TextUtils.isEmpty(account.header))
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(account.emojis.isEmpty()){
parsedName= account.getDisplayName();
parsedName=account.displayName;
}else{
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis);
parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
}
}

View File

@@ -13,10 +13,8 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.SearchResult;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.SimpleViewHolder;
@@ -98,6 +96,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
@Override
public void onPageSelected(int position){
if(position==0)
return;
Fragment _page=getFragmentForPage(position);
if(_page instanceof BaseRecyclerFragment<?> page){
if(!page.loaded && !page.isDataLoading())
@@ -157,7 +157,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
}
});
disableDiscover=AccountSessionManager.get(accountID).getInstance().map(Instance::isAkkoma).orElse(false);
disableDiscover=getArguments().getBoolean("disableDiscover");
searchView=view.findViewById(R.id.search_fragment);
if(searchFragment==null){
searchFragment=new SearchFragment();
@@ -289,19 +289,15 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=new FrameLayout(parent.getContext());
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
}
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){}
@Override
public int getItemCount(){

View File

@@ -2,8 +2,8 @@ package org.joinmastodon.android.fragments.discover;
import android.graphics.Rect;
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.widget.ImageView;
@@ -14,12 +14,14 @@ import org.joinmastodon.android.api.requests.trends.GetTrendingLinks;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.model.viewmodel.CardViewModel;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -30,9 +32,11 @@ 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.ListImageLoaderAdapter;
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
@@ -56,8 +60,6 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS, accountID);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
}
@Override

View File

@@ -4,7 +4,6 @@ import android.net.Uri;
import android.os.Bundle;
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Status;
@@ -18,7 +17,6 @@ import me.grishka.appkit.utils.MergeRecyclerAdapter;
public class DiscoverPostsFragment extends StatusListFragment{
private DiscoverInfoBannerHelper bannerHelper;
private int offset;
@Override
public void onCreate(Bundle savedInstanceState){
@@ -27,17 +25,12 @@ public class DiscoverPostsFragment extends StatusListFragment{
}
@Override
protected void doLoadData(int o, int count){
if(refreshing) offset=0;
protected void doLoadData(int offset, int count){
currentRequest=new GetTrendingStatuses(offset, count)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
boolean empty=result.isEmpty();
offset+=result.size();
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, !empty);
onDataLoaded(result, !result.isEmpty());
bannerHelper.onBannerBecameVisible();
}
}).exec(accountID);

View File

@@ -29,14 +29,15 @@ public class FederatedTimelineFragment extends StatusListFragment{
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(false, false, getMaxID(), count, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetPublicTimeline(false, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
boolean more=applyMaxID(result);
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
if(!result.isEmpty())
maxID=result.get(result.size()-1).id;
boolean empty=result.isEmpty();
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
onDataLoaded(result, !empty);
bannerHelper.onBannerBecameVisible();
}
})

View File

@@ -2,6 +2,7 @@ package org.joinmastodon.android.fragments.discover;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -29,14 +30,15 @@ public class LocalTimelineFragment extends StatusListFragment{
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetPublicTimeline(true, false, getMaxID(), count, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetPublicTimeline(true, false, refreshing ? null : maxID, count, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
boolean more=applyMaxID(result);
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
if(!result.isEmpty())
maxID=result.get(result.size()-1).id;
boolean empty=result.isEmpty();
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.PUBLIC);
onDataLoaded(result, !empty);
bannerHelper.onBannerBecameVisible();
}
})

View File

@@ -23,6 +23,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.tabs.TabLayout;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
@@ -30,13 +31,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.Objects;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class SearchFragment extends BaseStatusListFragment<SearchResult>{
@@ -97,7 +97,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
args.putParcelable("profileAccount", Parcels.wrap(res.account));
Nav.go(getActivity(), ProfileFragment.class, args);
}
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
case HASHTAG -> UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
case STATUS -> {
Status status=res.status.getContentStatus();
Bundle args=new Bundle();
@@ -113,7 +113,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
}
@Override
protected void doLoadData(int _offset, int count){
protected void doLoadData(int offset, int count){
GetSearchResults.Type type;
if(currentFilter.size()==1){
type=switch(currentFilter.iterator().next()){
@@ -128,22 +128,8 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
dataLoaded();
return;
}
String maxID=null;
// TODO server-side bug
/*int offset=0;
if(_offset>0){
if(type==GetSearchResults.Type.STATUSES){
if(!preloadedData.isEmpty())
maxID=preloadedData.get(preloadedData.size()-1).status.id;
else if(!data.isEmpty())
maxID=data.get(data.size()-1).status.id;
}else{
offset=_offset;
}
}*/
int offset=_offset;
currentRequest=new GetSearchResults(currentQuery, type, type==null, maxID, offset, type==null ? 0 : count)
.setCallback(new SimpleCallback<>(this){
currentRequest=new GetSearchResults(currentQuery, type, true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(SearchResults result){
ArrayList<SearchResult> results=new ArrayList<>();
@@ -156,18 +142,23 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult>{
results.add(new SearchResult(tag));
}
if(result.statuses!=null){
Set<String> alreadyLoadedStatuses=data.stream().filter(r->r.type==SearchResult.Type.STATUS).map(r->r.status.id).collect(Collectors.toSet());
for(Status status:result.statuses){
if(!alreadyLoadedStatuses.contains(status.id))
results.add(new SearchResult(status));
}
for(Status status:result.statuses)
results.add(new SearchResult(status));
}
prevDisplayItems=new ArrayList<>(displayItems);
unfilteredResults=results;
onDataLoaded(filterSearchResults(results), type!=null && !results.isEmpty());
onDataLoaded(filterSearchResults(results), false);
}
@Override
public void onError(ErrorResponse error){
currentRequest=null;
Activity a=getActivity();
if(a==null)
return;
error.showToast(a);
}
})
.setTimeout(180000) // 3 minutes (searches can take a long time)
.exec(accountID);
}

View File

@@ -10,6 +10,7 @@ import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
@@ -25,7 +26,7 @@ import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MainActivity;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.search.GetSearchResults;
import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -37,16 +38,19 @@ import org.joinmastodon.android.model.SearchResults;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.model.viewmodel.SearchResultViewModel;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.SearchViewHelper;
import org.joinmastodon.android.ui.adapters.GenericListItemsAdapter;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.joinmastodon.android.ui.viewholders.SimpleListItemViewHolder;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -115,14 +119,14 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
onDataLoaded(results.stream().map(sr->{
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
if(sr.type==SearchResult.Type.HASHTAG){
vm.hashtagItem.setOnClick(i->openHashtag(sr));
vm.hashtagItem.onClick=()->openHashtag(sr);
}
return vm;
}).collect(Collectors.toList()), false);
recentsHeader.setVisible(!data.isEmpty());
});
}else{
currentRequest=new GetSearchResults(currentQuery, null, false, null, 0, 0)
currentRequest=new GetSearchResults(currentQuery, null, false)
.limit(2)
.setCallback(new SimpleCallback<>(this){
@Override
@@ -132,7 +136,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
.map(sr->{
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
if(sr.type==SearchResult.Type.HASHTAG){
vm.hashtagItem.setOnClick(i->openHashtag(sr));
vm.hashtagItem.onClick=()->openHashtag(sr);
}
return vm;
})
@@ -377,72 +381,30 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}
private void openHashtag(SearchResult res){
wrapSuicideDialog(()->{
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
});
UiUtils.openHashtagTimeline(getActivity(), accountID, res.hashtag.name, res.hashtag.following);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putRecentSearch(res);
}
private boolean isInRecentMode(){
return TextUtils.isEmpty(currentQuery);
}
private void wrapSuicideDialog(Runnable r){
if(!GlobalUserPreferences.showSuicideHelp || currentQuery==null){
r.run();
return;
}
String[] terms=getContext().getString(R.string.sk_suicide_search_terms).toLowerCase().split(",");
String query=currentQuery.trim().toLowerCase();
boolean termMatches=false;
for(String term : terms){
if(query.contains(term)){
termMatches=true;
break;
}
}
if(!termMatches){
r.run();
return;
}
String url=getContext().getString(R.string.sk_suicide_helplines_url);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_search_suicide_title)
.setMessage(R.string.sk_search_suicide_message)
.setNegativeButton(R.string.sk_do_not_show_again, (dialog, which)->{
GlobalUserPreferences.showSuicideHelp = false;
GlobalUserPreferences.save();
r.run();
})
.setNeutralButton(R.string.sk_search_suicide_hotlines, (dialog, which)->UiUtils.launchWebBrowser(getContext(), url))
.setPositiveButton(R.string.ok, (dialog, which)->r.run())
.setOnDismissListener((dialog)->{})
.show();
}
private void onSearchViewEnter(){
if(TextUtils.isEmpty(currentQuery) || currentQuery.trim().isEmpty())
return;
wrapSuicideDialog(()->deliverResult(currentQuery, null));
deliverResult(currentQuery, null);
}
private void onOpenURLClick(ListItem<?> item_){
private void onOpenURLClick(){
UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false);
}
private void onGoToHashtagClick(ListItem<?> item_){
wrapSuicideDialog(()->{
String q=searchViewHelper.getQuery();
if(q.startsWith("#"))
q=q.substring(1);
UiUtils.openHashtagTimeline(getActivity(), accountID, q);
});
private void onGoToHashtagClick(){
String q=searchViewHelper.getQuery();
if(q.startsWith("#"))
q=q.substring(1);
UiUtils.openHashtagTimeline(getActivity(), accountID, q, null);
}
private void onGoToAccountClick(ListItem<?> item_){
private void onGoToAccountClick(){
String q=searchViewHelper.getQuery();
if(!q.startsWith("@")){
q="@"+q;
@@ -459,12 +421,12 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}).ifPresent(progress -> progress.wrapProgress((Activity) getContext(), R.string.loading, true));
}
private void onGoToStatusSearchClick(ListItem<?> item_){
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS));
private void onGoToStatusSearchClick(){
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS);
}
private void onGoToAccountSearchClick(ListItem<?> item_){
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT));
private void onGoToAccountSearchClick(){
deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT);
}
private void onClearRecentClick(){

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.fragments.discover;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
@@ -10,6 +9,8 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.HashtagChartView;
@@ -33,8 +34,6 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
}
@Override
@@ -106,7 +105,7 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
@Override
public void onClick(){
UiUtils.openHashtagTimeline(getActivity(), accountID, item);
UiUtils.openHashtagTimeline(getActivity(), accountID, item.name, item.following);
}
}
}

View File

@@ -165,7 +165,9 @@ public class AccountActivationFragment extends ToolbarFragment{
private void tryGetAccount(){
if(AccountSessionManager.getInstance().tryGetAccount(accountID)==null){
uiHandler.removeCallbacks(pollRunnable);
((MainActivity)getActivity()).restartHomeFragment();
getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class);
startActivity(intent);
return;
}
currentRequest=new GetOwnAccount()

View File

@@ -83,10 +83,9 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
@Override
protected void updateFilteredList(){
String query=getCurrentSearchQuery();
boolean addFakeInstance=query.length()>0 && query.matches("^\\S+\\.[^\\.]+$");
boolean addFakeInstance = currentSearchQuery.length()>0 && currentSearchQuery.matches("^\\S+\\.[^\\.]+$");
if(addFakeInstance){
fakeInstance.domain=fakeInstance.normalizedDomain=query;
fakeInstance.domain=fakeInstance.normalizedDomain=currentSearchQuery;
fakeInstance.description=getString(R.string.loading_instance);
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
@@ -100,12 +99,12 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
}
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
filteredData.clear();
if(query.length()>0){
if(currentSearchQuery.length()>0){
boolean foundExactMatch=false;
for(CatalogInstance inst:data){
if(inst.normalizedDomain.contains(query)){
if(inst.normalizedDomain.contains(currentSearchQuery)){
filteredData.add(inst);
if(inst.normalizedDomain.equals(query))
if(inst.normalizedDomain.equals(currentSearchQuery))
foundExactMatch=true;
}
}

View File

@@ -93,10 +93,10 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
updateFilteredList();
searchEdit.removeCallbacks(searchDebouncer);
Instance instance=instancesCache.get(normalizeInstanceDomain(getCurrentSearchQuery()));
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
if(instance==null){
showProgressDialog();
loadInstanceInfo(getCurrentSearchQuery(), false);
loadInstanceInfo(currentSearchQuery, false);
}else{
proceedWithAuthOrSignup(instance);
}
@@ -106,7 +106,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
protected void onSearchChangedDebounced(){
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
updateFilteredList();
loadInstanceInfo(getCurrentSearchQuery(), false);
loadInstanceInfo(currentSearchQuery, false);
}
protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){
@@ -126,16 +126,9 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
instanceProgressDialog.show();
}
protected String getCurrentSearchQuery(){
String[] parts=currentSearchQuery.split("@");
return parts.length>0 ? parts[parts.length-1] : "";
}
protected String normalizeInstanceDomain(String _domain){
if(TextUtils.isEmpty(_domain))
return null;
String[] parts=_domain.split("@");
_domain=parts[parts.length - 1];
if(_domain.contains(":")){
try{
_domain=Uri.parse(_domain).getAuthority();
@@ -205,7 +198,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
instanceProgressDialog=null;
proceedWithAuthOrSignup(result);
}
if(Objects.equals(domain, getCurrentSearchQuery()) || Objects.equals(getCurrentSearchQuery(), redirects.get(domain)) || Objects.equals(getCurrentSearchQuery(), redirectsInverse.get(domain))){
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
boolean found=false;
for(CatalogInstance ci:filteredData){
if(ci.domain.equals(domain) && ci!=fakeInstance){

View File

@@ -3,6 +3,7 @@ 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;

View File

@@ -271,7 +271,7 @@ public class SignupFragment extends ToolbarFragment{
@Override
public void tail(Node node, int depth){
if(node instanceof Element){
ssb.setSpan(new LinkSpan("", SignupFragment.this::onGoBackLinkClick, LinkSpan.Type.CUSTOM, null, null, null), spanStart, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
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);
}
}

View File

@@ -83,11 +83,10 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetAccountStatuses(reportAccount.id, getMaxID(), null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES)
currentRequest=new GetAccountStatuses(reportAccount.id, offset>0 ? getMaxID() : null, null, count, GetAccountStatuses.Filter.OWN_POSTS_AND_REPLIES)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
if(getActivity()==null) return;
for(Status s:result){
s.sensitive=true;
}
@@ -104,8 +103,8 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
else
selectedIDs.add(id);
CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class);
if(holder!=null) holder.rebind();
else notifyItemChanged(id, CheckableHeaderStatusDisplayItem.class);
if(holder!=null)
holder.rebind();
}
@Override

View File

@@ -34,6 +34,7 @@ import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class ReportDoneFragment extends MastodonToolbarFragment{
@@ -226,7 +227,7 @@ public class ReportDoneFragment extends MastodonToolbarFragment{
@Override
protected int getNavigationIconDrawableResource(){
return R.drawable.ic_fluent_dismiss_24_regular;
return R.drawable.ic_baseline_close_24;
}
@Override

View File

@@ -83,7 +83,7 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
reportStatus=Parcels.unwrap(getArguments().getParcelable("status"));
if(reportStatus!=null){
Status hiddenStatus=reportStatus.clone();
if(hiddenStatus.spoilerText==null) hiddenStatus.spoilerText=getString(R.string.post_hidden);
hiddenStatus.spoilerText=getString(R.string.post_hidden);
onDataLoaded(Collections.singletonList(hiddenStatus));
setTitle(R.string.report_title_post);
}else{
@@ -168,6 +168,17 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
((UsableRecyclerView)list).setIncludeMarginsInItemHitbox(false);
if(reportStatus!=null){
list.addItemDecoration(new RecyclerView.ItemDecoration(){
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
outRect.left=V.dp(16);
outRect.right=V.dp(16);
}
}
});
list.addItemDecoration(new RecyclerView.ItemDecoration(){
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
{
@@ -211,6 +222,10 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
if(holder instanceof StatusDisplayItem.Holder<?>){
outRect.left=outRect.right=V.dp(16);
}
int index=holder.getAbsoluteAdapterPosition()-mergeAdapter.getPositionForAdapter(adapter);
if(index==displayItems.size()){
outRect.top=V.dp(32);
}
}
});
}
@@ -236,6 +251,18 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
return null;
}
@Override
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){
if((Object)holder instanceof MediaGridStatusDisplayItem.Holder h){
View layout=h.getLayout();
layout.setOutlineProvider(OutlineProviders.roundedRect(8));
layout.setClipToOutline(true);
View overlay=h.getSensitiveOverlay();
overlay.setOutlineProvider(OutlineProviders.roundedRect(8));
overlay.setClipToOutline(true);
}
}
@Override
public void putRelationship(String id, Relationship rel){
super.putRelationship(id, rel);

View File

@@ -73,7 +73,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
durationItem=new ListItem<>(R.string.settings_filter_duration, 0, this::onDurationClick),
wordsItem=new ListItem<>(R.string.settings_filter_muted_words, 0, this::onWordsClick),
contextItem=new ListItem<>(R.string.settings_filter_context, 0, this::onContextClick),
cwItem=new CheckableListItem<>(R.string.settings_filter_show_cw, R.string.settings_filter_show_cw_explanation, CheckableListItem.Style.SWITCH, filter==null || filter.filterAction==FilterAction.WARN, this::toggleCheckableItem)
cwItem=new CheckableListItem<>(R.string.settings_filter_show_cw, R.string.settings_filter_show_cw_explanation, CheckableListItem.Style.SWITCH, filter==null || filter.filterAction==FilterAction.WARN, ()->toggleCheckableItem(cwItem))
));
if(filter!=null){
@@ -113,7 +113,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
return 1;
}
private void onDurationClick(ListItem<Void> item_){
private void onDurationClick(){
int[] durationOptions={
1800,
3600,
@@ -182,21 +182,21 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
alert.setOnDismissListener(dialog->callback.accept(null));
}
private void onWordsClick(ListItem<Void> item){
private void onWordsClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) keywords.stream().map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new)));
Nav.goForResult(getActivity(), FilterWordsFragment.class, args, WORDS_RESULT, this);
}
private void onContextClick(ListItem<Void> item){
private void onContextClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
args.putSerializable("context", context);
Nav.goForResult(getActivity(), FilterContextFragment.class, args, CONTEXT_RESULT, this);
}
private void onDeleteClick(ListItem<Void> item_){
private void onDeleteClick(){
AlertDialog alert=new M3AlertDialogBuilder(getActivity())
.setTitle(getString(R.string.settings_delete_filter_title, filter.title))
.setMessage(R.string.settings_delete_filter_confirmation)

View File

@@ -22,9 +22,10 @@ public class FilterContextFragment extends BaseSettingsFragment<FilterContext> i
setTitle(R.string.settings_filter_context);
context=(EnumSet<FilterContext>) getArguments().getSerializable("context");
onDataLoaded(Arrays.stream(FilterContext.values()).map(c->{
CheckableListItem<FilterContext> item=new CheckableListItem<>(c.getDisplayNameRes(), 0, CheckableListItem.Style.CHECKBOX, context.contains(c), this::toggleCheckableItem);
CheckableListItem<FilterContext> item=new CheckableListItem<>(c.getDisplayNameRes(), 0, CheckableListItem.Style.CHECKBOX, context.contains(c), null);
item.parentObject=c;
item.isEnabled=true;
item.onClick=()->toggleCheckableItem(item);
return item;
}).collect(Collectors.toList()));
}

View File

@@ -1,6 +1,11 @@
package org.joinmastodon.android.fragments.settings;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.IntEvaluator;
import android.animation.ObjectAnimator;
import android.app.AlertDialog;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
@@ -22,7 +27,6 @@ import org.joinmastodon.android.model.FilterKeyword;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.ActionModeHelper;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
@@ -33,6 +37,7 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.V;
@@ -55,7 +60,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
FilterKeyword word=Parcels.unwrap(p);
ListItem<FilterKeyword> item=new ListItem<>(word.keyword, null, null, word);
item.isEnabled=true;
item.setOnClick(this::onWordClick);
item.onClick=()->onWordClick(item);
return item;
}).collect(Collectors.toList()));
setHasOptionsMenu(true);
@@ -92,7 +97,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setImageResource(R.drawable.ic_fluent_add_24_regular);
fab.setImageResource(R.drawable.ic_add_24px);
fab.setContentDescription(getString(R.string.add_muted_word));
fab.setOnClickListener(v->onFabClick());
}
@@ -109,7 +114,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.selectable_list, menu);
inflater.inflate(R.menu.settings_filter_words, menu);
}
@Override
@@ -169,7 +174,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
w.keyword=input;
ListItem<FilterKeyword> item=new ListItem<>(w.keyword, null, null, w);
item.isEnabled=true;
item.setOnClick(this::onWordClick);
item.onClick=()->onWordClick(item);
data.add(item);
itemsAdapter.notifyItemInserted(data.size()-1);
}else{
@@ -223,15 +228,29 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
return;
V.setVisibilityAnimated(fab, View.GONE);
actionMode=ActionModeHelper.startActionMode(this, ()->elevationOnScrollListener.getCurrentStatusBarColor(), new ActionMode.Callback(){
actionMode=getActivity().startActionMode(new ActionMode.Callback(){
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu){
ObjectAnimator anim=ObjectAnimator.ofInt(getActivity().getWindow(), "statusBarColor", elevationOnScrollListener.getCurrentStatusBarColor(), UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary));
anim.setEvaluator(new IntEvaluator(){
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
return UiUtils.alphaBlendColors(startValue, endValue, fraction);
}
});
anim.start();
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(FilterWordsFragment.this);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
mode.getMenuInflater().inflate(R.menu.settings_filter_words_action_mode, menu);
for(int i=0;i<menu.size();i++){
Drawable icon=menu.getItem(i).getIcon().mutate();
icon.setTint(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnPrimary));
menu.getItem(i).setIcon(icon);
}
deleteItem=menu.findItem(R.id.delete);
return true;
}
@@ -247,6 +266,21 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
@Override
public void onDestroyActionMode(ActionMode mode){
leaveSelectionMode(true);
ObjectAnimator anim=ObjectAnimator.ofInt(getActivity().getWindow(), "statusBarColor", UiUtils.getThemeColor(getActivity(), R.attr.colorM3Primary), elevationOnScrollListener.getCurrentStatusBarColor());
anim.setEvaluator(new IntEvaluator(){
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue){
return UiUtils.alphaBlendColors(startValue, endValue, fraction);
}
});
anim.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
getActivity().getWindow().setStatusBarColor(0);
}
});
anim.start();
((FragmentStackActivity) getActivity()).invalidateSystemBarColors(FilterWordsFragment.this);
}
});
@@ -255,7 +289,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
ListItem<FilterKeyword> item=data.get(i);
CheckableListItem<FilterKeyword> newItem=new CheckableListItem<>(item.title, null, CheckableListItem.Style.CHECKBOX, selectAll, null);
newItem.isEnabled=true;
newItem.setOnClick(this::onSelectionModeWordClick);
newItem.onClick=()->onSelectionModeWordClick(newItem);
newItem.parentObject=item.parentObject;
if(selectAll)
selectedItems.add(newItem);
@@ -279,7 +313,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
ListItem<FilterKeyword> item=data.get(i);
ListItem<FilterKeyword> newItem=new ListItem<>(item.title, null, null);
newItem.isEnabled=true;
newItem.setOnClick(this::onWordClick);
newItem.onClick=()->onWordClick(newItem);
newItem.parentObject=item.parentObject;
data.set(i, newItem);
}

View File

@@ -16,6 +16,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.List;
import java.util.Objects;
import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.imageloader.ImageCache;
@@ -25,32 +26,23 @@ import me.grishka.appkit.utils.V;
public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
private ListItem<Void> mediaCacheItem;
private AccountSession session;
private boolean timelineCacheCleared=false;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
session=AccountSessionManager.get(accountID);
AccountSession s=AccountSessionManager.get(accountID);
onDataLoaded(List.of(
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, i->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag))),
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")),
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick),
new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick)
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->UiUtils.openHashtagTimeline(getActivity(), accountID, getString(R.string.donate_hashtag), null)),
new ListItem<>(R.string.sk_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.repo_url))),
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/terms")),
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick)
));
updateMediaCacheItem();
}
@Override
protected void onHidden(){
super.onHidden();
if(timelineCacheCleared) getActivity().recreate();
}
@Override
protected void doLoadData(int offset, int count){}
@@ -71,7 +63,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
return adapter;
}
private void onClearMediaCacheClick(ListItem<?> item){
private void onClearMediaCacheClick(){
MastodonAPIController.runInBackground(()->{
Activity activity=getActivity();
ImageCache.getInstance(getActivity()).clear();
@@ -82,12 +74,6 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
});
}
private void onClearTimelineCacheClick(ListItem<?> item){
session.getCacheController().putHomeTimeline(List.of(), true);
Toast.makeText(getContext(), R.string.sk_timeline_cache_cleared, Toast.LENGTH_SHORT).show();
timelineCacheCleared=true;
}
private void updateMediaCacheItem(){
long size=ImageCache.getInstance(getActivity()).getDiskCache().size();
mediaCacheItem.subtitle=UiUtils.formatFileSize(getActivity(), size, false);

View File

@@ -20,7 +20,6 @@ import org.joinmastodon.android.utils.MastodonLanguage;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
private ListItem<Void> languageItem;
@@ -46,20 +45,20 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
List<ListItem<Void>> items = new ArrayList<>(List.of(
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick),
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, i->toggleCheckableItem(altTextItem)),
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, i->toggleCheckableItem(playGifsItem)),
overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, i->toggleCheckableItem(overlayMediaItem)),
customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_fluent_link_24_regular, i->toggleCheckableItem(customTabsItem)),
confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_fluent_person_delete_24_regular, i->toggleCheckableItem(confirmUnfollowItem)),
confirmBoostItem=new CheckableListItem<>(R.string.settings_confirm_boost, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmBoost, R.drawable.ic_fluent_arrow_repeat_all_24_regular, i->toggleCheckableItem(confirmBoostItem)),
confirmDeleteItem=new CheckableListItem<>(R.string.settings_confirm_delete_post, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmDeletePost, R.drawable.ic_fluent_delete_24_regular, i->toggleCheckableItem(confirmDeleteItem)),
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, ()->toggleCheckableItem(altTextItem)),
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, ()->toggleCheckableItem(playGifsItem)),
overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, ()->toggleCheckableItem(overlayMediaItem)),
customTabsItem=new CheckableListItem<>(R.string.settings_custom_tabs, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.useCustomTabs, R.drawable.ic_fluent_link_24_regular, ()->toggleCheckableItem(customTabsItem)),
confirmUnfollowItem=new CheckableListItem<>(R.string.settings_confirm_unfollow, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmUnfollow, R.drawable.ic_fluent_person_delete_24_regular, ()->toggleCheckableItem(confirmUnfollowItem)),
confirmBoostItem=new CheckableListItem<>(R.string.settings_confirm_boost, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmBoost, R.drawable.ic_fluent_arrow_repeat_all_24_regular, ()->toggleCheckableItem(confirmBoostItem)),
confirmDeleteItem=new CheckableListItem<>(R.string.settings_confirm_delete_post, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.confirmDeletePost, R.drawable.ic_fluent_delete_24_regular, ()->toggleCheckableItem(confirmDeleteItem)),
prefixRepliesItem=new ListItem<>(R.string.sk_settings_prefix_reply_cw_with_re, getPrefixWithRepliesString(), R.drawable.ic_fluent_arrow_reply_24_regular, this::onPrefixRepliesClick),
forwardReportsItem=new CheckableListItem<>(R.string.sk_settings_forward_report_default, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.forwardReportDefault, R.drawable.ic_fluent_arrow_forward_24_regular, i->toggleCheckableItem(forwardReportsItem)),
loadNewPostsItem=new CheckableListItem<>(R.string.sk_settings_load_new_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.loadNewPosts, R.drawable.ic_fluent_arrow_sync_24_regular, i->onLoadNewPostsClick()),
seeNewPostsBtnItem=new CheckableListItem<>(R.string.sk_settings_see_new_posts_button, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNewPostsButton, R.drawable.ic_fluent_arrow_up_24_regular, i->toggleCheckableItem(seeNewPostsBtnItem)),
remoteLoadingItem=new CheckableListItem<>(R.string.sk_settings_allow_remote_loading, R.string.sk_settings_allow_remote_loading_explanation, CheckableListItem.Style.SWITCH, GlobalUserPreferences.allowRemoteLoading, R.drawable.ic_fluent_communication_24_regular, i->toggleCheckableItem(remoteLoadingItem), true),
showBoostsItem=new CheckableListItem<>(R.string.sk_settings_show_boosts, 0, CheckableListItem.Style.SWITCH, lp.showBoosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, i->toggleCheckableItem(showBoostsItem)),
showRepliesItem=new CheckableListItem<>(R.string.sk_settings_show_replies, 0, CheckableListItem.Style.SWITCH, lp.showReplies, R.drawable.ic_fluent_arrow_reply_24_regular, i->toggleCheckableItem(showRepliesItem))
forwardReportsItem=new CheckableListItem<>(R.string.sk_settings_forward_report_default, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.forwardReportDefault, R.drawable.ic_fluent_arrow_forward_24_regular, ()->toggleCheckableItem(forwardReportsItem)),
loadNewPostsItem=new CheckableListItem<>(R.string.sk_settings_load_new_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.loadNewPosts, R.drawable.ic_fluent_arrow_sync_24_regular, this::onLoadNewPostsClick),
seeNewPostsBtnItem=new CheckableListItem<>(R.string.sk_settings_see_new_posts_button, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNewPostsButton, R.drawable.ic_fluent_arrow_up_24_regular, ()->toggleCheckableItem(seeNewPostsBtnItem)),
remoteLoadingItem=new CheckableListItem<>(R.string.sk_settings_allow_remote_loading, R.string.sk_settings_allow_remote_loading_explanation, CheckableListItem.Style.SWITCH, GlobalUserPreferences.allowRemoteLoading, R.drawable.ic_fluent_communication_24_regular, ()->toggleCheckableItem(remoteLoadingItem), true),
showBoostsItem=new CheckableListItem<>(R.string.sk_settings_show_boosts, 0, CheckableListItem.Style.SWITCH, lp.showBoosts, R.drawable.ic_fluent_arrow_repeat_all_24_regular, ()->toggleCheckableItem(showBoostsItem)),
showRepliesItem=new CheckableListItem<>(R.string.sk_settings_show_replies, 0, CheckableListItem.Style.SWITCH, lp.showReplies, R.drawable.ic_fluent_arrow_reply_24_regular, ()->toggleCheckableItem(showRepliesItem))
));
if(isInstanceAkkoma()) items.add(
@@ -93,7 +92,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
@Override
protected void doLoadData(int offset, int count){}
private void onDefaultLanguageClick(ListItem<?> item){
private void onDefaultLanguageClick(){
if (languageResolver == null) return;
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, new ComposeLanguageAlertViewController.SelectedOption(postLanguage), null, languageResolver);
new M3AlertDialogBuilder(getActivity())
@@ -112,14 +111,14 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
.show();
}
private void onPrefixRepliesClick(ListItem<?> item){
private void onPrefixRepliesClick(){
int selected=GlobalUserPreferences.prefixReplies.ordinal();
int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_prefix_reply_cw_with_re)
.setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_prefix_replies_never, R.string.sk_settings_prefix_replies_always, R.string.sk_settings_prefix_replies_to_others).mapToObj(this::getString).toArray(String[]::new),
selected, (dlg, which)->newSelected[0]=which)
.setPositiveButton(R.string.ok, (dlg, which)->{
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{
GlobalUserPreferences.prefixReplies=GlobalUserPreferences.PrefixRepliesMode.values()[newSelected[0]];
prefixRepliesItem.subtitleRes=getPrefixWithRepliesString();
rebindItem(prefixRepliesItem);
@@ -128,7 +127,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
.show();
}
private void onReplyVisibilityClick(ListItem<?> item){
private void onReplyVisibilityClick(){
AccountLocalPreferences lp=getLocalPrefs();
int selected=lp.timelineReplyVisibility==null ? 2 : switch(lp.timelineReplyVisibility){
case "following" -> 0;
@@ -139,8 +138,8 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_prefix_reply_cw_with_re)
.setSingleChoiceItems((String[]) IntStream.of(R.string.sk_settings_reply_visibility_following, R.string.sk_settings_reply_visibility_self, R.string.sk_settings_reply_visibility_all).mapToObj(this::getString).toArray(String[]::new),
selected, (dlg, which)->newSelected[0]=which)
.setPositiveButton(R.string.ok, (dlg, which)->{
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{
lp.timelineReplyVisibility=switch(newSelected[0]){
case 0 -> "following";
case 1 -> "self";
@@ -167,7 +166,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
GlobalUserPreferences.overlayMedia=overlayMediaItem.checked;
GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
GlobalUserPreferences.altTextReminders=altTextItem.checked;
GlobalUserPreferences.confirmUnfollow=confirmUnfollowItem.checked;
GlobalUserPreferences.confirmUnfollow=customTabsItem.checked;
GlobalUserPreferences.confirmBoost=confirmBoostItem.checked;
GlobalUserPreferences.confirmDeletePost=confirmDeleteItem.checked;
GlobalUserPreferences.forwardReportDefault=forwardReportsItem.checked;
@@ -176,8 +175,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
GlobalUserPreferences.allowRemoteLoading=remoteLoadingItem.checked;
GlobalUserPreferences.save();
AccountLocalPreferences lp=getLocalPrefs();
boolean restartPlease=lp.showBoosts!=showBoostsItem.checked
|| lp.showReplies!=showRepliesItem.checked;
lp.showBoosts=showBoostsItem.checked;
lp.showReplies=showRepliesItem.checked;
lp.save();
@@ -188,7 +185,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
s.preferences.postingDefaultLanguage=newPostLanguage.language.getLanguage();
s.savePreferencesLater();
}
if(restartPlease) getActivity().recreate();
}
@Override

View File

@@ -39,7 +39,7 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
@Override
protected void doLoadData(int offset, int count){}
private void onTestEmailConfirmClick(ListItem<?> item){
private void onTestEmailConfirmClick(){
AccountSession sess=AccountSessionManager.getInstance().getAccount(accountID);
sess.activated=false;
sess.activationInfo=new AccountActivationInfo("test@email", System.currentTimeMillis());
@@ -49,18 +49,18 @@ public class SettingsDebugFragment extends BaseSettingsFragment<Void>{
Nav.goClearingStack(getActivity(), AccountActivationFragment.class, args);
}
private void onForceSelfUpdateClick(ListItem<?> item){
private void onForceSelfUpdateClick(){
GithubSelfUpdater.forceUpdate=true;
GithubSelfUpdater.getInstance().maybeCheckForUpdates();
restartUI();
}
private void onResetUpdaterClick(ListItem<?> item){
private void onResetUpdaterClick(){
GithubSelfUpdater.getInstance().reset();
restartUI();
}
private void onResetDiscoverBannersClick(ListItem<?> item){
private void onResetDiscoverBannersClick(){
DiscoverInfoBannerHelper.reset();
restartUI();
}

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments.settings;
import android.app.Activity;
import android.app.AlertDialog;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Build;
@@ -18,7 +17,6 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
@@ -29,8 +27,6 @@ import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import me.grishka.appkit.FragmentStackActivity;
@@ -41,7 +37,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> revealCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem;
// MEGALODON
private CheckableListItem<Void> trueBlackModeItem, marqueeItem, disableSwipeItem, reduceMotionItem, altIndicatorItem, noAltIndicatorItem, collapsePostsItem, spectatorModeItem, hideFabItem, translateOpenedItem, disablePillItem, showNavigationLabelsItem, likeIconItem, underlinedLinksItem;
private CheckableListItem<Void> trueBlackModeItem, marqueeItem, disableSwipeItem, reduceMotionItem, altIndicatorItem, noAltIndicatorItem, collapsePostsItem, spectatorModeItem, hideFabItem, translateOpenedItem, disablePillItem, showNavigationLabelsItem;
private ListItem<Void> colorItem, publishTextItem, autoRevealCWsItem;
private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem;
@@ -55,30 +51,28 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
lp=s.getLocalPreferences();
onDataLoaded(List.of(
themeItem=new ListItem<>(R.string.settings_theme, getAppearanceValue(), R.drawable.ic_fluent_weather_moon_24_regular, this::onAppearanceClick),
colorItem=new ListItem<>(getString(R.string.sk_settings_color_palette), getColorPaletteValue(), R.drawable.ic_fluent_color_24_regular, this::onColorClick),
trueBlackModeItem=new CheckableListItem<>(R.string.sk_settings_true_black, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.trueBlackTheme, R.drawable.ic_fluent_dark_theme_24_regular, i->onTrueBlackModeClick(), true),
colorItem=new ListItem<>(R.string.sk_settings_color_palette, getColorPaletteValue(), R.drawable.ic_fluent_color_24_regular, this::onColorClick),
trueBlackModeItem=new CheckableListItem<>(R.string.sk_settings_true_black, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.trueBlackTheme, R.drawable.ic_fluent_dark_theme_24_regular, this::onTrueBlackModeClick, true),
publishTextItem=new ListItem<>(getString(R.string.sk_settings_publish_button_text), getPublishButtonText(), R.drawable.ic_fluent_send_24_regular, this::onPublishTextClick),
autoRevealCWsItem=new ListItem<>(R.string.sk_settings_auto_reveal_equal_spoilers, getAutoRevealSpoilersText(), R.drawable.ic_fluent_eye_24_regular, this::onAutoRevealSpoilersClick),
revealCWsItem=new CheckableListItem<>(R.string.sk_settings_always_reveal_content_warnings, 0, CheckableListItem.Style.SWITCH, lp.revealCWs, R.drawable.ic_fluent_chat_warning_24_regular, i->toggleCheckableItem(revealCWsItem)),
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_fluent_flag_24_regular, i->toggleCheckableItem(hideSensitiveMediaItem)),
interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, 0, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_fluent_number_row_24_regular, i->toggleCheckableItem(interactionCountsItem)),
emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_fluent_emoji_24_regular, i->toggleCheckableItem(emojiInNamesItem)),
marqueeItem=new CheckableListItem<>(R.string.sk_settings_enable_marquee, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.toolbarMarquee, R.drawable.ic_fluent_text_more_24_regular, i->toggleCheckableItem(marqueeItem)),
reduceMotionItem=new CheckableListItem<>(R.string.sk_settings_reduce_motion, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.reduceMotion, R.drawable.ic_fluent_star_emphasis_24_regular, i->toggleCheckableItem(reduceMotionItem)),
disableSwipeItem=new CheckableListItem<>(R.string.sk_settings_tabs_disable_swipe, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableSwipe, R.drawable.ic_fluent_swipe_right_24_regular, i->toggleCheckableItem(disableSwipeItem)),
altIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showAltIndicator, R.drawable.ic_fluent_scan_text_24_regular, i->toggleCheckableItem(altIndicatorItem)),
noAltIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_no_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNoAltIndicator, R.drawable.ic_fluent_important_24_regular, i->toggleCheckableItem(noAltIndicatorItem)),
collapsePostsItem=new CheckableListItem<>(R.string.sk_settings_collapse_long_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.collapseLongPosts, R.drawable.ic_fluent_chevron_down_24_regular, i->toggleCheckableItem(collapsePostsItem)),
spectatorModeItem=new CheckableListItem<>(R.string.sk_settings_hide_interaction, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.spectatorMode, R.drawable.ic_fluent_star_off_24_regular, i->toggleCheckableItem(spectatorModeItem)),
hideFabItem=new CheckableListItem<>(R.string.sk_settings_hide_fab, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.autoHideFab, R.drawable.ic_fluent_edit_24_regular, i->toggleCheckableItem(hideFabItem)),
translateOpenedItem=new CheckableListItem<>(R.string.sk_settings_translate_only_opened, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.translateButtonOpenedOnly, R.drawable.ic_fluent_translate_24_regular, i->toggleCheckableItem(translateOpenedItem)),
likeIconItem=new CheckableListItem<>(R.string.sk_settings_like_icon, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.likeIcon, R.drawable.ic_fluent_heart_24_regular, i->toggleCheckableItem(likeIconItem)),
underlinedLinksItem=new CheckableListItem<>(R.string.sk_settings_underlined_links, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.underlinedLinks, R.drawable.ic_fluent_text_underline_24_regular, i->toggleCheckableItem(underlinedLinksItem)),
disablePillItem=new CheckableListItem<>(R.string.sk_disable_pill_shaped_active_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableM3PillActiveIndicator, R.drawable.ic_fluent_pill_24_regular, i->toggleCheckableItem(disablePillItem)),
showNavigationLabelsItem=new CheckableListItem<>(R.string.sk_settings_show_labels_in_navigation_bar, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNavigationLabels, R.drawable.ic_fluent_tag_24_regular, i->toggleCheckableItem(showNavigationLabelsItem), true),
pronounsInTimelinesItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_timelines, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInTimelines, 0, i->toggleCheckableItem(pronounsInTimelinesItem)),
pronounsInThreadsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_threads, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInThreads, 0, i->toggleCheckableItem(pronounsInThreadsItem)),
pronounsInUserListingsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_user_listings, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInUserListings, 0, i->toggleCheckableItem(pronounsInUserListingsItem))
revealCWsItem=new CheckableListItem<>(R.string.sk_settings_always_reveal_content_warnings, 0, CheckableListItem.Style.SWITCH, lp.revealCWs, R.drawable.ic_fluent_chat_warning_24_regular, ()->toggleCheckableItem(revealCWsItem)),
hideSensitiveMediaItem=new CheckableListItem<>(R.string.settings_hide_sensitive_media, 0, CheckableListItem.Style.SWITCH, lp.hideSensitiveMedia, R.drawable.ic_fluent_flag_24_regular, ()->toggleCheckableItem(hideSensitiveMediaItem)),
interactionCountsItem=new CheckableListItem<>(R.string.settings_show_interaction_counts, 0, CheckableListItem.Style.SWITCH, lp.showInteractionCounts, R.drawable.ic_fluent_number_row_24_regular, ()->toggleCheckableItem(interactionCountsItem)),
emojiInNamesItem=new CheckableListItem<>(R.string.settings_show_emoji_in_names, 0, CheckableListItem.Style.SWITCH, lp.customEmojiInNames, R.drawable.ic_fluent_emoji_24_regular, ()->toggleCheckableItem(emojiInNamesItem)),
marqueeItem=new CheckableListItem<>(R.string.sk_settings_enable_marquee, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.toolbarMarquee, R.drawable.ic_fluent_text_more_24_regular, ()->toggleCheckableItem(marqueeItem)),
reduceMotionItem=new CheckableListItem<>(R.string.sk_settings_reduce_motion, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.reduceMotion, R.drawable.ic_fluent_star_emphasis_24_regular, ()->toggleCheckableItem(reduceMotionItem)),
disableSwipeItem=new CheckableListItem<>(R.string.sk_settings_tabs_disable_swipe, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableSwipe, R.drawable.ic_fluent_swipe_right_24_regular, ()->toggleCheckableItem(disableSwipeItem)),
altIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showAltIndicator, R.drawable.ic_fluent_scan_text_24_regular, ()->toggleCheckableItem(altIndicatorItem)),
noAltIndicatorItem=new CheckableListItem<>(R.string.sk_settings_show_no_alt_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNoAltIndicator, R.drawable.ic_fluent_important_24_regular, ()->toggleCheckableItem(noAltIndicatorItem)),
collapsePostsItem=new CheckableListItem<>(R.string.sk_settings_collapse_long_posts, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.collapseLongPosts, R.drawable.ic_fluent_chevron_down_24_regular, ()->toggleCheckableItem(collapsePostsItem)),
spectatorModeItem=new CheckableListItem<>(R.string.sk_settings_hide_interaction, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.spectatorMode, R.drawable.ic_fluent_star_off_24_regular, ()->toggleCheckableItem(spectatorModeItem)),
hideFabItem=new CheckableListItem<>(R.string.sk_settings_hide_fab, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.autoHideFab, R.drawable.ic_fluent_edit_24_regular, ()->toggleCheckableItem(hideFabItem)),
translateOpenedItem=new CheckableListItem<>(R.string.sk_settings_translate_only_opened, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.translateButtonOpenedOnly, R.drawable.ic_fluent_translate_24_regular, ()->toggleCheckableItem(translateOpenedItem)),
disablePillItem=new CheckableListItem<>(R.string.sk_disable_pill_shaped_active_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableM3PillActiveIndicator, R.drawable.ic_fluent_pill_24_regular, ()->toggleCheckableItem(disablePillItem)),
showNavigationLabelsItem=new CheckableListItem<>(R.string.sk_settings_show_labels_in_navigation_bar, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNavigationLabels, R.drawable.ic_fluent_tag_24_regular, ()->toggleCheckableItem(showNavigationLabelsItem), true),
pronounsInTimelinesItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_timelines, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInTimelines, 0, ()->toggleCheckableItem(pronounsInTimelinesItem)),
pronounsInThreadsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_threads, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInThreads, 0, ()->toggleCheckableItem(pronounsInThreadsItem)),
pronounsInUserListingsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_user_listings, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInUserListings, 0, ()->toggleCheckableItem(pronounsInUserListingsItem))
));
trueBlackModeItem.checkedChangeListener=checked->onTrueBlackModeClick();
}
@@ -100,9 +94,9 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
protected void onHidden(){
super.onHidden();
boolean restartPlease=GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked
|| GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked
|| GlobalUserPreferences.likeIcon!=likeIconItem.checked;
boolean restartPlease=
GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked ||
GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked;
lp.revealCWs=revealCWsItem.checked;
lp.hideSensitiveMedia=hideSensitiveMediaItem.checked;
@@ -118,8 +112,6 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
GlobalUserPreferences.spectatorMode=spectatorModeItem.checked;
GlobalUserPreferences.autoHideFab=hideFabItem.checked;
GlobalUserPreferences.translateButtonOpenedOnly=translateOpenedItem.checked;
GlobalUserPreferences.likeIcon=likeIconItem.checked;
GlobalUserPreferences.underlinedLinks=underlinedLinksItem.checked;
GlobalUserPreferences.disableM3PillActiveIndicator=disablePillItem.checked;
GlobalUserPreferences.showNavigationLabels=showNavigationLabelsItem.checked;
GlobalUserPreferences.displayPronounsInTimelines=pronounsInTimelinesItem.checked;
@@ -138,11 +130,17 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
};
}
private String getColorPaletteValue(){
ColorPreference color=AccountSessionManager.get(accountID).getLocalPreferences().color;
return color==null
? getString(R.string.sk_settings_color_palette_default, getString(GlobalUserPreferences.color.getName()))
: getString(color.getName());
private @StringRes int getColorPaletteValue(){
return switch(GlobalUserPreferences.color){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
};
}
private String getPublishButtonText() {
@@ -166,7 +164,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
maybeApplyNewThemeRightNow(null, null, prev);
}
private void onAppearanceClick(ListItem<?> item_){
private void onAppearanceClick(){
int selected=switch(GlobalUserPreferences.theme){
case LIGHT -> 0;
case DARK -> 1;
@@ -197,44 +195,30 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show();
}
private void onColorClick(ListItem<?> item_){
boolean multiple=AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
int indexOffset=multiple ? 1 : 0;
int selected=lp.color==null ? 0 : lp.color.ordinal() + indexOffset;
private void onColorClick(){
int selected=GlobalUserPreferences.color.ordinal();
int[] newSelected={selected};
List<String> items=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).collect(Collectors.toList());
if(multiple)
items.add(0, getString(R.string.sk_settings_color_palette_default, items.get(GlobalUserPreferences.color.ordinal())));
Consumer<Boolean> save=(asDefault)->{
boolean defaultSelected=multiple && newSelected[0]==0;
ColorPreference pref=defaultSelected ? null : ColorPreference.values()[newSelected[0]-indexOffset];
if(pref!=lp.color){
ColorPreference prev=lp.color;
lp.color=asDefault ? null : pref;
lp.save();
if((asDefault || !multiple) && pref!=null){
GlobalUserPreferences.color=pref;
GlobalUserPreferences.save();
}
colorItem.subtitle=getColorPaletteValue();
rebindItem(colorItem);
if(prev==null && pref!=null) restartActivityToApplyNewTheme();
else maybeApplyNewThemeRightNow(null, prev, null);
}
};
AlertDialog.Builder alert=new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_color_palette)
.setSingleChoiceItems(items.toArray(String[]::new),
String[] names=Arrays.stream(GlobalUserPreferences.ColorPreference.values()).map(GlobalUserPreferences.ColorPreference::getName).map(this::getString).toArray(String[]::new);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.settings_theme)
.setSingleChoiceItems(names,
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->save.accept(false))
.setNegativeButton(R.string.cancel, null);
if(multiple) alert.setNeutralButton(R.string.sk_set_as_default, (dlg, item)->save.accept(true));
alert.show();
.setPositiveButton(R.string.ok, (dlg, item)->{
GlobalUserPreferences.ColorPreference pref=GlobalUserPreferences.ColorPreference.values()[newSelected[0]];
if(pref!=GlobalUserPreferences.color){
GlobalUserPreferences.ColorPreference prev=GlobalUserPreferences.color;
GlobalUserPreferences.color=pref;
GlobalUserPreferences.save();
colorItem.subtitleRes=getColorPaletteValue();
rebindItem(colorItem);
maybeApplyNewThemeRightNow(null, prev, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
}
private void onPublishTextClick(ListItem<?> item_){
private void onPublishTextClick(){
TextInputFrameLayout input = new TextInputFrameLayout(
getContext(),
getString(R.string.publish),
@@ -257,7 +241,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show();
}
private void onAutoRevealSpoilersClick(ListItem<?> item_){
private void onAutoRevealSpoilersClick(){
int selected=GlobalUserPreferences.autoRevealEqualSpoilers.ordinal();
int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity())
@@ -273,17 +257,17 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show();
}
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, ColorPreference prevColor, Boolean prevTrueBlack){
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, GlobalUserPreferences.ColorPreference prevColor, Boolean prevTrueBlack){
if(prevTheme==null) prevTheme=GlobalUserPreferences.theme;
if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme;
if(prevColor==null) prevColor=lp.getCurrentColor();
if(prevColor==null) prevColor=GlobalUserPreferences.color;
boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK ||
(prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK ||
(GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewBlack=GlobalUserPreferences.trueBlackTheme;
if(isCurrentDark!=isNewDark || prevColor!=lp.getCurrentColor() || (isNewDark && prevTrueBlack!=isNewBlack)){
if(isCurrentDark!=isNewDark || prevColor!=GlobalUserPreferences.color || (isNewDark && prevTrueBlack!=isNewBlack)){
restartActivityToApplyNewTheme();
}
}

View File

@@ -55,7 +55,7 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
adapter.addAdapter(super.getAdapter());
adapter.addAdapter(new GenericListItemsAdapter<>(Collections.singletonList(
new ListItem<Void>(R.string.settings_add_filter, 0, R.drawable.ic_fluent_add_24_regular, this::onAddFilterClick)
new ListItem<Void>(R.string.settings_add_filter, 0, R.drawable.ic_add_24px, this::onAddFilterClick)
)));
return adapter;
}
@@ -67,14 +67,16 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
Nav.go(getActivity(), EditFilterFragment.class, args);
}
private void onAddFilterClick(ListItem<?> item){
private void onAddFilterClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), EditFilterFragment.class, args);
}
private ListItem<Filter> makeListItem(Filter f){
ListItem<Filter> item=new ListItem<>(f.title, getString(f.isActive() ? R.string.filter_active : R.string.filter_inactive), this::onFilterClick, f);
ListItem<Filter> item=new ListItem<>(f.title, getString(f.isActive() ? R.string.filter_active : R.string.filter_inactive), null, f);
item.onClick=()->onFilterClick(item);
item.isEnabled=true;
return item;
}

View File

@@ -19,7 +19,6 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import me.grishka.appkit.Nav;
@@ -37,15 +36,15 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
lp=s.getLocalPreferences();
onDataLoaded(List.of(
new ListItem<>(AccountSessionManager.get(accountID).domain, getString(R.string.settings_server_explanation), R.drawable.ic_fluent_server_24_regular, this::onServerClick),
new ListItem<>(R.string.sk_settings_profile, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/profile")),
new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")),
new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true),
contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, i->onContentTypeClick()),
new ListItem<>(R.string.sk_settings_profile, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/profile")),
new ListItem<>(R.string.sk_settings_posting, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")),
new ListItem<>(R.string.sk_settings_auth, 0, R.drawable.ic_fluent_open_24_regular, ()->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true),
contentTypesItem=new CheckableListItem<>(R.string.sk_settings_content_types, R.string.sk_settings_content_types_explanation, CheckableListItem.Style.SWITCH, lp.contentTypesEnabled, R.drawable.ic_fluent_text_edit_style_24_regular, this::onContentTypeClick),
defaultContentTypeItem=new ListItem<>(R.string.sk_settings_default_content_type, lp.defaultContentType.getName(), R.drawable.ic_fluent_text_bold_24_regular, this::onDefaultContentTypeClick, 0, true),
emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, i->onEmojiReactionsClick()),
emojiReactionsItem=new CheckableListItem<>(R.string.sk_settings_emoji_reactions, R.string.sk_settings_emoji_reactions_explanation, CheckableListItem.Style.SWITCH, lp.emojiReactionsEnabled, R.drawable.ic_fluent_emoji_laugh_24_regular, this::onEmojiReactionsClick),
showEmojiReactionsItem=new ListItem<>(R.string.sk_settings_show_emoji_reactions, getShowEmojiReactionsString(), R.drawable.ic_fluent_emoji_24_regular, this::onShowEmojiReactionsClick, 0, true),
localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, i->onLocalOnlyClick()),
glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, i->toggleCheckableItem(glitchModeItem))
localOnlyItem=new CheckableListItem<>(R.string.sk_settings_support_local_only, R.string.sk_settings_local_only_explanation, CheckableListItem.Style.SWITCH, lp.localOnlySupported, R.drawable.ic_fluent_eye_24_regular, this::onLocalOnlyClick),
glitchModeItem=new CheckableListItem<>(R.string.sk_settings_glitch_instance, R.string.sk_settings_glitch_mode_explanation, CheckableListItem.Style.SWITCH, lp.glitchInstance, R.drawable.ic_fluent_eye_24_filled, ()->toggleCheckableItem(glitchModeItem))
));
contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
defaultContentTypeItem.isEnabled=contentTypesItem.checked;
@@ -69,7 +68,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
E.post(new StatusDisplaySettingsChangedEvent(accountID));
}
private void onServerClick(ListItem<?> item){
private void onServerClick(){
Bundle args=new Bundle();
args.putString("account", accountID);
Nav.go(getActivity(), SettingsServerFragment.class, args);
@@ -88,23 +87,23 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName();
}
private void onDefaultContentTypeClick(ListItem<?> item_){
List<ContentType> supportedContentTypes=Arrays.stream(ContentType.values())
.filter(t->t.supportedByInstance(getInstance().orElse(null)))
.collect(Collectors.toList());
int selected=supportedContentTypes.indexOf(lp.defaultContentType);
private void onDefaultContentTypeClick(){
int selected=lp.defaultContentType.ordinal();
int[] newSelected={selected};
String[] names=supportedContentTypes.stream()
ContentType[] supportedContentTypes=Arrays.stream(ContentType.values())
.filter(t->t.supportedByInstance(getInstance().orElse(null)))
.toArray(ContentType[]::new);
String[] names=Arrays.stream(supportedContentTypes)
.map(ContentType::getName)
.map(this::getString)
.toArray(String[]::new);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_default_content_type)
.setTitle(R.string.settings_theme)
.setSingleChoiceItems(names,
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{
ContentType type=supportedContentTypes.get(newSelected[0]);
ContentType type=supportedContentTypes[newSelected[0]];
lp.defaultContentType=type;
defaultContentTypeItem.subtitleRes=type.getName();
rebindItem(defaultContentTypeItem);
@@ -113,7 +112,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
.show();
}
private void onShowEmojiReactionsClick(ListItem<?> item_){
private void onShowEmojiReactionsClick(){
int selected=lp.showEmojiReactions.ordinal();
int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity())

View File

@@ -18,7 +18,6 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -50,32 +49,27 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
account=AccountSessionManager.get(accountID);
account = AccountSessionManager.get(accountID);
setTitle(R.string.settings);
setSubtitle(account.getFullUsername());
onDataLoaded(List.of(
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick),
new ListItem<>(R.string.settings_display, 0, R.drawable.ic_fluent_color_24_regular, this::onDisplayClick),
new ListItem<>(R.string.settings_privacy, 0, R.drawable.ic_fluent_shield_24_regular, this::onPrivacyClick),
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick),
new ListItem<>(R.string.sk_settings_instance, 0, R.drawable.ic_fluent_server_24_regular, this::onInstanceClick),
new ListItem<>(getString(R.string.about_app, getString(R.string.sk_app_name)), null, R.drawable.ic_fluent_info_24_regular, this::onAboutClick, null, 0, true),
new ListItem<>(R.string.manage_accounts, 0, R.drawable.ic_fluent_person_swap_24_regular, this::onManageAccountsClick),
new ListItem<>(R.string.log_out, 0, R.drawable.ic_fluent_sign_out_24_regular, this::onLogOutClick, R.attr.colorM3Error, false)
));
Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if(!instance.isAkkoma()){
data.add(3, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
}
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if (!instance.isAkkoma())
data.add(2, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, i->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
}
AccountSession session=AccountSessionManager.get(accountID);
session.reloadPreferences(null);
session.updateAccountInfo();
account.reloadPreferences(null);
E.register(this);
}
@@ -131,45 +125,39 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
return args;
}
private void onBehaviorClick(ListItem<?> item_){
private void onBehaviorClick(){
Nav.go(getActivity(), SettingsBehaviorFragment.class, makeFragmentArgs());
}
private void onDisplayClick(ListItem<?> item_){
private void onDisplayClick(){
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
}
private void onPrivacyClick(ListItem<?> item_){
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
}
private void onFiltersClick(ListItem<?> item_){
private void onFiltersClick(){
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
}
private void onNotificationsClick(ListItem<?> item_){
private void onNotificationsClick(){
Nav.go(getActivity(), SettingsNotificationsFragment.class, makeFragmentArgs());
}
private void onInstanceClick(ListItem<?> item_){
private void onInstanceClick(){
Nav.go(getActivity(), SettingsInstanceFragment.class, makeFragmentArgs());
}
private void onAboutClick(ListItem<?> item_){
private void onAboutClick(){
Nav.go(getActivity(), SettingsAboutAppFragment.class, makeFragmentArgs());
}
private void onManageAccountsClick(ListItem<?> item){
new AccountSwitcherSheet(getActivity(), null).show();
}
private void onLogOutClick(ListItem<?> item_){
private void onLogOutClick(){
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new M3AlertDialogBuilder(getActivity())
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
loggedOut=true;
((MainActivity)getActivity()).restartActivity();
getActivity().finish();
Intent intent=new Intent(getActivity(), MainActivity.class);
startActivity(intent);
}))
.setNegativeButton(R.string.cancel, null)
.show();

View File

@@ -27,6 +27,7 @@ import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.unifiedpush.android.connector.RegistrationDialogContent;
import org.unifiedpush.android.connector.UnifiedPush;
import java.time.Instant;
@@ -72,21 +73,21 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
useUnifiedPush=!getDistributor(getContext()).isEmpty();
onDataLoaded(List.of(
pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_fluent_alert_snooze_24_regular, i->onPauseNotificationsClick(false)),
pauseItem=new CheckableListItem<>(getString(R.string.pause_all_notifications), getPauseItemSubtitle(), CheckableListItem.Style.SWITCH, false, R.drawable.ic_fluent_alert_snooze_24_regular, ()->onPauseNotificationsClick(false)),
policyItem=new ListItem<>(R.string.settings_notifications_policy, 0, R.drawable.ic_fluent_people_24_regular, this::onNotificationsPolicyClick, 0, true),
mentionsItem=new CheckableListItem<>(R.string.notification_type_mentions_and_replies, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.mention, R.drawable.ic_fluent_mention_24_regular, i->toggleCheckableItem(mentionsItem)),
boostsItem=new CheckableListItem<>(R.string.notification_type_reblog, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, i->toggleCheckableItem(boostsItem)),
favoritesItem=new CheckableListItem<>(R.string.notification_type_favorite, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.favourite, R.drawable.ic_fluent_star_24_regular, i->toggleCheckableItem(favoritesItem)),
followersItem=new CheckableListItem<>(R.string.notification_type_follow, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.follow, R.drawable.ic_fluent_person_add_24_regular, i->toggleCheckableItem(followersItem)),
pollsItem=new CheckableListItem<>(R.string.notification_type_poll, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.poll, R.drawable.ic_fluent_poll_24_regular, i->toggleCheckableItem(pollsItem)),
updateItem=new CheckableListItem<>(R.string.sk_notification_type_update, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.update, R.drawable.ic_fluent_history_24_regular, i->toggleCheckableItem(updateItem)),
postsItem=new CheckableListItem<>(R.string.sk_notification_type_posts, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.status, R.drawable.ic_fluent_chat_24_regular, i->toggleCheckableItem(postsItem), true),
mentionsItem=new CheckableListItem<>(R.string.notification_type_mentions_and_replies, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.mention, R.drawable.ic_fluent_mention_24_regular, ()->toggleCheckableItem(mentionsItem)),
boostsItem=new CheckableListItem<>(R.string.notification_type_reblog, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.reblog, R.drawable.ic_fluent_arrow_repeat_all_24_regular, ()->toggleCheckableItem(boostsItem)),
favoritesItem=new CheckableListItem<>(R.string.notification_type_favorite, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.favourite, R.drawable.ic_fluent_star_24_regular, ()->toggleCheckableItem(favoritesItem)),
followersItem=new CheckableListItem<>(R.string.notification_type_follow, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.follow, R.drawable.ic_fluent_person_add_24_regular, ()->toggleCheckableItem(followersItem)),
pollsItem=new CheckableListItem<>(R.string.notification_type_poll, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.poll, R.drawable.ic_fluent_poll_24_regular, ()->toggleCheckableItem(pollsItem)),
updateItem=new CheckableListItem<>(R.string.sk_notification_type_update, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.update, R.drawable.ic_fluent_history_24_regular, ()->toggleCheckableItem(updateItem)),
postsItem=new CheckableListItem<>(R.string.sk_notification_type_posts, 0, CheckableListItem.Style.CHECKBOX, pushSubscription.alerts.status, R.drawable.ic_fluent_chat_24_regular, ()->toggleCheckableItem(postsItem), true),
uniformIconItem=new CheckableListItem<>(R.string.sk_settings_uniform_icon_for_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.uniformNotificationIcon, R.drawable.ic_ntf_logo, i->toggleCheckableItem(uniformIconItem)),
deleteItem=new CheckableListItem<>(R.string.sk_settings_enable_delete_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enableDeleteNotifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, i->toggleCheckableItem(deleteItem)),
onlyLatestItem=new CheckableListItem<>(R.string.sk_settings_single_notification, 0, CheckableListItem.Style.SWITCH, lp.keepOnlyLatestNotification, R.drawable.ic_fluent_convert_range_24_regular, i->toggleCheckableItem(onlyLatestItem), true),
unifiedPushItem=new CheckableListItem<>(R.string.sk_settings_unifiedpush, 0, CheckableListItem.Style.SWITCH, useUnifiedPush, R.drawable.ic_fluent_alert_arrow_up_24_regular, i->onUnifiedPushClick(), true)
uniformIconItem=new CheckableListItem<>(R.string.sk_settings_uniform_icon_for_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.uniformNotificationIcon, R.drawable.ic_ntf_logo, ()->toggleCheckableItem(uniformIconItem)),
deleteItem=new CheckableListItem<>(R.string.sk_settings_enable_delete_notifications, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enableDeleteNotifications, R.drawable.ic_fluent_mail_inbox_dismiss_24_regular, ()->toggleCheckableItem(deleteItem)),
onlyLatestItem=new CheckableListItem<>(R.string.sk_settings_single_notification, 0, CheckableListItem.Style.SWITCH, lp.keepOnlyLatestNotification, R.drawable.ic_fluent_convert_range_24_regular, ()->toggleCheckableItem(onlyLatestItem), true),
unifiedPushItem=new CheckableListItem<>(R.string.sk_settings_unifiedpush, 0, CheckableListItem.Style.SWITCH, useUnifiedPush, R.drawable.ic_fluent_alert_arrow_up_24_regular, this::onUnifiedPush, true)
));
//only enable when distributors, who can receive notifications, are available
@@ -97,7 +98,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
typeItems=List.of(mentionsItem, boostsItem, favoritesItem, followersItem, pollsItem, updateItem, postsItem);
pauseItem.checkedChangeListener=checked->onPauseNotificationsClick(true);
unifiedPushItem.checkedChangeListener=checked->onUnifiedPushClick();
unifiedPushItem.checkedChangeListener=checked->onUnifiedPush();
updatePolicyItem(null);
updatePauseItem();
}
@@ -253,7 +254,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
}
private void onNotificationsPolicyClick(ListItem<?> item_){
private void onNotificationsPolicyClick(){
String[] items=Stream.of(
R.string.notifications_policy_anyone,
R.string.notifications_policy_followed,
@@ -327,7 +328,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
}
}
private void onUnifiedPushClick(){
private void onUnifiedPush(){
if(getDistributor(getContext()).isEmpty()){
List<String> distributors = UnifiedPush.getDistributors(getContext(), new ArrayList<>());
showUnifiedPushRegisterDialog(distributors);

View File

@@ -1,99 +0,0 @@
package org.joinmastodon.android.fragments.settings;
import android.os.Bundle;
import androidx.annotation.StringRes;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import java.util.ArrayList;
import java.util.List;
public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> discoverableItem, indexableItem, lockedItem;
private ListItem<Void> privacyItem;
private StatusPrivacy privacy=null;
private Instance instance;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setTitle(R.string.settings_privacy);
AccountSession session=AccountSessionManager.get(accountID);
Account self=session.self;
instance=AccountSessionManager.getInstance().getInstanceInfo(session.domain);
privacy=self.source.privacy;
onDataLoaded(List.of(
privacyItem=new ListItem<>(R.string.sk_settings_default_visibility, getPrivacyString(privacy), R.drawable.ic_fluent_eye_24_regular, this::onPrivacyClick, 0, true),
lockedItem=new CheckableListItem<>(R.string.sk_settings_lock_account, 0, CheckableListItem.Style.SWITCH, self.locked, R.drawable.ic_fluent_person_available_24_regular, i->toggleCheckableItem(lockedItem))
));
if(!instance.isAkkoma()){
data.addAll(List.of(
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_fluent_thumb_like_dislike_24_regular, i->toggleCheckableItem(discoverableItem)),
indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_fluent_search_24_regular, i->toggleCheckableItem(indexableItem))
));
if(self.source.indexable==null)
indexableItem.isEnabled=false;
}
}
@Override
protected void doLoadData(int offset, int count){}
private @StringRes int getPrivacyString(StatusPrivacy p){
if(p==null) return R.string.visibility_public;
return switch(p){
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;
};
}
private void onPrivacyClick(ListItem<?> item_){
Account self=AccountSessionManager.get(accountID).self;
List<StatusPrivacy> options=new ArrayList<>(List.of(StatusPrivacy.PUBLIC, StatusPrivacy.UNLISTED, StatusPrivacy.PRIVATE, StatusPrivacy.DIRECT));
if(instance.isAkkoma()) options.add(StatusPrivacy.LOCAL);
int selected=options.indexOf(self.source.privacy);
int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_default_visibility)
.setSingleChoiceItems(options.stream().map(this::getPrivacyString).map(this::getString).toArray(String[]::new),
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{
privacy=options.get(newSelected[0]);
privacyItem.subtitleRes=getPrivacyString(privacy);
rebindItem(privacyItem);
})
.setNegativeButton(R.string.cancel, null)
.show();
}
@Override
public void onPause(){
super.onPause();
AccountSession s=AccountSessionManager.get(accountID);
Account self=s.self;
boolean savePlease=self.locked!=lockedItem.checked
|| self.source.privacy!=privacy
|| (discoverableItem!=null && self.discoverable!=discoverableItem.checked)
|| (indexableItem!=null && self.source.indexable!=null && self.source.indexable!=indexableItem.checked);
if(savePlease){
if(discoverableItem!=null) self.discoverable=discoverableItem.checked;
if(indexableItem!=null) self.source.indexable=indexableItem.checked;
self.locked=lockedItem.checked;
s.preferences.postingDefaultVisibility=privacy;
s.savePreferencesLater();
}
}
}

View File

@@ -145,7 +145,7 @@ public class SettingsServerAboutFragment extends LoaderFragment{
if(!TextUtils.isEmpty(instance.email)){
needDivider=true;
SimpleListItemViewHolder holder=new SimpleListItemViewHolder(getActivity(), scrollingLayout);
ListItem<Void> item=new ListItem<>(R.string.send_email_to_server_admin, 0, R.drawable.ic_fluent_mail_24_regular, i->{});
ListItem<Void> item=new ListItem<>(R.string.send_email_to_server_admin, 0, R.drawable.ic_fluent_mail_24_regular, ()->{});
holder.bind(item);
holder.itemView.setBackground(UiUtils.getThemeDrawable(getActivity(), android.R.attr.selectableItemBackground));
holder.itemView.setOnClickListener(v->openAdminEmail());

View File

@@ -153,21 +153,18 @@ public class SettingsServerFragment extends AppKitFragment{
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
FrameLayout view=new FrameLayout(parent.getContext());
FrameLayout view=tabViews[viewType];
((ViewGroup)view.getParent()).removeView(view);
view.setVisibility(View.VISIBLE);
view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return new SimpleViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SimpleViewHolder holder, int position){
FrameLayout view=tabViews[position];
if(view.getParent() instanceof ViewGroup parent)
parent.removeView(view);
view.setVisibility(View.VISIBLE);
((FrameLayout)holder.itemView).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Fragment fragment=getFragmentForPage(position);
if(!fragment.isAdded()){
getChildFragmentManager().beginTransaction().add(view.getId(), fragment).commit();
getChildFragmentManager().beginTransaction().add(holder.itemView.getId(), fragment).commit();
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw(){

View File

@@ -6,11 +6,11 @@ import android.text.TextUtils;
import androidx.annotation.Nullable;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.parceler.Parcel;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
/**
@@ -23,22 +23,22 @@ public class Account extends BaseModel implements Searchable{
/**
* The account id
*/
// @RequiredField
@RequiredField
public String id;
/**
* The username of the account, not including domain.
*/
// @RequiredField
@RequiredField
public String username;
/**
* The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
*/
// @RequiredField
@RequiredField
public String acct;
/**
* The location of the user's profile page.
*/
// @RequiredField
@RequiredField
public String url;
// Display attributes
@@ -51,12 +51,12 @@ public class Account extends BaseModel implements Searchable{
/**
* The profile's bio / description.
*/
// @RequiredField
@RequiredField
public String note;
/**
* An image icon that is shown next to statuses and in the profile.
*/
// @RequiredField
@RequiredField
public String avatar;
/**
* A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.
@@ -134,7 +134,6 @@ public class Account extends BaseModel implements Searchable{
* When a timed mute will expire, if applicable.
*/
public Instant muteExpiresAt;
public boolean noindex;
public List<Role> roles;
@@ -158,26 +157,16 @@ public class Account extends BaseModel implements Searchable{
if(fields!=null){
for(AccountField f:fields)
f.postprocess();
}else{
fields=Collections.emptyList();
}
if(emojis!=null){
for(Emoji e:emojis)
e.postprocess();
}else{
emojis=Collections.emptyList();
}
if(moved!=null)
moved.postprocess();
if(fqn==null) fqn=getFullyQualifiedName();
if(id==null) id="";
if(username==null) username="";
if(TextUtils.isEmpty(displayName))
displayName=!TextUtils.isEmpty(username) ? username : "";
if(acct==null) acct="";
if(url==null) url="";
if(note==null) note="";
if(avatar==null) avatar="";
displayName=username;
if(fqn == null) fqn = getFullyQualifiedName();
}
public boolean isLocal(){
@@ -202,15 +191,9 @@ public class Account extends BaseModel implements Searchable{
}
public String getFullyQualifiedName() {
if (TextUtils.isEmpty(acct))
return "";
return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL();
}
public String getDisplayName(){
return '\u2068'+displayName+'\u2069';
}
@Override
public String toString(){
return "Account{"+
@@ -238,7 +221,6 @@ public class Account extends BaseModel implements Searchable{
", source="+source+
", suspended="+suspended+
", muteExpiresAt="+muteExpiresAt+
", noindex="+noindex+
'}';
}
}

View File

@@ -52,7 +52,7 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
public Status toStatus() {
Status s=Status.ofFake(id, content, publishedAt);
s.createdAt=startsAt != null ? startsAt : publishedAt;
s.createdAt=startsAt != null ? startsAt : publishedAt;
s.reactions=reactions;
if(updatedAt != null) s.editedAt=updatedAt;
return s;

View File

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

View File

@@ -1,6 +1,5 @@
package org.joinmastodon.android.model;
import java.util.ArrayList;
import java.util.List;
public class EmojiCategory{
@@ -11,8 +10,4 @@ public class EmojiCategory{
this.title=title;
this.emojis=emojis;
}
public EmojiCategory(EmojiCategory category){
this.title = category.title;
this.emojis = new ArrayList<>(category.emojis);
}
}

View File

@@ -14,7 +14,7 @@ public class FilterResult extends BaseModel {
@Override
public void postprocess() throws ObjectValidationException {
super.postprocess();
if(filter!=null) filter.postprocess();
if(filter!=null) filter.postprocess();
if(keywordMatches==null) keywordMatches=List.of();
}
}

View File

@@ -23,7 +23,6 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
", following="+following+
", history="+history+
", statusesCount="+statusesCount+
", following="+following+
'}';
}
@@ -31,19 +30,4 @@ public class Hashtag extends BaseModel implements DisplayItemsParent{
public String getID(){
return name;
}
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Hashtag hashtag=(Hashtag) o;
return name.equals(hashtag.name);
}
@Override
public int hashCode(){
return name.hashCode();
}
}

View File

@@ -20,22 +20,4 @@ public class Mention extends BaseModel{
", url='"+url+'\''+
'}';
}
@Override
public boolean equals(Object o){
if(this==o) return true;
if(o==null || getClass()!=o.getClass()) return false;
Mention mention=(Mention) o;
if(!id.equals(mention.id)) return false;
return url.equals(mention.url);
}
@Override
public int hashCode(){
int result=id.hashCode();
result=31*result+url.hashCode();
return result;
}
}

View File

@@ -37,8 +37,6 @@ public class Source extends BaseModel{
* The number of pending follow requests.
*/
public int followRequestCount;
public Boolean indexable;
public boolean hideCollections;
@Override
public void postprocess() throws ObjectValidationException{
@@ -56,8 +54,6 @@ public class Source extends BaseModel{
", sensitive="+sensitive+
", language='"+language+'\''+
", followRequestCount="+followRequestCount+
", indexable="+indexable+
", hideCollections="+hideCollections+
'}';
}
}

View File

@@ -5,35 +5,26 @@ import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDese
import android.text.TextUtils;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import androidx.annotation.NonNull;
import com.github.bottomSoftwareFoundation.bottom.Bottom;
import com.github.bottomSoftwareFoundation.bottom.TranslationError;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.utils.StatusTextEncoder;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.parceler.Parcel;
import java.lang.reflect.Type;
import java.time.Instant;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.List;
@Parcel
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
@@ -91,11 +82,10 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient boolean spoilerRevealed;
public transient boolean sensitiveRevealed;
public transient boolean textExpanded, textExpandable;
public transient String hasGapAfter;
public transient boolean hasGapAfter;
public transient TranslatedStatus translation;
public transient boolean translationShown;
private transient String strippedText;
public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation;
public transient boolean fromStatusCreated;
public Status(){}
@@ -195,10 +185,6 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
return reblog!=null ? reblog : this;
}
public String getContentStatusID(){
return getContentStatus().id;
}
public String getStrippedText(){
if(strippedText==null)
strippedText=HtmlParser.strip(content);
@@ -215,38 +201,6 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
return (Status) super.clone();
}
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 boolean isEligibleForTranslation(AccountSession session){
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
boolean translateEnabled = instanceInfo != null &&
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null &&
instanceInfo.v2.configuration.translation.enabled;
try {
String bottomText = BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN)
: null;
if(bottomText==null || bottomText.length()==0 || bottomText.equals("\u0005")) bottomText=null;
if(bottomText!=null){
translation=new Translation();
translation.content=bottomText;
translation.detectedSourceLanguage="\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48";
translation.provider="bottom-java";
return true;
}
} catch (TranslationError ignored) {}
return translateEnabled && !TextUtils.isEmpty(content) && !TextUtils.isEmpty(language)
&& !Objects.equals(Locale.getDefault().getLanguage(), language)
&& (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED);
}
public enum TranslationState{
HIDDEN,
SHOWN,
LOADING
}
public boolean isReblogPermitted(String accountID){
return visibility.isReblogPermitted(account.id.equals(
AccountSessionManager.getInstance().getAccount(accountID).self.id

View File

@@ -219,7 +219,7 @@ public class TimelineDefinition {
args.putString("listID", listId);
args.putBoolean("listIsExclusive", listIsExclusive);
} else if (type == TimelineType.HASHTAG) {
args.putString("hashtagName", hashtagName);
args.putString("hashtag", hashtagName);
args.putBoolean("localOnly", hashtagLocalOnly);
args.putStringArrayList("any", hashtagAny == null ? new ArrayList<>() : new ArrayList<>(hashtagAny));
args.putStringArrayList("all", hashtagAll == null ? new ArrayList<>() : new ArrayList<>(hashtagAll));

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