Compare commits

..

2 Commits

Author SHA1 Message Date
sk
fdff407514 refactor code 2023-10-06 15:01:17 +02:00
LucasGGamerM
422c3c3809 fix(translations): fix crash when status language is null 2023-10-02 17:00:21 -03:00
336 changed files with 2598 additions and 3161 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,8 +39,7 @@ import me.grishka.appkit.api.ErrorResponse;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent { public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState){ protected void onCreate(@Nullable Bundle savedInstanceState){
AccountSession session=getCurrentSession(); UiUtils.setUserPreferredTheme(this);
UiUtils.setUserPreferredTheme(this, session);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if(savedInstanceState==null){ if(savedInstanceState==null){
@@ -218,36 +217,6 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent); if (fragment != null) callFragmentToProvideAssistContent(fragment, assistContent);
} }
public AccountSession getCurrentSession(){
AccountSession session;
Bundle args=new Bundle();
Intent intent=getIntent();
if(intent.hasExtra("fromExternalShare")) {
return AccountSessionManager.getInstance()
.getAccount(intent.getStringExtra("account"));
}
boolean fromNotification = intent.getBooleanExtra("fromNotification", false);
boolean hasNotification = intent.hasExtra("notification");
if(fromNotification){
String accountID=intent.getStringExtra("accountID");
try{
session=AccountSessionManager.getInstance().getAccount(accountID);
if(!hasNotification) args.putString("tab", "notifications");
}catch(IllegalStateException x){
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
}else{
session=AccountSessionManager.getInstance().getLastActiveAccount();
}
return session;
}
public void restartActivity(){
finish();
startActivity(new Intent(this, MainActivity.class));
}
public void restartHomeFragment(){ public void restartHomeFragment(){
if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){ if(AccountSessionManager.getInstance().getLoggedInAccounts().isEmpty()){
showFragmentClearingBackStack(new CustomWelcomeFragment()); showFragmentClearingBackStack(new CustomWelcomeFragment());

View File

@@ -14,6 +14,7 @@ import android.content.Intent;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; 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.PushNotification;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy; import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
@@ -210,7 +212,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if (!GlobalUserPreferences.uniformNotificationIcon) { if (!GlobalUserPreferences.uniformNotificationIcon) {
builder.setSmallIcon(switch (pn.notificationType) { builder.setSmallIcon(switch (pn.notificationType) {
case FAVORITE -> GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled; case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled; case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled; case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
case MENTION -> R.drawable.ic_fluent_mention_24_filled; case MENTION -> R.drawable.ic_fluent_mention_24_filled;

View File

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

View File

@@ -97,7 +97,7 @@ public class PushSubscriptionManager{
deviceToken=getPrefs().getString("deviceToken", null); deviceToken=getPrefs().getString("deviceToken", null);
int tokenVersion=getPrefs().getInt("version", 0); int tokenVersion=getPrefs().getInt("version", 0);
if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){ if(!TextUtils.isEmpty(deviceToken) && tokenVersion==BuildConfig.VERSION_CODE){
registerAllAccountsForPush(false); registerAllAccountsForPush(true); // TODO: revert this before release
return; return;
} }
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token..."); Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");

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

View File

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

View File

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

View File

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

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; package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ScheduledStatus;
public class ScheduledStatusDeletedEvent{ public class ScheduledStatusDeletedEvent{
public final String id; public final String id;
public final String accountID; public final String accountID;

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
@@ -33,6 +34,7 @@ import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
@@ -88,7 +90,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected Rect tmpRect=new Rect(); protected Rect tmpRect=new Rect();
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView); protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, MediaAttachmentViewController> attachmentViewsPool=new TypedObjectPool<>(this::makeNewMediaAttachmentView);
protected boolean currentlyScrolling; protected boolean currentlyScrolling;
protected String maxID;
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
@@ -155,8 +156,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
protected String getMaxID(){ protected String getMaxID(){
if(refreshing) return null;
if(maxID!=null) return maxID;
if(!preloadedData.isEmpty()) if(!preloadedData.isEmpty())
return preloadedData.get(preloadedData.size()-1).getID(); return preloadedData.get(preloadedData.size()-1).getID();
else if(!data.isEmpty()) else if(!data.isEmpty())
@@ -165,12 +164,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null; return null;
} }
protected boolean applyMaxID(List<Status> result){
boolean empty=result.isEmpty();
if(!empty) maxID=result.get(result.size()-1).id;
return !empty;
}
protected abstract List<StatusDisplayItem> buildDisplayItems(T s); protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
protected abstract void addAccountToKnown(T s); protected abstract void addAccountToKnown(T s);
@@ -557,21 +550,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) { public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
Status status = holder.getItem().status; Status status = holder.getItem().status;
if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false); MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class);
MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class); if (mediaGrid != null) {
if(mediaGrid!=null){ if (!status.sensitiveRevealed) mediaGrid.revealSensitive();
if(!status.sensitiveRevealed) mediaGrid.revealSensitive();
else mediaGrid.hideSensitive(); else mediaGrid.hideSensitive();
}else{ } else {
status.sensitiveRevealed=false; // media grid's methods normally change the status' state - we still want to be able
notifyItemChangedAfter(holder.getItem(), MediaGridStatusDisplayItem.class); // to do this if the media grid is not bound, tho - so, doing it ourselves here
status.sensitiveRevealed = !status.sensitiveRevealed;
} }
holder.rebind();
} }
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) { public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true); if(header != null) header.rebind();
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
} }
protected void toggleSpoiler(Status status, String itemID){ protected void toggleSpoiler(Status status, String itemID){
@@ -580,8 +573,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
status.sensitiveRevealed = false; status.sensitiveRevealed = false;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class); SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null) spoiler.rebind(); if(spoiler!=null)
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class); spoiler.rebind();
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class)); SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
int index=displayItems.indexOf(spoilerItem); int index=displayItems.indexOf(spoilerItem);
@@ -593,29 +586,39 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size()); adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
} }
notifyItemChanged(itemID, TextStatusDisplayItem.class); TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null)
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.rebind(); if(header!=null)
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class); header.rebind();
list.invalidateItemDecorations(); list.invalidateItemDecorations();
} }
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) { public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
Status s=holder.getItem().status; if (holder.getItem().status.textExpandable != expandable && list != null) {
if(s.textExpandable!=expandable && list!=null) { holder.getItem().status.textExpandable = expandable;
s.textExpandable=expandable; HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); if (header != null) header.rebind();
if(header!=null) header.bindCollapseButton();
} }
} }
public void onToggleExpanded(Status status, String itemID) { public void onToggleExpanded(Status status, String itemID) {
status.textExpanded = !status.textExpanded; status.textExpanded = !status.textExpanded;
notifyItemChanged(itemID, TextStatusDisplayItem.class); TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.animateExpandToggle(); if (text != null) text.rebind();
else notifyItemChanged(itemID, HeaderStatusDisplayItem.class); if (header != null) header.rebind();
}
public void updateEmojiReactions(Status status, String itemID){
EmojiReactionsStatusDisplayItem.Holder reactions=findHolderOfType(itemID, EmojiReactionsStatusDisplayItem.Holder.class);
if(reactions != null){
reactions.getItem().status.reactions.clear();
reactions.getItem().status.reactions.addAll(status.reactions);
reactions.rebind();
}
} }
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){} public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
@@ -674,61 +677,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null; return null;
} }
/**
* Use this as a fallback if findHolderOfType fails to find the ViewHolder.
* It might still be bound but off-screen and therefore not a child of the RecyclerView -
* resulting in the ViewHolder displaying an outdated state once scrolled back into view.
*/
protected <I extends StatusDisplayItem> int notifyItemChanged(String id, Class<I> type){
boolean encounteredParent=false;
for(int i=0; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
boolean idEquals=id.equals(item.parentID);
if(!encounteredParent && idEquals) encounteredParent=true; // reached top of the parent
else if(encounteredParent && !idEquals) break; // passed by bottom of the parent. man muss ja wissen wann schluss is
if(idEquals && type.isInstance(item)){
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedAfter(StatusDisplayItem afterThis, Class<I> type){
int startIndex=displayItems.indexOf(afterThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem");
String parentID=afterThis.parentID;
for(int i=startIndex; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class<I> type){
int startIndex=displayItems.indexOf(beforeThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem");
String parentID=beforeThis.parentID;
for(int i=startIndex; i>=0; i--){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
@Nullable @Nullable
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){ protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
for(int i=0; i<list.getChildCount(); i++){ for(int i=0;i<list.getChildCount();i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i)); RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder)) if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
return type.cast(holder); return type.cast(holder);
@@ -850,8 +801,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(text!=null){ if(text!=null){
text.updateTranslation(true); text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition()); imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
} }
} }
@@ -863,8 +812,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){ if(text!=null){
text.updateTranslation(true); text.updateTranslation(true);
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
} }
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error) .setTitle(R.string.error)
@@ -881,8 +828,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(text!=null){ if(text!=null){
text.updateTranslation(true); text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition()); imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
} }
} }
@@ -901,7 +846,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(getContext()==null) return; if(getContext()==null) return;
super.onDataLoaded(d, more); super.onDataLoaded(d, more);
// more available, but the page isn't even full yet? seems wrong, let's load some more // more available, but the page isn't even full yet? seems wrong, let's load some more
if(more && data.size() < itemsPerPage){ if(more && d.size() < itemsPerPage){
preloader.onScrolledToLastItem(); preloader.onScrolledToLastItem();
} }
} }

View File

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

View File

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

View File

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

View File

@@ -169,7 +169,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
} }
private void updateOptionsMenu() { private void updateOptionsMenu() {
if(getActivity()==null) return; if (getActivity() == null) return;
optionsMenu.clear(); optionsMenu.clear();
timelineByMenuItem.clear(); timelineByMenuItem.clear();

View File

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

View File

@@ -20,6 +20,7 @@ import org.joinmastodon.android.api.requests.accounts.GetFollowRequests;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.HeaderPaginationList;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
@@ -82,7 +83,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(HeaderPaginationList<Account> result){ public void onSuccess(HeaderPaginationList<Account> result){
if(getActivity()==null) return; if (getActivity() == null) return;
if(result.nextPageUri!=null) if(result.nextPageUri!=null)
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else
@@ -270,7 +271,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id); relationship=relationships.get(item.account.id);
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account); UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(relationship==null || !relationship.followedBy){ if(relationship==null || !relationship.followedBy){
actionWrap.setVisibility(View.GONE); actionWrap.setVisibility(View.GONE);
@@ -365,9 +366,9 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000); coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(account.emojis.isEmpty()){ if(account.emojis.isEmpty()){
parsedName= account.getDisplayName(); parsedName=account.displayName;
}else{ }else{
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis); parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio)); emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
} }
} }

View File

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

View File

@@ -21,7 +21,6 @@ import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.requests.tags.GetTag; import org.joinmastodon.android.api.requests.tags.GetTag;
import org.joinmastodon.android.api.requests.tags.SetTagFollowed; import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline; import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -86,19 +85,16 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
@Override @Override
protected TimelineDefinition makeTimelineDefinition() { protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofHashtag(hashtagName); return TimelineDefinition.ofHashtag(hashtag);
} }
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetHashtagTimeline(hashtagName, getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetHashtagTimeline(hashtagName, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return; onDataLoaded(result, !result.isEmpty());
boolean more=applyMaxID(result);
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
} }
}) })
.exec(accountID); .exec(accountID);
@@ -221,6 +217,8 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag); followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
pinMenuItem=optionsMenu.findItem(R.id.pin); pinMenuItem=optionsMenu.findItem(R.id.pin);
followMenuItem.setVisible(toolbarContentVisible); followMenuItem.setVisible(toolbarContentVisible);
followMenuItem.setTitle(getString(hashtag.following ? R.string.unfollow_user : R.string.follow_user, "#"+hashtagName));
followMenuItem.setIcon(hashtag.following ? R.drawable.ic_fluent_person_delete_24_filled : R.drawable.ic_fluent_person_add_24_regular);
pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS); pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
super.updatePinButton(pinMenuItem); super.updatePinButton(pinMenuItem);
if(toolbarContentVisible){ if(toolbarContentVisible){
@@ -262,7 +260,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
} }
private void updateHeader(){ private void updateHeader(){
if(hashtag==null || getActivity()==null) if(hashtag==null)
return; return;
if(hashtag.history!=null && !hashtag.history.isEmpty()){ if(hashtag.history!=null && !hashtag.history.isEmpty()){

View File

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

View File

@@ -287,7 +287,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
new GetAnnouncements(false).setCallback(new Callback<>() { new GetAnnouncements(false).setCallback(new Callback<>() {
@Override @Override
public void onSuccess(List<Announcement> result) { public void onSuccess(List<Announcement> result) {
if(getActivity()==null) return; if (getActivity() == null) return;
if (result.stream().anyMatch(a -> !a.read)) { if (result.stream().anyMatch(a -> !a.read)) {
announcementsBadged = true; announcementsBadged = true;
announcements.setVisible(false); announcements.setVisible(false);
@@ -381,7 +381,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
} }
private void updateOverflowMenu() { private void updateOverflowMenu() {
if(getActivity()==null) return; if (getActivity() == null) return;
Menu m = overflowPopup.getMenu(); Menu m = overflowPopup.getMenu();
m.clear(); m.clear();
overflowPopup.inflate(R.menu.home_overflow); overflowPopup.inflate(R.menu.home_overflow);
@@ -400,9 +400,10 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
addListsToOverflowMenu(); addListsToOverflowMenu();
addHashtagsToOverflowMenu(); addHashtagsToOverflowMenu();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI()) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
m.setGroupDividerEnabled(true); m.setGroupDividerEnabled(true);
} }
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){

View File

@@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.requests.markers.SaveMarkers; import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline; import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.CacheablePaginatedResponse; import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
@@ -19,7 +20,6 @@ import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -49,6 +49,16 @@ public class HomeTimelineFragment extends StatusListFragment {
loadData(); loadData();
} }
private boolean typeFilterPredicate(Status s) {
AccountLocalPreferences lp=getLocalPrefs();
return (lp.showReplies || s.inReplyToId == null) &&
(lp.showBoosts || s.reblog == null);
}
private List<Status> filterPosts(List<Status> items) {
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
}
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance() AccountSessionManager.getInstance()
@@ -56,12 +66,11 @@ public class HomeTimelineFragment extends StatusListFragment {
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){ .getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
@Override @Override
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){ public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
if(getActivity()==null) return; if (getActivity() == null) return;
boolean empty=result.items.isEmpty(); List<Status> filteredItems = filterPosts(result.items);
maxID=result.maxID; maxID=result.maxID;
AccountSessionManager.get(accountID).filterStatuses(result.items, getFilterContext()); onDataLoaded(filteredItems, !result.items.isEmpty());
onDataLoaded(result.items, !empty); if(result.isFromCache())
if(result.isFromCache() && GlobalUserPreferences.loadNewPosts)
loadNewPosts(); loadNewPosts();
} }
}); });
@@ -74,7 +83,7 @@ public class HomeTimelineFragment extends StatusListFragment {
list.addOnScrollListener(new RecyclerView.OnScrollListener(){ list.addOnScrollListener(new RecyclerView.OnScrollListener(){
@Override @Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){
if(parent!=null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){ if(parent != null && parent.isNewPostsBtnShown() && list.getChildAdapterPosition(list.getChildAt(0))<=getMainAdapterOffset()){
parent.hideNewPostsButton(); parent.hideNewPostsButton();
} }
} }
@@ -87,7 +96,7 @@ public class HomeTimelineFragment extends StatusListFragment {
if(!getArguments().getBoolean("noAutoLoad")){ if(!getArguments().getBoolean("noAutoLoad")){
if(!loaded && !dataLoading){ if(!loaded && !dataLoading){
loadData(); loadData();
}else if(!dataLoading && GlobalUserPreferences.loadNewPosts){ }else if(!dataLoading){
loadNewPosts(); loadNewPosts();
} }
} }
@@ -117,54 +126,45 @@ public class HomeTimelineFragment extends StatusListFragment {
} }
public void onStatusCreated(Status status){ public void onStatusCreated(Status status){
if(status.reblog!=null) return;
prependItems(Collections.singletonList(status), true); prependItems(Collections.singletonList(status), true);
} }
private void loadNewPosts(){ private void loadNewPosts(){
if (!GlobalUserPreferences.loadNewPosts) return;
dataLoading=true; dataLoading=true;
// we only care about the data that was actually retrieved from the timeline api since
// user-created statuses are probably in the wrong position
List<Status> dataFromTimeline=data.stream().filter(s->!s.fromStatusCreated).collect(Collectors.toList());
// The idea here is that we request the timeline such that if there are fewer than `limit` posts, // The idea here is that we request the timeline such that if there are fewer than `limit` posts,
// we'll get the currently topmost post as last in the response. This way we know there's no gap // we'll get the currently topmost post as last in the response. This way we know there's no gap
// between the existing and newly loaded parts of the timeline. // between the existing and newly loaded parts of the timeline.
String sinceID=dataFromTimeline.size()>1 ? dataFromTimeline.get(1).id : "1"; String sinceID=data.size()>1 ? data.get(1).id : "1";
currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetHomeTimeline(null, null, 20, sinceID, getLocalPrefs().timelineReplyVisibility)
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
currentRequest=null; currentRequest=null;
dataLoading=false; dataLoading=false;
result = filterPosts(result);
if(result.isEmpty() || getActivity()==null) if(result.isEmpty() || getActivity()==null)
return; return;
Status last=result.get(result.size()-1); Status last=result.get(result.size()-1);
List<Status> toAdd; List<Status> toAdd;
if(!dataFromTimeline.isEmpty() && last.id.equals(dataFromTimeline.get(0).id)){ // This part intersects with the existing one if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one
toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post toAdd=result.subList(0, result.size()-1); // Remove the already known last post
}else{ }else{
last.hasGapAfter=last.id; result.get(result.size()-1).hasGapAfter=true;
toAdd=result; toAdd=result;
} }
if(!toAdd.isEmpty())
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(new ArrayList<>(toAdd), false);
// removing statuses that come up as duplicates (hopefully only posts and boosts that were locally created
// and thus were already prepended to the timeline earlier)
List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList());
toAdd.removeIf(s->existingIds.contains(s.getID()));
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext()); AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
if(!toAdd.isEmpty()){ if(!toAdd.isEmpty()){
prependItems(toAdd, true); prependItems(toAdd, true);
if(parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton(); if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
} }
refreshDone();
} }
@Override @Override
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
currentRequest=null; currentRequest=null;
dataLoading=false; dataLoading=false;
refreshDone();
} }
}) })
.exec(accountID); .exec(accountID);
@@ -182,10 +182,10 @@ public class HomeTimelineFragment extends StatusListFragment {
gap.loading=true; gap.loading=true;
dataLoading=true; dataLoading=true;
String maxID=null; String maxID = null;
String minID=null; String minID = null;
if (downwards) { if (downwards) {
maxID=item.getItem().getMaxID(); maxID = item.getItemID();
} else { } else {
int gapPos=displayItems.indexOf(gap); int gapPos=displayItems.indexOf(gap);
StatusDisplayItem nextItem=displayItems.get(gapPos + 1); StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
@@ -202,13 +202,12 @@ public class HomeTimelineFragment extends StatusListFragment {
int gapPos=displayItems.indexOf(gap); int gapPos=displayItems.indexOf(gap);
if(gapPos==-1) if(gapPos==-1)
return; return;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
if(result.isEmpty()){ if(result.isEmpty()){
displayItems.remove(gapPos); displayItems.remove(gapPos);
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos); adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
Status gapStatus=getStatusByID(gap.parentID); Status gapStatus=getStatusByID(gap.parentID);
if(gapStatus!=null){ if(gapStatus!=null){
gapStatus.hasGapAfter=null; gapStatus.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
} }
}else{ }else{
@@ -221,7 +220,7 @@ public class HomeTimelineFragment extends StatusListFragment {
idsBelowGap.add(s.id); idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){ }else if(s.id.equals(gap.parentID)){
belowGap=true; belowGap=true;
s.hasGapAfter=null; s.hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{ }else{
gapPostIndex++; gapPostIndex++;
@@ -234,8 +233,7 @@ public class HomeTimelineFragment extends StatusListFragment {
break; break;
} }
if(endIndex==result.size()){ if(endIndex==result.size()){
Status last=result.get(result.size()-1); result.get(result.size()-1).hasGapAfter=true;
last.hasGapAfter=last.id;
}else{ }else{
result=result.subList(0, endIndex); result=result.subList(0, endIndex);
} }
@@ -280,7 +278,7 @@ public class HomeTimelineFragment extends StatusListFragment {
.filter(s->Objects.equals(s.id, gap.parentID)) .filter(s->Objects.equals(s.id, gap.parentID))
.findFirst(); .findFirst();
if (gapStatus.isPresent()) { if (gapStatus.isPresent()) {
gapStatus.get().hasGapAfter=null; gapStatus.get().hasGapAfter=false;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
} }
targetList.clear(); targetList.clear();
@@ -331,8 +329,8 @@ public class HomeTimelineFragment extends StatusListFragment {
currentRequest=null; currentRequest=null;
dataLoading=false; dataLoading=false;
} }
if(parent!=null) parent.hideNewPostsButton(); if (parent != null) parent.hideNewPostsButton();
loadNewPosts(); super.onRefresh();
} }
@Override @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.GetList;
import org.joinmastodon.android.api.requests.lists.UpdateList; import org.joinmastodon.android.api.requests.lists.UpdateList;
import org.joinmastodon.android.api.requests.timelines.GetListTimeline; import org.joinmastodon.android.api.requests.timelines.GetListTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.ListDeletedEvent; import org.joinmastodon.android.events.ListDeletedEvent;
import org.joinmastodon.android.events.ListUpdatedCreatedEvent; import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
@@ -26,8 +25,10 @@ import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ListEditor; import org.joinmastodon.android.ui.views.ListEditor;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -62,7 +63,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
new GetList(listID).setCallback(new Callback<>() { new GetList(listID).setCallback(new Callback<>() {
@Override @Override
public void onSuccess(ListTimeline listTimeline) { public void onSuccess(ListTimeline listTimeline) {
if(getActivity()==null) return; if (getActivity() == null) return;
// TODO: save updated info // TODO: save updated info
if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title); if (!listTimeline.title.equals(listTitle)) setTitle(listTimeline.title);
if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) { if (listTimeline.repliesPolicy != null && !listTimeline.repliesPolicy.equals(repliesPolicy)) {
@@ -100,7 +101,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() { new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
@Override @Override
public void onSuccess(ListTimeline list) { public void onSuccess(ListTimeline list) {
if(getActivity()==null) return; if (getActivity() == null) return;
setTitle(list.title); setTitle(list.title);
listTitle = list.title; listTitle = list.title;
repliesPolicy = list.repliesPolicy; repliesPolicy = list.repliesPolicy;
@@ -133,14 +134,13 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
@Override @Override
protected void doLoadData(int offset, int count) { protected void doLoadData(int offset, int count) {
currentRequest=new GetListTimeline(listID, getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility) currentRequest=new GetListTimeline(listID, offset==0 ? null : getMaxID(), null, count, null, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this) { .setCallback(new SimpleCallback<>(this) {
@Override @Override
public void onSuccess(List<Status> result) { public void onSuccess(List<Status> result) {
if(getActivity()==null) return; if (getActivity() == null) return;
boolean more=applyMaxID(result); result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext()); onDataLoaded(result, !result.isEmpty());
onDataLoaded(result, more);
} }
}) })
.exec(accountID); .exec(accountID);

View File

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

View File

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

View File

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

View File

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

View File

@@ -133,7 +133,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ImageView avatar; private ImageView avatar;
private CoverImageView cover; private CoverImageView cover;
private View avatarBorder; private View avatarBorder;
private View usernameWrap;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel; private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
private ImageView lockIcon, botIcon; private ImageView lockIcon, botIcon;
private ProgressBarButton actionButton, notifyButton; private ProgressBarButton actionButton, notifyButton;
@@ -234,7 +233,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
cover=content.findViewById(R.id.cover); cover=content.findViewById(R.id.cover);
avatarBorder=content.findViewById(R.id.avatar_border); avatarBorder=content.findViewById(R.id.avatar_border);
name=content.findViewById(R.id.name); name=content.findViewById(R.id.name);
usernameWrap=content.findViewById(R.id.username_wrap);
username=content.findViewById(R.id.username); username=content.findViewById(R.id.username);
lockIcon=content.findViewById(R.id.lock_icon); lockIcon=content.findViewById(R.id.lock_icon);
botIcon=content.findViewById(R.id.bot_icon); botIcon=content.findViewById(R.id.bot_icon);
@@ -482,7 +480,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(Account result){ public void onSuccess(Account result){
if(getActivity()==null) return; if (getActivity() == null) return;
onAccountLoaded(result); onAccountLoaded(result);
} }
}) })
@@ -621,14 +619,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private void bindHeaderView(){ private void bindHeaderView(){
setTitle(account.getDisplayName()); setTitle(account.displayName);
setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount)); setSubtitle(getResources().getQuantityString(R.plurals.x_posts, (int)(account.statusesCount%1000), account.statusesCount));
ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest( ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(
TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() : TextUtils.isEmpty(account.avatar) ? getSession().getDefaultAvatarUrl() :
GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic,
V.dp(100), V.dp(100))); V.dp(100), V.dp(100)));
ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.header : account.headerStatic, 1000, 1000)); 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) if(AccountSessionManager.get(accountID).getLocalPreferences().customEmojiInNames)
HtmlParser.parseCustomEmoji(ssb, account.emojis); HtmlParser.parseCustomEmoji(ssb, account.emojis);
name.setText(ssb); name.setText(ssb);
@@ -755,21 +753,20 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1; boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
MenuItem openWithAccounts = menu.findItem(R.id.open_with_account); MenuItem openWithAccounts = menu.findItem(R.id.open_with_account);
openWithAccounts.setVisible(hasMultipleAccounts); openWithAccounts.setVisible(hasMultipleAccounts);
SubMenu accountsMenu=openWithAccounts.getSubMenu(); SubMenu accountsMenu = openWithAccounts.getSubMenu();
if(hasMultipleAccounts){ if (hasMultipleAccounts) {
accountsMenu.clear(); accountsMenu.clear();
UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL( UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL(
getActivity(), s.getID(), account.url, false getActivity(), s.getID(), account.url, false
)); ));
} }
menu.findItem(R.id.share).setTitle(R.string.share_user);
if(isOwnProfile) { if(isOwnProfile) {
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false); if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
return; 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.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); mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), mute); UiUtils.insetPopupMenuIcon(getContext(), mute);
@@ -777,22 +774,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.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following); menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following); menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
if (relationship.following) { 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.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); 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); 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 { } else {
hideBoosts.setVisible(false); menu.findItem(R.id.hide_boosts).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);
} }
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 @Override
@@ -804,11 +798,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
intent.putExtra(Intent.EXTRA_TEXT, account.url); intent.putExtra(Intent.EXTRA_TEXT, account.url);
startActivity(Intent.createChooser(intent, item.getTitle())); startActivity(Intent.createChooser(intent, item.getTitle()));
}else if(id==R.id.mute){ }else if(id==R.id.mute){
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship); confirmToggleMuted();
}else if(id==R.id.block){ }else if(id==R.id.block){
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship); confirmToggleBlocked();
}else if(id==R.id.soft_block){ }else if(id==R.id.soft_block){
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship); confirmSoftBlockUser();
}else if(id==R.id.report){ }else if(id==R.id.report){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@@ -898,7 +892,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
} }
private void updateRelationship(){ private void updateRelationship(){
if(getActivity()==null) return; if (getActivity() == null) return;
invalidateOptionsMenu(); invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE); actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE); notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
@@ -1105,7 +1099,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
updateMetadataHeight(); updateMetadataHeight();
Toolbar toolbar=getToolbar(); 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)); close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant));
toolbar.setNavigationIcon(close); toolbar.setNavigationIcon(close);
toolbar.setNavigationContentDescription(R.string.discard); toolbar.setNavigationContentDescription(R.string.discard);
@@ -1120,7 +1114,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
name.setVisibility(View.GONE); name.setVisibility(View.GONE);
rolesView.setVisibility(View.GONE); rolesView.setVisibility(View.GONE);
usernameWrap.setVisibility(View.GONE); username.setVisibility(View.GONE);
bio.setVisibility(View.GONE); bio.setVisibility(View.GONE);
countersLayout.setVisibility(View.GONE); countersLayout.setVisibility(View.GONE);
@@ -1169,7 +1163,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bioEditWrap.setVisibility(View.GONE); bioEditWrap.setVisibility(View.GONE);
name.setVisibility(View.VISIBLE); name.setVisibility(View.VISIBLE);
rolesView.setVisibility(View.VISIBLE); rolesView.setVisibility(View.VISIBLE);
usernameWrap.setVisibility(View.VISIBLE); username.setVisibility(View.VISIBLE);
bio.setVisibility(View.VISIBLE); bio.setVisibility(View.VISIBLE);
countersLayout.setVisibility(View.VISIBLE); countersLayout.setVisibility(View.VISIBLE);
refreshLayout.setEnabled(true); refreshLayout.setEnabled(true);
@@ -1181,7 +1175,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
imm.hideSoftInputFromWindow(content.getWindowToken(), 0); imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
V.setVisibilityAnimated(fab, View.VISIBLE); V.setVisibilityAnimated(fab, View.VISIBLE);
bindHeaderView(); bindHeaderView();
V.setVisibilityAnimated(fab, View.VISIBLE);
} }
private void saveAndExitEditMode(){ private void saveAndExitEditMode(){
@@ -1196,7 +1189,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
savingEdits=false; savingEdits=false;
account=result; account=result;
AccountSessionManager.getInstance().updateAccountInfo(accountID, account); AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
if(getActivity()==null) return; if (getActivity() == null) return;
exitEditMode(); exitEditMode();
setActionProgressVisible(false); setActionProgressVisible(false);
} }
@@ -1211,6 +1204,18 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.exec(accountID); .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){ private void updateRelationship(Relationship r){
relationship=r; relationship=r;
updateRelationship(); updateRelationship();

View File

@@ -119,15 +119,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 @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count) currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)
@@ -138,7 +129,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
nextMaxID=result.nextPageUri.getQueryParameter("max_id"); nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else else
nextMaxID=null; nextMaxID=null;
if(getActivity()==null) return; if (getActivity() == null) return;
onDataLoaded(result, nextMaxID!=null); onDataLoaded(result, nextMaxID!=null);
} }
}) })

View File

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

View File

@@ -48,8 +48,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<Status> result){ public void onSuccess(List<Status> result){
if(getActivity()==null) return;
Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed()); Collections.sort(result, Comparator.comparing((Status s)->s.createdAt).reversed());
if (getActivity() == null) return;
onDataLoaded(result, false); onDataLoaded(result, false);
} }
}) })

View File

@@ -13,7 +13,6 @@ import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent;
@@ -29,7 +28,6 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -44,12 +42,10 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id); boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id);
int flags = 0; int flags = 0;
AccountLocalPreferences lp=getLocalPrefs(); AccountLocalPreferences lp=getLocalPrefs();
if(GlobalUserPreferences.spectatorMode) if (GlobalUserPreferences.spectatorMode)
flags |= StatusDisplayItem.FLAG_NO_FOOTER; 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; 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); return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags);
} }
@@ -144,12 +140,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
} }
} }
public Status getContentStatusByID(String id){ protected Status getContentStatusByID(String id){
Status s=getStatusByID(id); Status s=getStatusByID(id);
return s==null ? null : s.getContentStatus(); return s==null ? null : s.getContentStatus();
} }
public Status getStatusByID(String id){ protected Status getStatusByID(String id){
for(Status s:data){ for(Status s:data){
if(s.id.equals(id)){ if(s.id.equals(id)){
return s; return s;
@@ -176,58 +172,43 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
} }
} }
private void iterateRemoveStatus(List<Status> l, String id){ protected void removeStatus(Status status){
Iterator<Status> it=l.iterator(); data.remove(status);
while(it.hasNext()){ preloadedData.remove(status);
if(it.next().getContentStatus().id.equals(id)){ int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
it.remove(); for(int i=0;i<displayItems.size();i++){
StatusDisplayItem item = displayItems.get(i);
if(status.id.equals(item.parentID)){
index=i;
break;
} }
if (item.parentID.equals(status.inReplyToId)) {
if (ancestorFirstIndex == -1) ancestorFirstIndex = i;
ancestorLastIndex = i;
} }
} }
private void removeStatusDisplayItems(Status status, int index, int ancestorFirstIndex, int ancestorLastIndex, boolean deleteContent){
// did we find an ancestor that is also the status' neighbor? // did we find an ancestor that is also the status' neighbor?
if(ancestorFirstIndex>=0 && ancestorLastIndex==index-1){ if (ancestorFirstIndex >= 0 && ancestorLastIndex == index - 1) {
for(int i=ancestorFirstIndex; i<=ancestorLastIndex; i++){ for (int i = ancestorFirstIndex; i <= ancestorLastIndex; i++) {
StatusDisplayItem item=displayItems.get(i); StatusDisplayItem item = displayItems.get(i);
String id=deleteContent ? item.getContentID() : item.parentID;
// update ancestor to have no descendant anymore // update ancestor to have no descendant anymore
if(id.equals(status.inReplyToId)) item.hasDescendantNeighbor=false; if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
} }
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex-ancestorFirstIndex+1); adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
} }
if(index==-1) return; if(index==-1)
return;
int lastIndex; int lastIndex;
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){ for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
StatusDisplayItem item=displayItems.get(lastIndex); if(!displayItems.get(lastIndex).parentID.equals(status.id))
String id=deleteContent ? item.getContentID() : item.parentID; break;
if(!id.equals(status.id)) break;
} }
displayItems.subList(index, lastIndex).clear(); displayItems.subList(index, lastIndex).clear();
adapter.notifyItemRangeRemoved(index, lastIndex-index); adapter.notifyItemRangeRemoved(index, lastIndex-index);
} }
protected void removeStatus(Status status){
boolean deleteContent=status==status.getContentStatus();
int ancestorFirstIndex=-1, ancestorLastIndex=-1;
for(int i=0;i<displayItems.size();i++){
StatusDisplayItem item=displayItems.get(i);
String id=deleteContent ? item.getContentID() : item.parentID;
if(id.equals(status.id)){
removeStatusDisplayItems(status, i, ancestorFirstIndex, ancestorLastIndex, deleteContent);
ancestorFirstIndex=ancestorLastIndex=-1;
continue;
}
if(id.equals(status.inReplyToId)){
if(ancestorFirstIndex==-1) ancestorFirstIndex=i;
ancestorLastIndex=i;
}
}
iterateRemoveStatus(data, status.id);
iterateRemoveStatus(preloadedData, status.id);
}
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
@@ -294,18 +275,6 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
removeStatus(status); removeStatus(status);
} }
@Subscribe
public void onReblogDeleted(ReblogDeletedEvent ev){
if(!ev.accountID.equals(accountID))
return;
for(Status item : data){
if(item.getContentStatus().id.equals(ev.statusID) && item.reblog!=null){
removeStatus(item);
break;
}
}
}
@Subscribe @Subscribe
public void onStatusCreated(StatusCreatedEvent ev){ public void onStatusCreated(StatusCreatedEvent ev){
if(!ev.accountID.equals(accountID)) 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.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusByID; import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.requests.statuses.GetStatusContext; import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusUpdatedEvent; import org.joinmastodon.android.events.StatusUpdatedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
@@ -31,6 +31,7 @@ import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent; import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@@ -64,7 +65,7 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
knownAccounts.put(inReplyToAccount.id, inReplyToAccount); knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
data.add(mainStatus); data.add(mainStatus);
onAppendItems(Collections.singletonList(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); transitionFinished = getArguments().getBoolean("noTransition", false);
} }
@@ -194,8 +195,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
// TODO: figure out how this code works // TODO: figure out how this code works
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result); if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
filterStatuses(result.descendants); result.descendants=filterStatuses(result.descendants);
filterStatuses(result.ancestors); result.ancestors=filterStatuses(result.ancestors);
restoreStatusStates(result.descendants, oldData); restoreStatusStates(result.descendants, oldData);
restoreStatusStates(result.ancestors, oldData); restoreStatusStates(result.ancestors, oldData);
@@ -331,8 +332,11 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void filterStatuses(List<Status> statuses){ private List<Status> filterStatuses(List<Status> statuses){
AccountSessionManager.get(accountID).filterStatuses(statuses, getFilterContext()); StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext());
return statuses.stream()
.filter(statusFilterPredicate)
.collect(Collectors.toList());
} }
@Override @Override

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
@@ -66,8 +65,6 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
} }
@Override @Override
@@ -80,7 +77,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(List<FollowSuggestion> result){ public void onSuccess(List<FollowSuggestion> result){
if(getActivity()==null) return; if (getActivity() == null) return;
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false); onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
loadRelationships(); loadRelationships();
} }
@@ -115,7 +112,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public void onSuccess(List<Relationship> result){ public void onSuccess(List<Relationship> result){
relationshipsRequest=null; relationshipsRequest=null;
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity())); relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
if(getActivity()==null) return; if (getActivity() == null) return;
if(list==null) if(list==null)
return; return;
for(int i=0;i<list.getChildCount();i++){ for(int i=0;i<list.getChildCount();i++){
@@ -261,7 +258,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=relationships.get(item.account.id); relationship=relationships.get(item.account.id);
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account); UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(relationship==null){ if(relationship==null){
actionWrap.setVisibility(View.GONE); actionWrap.setVisibility(View.GONE);
@@ -330,9 +327,9 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000); coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
if(account.emojis.isEmpty()){ if(account.emojis.isEmpty()){
parsedName= account.getDisplayName(); parsedName=account.displayName;
}else{ }else{
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis); parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio)); emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
} }
} }

View File

@@ -13,10 +13,8 @@ import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.IsOnTop; import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ScrollableToTop; import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.SearchResult; import org.joinmastodon.android.model.SearchResult;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.SimpleViewHolder; import org.joinmastodon.android.ui.SimpleViewHolder;
@@ -98,6 +96,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){ pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
@Override @Override
public void onPageSelected(int position){ public void onPageSelected(int position){
if(position==0)
return;
Fragment _page=getFragmentForPage(position); Fragment _page=getFragmentForPage(position);
if(_page instanceof BaseRecyclerFragment<?> page){ if(_page instanceof BaseRecyclerFragment<?> page){
if(!page.loaded && !page.isDataLoading()) 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); searchView=view.findViewById(R.id.search_fragment);
if(searchFragment==null){ if(searchFragment==null){
searchFragment=new SearchFragment(); searchFragment=new SearchFragment();

View File

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

View File

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

View File

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

View File

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

View File

@@ -115,7 +115,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
onDataLoaded(results.stream().map(sr->{ onDataLoaded(results.stream().map(sr->{
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true); SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
if(sr.type==SearchResult.Type.HASHTAG){ if(sr.type==SearchResult.Type.HASHTAG){
vm.hashtagItem.setOnClick(i->openHashtag(sr)); vm.hashtagItem.onClick=()->openHashtag(sr);
} }
return vm; return vm;
}).collect(Collectors.toList()), false); }).collect(Collectors.toList()), false);
@@ -132,7 +132,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
.map(sr->{ .map(sr->{
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false); SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
if(sr.type==SearchResult.Type.HASHTAG){ if(sr.type==SearchResult.Type.HASHTAG){
vm.hashtagItem.setOnClick(i->openHashtag(sr)); vm.hashtagItem.onClick=()->openHashtag(sr);
} }
return vm; return vm;
}) })
@@ -429,11 +429,11 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
wrapSuicideDialog(()->deliverResult(currentQuery, null)); wrapSuicideDialog(()->deliverResult(currentQuery, null));
} }
private void onOpenURLClick(ListItem<?> item_){ private void onOpenURLClick(){
UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false); UiUtils.openURL(getContext(), accountID, searchViewHelper.getQuery(), false);
} }
private void onGoToHashtagClick(ListItem<?> item_){ private void onGoToHashtagClick(){
wrapSuicideDialog(()->{ wrapSuicideDialog(()->{
String q=searchViewHelper.getQuery(); String q=searchViewHelper.getQuery();
if(q.startsWith("#")) if(q.startsWith("#"))
@@ -442,7 +442,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}); });
} }
private void onGoToAccountClick(ListItem<?> item_){ private void onGoToAccountClick(){
String q=searchViewHelper.getQuery(); String q=searchViewHelper.getQuery();
if(!q.startsWith("@")){ if(!q.startsWith("@")){
q="@"+q; q="@"+q;
@@ -459,11 +459,11 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
}).ifPresent(progress -> progress.wrapProgress((Activity) getContext(), R.string.loading, true)); }).ifPresent(progress -> progress.wrapProgress((Activity) getContext(), R.string.loading, true));
} }
private void onGoToStatusSearchClick(ListItem<?> item_){ private void onGoToStatusSearchClick(){
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS)); wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.STATUS));
} }
private void onGoToAccountSearchClick(ListItem<?> item_){ private void onGoToAccountSearchClick(){
wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT)); wrapSuicideDialog(()->deliverResult(searchViewHelper.getQuery(), SearchResult.Type.ACCOUNT));
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -227,7 +227,7 @@ public class ReportDoneFragment extends MastodonToolbarFragment{
@Override @Override
protected int getNavigationIconDrawableResource(){ protected int getNavigationIconDrawableResource(){
return R.drawable.ic_fluent_dismiss_24_regular; return R.drawable.ic_baseline_close_24;
} }
@Override @Override

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), durationItem=new ListItem<>(R.string.settings_filter_duration, 0, this::onDurationClick),
wordsItem=new ListItem<>(R.string.settings_filter_muted_words, 0, this::onWordsClick), wordsItem=new ListItem<>(R.string.settings_filter_muted_words, 0, this::onWordsClick),
contextItem=new ListItem<>(R.string.settings_filter_context, 0, this::onContextClick), 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){ if(filter!=null){
@@ -113,7 +113,7 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
return 1; return 1;
} }
private void onDurationClick(ListItem<Void> item_){ private void onDurationClick(){
int[] durationOptions={ int[] durationOptions={
1800, 1800,
3600, 3600,
@@ -182,21 +182,21 @@ public class EditFilterFragment extends BaseSettingsFragment<Void> implements On
alert.setOnDismissListener(dialog->callback.accept(null)); alert.setOnDismissListener(dialog->callback.accept(null));
} }
private void onWordsClick(ListItem<Void> item){ private void onWordsClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelableArrayList("words", (ArrayList<? extends Parcelable>) keywords.stream().map(Parcels::wrap).collect(Collectors.toCollection(ArrayList::new))); 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); Nav.goForResult(getActivity(), FilterWordsFragment.class, args, WORDS_RESULT, this);
} }
private void onContextClick(ListItem<Void> item){ private void onContextClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putSerializable("context", context); args.putSerializable("context", context);
Nav.goForResult(getActivity(), FilterContextFragment.class, args, CONTEXT_RESULT, this); Nav.goForResult(getActivity(), FilterContextFragment.class, args, CONTEXT_RESULT, this);
} }
private void onDeleteClick(ListItem<Void> item_){ private void onDeleteClick(){
AlertDialog alert=new M3AlertDialogBuilder(getActivity()) AlertDialog alert=new M3AlertDialogBuilder(getActivity())
.setTitle(getString(R.string.settings_delete_filter_title, filter.title)) .setTitle(getString(R.string.settings_delete_filter_title, filter.title))
.setMessage(R.string.settings_delete_filter_confirmation) .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); setTitle(R.string.settings_filter_context);
context=(EnumSet<FilterContext>) getArguments().getSerializable("context"); context=(EnumSet<FilterContext>) getArguments().getSerializable("context");
onDataLoaded(Arrays.stream(FilterContext.values()).map(c->{ 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.parentObject=c;
item.isEnabled=true; item.isEnabled=true;
item.onClick=()->toggleCheckableItem(item);
return item; return item;
}).collect(Collectors.toList())); }).collect(Collectors.toList()));
} }

View File

@@ -1,6 +1,11 @@
package org.joinmastodon.android.fragments.settings; 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.app.AlertDialog;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; 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.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; 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.SimpleTextWatcher;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout; import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout;
@@ -33,6 +37,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.fragments.OnBackPressedListener; import me.grishka.appkit.fragments.OnBackPressedListener;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
@@ -55,7 +60,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
FilterKeyword word=Parcels.unwrap(p); FilterKeyword word=Parcels.unwrap(p);
ListItem<FilterKeyword> item=new ListItem<>(word.keyword, null, null, word); ListItem<FilterKeyword> item=new ListItem<>(word.keyword, null, null, word);
item.isEnabled=true; item.isEnabled=true;
item.setOnClick(this::onWordClick); item.onClick=()->onWordClick(item);
return item; return item;
}).collect(Collectors.toList())); }).collect(Collectors.toList()));
setHasOptionsMenu(true); setHasOptionsMenu(true);
@@ -92,7 +97,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab); fab=view.findViewById(R.id.fab);
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.setContentDescription(getString(R.string.add_muted_word));
fab.setOnClickListener(v->onFabClick()); fab.setOnClickListener(v->onFabClick());
} }
@@ -109,7 +114,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.selectable_list, menu); inflater.inflate(R.menu.settings_filter_words, menu);
} }
@Override @Override
@@ -169,7 +174,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
w.keyword=input; w.keyword=input;
ListItem<FilterKeyword> item=new ListItem<>(w.keyword, null, null, w); ListItem<FilterKeyword> item=new ListItem<>(w.keyword, null, null, w);
item.isEnabled=true; item.isEnabled=true;
item.setOnClick(this::onWordClick); item.onClick=()->onWordClick(item);
data.add(item); data.add(item);
itemsAdapter.notifyItemInserted(data.size()-1); itemsAdapter.notifyItemInserted(data.size()-1);
}else{ }else{
@@ -223,15 +228,29 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
return; return;
V.setVisibilityAnimated(fab, View.GONE); V.setVisibilityAnimated(fab, View.GONE);
actionMode=ActionModeHelper.startActionMode(this, ()->elevationOnScrollListener.getCurrentStatusBarColor(), new ActionMode.Callback(){ actionMode=getActivity().startActionMode(new ActionMode.Callback(){
@Override @Override
public boolean onCreateActionMode(ActionMode mode, Menu menu){ 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; return true;
} }
@Override @Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu){ public boolean onPrepareActionMode(ActionMode mode, Menu menu){
mode.getMenuInflater().inflate(R.menu.settings_filter_words_action_mode, 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); deleteItem=menu.findItem(R.id.delete);
return true; return true;
} }
@@ -247,6 +266,21 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
@Override @Override
public void onDestroyActionMode(ActionMode mode){ public void onDestroyActionMode(ActionMode mode){
leaveSelectionMode(true); 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); ListItem<FilterKeyword> item=data.get(i);
CheckableListItem<FilterKeyword> newItem=new CheckableListItem<>(item.title, null, CheckableListItem.Style.CHECKBOX, selectAll, null); CheckableListItem<FilterKeyword> newItem=new CheckableListItem<>(item.title, null, CheckableListItem.Style.CHECKBOX, selectAll, null);
newItem.isEnabled=true; newItem.isEnabled=true;
newItem.setOnClick(this::onSelectionModeWordClick); newItem.onClick=()->onSelectionModeWordClick(newItem);
newItem.parentObject=item.parentObject; newItem.parentObject=item.parentObject;
if(selectAll) if(selectAll)
selectedItems.add(newItem); selectedItems.add(newItem);
@@ -279,7 +313,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
ListItem<FilterKeyword> item=data.get(i); ListItem<FilterKeyword> item=data.get(i);
ListItem<FilterKeyword> newItem=new ListItem<>(item.title, null, null); ListItem<FilterKeyword> newItem=new ListItem<>(item.title, null, null);
newItem.isEnabled=true; newItem.isEnabled=true;
newItem.setOnClick(this::onWordClick); newItem.onClick=()->onWordClick(newItem);
newItem.parentObject=item.parentObject; newItem.parentObject=item.parentObject;
data.set(i, newItem); 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 org.joinmastodon.android.ui.utils.UiUtils;
import java.util.List; import java.util.List;
import java.util.Objects;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import me.grishka.appkit.imageloader.ImageCache; import me.grishka.appkit.imageloader.ImageCache;
@@ -25,32 +26,23 @@ import me.grishka.appkit.utils.V;
public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
private ListItem<Void> mediaCacheItem; private ListItem<Void> mediaCacheItem;
private AccountSession session;
private boolean timelineCacheCleared=false;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setTitle(getString(R.string.about_app, getString(R.string.sk_app_name))); setTitle(getString(R.string.about_app, getString(R.string.sk_app_name)));
session=AccountSessionManager.get(accountID); AccountSession s=AccountSessionManager.get(accountID);
onDataLoaded(List.of( 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_donate, 0, R.drawable.ic_fluent_heart_24_regular, ()->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.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, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")), 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, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true), 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), 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)
)); ));
updateMediaCacheItem(); updateMediaCacheItem();
} }
@Override
protected void onHidden(){
super.onHidden();
if(timelineCacheCleared) getActivity().recreate();
}
@Override @Override
protected void doLoadData(int offset, int count){} protected void doLoadData(int offset, int count){}
@@ -71,7 +63,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
return adapter; return adapter;
} }
private void onClearMediaCacheClick(ListItem<?> item){ private void onClearMediaCacheClick(){
MastodonAPIController.runInBackground(()->{ MastodonAPIController.runInBackground(()->{
Activity activity=getActivity(); Activity activity=getActivity();
ImageCache.getInstance(getActivity()).clear(); 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(){ private void updateMediaCacheItem(){
long size=ImageCache.getInstance(getActivity()).getDiskCache().size(); long size=ImageCache.getInstance(getActivity()).getDiskCache().size();
mediaCacheItem.subtitle=UiUtils.formatFileSize(getActivity(), size, false); mediaCacheItem.subtitle=UiUtils.formatFileSize(getActivity(), size, false);

View File

@@ -46,20 +46,20 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
List<ListItem<Void>> items = new ArrayList<>(List.of( 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), 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)), 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, i->toggleCheckableItem(playGifsItem)), 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, i->toggleCheckableItem(overlayMediaItem)), 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, i->toggleCheckableItem(customTabsItem)), 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, i->toggleCheckableItem(confirmUnfollowItem)), 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, i->toggleCheckableItem(confirmBoostItem)), 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, i->toggleCheckableItem(confirmDeleteItem)), 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), 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)), 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, i->onLoadNewPostsClick()), 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, i->toggleCheckableItem(seeNewPostsBtnItem)), 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, i->toggleCheckableItem(remoteLoadingItem), true), 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, i->toggleCheckableItem(showBoostsItem)), 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, i->toggleCheckableItem(showRepliesItem)) 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( if(isInstanceAkkoma()) items.add(
@@ -93,7 +93,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
@Override @Override
protected void doLoadData(int offset, int count){} protected void doLoadData(int offset, int count){}
private void onDefaultLanguageClick(ListItem<?> item){ private void onDefaultLanguageClick(){
if (languageResolver == null) return; if (languageResolver == null) return;
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, new ComposeLanguageAlertViewController.SelectedOption(postLanguage), null, languageResolver); ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), null, new ComposeLanguageAlertViewController.SelectedOption(postLanguage), null, languageResolver);
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
@@ -112,14 +112,14 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
.show(); .show();
} }
private void onPrefixRepliesClick(ListItem<?> item){ private void onPrefixRepliesClick(){
int selected=GlobalUserPreferences.prefixReplies.ordinal(); int selected=GlobalUserPreferences.prefixReplies.ordinal();
int[] newSelected={selected}; int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_prefix_reply_cw_with_re) .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), .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) selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, which)->{ .setPositiveButton(R.string.ok, (dlg, item)->{
GlobalUserPreferences.prefixReplies=GlobalUserPreferences.PrefixRepliesMode.values()[newSelected[0]]; GlobalUserPreferences.prefixReplies=GlobalUserPreferences.PrefixRepliesMode.values()[newSelected[0]];
prefixRepliesItem.subtitleRes=getPrefixWithRepliesString(); prefixRepliesItem.subtitleRes=getPrefixWithRepliesString();
rebindItem(prefixRepliesItem); rebindItem(prefixRepliesItem);
@@ -128,7 +128,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
.show(); .show();
} }
private void onReplyVisibilityClick(ListItem<?> item){ private void onReplyVisibilityClick(){
AccountLocalPreferences lp=getLocalPrefs(); AccountLocalPreferences lp=getLocalPrefs();
int selected=lp.timelineReplyVisibility==null ? 2 : switch(lp.timelineReplyVisibility){ int selected=lp.timelineReplyVisibility==null ? 2 : switch(lp.timelineReplyVisibility){
case "following" -> 0; case "following" -> 0;
@@ -139,8 +139,8 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_prefix_reply_cw_with_re) .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), .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) selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, which)->{ .setPositiveButton(R.string.ok, (dlg, item)->{
lp.timelineReplyVisibility=switch(newSelected[0]){ lp.timelineReplyVisibility=switch(newSelected[0]){
case 0 -> "following"; case 0 -> "following";
case 1 -> "self"; case 1 -> "self";
@@ -167,7 +167,7 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
GlobalUserPreferences.overlayMedia=overlayMediaItem.checked; GlobalUserPreferences.overlayMedia=overlayMediaItem.checked;
GlobalUserPreferences.useCustomTabs=customTabsItem.checked; GlobalUserPreferences.useCustomTabs=customTabsItem.checked;
GlobalUserPreferences.altTextReminders=altTextItem.checked; GlobalUserPreferences.altTextReminders=altTextItem.checked;
GlobalUserPreferences.confirmUnfollow=confirmUnfollowItem.checked; GlobalUserPreferences.confirmUnfollow=customTabsItem.checked;
GlobalUserPreferences.confirmBoost=confirmBoostItem.checked; GlobalUserPreferences.confirmBoost=confirmBoostItem.checked;
GlobalUserPreferences.confirmDeletePost=confirmDeleteItem.checked; GlobalUserPreferences.confirmDeletePost=confirmDeleteItem.checked;
GlobalUserPreferences.forwardReportDefault=forwardReportsItem.checked; GlobalUserPreferences.forwardReportDefault=forwardReportsItem.checked;
@@ -176,8 +176,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
GlobalUserPreferences.allowRemoteLoading=remoteLoadingItem.checked; GlobalUserPreferences.allowRemoteLoading=remoteLoadingItem.checked;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
AccountLocalPreferences lp=getLocalPrefs(); AccountLocalPreferences lp=getLocalPrefs();
boolean restartPlease=lp.showBoosts!=showBoostsItem.checked
|| lp.showReplies!=showRepliesItem.checked;
lp.showBoosts=showBoostsItem.checked; lp.showBoosts=showBoostsItem.checked;
lp.showReplies=showRepliesItem.checked; lp.showReplies=showRepliesItem.checked;
lp.save(); lp.save();
@@ -188,7 +186,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
s.preferences.postingDefaultLanguage=newPostLanguage.language.getLanguage(); s.preferences.postingDefaultLanguage=newPostLanguage.language.getLanguage();
s.savePreferencesLater(); s.savePreferencesLater();
} }
if(restartPlease) getActivity().recreate();
} }
@Override @Override

View File

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

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.fragments.settings; package org.joinmastodon.android.fragments.settings;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.os.Build; import android.os.Build;
@@ -18,7 +17,6 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent; import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
@@ -29,8 +27,6 @@ import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
@@ -41,7 +37,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> revealCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem; private CheckableListItem<Void> revealCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem;
// MEGALODON // 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 ListItem<Void> colorItem, publishTextItem, autoRevealCWsItem;
private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem; private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem;
@@ -55,30 +51,28 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
lp=s.getLocalPreferences(); lp=s.getLocalPreferences();
onDataLoaded(List.of( onDataLoaded(List.of(
themeItem=new ListItem<>(R.string.settings_theme, getAppearanceValue(), R.drawable.ic_fluent_weather_moon_24_regular, this::onAppearanceClick), 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), 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, i->onTrueBlackModeClick(), true), 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), 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), 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)), 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, i->toggleCheckableItem(hideSensitiveMediaItem)), 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, i->toggleCheckableItem(interactionCountsItem)), 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, i->toggleCheckableItem(emojiInNamesItem)), 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, i->toggleCheckableItem(marqueeItem)), 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, i->toggleCheckableItem(reduceMotionItem)), 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, i->toggleCheckableItem(disableSwipeItem)), 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, i->toggleCheckableItem(altIndicatorItem)), 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, i->toggleCheckableItem(noAltIndicatorItem)), 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, i->toggleCheckableItem(collapsePostsItem)), 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, i->toggleCheckableItem(spectatorModeItem)), 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, i->toggleCheckableItem(hideFabItem)), 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, i->toggleCheckableItem(translateOpenedItem)), 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)),
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)), 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)),
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)), 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),
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)), pronounsInTimelinesItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_timelines, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInTimelines, 0, ()->toggleCheckableItem(pronounsInTimelinesItem)),
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), pronounsInThreadsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_threads, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInThreads, 0, ()->toggleCheckableItem(pronounsInThreadsItem)),
pronounsInTimelinesItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_timelines, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInTimelines, 0, i->toggleCheckableItem(pronounsInTimelinesItem)), pronounsInUserListingsItem=new CheckableListItem<>(R.string.sk_settings_display_pronouns_in_user_listings, 0, CheckableListItem.Style.CHECKBOX, GlobalUserPreferences.displayPronounsInUserListings, 0, ()->toggleCheckableItem(pronounsInUserListingsItem))
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))
)); ));
trueBlackModeItem.checkedChangeListener=checked->onTrueBlackModeClick(); trueBlackModeItem.checkedChangeListener=checked->onTrueBlackModeClick();
} }
@@ -100,9 +94,9 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
protected void onHidden(){ protected void onHidden(){
super.onHidden(); super.onHidden();
boolean restartPlease=GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked boolean restartPlease=
|| GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked ||
|| GlobalUserPreferences.likeIcon!=likeIconItem.checked; GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked;
lp.revealCWs=revealCWsItem.checked; lp.revealCWs=revealCWsItem.checked;
lp.hideSensitiveMedia=hideSensitiveMediaItem.checked; lp.hideSensitiveMedia=hideSensitiveMediaItem.checked;
@@ -118,8 +112,6 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
GlobalUserPreferences.spectatorMode=spectatorModeItem.checked; GlobalUserPreferences.spectatorMode=spectatorModeItem.checked;
GlobalUserPreferences.autoHideFab=hideFabItem.checked; GlobalUserPreferences.autoHideFab=hideFabItem.checked;
GlobalUserPreferences.translateButtonOpenedOnly=translateOpenedItem.checked; GlobalUserPreferences.translateButtonOpenedOnly=translateOpenedItem.checked;
GlobalUserPreferences.likeIcon=likeIconItem.checked;
GlobalUserPreferences.underlinedLinks=underlinedLinksItem.checked;
GlobalUserPreferences.disableM3PillActiveIndicator=disablePillItem.checked; GlobalUserPreferences.disableM3PillActiveIndicator=disablePillItem.checked;
GlobalUserPreferences.showNavigationLabels=showNavigationLabelsItem.checked; GlobalUserPreferences.showNavigationLabels=showNavigationLabelsItem.checked;
GlobalUserPreferences.displayPronounsInTimelines=pronounsInTimelinesItem.checked; GlobalUserPreferences.displayPronounsInTimelines=pronounsInTimelinesItem.checked;
@@ -138,11 +130,17 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
}; };
} }
private String getColorPaletteValue(){ private @StringRes int getColorPaletteValue(){
ColorPreference color=AccountSessionManager.get(accountID).getLocalPreferences().color; return switch(GlobalUserPreferences.color){
return color==null case MATERIAL3 -> R.string.sk_color_palette_material3;
? getString(R.string.sk_settings_color_palette_default, getString(GlobalUserPreferences.color.getName())) case PINK -> R.string.sk_color_palette_pink;
: getString(color.getName()); 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() { private String getPublishButtonText() {
@@ -166,7 +164,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
maybeApplyNewThemeRightNow(null, null, prev); maybeApplyNewThemeRightNow(null, null, prev);
} }
private void onAppearanceClick(ListItem<?> item_){ private void onAppearanceClick(){
int selected=switch(GlobalUserPreferences.theme){ int selected=switch(GlobalUserPreferences.theme){
case LIGHT -> 0; case LIGHT -> 0;
case DARK -> 1; case DARK -> 1;
@@ -197,44 +195,30 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show(); .show();
} }
private void onColorClick(ListItem<?> item_){ private void onColorClick(){
boolean multiple=AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1; int selected=GlobalUserPreferences.color.ordinal();
int indexOffset=multiple ? 1 : 0;
int selected=lp.color==null ? 0 : lp.color.ordinal() + indexOffset;
int[] newSelected={selected}; int[] newSelected={selected};
List<String> items=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).collect(Collectors.toList()); String[] names=Arrays.stream(GlobalUserPreferences.ColorPreference.values()).map(GlobalUserPreferences.ColorPreference::getName).map(this::getString).toArray(String[]::new);
if(multiple) new M3AlertDialogBuilder(getActivity())
items.add(0, getString(R.string.sk_settings_color_palette_default, items.get(GlobalUserPreferences.color.ordinal()))); .setTitle(R.string.settings_theme)
.setSingleChoiceItems(names,
Consumer<Boolean> save=(asDefault)->{ selected, (dlg, item)->newSelected[0]=item)
boolean defaultSelected=multiple && newSelected[0]==0; .setPositiveButton(R.string.ok, (dlg, item)->{
ColorPreference pref=defaultSelected ? null : ColorPreference.values()[newSelected[0]-indexOffset]; GlobalUserPreferences.ColorPreference pref=GlobalUserPreferences.ColorPreference.values()[newSelected[0]];
if(pref!=lp.color){ if(pref!=GlobalUserPreferences.color){
ColorPreference prev=lp.color; GlobalUserPreferences.ColorPreference prev=GlobalUserPreferences.color;
lp.color=asDefault ? null : pref;
lp.save();
if((asDefault || !multiple) && pref!=null){
GlobalUserPreferences.color=pref; GlobalUserPreferences.color=pref;
GlobalUserPreferences.save(); GlobalUserPreferences.save();
} colorItem.subtitleRes=getColorPaletteValue();
colorItem.subtitle=getColorPaletteValue();
rebindItem(colorItem); rebindItem(colorItem);
if(prev==null && pref!=null) restartActivityToApplyNewTheme(); maybeApplyNewThemeRightNow(null, prev, null);
else maybeApplyNewThemeRightNow(null, prev, null);
} }
}; })
.setNegativeButton(R.string.cancel, null)
AlertDialog.Builder alert=new M3AlertDialogBuilder(getActivity()) .show();
.setTitle(R.string.sk_settings_color_palette)
.setSingleChoiceItems(items.toArray(String[]::new),
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();
} }
private void onPublishTextClick(ListItem<?> item_){ private void onPublishTextClick(){
TextInputFrameLayout input = new TextInputFrameLayout( TextInputFrameLayout input = new TextInputFrameLayout(
getContext(), getContext(),
getString(R.string.publish), getString(R.string.publish),
@@ -257,7 +241,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show(); .show();
} }
private void onAutoRevealSpoilersClick(ListItem<?> item_){ private void onAutoRevealSpoilersClick(){
int selected=GlobalUserPreferences.autoRevealEqualSpoilers.ordinal(); int selected=GlobalUserPreferences.autoRevealEqualSpoilers.ordinal();
int[] newSelected={selected}; int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
@@ -273,17 +257,17 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show(); .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(prevTheme==null) prevTheme=GlobalUserPreferences.theme;
if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme; if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme;
if(prevColor==null) prevColor=lp.getCurrentColor(); if(prevColor==null) prevColor=GlobalUserPreferences.color;
boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK || boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK ||
(prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive()); (prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK || boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK ||
(GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive()); (GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewBlack=GlobalUserPreferences.trueBlackTheme; boolean isNewBlack=GlobalUserPreferences.trueBlackTheme;
if(isCurrentDark!=isNewDark || prevColor!=lp.getCurrentColor() || (isNewDark && prevTrueBlack!=isNewBlack)){ if(isCurrentDark!=isNewDark || prevColor!=GlobalUserPreferences.color || (isNewDark && prevTrueBlack!=isNewBlack)){
restartActivityToApplyNewTheme(); restartActivityToApplyNewTheme();
} }
} }

View File

@@ -55,7 +55,7 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter(); MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
adapter.addAdapter(super.getAdapter()); adapter.addAdapter(super.getAdapter());
adapter.addAdapter(new GenericListItemsAdapter<>(Collections.singletonList( 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; return adapter;
} }
@@ -67,14 +67,16 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
Nav.go(getActivity(), EditFilterFragment.class, args); Nav.go(getActivity(), EditFilterFragment.class, args);
} }
private void onAddFilterClick(ListItem<?> item){ private void onAddFilterClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
Nav.go(getActivity(), EditFilterFragment.class, args); Nav.go(getActivity(), EditFilterFragment.class, args);
} }
private ListItem<Filter> makeListItem(Filter f){ 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; return item;
} }

View File

@@ -19,7 +19,6 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
@@ -37,15 +36,15 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
lp=s.getLocalPreferences(); lp=s.getLocalPreferences();
onDataLoaded(List.of( 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<>(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_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, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/settings/preferences/other")), 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, i->UiUtils.launchWebBrowser(getActivity(), "https://"+s.domain+"/auth/edit"), 0, true), 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, i->onContentTypeClick()), 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), 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), 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()), 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, i->toggleCheckableItem(glitchModeItem)) 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(); contentTypesItem.checkedChangeListener=checked->onContentTypeClick();
defaultContentTypeItem.isEnabled=contentTypesItem.checked; defaultContentTypeItem.isEnabled=contentTypesItem.checked;
@@ -69,7 +68,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
E.post(new StatusDisplaySettingsChangedEvent(accountID)); E.post(new StatusDisplaySettingsChangedEvent(accountID));
} }
private void onServerClick(ListItem<?> item){ private void onServerClick(){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
Nav.go(getActivity(), SettingsServerFragment.class, args); Nav.go(getActivity(), SettingsServerFragment.class, args);
@@ -88,23 +87,23 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName(); defaultContentTypeItem.subtitleRes=lp.defaultContentType.getName();
} }
private void onDefaultContentTypeClick(ListItem<?> item_){ private void onDefaultContentTypeClick(){
List<ContentType> supportedContentTypes=Arrays.stream(ContentType.values()) int selected=lp.defaultContentType.ordinal();
.filter(t->t.supportedByInstance(getInstance().orElse(null)))
.collect(Collectors.toList());
int selected=supportedContentTypes.indexOf(lp.defaultContentType);
int[] newSelected={selected}; 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(ContentType::getName)
.map(this::getString) .map(this::getString)
.toArray(String[]::new); .toArray(String[]::new);
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_default_content_type) .setTitle(R.string.settings_theme)
.setSingleChoiceItems(names, .setSingleChoiceItems(names,
selected, (dlg, item)->newSelected[0]=item) selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{ .setPositiveButton(R.string.ok, (dlg, item)->{
ContentType type=supportedContentTypes.get(newSelected[0]); ContentType type=supportedContentTypes[newSelected[0]];
lp.defaultContentType=type; lp.defaultContentType=type;
defaultContentTypeItem.subtitleRes=type.getName(); defaultContentTypeItem.subtitleRes=type.getName();
rebindItem(defaultContentTypeItem); rebindItem(defaultContentTypeItem);
@@ -113,7 +112,7 @@ public class SettingsInstanceFragment extends BaseSettingsFragment<Void> impleme
.show(); .show();
} }
private void onShowEmojiReactionsClick(ListItem<?> item_){ private void onShowEmojiReactionsClick(){
int selected=lp.showEmojiReactions.ordinal(); int selected=lp.showEmojiReactions.ordinal();
int[] newSelected={selected}; int[] newSelected={selected};
new M3AlertDialogBuilder(getActivity()) 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.events.SelfUpdateStateChangedEvent;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.AccountSwitcherSheet;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter; import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
@@ -50,27 +49,26 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
account=AccountSessionManager.get(accountID); account = AccountSessionManager.get(accountID);
setTitle(R.string.settings); setTitle(R.string.settings);
setSubtitle(account.getFullUsername()); setSubtitle(account.getFullUsername());
onDataLoaded(List.of( onDataLoaded(List.of(
new ListItem<>(R.string.settings_behavior, 0, R.drawable.ic_fluent_settings_24_regular, this::onBehaviorClick), 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_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_privacy, 0, R.drawable.ic_privacy_tip_24px, this::onPrivacyClick),
new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_filter_alt_24px, this::onFiltersClick),
new ListItem<>(R.string.settings_notifications, 0, R.drawable.ic_fluent_alert_24_regular, this::onNotificationsClick), 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<>(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<>(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) 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); Instance instance = AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if(!instance.isAkkoma()){ if (!instance.isAkkoma())
data.add(3, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick)); 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")){ 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); AccountSession session=AccountSessionManager.get(accountID);
@@ -131,45 +129,41 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
return args; return args;
} }
private void onBehaviorClick(ListItem<?> item_){ private void onBehaviorClick(){
Nav.go(getActivity(), SettingsBehaviorFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsBehaviorFragment.class, makeFragmentArgs());
} }
private void onDisplayClick(ListItem<?> item_){ private void onDisplayClick(){
Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsDisplayFragment.class, makeFragmentArgs());
} }
private void onPrivacyClick(ListItem<?> item_){ private void onPrivacyClick(){
Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsPrivacyFragment.class, makeFragmentArgs());
} }
private void onFiltersClick(ListItem<?> item_){ private void onFiltersClick(){
Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsFiltersFragment.class, makeFragmentArgs());
} }
private void onNotificationsClick(ListItem<?> item_){ private void onNotificationsClick(){
Nav.go(getActivity(), SettingsNotificationsFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsNotificationsFragment.class, makeFragmentArgs());
} }
private void onInstanceClick(ListItem<?> item_){ private void onInstanceClick(){
Nav.go(getActivity(), SettingsInstanceFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsInstanceFragment.class, makeFragmentArgs());
} }
private void onAboutClick(ListItem<?> item_){ private void onAboutClick(){
Nav.go(getActivity(), SettingsAboutAppFragment.class, makeFragmentArgs()); Nav.go(getActivity(), SettingsAboutAppFragment.class, makeFragmentArgs());
} }
private void onManageAccountsClick(ListItem<?> item){ private void onLogOutClick(){
new AccountSwitcherSheet(getActivity(), null).show();
}
private void onLogOutClick(ListItem<?> item_){
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID); AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername())) .setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{ .setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
loggedOut=true; loggedOut=true;
((MainActivity)getActivity()).restartActivity(); ((MainActivity)getActivity()).restartHomeFragment();
})) }))
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .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.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter; import org.joinmastodon.android.ui.utils.HideableSingleViewRecyclerAdapter;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.unifiedpush.android.connector.RegistrationDialogContent;
import org.unifiedpush.android.connector.UnifiedPush; import org.unifiedpush.android.connector.UnifiedPush;
import java.time.Instant; import java.time.Instant;
@@ -72,21 +73,21 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
useUnifiedPush=!getDistributor(getContext()).isEmpty(); useUnifiedPush=!getDistributor(getContext()).isEmpty();
onDataLoaded(List.of( 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), 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)), 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, i->toggleCheckableItem(boostsItem)), 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, i->toggleCheckableItem(favoritesItem)), 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, i->toggleCheckableItem(followersItem)), 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, i->toggleCheckableItem(pollsItem)), 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, i->toggleCheckableItem(updateItem)), 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, i->toggleCheckableItem(postsItem), true), 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)), 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, i->toggleCheckableItem(deleteItem)), 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, i->toggleCheckableItem(onlyLatestItem), true), 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, i->onUnifiedPushClick(), 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 //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); typeItems=List.of(mentionsItem, boostsItem, favoritesItem, followersItem, pollsItem, updateItem, postsItem);
pauseItem.checkedChangeListener=checked->onPauseNotificationsClick(true); pauseItem.checkedChangeListener=checked->onPauseNotificationsClick(true);
unifiedPushItem.checkedChangeListener=checked->onUnifiedPushClick(); unifiedPushItem.checkedChangeListener=checked->onUnifiedPush();
updatePolicyItem(null); updatePolicyItem(null);
updatePauseItem(); updatePauseItem();
} }
@@ -253,7 +254,7 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); alert.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
} }
private void onNotificationsPolicyClick(ListItem<?> item_){ private void onNotificationsPolicyClick(){
String[] items=Stream.of( String[] items=Stream.of(
R.string.notifications_policy_anyone, R.string.notifications_policy_anyone,
R.string.notifications_policy_followed, 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()){ if(getDistributor(getContext()).isEmpty()){
List<String> distributors = UnifiedPush.getDistributors(getContext(), new ArrayList<>()); List<String> distributors = UnifiedPush.getDistributors(getContext(), new ArrayList<>());
showUnifiedPushRegisterDialog(distributors); showUnifiedPushRegisterDialog(distributors);

View File

@@ -2,98 +2,40 @@ package org.joinmastodon.android.fragments.settings;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.StringRes;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account; 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.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{ public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> discoverableItem, indexableItem, lockedItem; private CheckableListItem<Void> discoverableItem, indexableItem;
private ListItem<Void> privacyItem;
private StatusPrivacy privacy=null;
private Instance instance;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setTitle(R.string.settings_privacy); setTitle(R.string.settings_privacy);
AccountSession session=AccountSessionManager.get(accountID); Account self=AccountSessionManager.get(accountID).self;
Account self=session.self;
instance=AccountSessionManager.getInstance().getInstanceInfo(session.domain);
privacy=self.source.privacy;
onDataLoaded(List.of( 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), discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, ()->toggleCheckableItem(discoverableItem)),
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)) indexableItem=new CheckableListItem<>(R.string.settings_indexable, 0, CheckableListItem.Style.SWITCH, self.source.indexable!=null ? self.source.indexable : true, R.drawable.ic_search_24px, ()->toggleCheckableItem(indexableItem))
));
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) if(self.source.indexable==null)
indexableItem.isEnabled=false; indexableItem.isEnabled=false;
} }
}
@Override @Override
protected void doLoadData(int offset, int count){} 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 @Override
public void onPause(){ public void onPause(){
super.onPause(); super.onPause();
AccountSession s=AccountSessionManager.get(accountID); Account self=AccountSessionManager.get(accountID).self;
Account self=s.self; if(self.discoverable!=discoverableItem.checked || (self.source.indexable!=null && self.source.indexable!=indexableItem.checked)){
boolean savePlease=self.locked!=lockedItem.checked self.discoverable=discoverableItem.checked;
|| self.source.privacy!=privacy self.source.indexable=indexableItem.checked;
|| (discoverableItem!=null && self.discoverable!=discoverableItem.checked) AccountSessionManager.get(accountID).savePreferencesLater();
|| (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)){ if(!TextUtils.isEmpty(instance.email)){
needDivider=true; needDivider=true;
SimpleListItemViewHolder holder=new SimpleListItemViewHolder(getActivity(), scrollingLayout); 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.bind(item);
holder.itemView.setBackground(UiUtils.getThemeDrawable(getActivity(), android.R.attr.selectableItemBackground)); holder.itemView.setBackground(UiUtils.getThemeDrawable(getActivity(), android.R.attr.selectableItemBackground));
holder.itemView.setOnClickListener(v->openAdminEmail()); holder.itemView.setOnClickListener(v->openAdminEmail());

View File

@@ -6,6 +6,8 @@ import android.text.TextUtils;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.joinmastodon.android.api.ObjectValidationException; import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.parceler.Parcel; import org.parceler.Parcel;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +15,9 @@ import java.time.LocalDate;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
/** /**
* Represents a user of Mastodon and their associated profile. * Represents a user of Mastodon and their associated profile.
*/ */
@@ -158,26 +163,26 @@ public class Account extends BaseModel implements Searchable{
if(fields!=null){ if(fields!=null){
for(AccountField f:fields) for(AccountField f:fields)
f.postprocess(); f.postprocess();
}else{ } else {
fields=Collections.emptyList(); fields = Collections.emptyList();
} }
if(emojis!=null){ if(emojis!=null){
for(Emoji e:emojis) for(Emoji e:emojis)
e.postprocess(); e.postprocess();
}else{ } else {
emojis=Collections.emptyList(); emojis = Collections.emptyList();
} }
if(moved!=null) if(moved!=null)
moved.postprocess(); moved.postprocess();
if(fqn==null) fqn=getFullyQualifiedName(); if(fqn == null) fqn = getFullyQualifiedName();
if(id==null) id=""; if(id == null) id = "";
if(username==null) username=""; if(username == null) username = "";
if(TextUtils.isEmpty(displayName)) if(TextUtils.isEmpty(displayName))
displayName=!TextUtils.isEmpty(username) ? username : ""; displayName = !TextUtils.isEmpty(username) ? username : "";
if(acct==null) acct=""; if(acct == null) acct = "";
if(url==null) url=""; if(url == null) url = "";
if(note==null) note=""; if(note == null) note = "";
if(avatar==null) avatar=""; if(avatar == null) avatar = "";
} }
public boolean isLocal(){ public boolean isLocal(){
@@ -207,10 +212,6 @@ public class Account extends BaseModel implements Searchable{
return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL(); return fqn != null ? fqn : acct.split("@")[0] + "@" + getDomainFromURL();
} }
public String getDisplayName(){
return '\u2068'+displayName+'\u2069';
}
@Override @Override
public String toString(){ public String toString(){
return "Account{"+ return "Account{"+

View File

@@ -68,14 +68,6 @@ public class Attachment extends BaseModel{
return 1080; 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)
);
}
public double getDuration(){ public double getDuration(){
if(meta==null) if(meta==null)
return 0; return 0;

View File

@@ -26,13 +26,22 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent; import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.utils.StatusTextEncoder; import org.joinmastodon.android.utils.StatusTextEncoder;
import org.parceler.Parcel;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@Parcel @Parcel
@@ -91,11 +100,10 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient boolean spoilerRevealed; public transient boolean spoilerRevealed;
public transient boolean sensitiveRevealed; public transient boolean sensitiveRevealed;
public transient boolean textExpanded, textExpandable; public transient boolean textExpanded, textExpandable;
public transient String hasGapAfter; public transient boolean hasGapAfter;
private transient String strippedText; private transient String strippedText;
public transient TranslationState translationState=TranslationState.HIDDEN; public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation; public transient Translation translation;
public transient boolean fromStatusCreated;
public Status(){} public Status(){}

View File

@@ -33,9 +33,9 @@ public class AccountViewModel{
V.dp(50), V.dp(50)); V.dp(50), V.dp(50));
emojiHelper=new CustomEmojiHelper(); emojiHelper=new CustomEmojiHelper();
if(session.getLocalPreferences().customEmojiInNames) if(session.getLocalPreferences().customEmojiInNames)
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis); parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
else else
parsedName=account.getDisplayName(); parsedName=account.displayName;
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID);
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName); SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
ssb.append(parsedBio); ssb.append(parsedBio);

View File

@@ -9,42 +9,42 @@ public class CheckableListItem<T> extends ListItem<T>{
public boolean checked; public boolean checked;
public Consumer<Boolean> checkedChangeListener; public Consumer<Boolean> checkedChangeListener;
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick, T parentObject, boolean dividerAfter){ public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Runnable onClick, T parentObject, boolean dividerAfter){
super(title, subtitle, iconRes, (Consumer<ListItem<T>>)(Object)onClick, parentObject, 0, dividerAfter); super(title, subtitle, iconRes, onClick, parentObject, 0, dividerAfter);
this.style=style; this.style=style;
this.checked=checked; this.checked=checked;
} }
public CheckableListItem(String title, String subtitle, Style style, boolean checked, Consumer<CheckableListItem<T>> onClick){ public CheckableListItem(String title, String subtitle, Style style, boolean checked, Runnable onClick){
this(title, subtitle, style, checked, 0, onClick, null, false); this(title, subtitle, style, checked, 0, onClick, null, false);
} }
public CheckableListItem(String title, String subtitle, Style style, boolean checked, Consumer<CheckableListItem<T>> onClick, T parentObject){ public CheckableListItem(String title, String subtitle, Style style, boolean checked, Runnable onClick, T parentObject){
this(title, subtitle, style, checked, 0, onClick, parentObject, false); this(title, subtitle, style, checked, 0, onClick, parentObject, false);
} }
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick){ public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Runnable onClick){
this(title, subtitle, style, checked, iconRes, onClick, null, false); this(title, subtitle, style, checked, iconRes, onClick, null, false);
} }
public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick, T parentObject){ public CheckableListItem(String title, String subtitle, Style style, boolean checked, int iconRes, Runnable onClick, T parentObject){
this(title, subtitle, style, checked, iconRes, onClick, parentObject, false); this(title, subtitle, style, checked, iconRes, onClick, parentObject, false);
} }
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, Consumer<CheckableListItem<T>> onClick){ public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, Runnable onClick){
this(titleRes, subtitleRes, style, checked, 0, onClick, false); this(titleRes, subtitleRes, style, checked, 0, onClick, false);
} }
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, Consumer<CheckableListItem<T>> onClick, boolean dividerAfter){ public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, Runnable onClick, boolean dividerAfter){
this(titleRes, subtitleRes, style, checked, 0, onClick, dividerAfter); this(titleRes, subtitleRes, style, checked, 0, onClick, dividerAfter);
} }
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick){ public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, int iconRes, Runnable onClick){
this(titleRes, subtitleRes, style, checked, iconRes, onClick, false); this(titleRes, subtitleRes, style, checked, iconRes, onClick, false);
} }
public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, int iconRes, Consumer<CheckableListItem<T>> onClick, boolean dividerAfter){ public CheckableListItem(int titleRes, int subtitleRes, Style style, boolean checked, int iconRes, Runnable onClick, boolean dividerAfter){
super(titleRes, subtitleRes, iconRes, (Consumer<ListItem<T>>)(Object)onClick, 0, dividerAfter); super(titleRes, subtitleRes, iconRes, onClick, 0, dividerAfter);
this.style=style; this.style=style;
this.checked=checked; this.checked=checked;
} }

View File

@@ -2,8 +2,6 @@ package org.joinmastodon.android.model.viewmodel;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import java.util.function.Consumer;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
@@ -18,11 +16,11 @@ public class ListItem<T>{
public int iconRes; public int iconRes;
public int colorOverrideAttr; public int colorOverrideAttr;
public boolean dividerAfter; public boolean dividerAfter;
private Consumer<ListItem<T>> onClick; public Runnable onClick;
public boolean isEnabled=true; public boolean isEnabled=true;
public T parentObject; public T parentObject;
public ListItem(String title, String subtitle, int iconRes, Consumer<ListItem<T>> onClick, T parentObject, int colorOverrideAttr, boolean dividerAfter){ public ListItem(String title, String subtitle, int iconRes, Runnable onClick, T parentObject, int colorOverrideAttr, boolean dividerAfter){
this.title=title; this.title=title;
this.subtitle=subtitle; this.subtitle=subtitle;
this.iconRes=iconRes; this.iconRes=iconRes;
@@ -34,41 +32,45 @@ public class ListItem<T>{
isEnabled=false; isEnabled=false;
} }
public ListItem(String title, String subtitle, Consumer<ListItem<T>> onClick){ public ListItem(String title, String subtitle, Runnable onClick){
this(title, subtitle, 0, onClick, null, 0, false); this(title, subtitle, 0, onClick, null, 0, false);
} }
public ListItem(String title, String subtitle, Consumer<ListItem<T>> onClick, T parentObject){ public ListItem(String title, String subtitle, Runnable onClick, T parentObject){
this(title, subtitle, 0, onClick, parentObject, 0, false); this(title, subtitle, 0, onClick, parentObject, 0, false);
} }
public ListItem(String title, String subtitle, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick){ public ListItem(String title, String subtitle, @DrawableRes int iconRes, Runnable onClick){
this(title, subtitle, iconRes, onClick, null, 0, false); this(title, subtitle, iconRes, onClick, null, 0, false);
} }
public ListItem(String title, String subtitle, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick, T parentObject){ public ListItem(String title, String subtitle, @DrawableRes int iconRes, Runnable onClick, boolean dividerAfter){
this(title, subtitle, iconRes, onClick, null, 0, dividerAfter);
}
public ListItem(String title, String subtitle, @DrawableRes int iconRes, Runnable onClick, T parentObject){
this(title, subtitle, iconRes, onClick, parentObject, 0, false); this(title, subtitle, iconRes, onClick, parentObject, 0, false);
} }
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, Consumer<ListItem<T>> onClick){ public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, Runnable onClick){
this(null, null, 0, onClick, null, 0, false); this(null, null, 0, onClick, null, 0, false);
this.titleRes=titleRes; this.titleRes=titleRes;
this.subtitleRes=subtitleRes; this.subtitleRes=subtitleRes;
} }
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, Consumer<ListItem<T>> onClick, int colorOverrideAttr, boolean dividerAfter){ public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, Runnable onClick, int colorOverrideAttr, boolean dividerAfter){
this(null, null, 0, onClick, null, colorOverrideAttr, dividerAfter); this(null, null, 0, onClick, null, colorOverrideAttr, dividerAfter);
this.titleRes=titleRes; this.titleRes=titleRes;
this.subtitleRes=subtitleRes; this.subtitleRes=subtitleRes;
} }
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick){ public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Runnable onClick){
this(null, null, iconRes, onClick, null, 0, false); this(null, null, iconRes, onClick, null, 0, false);
this.titleRes=titleRes; this.titleRes=titleRes;
this.subtitleRes=subtitleRes; this.subtitleRes=subtitleRes;
} }
public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Consumer<ListItem<T>> onClick, int colorOverrideAttr, boolean dividerAfter){ public ListItem(@StringRes int titleRes, @StringRes int subtitleRes, @DrawableRes int iconRes, Runnable onClick, int colorOverrideAttr, boolean dividerAfter){
this(null, null, iconRes, onClick, null, colorOverrideAttr, dividerAfter); this(null, null, iconRes, onClick, null, colorOverrideAttr, dividerAfter);
this.titleRes=titleRes; this.titleRes=titleRes;
this.subtitleRes=subtitleRes; this.subtitleRes=subtitleRes;
@@ -77,13 +79,4 @@ public class ListItem<T>{
public int getItemViewType(){ public int getItemViewType(){
return colorOverrideAttr==0 ? R.id.list_item_simple : R.id.list_item_simple_tinted; return colorOverrideAttr==0 ? R.id.list_item_simple : R.id.list_item_simple_tinted;
} }
public void performClick(){
if(onClick!=null)
onClick.accept(this);
}
public <I extends ListItem<T>> void setOnClick(Consumer<I> onClick){
this.onClick=(Consumer<ListItem<T>>) onClick;
}
} }

View File

@@ -8,7 +8,7 @@ import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.text.SpannableStringBuilder; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
@@ -27,8 +27,6 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HomeFragment; import org.joinmastodon.android.fragments.HomeFragment;
import org.joinmastodon.android.fragments.SplashFragment; import org.joinmastodon.android.fragments.SplashFragment;
import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment; import org.joinmastodon.android.fragments.onboarding.CustomWelcomeFragment;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.CheckableRelativeLayout; import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
@@ -42,6 +40,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
@@ -148,7 +147,8 @@ public class AccountSwitcherSheet extends BottomSheet{
private void logOut(String accountID){ private void logOut(String accountID){
AccountSessionManager.get(accountID).logOut(activity, ()->{ AccountSessionManager.get(accountID).logOut(activity, ()->{
((MainActivity)activity).restartActivity(); dismiss();
((MainActivity)activity).restartHomeFragment();
}); });
} }
@@ -283,7 +283,7 @@ public class AccountSwitcherSheet extends BottomSheet{
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Override @Override
public void onBind(AccountSession item){ public void onBind(AccountSession item){
HtmlParser.setTextWithCustomEmoji(name, item.self.getDisplayName(), item.self.emojis); name.setText(item.self.displayName);
username.setText(item.getFullUsername()); username.setText(item.getFullUsername());
radioButton.setVisibility(externalShare ? View.GONE : View.VISIBLE); radioButton.setVisibility(externalShare ? View.GONE : View.VISIBLE);
extraBtnWrap.setVisibility(externalShare && openInApp ? View.VISIBLE : View.GONE); extraBtnWrap.setVisibility(externalShare && openInApp ? View.VISIBLE : View.GONE);
@@ -317,14 +317,14 @@ public class AccountSwitcherSheet extends BottomSheet{
@Override @Override
public void onClick(){ public void onClick(){
setOnDismissListener(null); setOnDismissListener(null);
if (onClick != null) {
dismiss(); dismiss();
if (onClick != null) {
onClick.accept(item.getID(), false); onClick.accept(item.getID(), false);
return; return;
} }
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null){ if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null){
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID()); AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
((MainActivity)activity).restartActivity(); ((MainActivity)activity).restartHomeFragment();
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -71,7 +71,7 @@ public class SearchViewHelper{
searchLayout.addView(searchEdit, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)); searchLayout.addView(searchEdit, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
clearSearchButton=new ImageButton(context); clearSearchButton=new ImageButton(context);
clearSearchButton.setImageResource(R.drawable.ic_fluent_dismiss_24_regular); clearSearchButton.setImageResource(R.drawable.ic_baseline_close_24);
clearSearchButton.setContentDescription(context.getString(R.string.clear)); clearSearchButton.setContentDescription(context.getString(R.string.clear));
clearSearchButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant))); clearSearchButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant)));
clearSearchButton.setBackground(UiUtils.getThemeDrawable(toolbarContext, android.R.attr.actionBarItemBackground)); clearSearchButton.setBackground(UiUtils.getThemeDrawable(toolbarContext, android.R.attr.actionBarItemBackground));

View File

@@ -26,7 +26,9 @@ import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ProgressBarButton; import org.joinmastodon.android.ui.views.ProgressBarButton;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -51,9 +53,9 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000); coverRequest=new UrlImageLoaderRequest(account.header, 1000, 1000);
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), parentFragment.getAccountID()); parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), parentFragment.getAccountID());
if(account.emojis.isEmpty()){ if(account.emojis.isEmpty()){
parsedName=account.getDisplayName(); parsedName=account.displayName;
}else{ }else{
parsedName=HtmlParser.parseCustomEmoji(account.getDisplayName(), account.emojis); parsedName=HtmlParser.parseCustomEmoji(account.displayName, account.emojis);
emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio)); emojiHelper.setText(new SpannableStringBuilder(parsedName).append(parsedBio));
} }
} }
@@ -147,7 +149,7 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE); followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
relationship=item.parentFragment.getRelationship(item.account.id); relationship=item.parentFragment.getRelationship(item.account.id);
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), null,true, false, false, item.account); UiUtils.setExtraTextInfo(item.parentFragment.getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
if(item.notification.type==Notification.Type.FOLLOW_REQUEST && (relationship==null || !relationship.followedBy)){ if(item.notification.type==Notification.Type.FOLLOW_REQUEST && (relationship==null || !relationship.followedBy)){
actionWrap.setVisibility(View.GONE); actionWrap.setVisibility(View.GONE);

View File

@@ -170,14 +170,12 @@ public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
@Override @Override
public void onBind(EmojiReactionsStatusDisplayItem item) { public void onBind(EmojiReactionsStatusDisplayItem item) {
if(emojiKeyboard != null) root.removeView(emojiKeyboard.getView()); if(emojiKeyboard != null) root.removeView(emojiKeyboard.getView());
addButton.setSelected(false);
AccountSession session=item.parentFragment.getSession(); AccountSession session=item.parentFragment.getSession();
item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null item.status.reactions.forEach(r->r.request=r.getUrl(item.playGifs)!=null
? new UrlImageLoaderRequest(r.getUrl(item.playGifs), V.sp(24), V.sp(24)) ? new UrlImageLoaderRequest(r.getUrl(item.playGifs), V.sp(24), V.sp(24))
: null); : null);
emojiKeyboard=new CustomEmojiPopupKeyboard( emojiKeyboard=new CustomEmojiPopupKeyboard(
(Activity) item.parentFragment.getContext(), (Activity) item.parentFragment.getContext(),
item.accountID,
AccountSessionManager.getInstance().getCustomEmojis(session.domain), AccountSessionManager.getInstance().getCustomEmojis(session.domain),
session.domain, true); session.domain, true);
emojiKeyboard.setListener(this); emojiKeyboard.setListener(this);

View File

@@ -13,10 +13,7 @@ import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.StatusEditHistoryFragment; import org.joinmastodon.android.fragments.StatusEditHistoryFragment;
import org.joinmastodon.android.fragments.account_list.StatusFavoritesListFragment; import org.joinmastodon.android.fragments.account_list.StatusFavoritesListFragment;
@@ -77,7 +74,6 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(ExtendedFooterStatusDisplayItem item){ public void onBind(ExtendedFooterStatusDisplayItem item){
Status s=item.status; Status s=item.status;
favorites.setCompoundDrawablesRelativeWithIntrinsicBounds(GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_20_regular : R.drawable.ic_fluent_star_20_regular, 0, 0, 0);
favorites.setText(context.getResources().getQuantityString(R.plurals.x_favorites, (int)(s.favouritesCount%1000), s.favouritesCount)); favorites.setText(context.getResources().getQuantityString(R.plurals.x_favorites, (int)(s.favouritesCount%1000), s.favouritesCount));
reblogs.setText(context.getResources().getQuantityString(R.plurals.x_reblogs, (int) (s.reblogsCount % 1000), s.reblogsCount)); reblogs.setText(context.getResources().getQuantityString(R.plurals.x_reblogs, (int) (s.reblogsCount % 1000), s.reblogsCount));
reblogs.setVisibility(s.visibility != StatusPrivacy.DIRECT ? View.VISIBLE : View.GONE); reblogs.setVisibility(s.visibility != StatusPrivacy.DIRECT ? View.VISIBLE : View.GONE);

View File

@@ -1,17 +1,24 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
public class FileStatusDisplayItem extends StatusDisplayItem{ public class FileStatusDisplayItem extends StatusDisplayItem{
private final Attachment attachment; private final Attachment attachment;

View File

@@ -1,10 +1,12 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import static org.joinmastodon.android.ui.utils.UiUtils.opacityIn;
import static org.joinmastodon.android.ui.utils.UiUtils.opacityOut;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -15,7 +17,6 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
@@ -56,14 +57,13 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{ public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
private final TextView replies, boosts, favorites; private final TextView replies, boosts, favorites;
private final View reply, boost, favorite, share, bookmark; private final View reply, boost, favorite, share, bookmark;
private final ImageView favIcon;
private View touchingView = null; private View touchingView = null;
private boolean longClickPerformed = false; private boolean longClickPerformed = false;
private final Runnable longClickRunnable = () -> { private final Runnable longClickRunnable = () -> {
longClickPerformed = touchingView != null && touchingView.performLongClick(); longClickPerformed = touchingView != null && touchingView.performLongClick();
if (longClickPerformed && touchingView != null) { if (longClickPerformed && touchingView != null) {
UiUtils.opacityIn(touchingView); touchingView.startAnimation(opacityIn);
touchingView.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start(); touchingView.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
} }
}; };
@@ -89,7 +89,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
favorite=findViewById(R.id.favorite_btn); favorite=findViewById(R.id.favorite_btn);
share=findViewById(R.id.share_btn); share=findViewById(R.id.share_btn);
bookmark=findViewById(R.id.bookmark_btn); bookmark=findViewById(R.id.bookmark_btn);
favIcon=findViewById(R.id.favorite_icon);
reply.setOnTouchListener(this::onButtonTouch); reply.setOnTouchListener(this::onButtonTouch);
reply.setOnClickListener(this::onReplyClick); reply.setOnClickListener(this::onReplyClick);
@@ -133,13 +132,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
boolean condenseBottom = !item.isMainStatus && item.hasDescendantNeighbor && boolean condenseBottom = !item.isMainStatus && item.hasDescendantNeighbor &&
!nextIsWarning; !nextIsWarning;
ColorStateList color=item.parentFragment.getResources().getColorStateList(
GlobalUserPreferences.likeIcon ? R.color.like_icon : R.color.favorite_icon, item.parentFragment.getContext().getTheme()
);
favIcon.setImageResource(GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_selector : R.drawable.ic_fluent_star_24_selector);
favIcon.setImageTintList(color);
favorites.setTextColor(color);
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams(); ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) itemView.getLayoutParams();
params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, params.setMargins(params.leftMargin, params.topMargin, params.rightMargin,
condenseBottom ? V.dp(-5) : 0); condenseBottom ? V.dp(-5) : 0);
@@ -168,20 +160,21 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
if (!longClickPerformed) v.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start(); if (!longClickPerformed) v.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
if (disabled) return true; if (disabled) return true;
if (action == MotionEvent.ACTION_UP && !longClickPerformed) v.performClick(); if (action == MotionEvent.ACTION_UP && !longClickPerformed) v.performClick();
else if (!longClickPerformed) UiUtils.opacityIn(v); else if (!longClickPerformed) v.startAnimation(opacityIn);
} else if (action == MotionEvent.ACTION_DOWN) { } else if (action == MotionEvent.ACTION_DOWN) {
longClickPerformed = false; longClickPerformed = false;
touchingView = v; touchingView = v;
v.setPivotX(V.sp(28));
v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start(); v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
if (disabled) return true; if (disabled) return true;
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout()); v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
UiUtils.opacityOut(v); v.startAnimation(opacityOut);
} }
return true; return true;
} }
private void onReplyClick(View v){ private void onReplyClick(View v){
UiUtils.opacityIn(v); v.startAnimation(opacityIn);
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", item.accountID); args.putString("account", item.accountID);
args.putParcelable("replyTo", Parcels.wrap(item.status)); args.putParcelable("replyTo", Parcels.wrap(item.status));
@@ -205,7 +198,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private void onBoostClick(View v){ private void onBoostClick(View v){
if (GlobalUserPreferences.confirmBoost) { if (GlobalUserPreferences.confirmBoost) {
UiUtils.opacityIn(v); v.startAnimation(opacityIn);
onBoostLongClick(v); onBoostLongClick(v);
return; return;
} }
@@ -214,7 +207,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void boostConsumer(View v, Status r) { private void boostConsumer(View v, Status r) {
UiUtils.opacityIn(v); v.startAnimation(opacityIn);
bindText(boosts, r.reblogsCount); bindText(boosts, r.reblogsCount);
} }
@@ -225,7 +218,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
AccountSession session = AccountSessionManager.getInstance().getAccount(item.accountID); AccountSession session = AccountSessionManager.getInstance().getAccount(item.accountID);
Consumer<StatusPrivacy> doReblog = (visibility) -> { Consumer<StatusPrivacy> doReblog = (visibility) -> {
UiUtils.opacityOut(v); v.startAnimation(opacityOut);
session.getStatusInteractionController() session.getStatusInteractionController()
.setReblogged(item.status, !item.status.reblogged, visibility, r->boostConsumer(v, r)); .setReblogged(item.status, !item.status.reblogged, visibility, r->boostConsumer(v, r));
dialog.dismiss(); dialog.dismiss();
@@ -278,7 +271,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
menu.findViewById(R.id.quote).setOnClickListener(c->{ menu.findViewById(R.id.quote).setOnClickListener(c->{
dialog.dismiss(); dialog.dismiss();
UiUtils.opacityIn(v); v.startAnimation(opacityIn);
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", item.accountID); args.putString("account", item.accountID);
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(item.accountID); AccountSession accountSession=AccountSessionManager.getInstance().getAccount(item.accountID);
@@ -303,7 +296,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private void onFavoriteClick(View v){ private void onFavoriteClick(View v){
favorite.setSelected(!item.status.favourited); favorite.setSelected(!item.status.favourited);
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{ AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setFavorited(item.status, !item.status.favourited, r->{
UiUtils.opacityIn(v); v.startAnimation(opacityIn);
bindText(favorites, r.favouritesCount); bindText(favorites, r.favouritesCount);
}); });
} }
@@ -317,7 +310,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
R.string.sk_favorite_as, R.string.sk_favorite_as,
R.string.sk_favorited_as, R.string.sk_favorited_as,
R.string.sk_already_favorited, R.string.sk_already_favorited,
GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_28_regular : R.drawable.ic_fluent_star_28_regular R.drawable.ic_fluent_star_28_regular
); );
return true; return true;
} }
@@ -325,7 +318,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private void onBookmarkClick(View v){ private void onBookmarkClick(View v){
bookmark.setSelected(!item.status.bookmarked); bookmark.setSelected(!item.status.bookmarked);
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked, r->{ AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked, r->{
UiUtils.opacityIn(v); v.startAnimation(opacityIn);
}); });
} }
@@ -344,7 +337,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void onShareClick(View v){ private void onShareClick(View v){
UiUtils.opacityIn(v); v.startAnimation(opacityIn);
Intent intent=new Intent(Intent.ACTION_SEND); Intent intent=new Intent(Intent.ACTION_SEND);
intent.setType("text/plain"); intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, item.status.url); intent.putExtra(Intent.EXTRA_TEXT, item.status.url);

View File

@@ -18,17 +18,13 @@ import me.grishka.appkit.utils.V;
public class GapStatusDisplayItem extends StatusDisplayItem{ public class GapStatusDisplayItem extends StatusDisplayItem{
public boolean loading; public boolean loading;
private final Status status; private Status status;
public GapStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status){ public GapStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status){
super(parentID, parentFragment); super(parentID, parentFragment);
this.status=status; this.status=status;
} }
public String getMaxID(){
return status.hasGapAfter;
}
@Override @Override
public Type getType(){ public Type getType(){
return Type.GAP; return Type.GAP;
@@ -58,8 +54,6 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
if(!item.loading){ if(!item.loading){
progressBottom.setVisibility(View.GONE); progressBottom.setVisibility(View.GONE);
progressTop.setVisibility(View.GONE); progressTop.setVisibility(View.GONE);
textTop.setAlpha(1);
textBottom.setAlpha(1);
} }
top.setClickable(!item.loading); top.setClickable(!item.loading);
bottom.setClickable(!item.loading); bottom.setClickable(!item.loading);
@@ -78,7 +72,7 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
private void onViewClick(View v){ private void onViewClick(View v){
if(item.loading) return; if(item.loading) return;
boolean isTop=v==top; boolean isTop=v==top;
UiUtils.opacityOut(isTop ? textTop : textBottom); (isTop ? textTop : textBottom).startAnimation(UiUtils.opacityOut);
V.setVisibilityAnimated((isTop ? progressTop : progressBottom), View.VISIBLE); V.setVisibilityAnimated((isTop ? progressTop : progressBottom), View.VISIBLE);
item.parentFragment.onGapClick(this, isTop); item.parentFragment.onGapClick(this, isTop);
} }

View File

@@ -66,7 +66,6 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class HeaderStatusDisplayItem extends StatusDisplayItem{ public class HeaderStatusDisplayItem extends StatusDisplayItem{
@@ -77,7 +76,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private SpannableStringBuilder parsedName; private SpannableStringBuilder parsedName;
public final Status status; public final Status status;
public boolean hasVisibilityToggle; private boolean hasVisibilityToggle;
boolean needBottomPadding; boolean needBottomPadding;
private CharSequence extraText; private CharSequence extraText;
private Notification notification; private Notification notification;
@@ -96,7 +95,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic, GlobalUserPreferences.playGifs ? user.avatar : user.avatarStatic,
V.dp(50), V.dp(50)); V.dp(50), V.dp(50));
this.accountID=accountID; this.accountID=accountID;
parsedName=new SpannableStringBuilder(user.getDisplayName()); parsedName=new SpannableStringBuilder(user.displayName);
this.status=status; this.status=status;
this.notification=notification; this.notification=notification;
this.scheduledStatus=scheduledStatus; this.scheduledStatus=scheduledStatus;
@@ -137,7 +136,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
} }
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView name, time, username, extraText; private final TextView name, time, username, extraText, pronouns;
private final View collapseBtn, timeUsernameSeparator; private final View collapseBtn, timeUsernameSeparator;
private final ImageView avatar, more, visibility, deleteNotification, unreadIndicator, markAsRead, collapseBtnIcon, botIcon; private final ImageView avatar, more, visibility, deleteNotification, unreadIndicator, markAsRead, collapseBtnIcon, botIcon;
private final PopupMenu optionsMenu; private final PopupMenu optionsMenu;
@@ -164,6 +163,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
collapseBtn=findViewById(R.id.collapse_btn); collapseBtn=findViewById(R.id.collapse_btn);
collapseBtnIcon=findViewById(R.id.collapse_btn_icon); collapseBtnIcon=findViewById(R.id.collapse_btn_icon);
extraText=findViewById(R.id.extra_text); extraText=findViewById(R.id.extra_text);
pronouns=findViewById(R.id.pronouns);
avatar.setOnClickListener(this::onAvaClick); avatar.setOnClickListener(this::onAvaClick);
avatar.setOutlineProvider(OutlineProviders.roundedRect(12)); avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
avatar.setClipToOutline(true); avatar.setClipToOutline(true);
@@ -208,6 +208,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}else if(item.scheduledStatus!=null){ }else if(item.scheduledStatus!=null){
args.putString("sourceText", item.status.text); args.putString("sourceText", item.status.text);
args.putString("sourceSpoiler", item.status.spoilerText); args.putString("sourceSpoiler", item.status.spoilerText);
args.putBoolean("redraftStatus", true);
args.putParcelable("scheduledStatus", Parcels.wrap(item.scheduledStatus)); args.putParcelable("scheduledStatus", Parcels.wrap(item.scheduledStatus));
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}else{ }else{
@@ -217,14 +218,14 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public void onSuccess(GetStatusSourceText.Response result){ public void onSuccess(GetStatusSourceText.Response result){
args.putString("sourceText", result.text); args.putString("sourceText", result.text);
args.putString("sourceSpoiler", result.spoilerText); args.putString("sourceSpoiler", result.spoilerText);
if(result.contentType!=null){ if (result.contentType != null) {
args.putString("sourceContentType", result.contentType.name()); args.putString("sourceContentType", result.contentType.name());
} }
if(redraft){ if (redraft) {
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{ UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}, true); }, true);
}else{ } else {
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
} }
} }
@@ -241,7 +242,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
if (item.scheduledStatus != null) { if (item.scheduledStatus != null) {
UiUtils.confirmDeleteScheduledPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.scheduledStatus, ()->{}); UiUtils.confirmDeleteScheduledPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.scheduledStatus, ()->{});
} else { } else {
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{}, false); UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
} }
}else if(id==R.id.pin || id==R.id.unpin) { }else if(id==R.id.pin || id==R.id.unpin) {
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{}); UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
@@ -293,6 +294,17 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
UiUtils.enablePopupMenuIcons(activity, optionsMenu); UiUtils.enablePopupMenuIcons(activity, optionsMenu);
} }
private void populateAccountsMenu(Menu menu) {
List<AccountSession> sessions=AccountSessionManager.getInstance().getLoggedInAccounts();
sessions.stream().filter(s -> !s.getID().equals(item.accountID)).forEach(s -> {
String username = "@"+s.self.username+"@"+s.domain;
menu.add(username).setOnMenuItemClickListener(c->{
UiUtils.openURL(item.parentFragment.getActivity(), s.getID(), item.status.url, false);
return true;
});
});
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Override @Override
public void onBind(HeaderStatusDisplayItem item){ public void onBind(HeaderStatusDisplayItem item){
@@ -319,19 +331,29 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
botIcon.setColorFilter(username.getCurrentTextColor()); botIcon.setColorFilter(username.getCurrentTextColor());
deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification!=null && !item.inset ? View.VISIBLE : View.GONE); deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification!=null && !item.inset ? View.VISIBLE : View.GONE);
visibility.setVisibility(item.hasVisibilityToggle ? View.VISIBLE : View.GONE);
if (item.hasVisibilityToggle){ if (item.hasVisibilityToggle){
boolean visible = item.status.sensitiveRevealed && (!item.status.hasSpoiler() || item.status.spoilerRevealed); boolean hidden = !item.status.sensitiveRevealed || (item.status.hasSpoiler() && !item.status.spoilerRevealed);
visibility.setAlpha(visible ? 1 : 0f);
visibility.setScaleY(visible ? 1 : 0.8f); // doing this because V.setVisibilityAnimated ignores changes between INVISIBLE and GONE
visibility.setScaleX(visible ? 1 : 0.8f); int newVis=hidden ? View.INVISIBLE : View.VISIBLE;
visibility.setEnabled(visible); if(newVis==View.INVISIBLE && visibility.getVisibility()==View.GONE)
visibility.setVisibility(newVis);
else
V.setVisibilityAnimated(visibility, newVis);
visibility.setEnabled(!hidden);
visibility.setContentDescription(item.parentFragment.getString(item.status.sensitiveRevealed ? R.string.spoiler_hide : R.string.spoiler_show));
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
visibility.setTooltipText(visibility.getContentDescription());
}
} else {
visibility.setVisibility(View.GONE);
} }
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0); itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
if(TextUtils.isEmpty(item.extraText)){ if(TextUtils.isEmpty(item.extraText)){
if (item.status != null) { if (item.status != null) {
boolean displayPronouns=item.parentFragment instanceof ThreadFragment ? GlobalUserPreferences.displayPronounsInThreads : GlobalUserPreferences.displayPronounsInTimelines; boolean displayPronouns=item.parentFragment instanceof ThreadFragment ? GlobalUserPreferences.displayPronounsInThreads : GlobalUserPreferences.displayPronounsInTimelines;
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), extraText, displayPronouns, item.status.visibility==StatusPrivacy.DIRECT, item.status.localOnly || item.status.visibility==StatusPrivacy.LOCAL, item.status.account); UiUtils.setExtraTextInfo(item.parentFragment.getContext(), extraText, pronouns, displayPronouns, item.status.visibility==StatusPrivacy.DIRECT, item.status.localOnly || item.status.visibility==StatusPrivacy.LOCAL, item.status.account);
} }
}else{ }else{
extraText.setVisibility(View.VISIBLE); extraText.setVisibility(View.VISIBLE);
@@ -375,40 +397,19 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
markAsRead.setVisibility(View.GONE); markAsRead.setVisibility(View.GONE);
} }
bindCollapseButton(); if (item.status == null || !item.status.textExpandable) {
collapseBtn.setVisibility(View.GONE);
itemView.setPaddingRelative(itemView.getPaddingStart(), itemView.getPaddingTop(), } else {
item.inset ? V.dp(10) : V.dp(4), itemView.getPaddingBottom());
}
public void bindCollapseButton(){
boolean expandable=item.status!=null && item.status.textExpandable;
collapseBtn.setVisibility(expandable ? View.VISIBLE : View.GONE);
if(expandable) {
bindCollapseButtonText();
collapseBtnIcon.setScaleY(item.status.textExpanded ? -1 : 1);
}
}
private void bindCollapseButtonText(){
String collapseText = item.parentFragment.getString(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand); String collapseText = item.parentFragment.getString(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand);
collapseBtn.setVisibility(item.status.textExpandable ? View.VISIBLE : View.GONE);
collapseBtn.setContentDescription(collapseText); collapseBtn.setContentDescription(collapseText);
if (GlobalUserPreferences.reduceMotion) collapseBtnIcon.setScaleY(item.status.textExpanded ? -1 : 1);
else collapseBtnIcon.animate().scaleY(item.status.textExpanded ? -1 : 1).start();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) collapseBtn.setTooltipText(collapseText); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) collapseBtn.setTooltipText(collapseText);
} }
public void animateExpandToggle(){ itemView.setPaddingRelative(itemView.getPaddingStart(), itemView.getPaddingTop(),
bindCollapseButtonText(); item.inset ? V.dp(10) : V.dp(4), itemView.getPaddingBottom());
collapseBtnIcon.animate().scaleY(item.status.textExpanded ? -1 : 1).start();
}
public void animateVisibilityToggle(boolean visible){
visibility.animate()
.alpha(visible ? 1 : 0)
.scaleX(visible ? 1 : 0.8f)
.scaleY(visible ? 1 : 0.8f)
.setInterpolator(CubicBezierInterpolator.DEFAULT)
.start();
visibility.setEnabled(visible);
} }
@Override @Override

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import static org.joinmastodon.android.MastodonApp.context; import static org.joinmastodon.android.MastodonApp.context;
import static org.joinmastodon.android.model.Notification.Type.PLEROMA_EMOJI_REACTION;
import static org.joinmastodon.android.ui.utils.UiUtils.generateFormattedString; import static org.joinmastodon.android.ui.utils.UiUtils.generateFormattedString;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -8,6 +9,7 @@ import android.app.Activity;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue; import android.util.TypedValue;
@@ -21,7 +23,6 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.NotificationsListFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Notification;
@@ -45,13 +46,11 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
private final String accountID; private final String accountID;
private final CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private final CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private final CharSequence text; private final CharSequence text;
private final CharSequence timestamp;
public NotificationHeaderStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Notification notification, String accountID){ public NotificationHeaderStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Notification notification, String accountID){
super(parentID, parentFragment); super(parentID, parentFragment);
this.notification=notification; this.notification=notification;
this.accountID=accountID; this.accountID=accountID;
this.timestamp=notification.createdAt==null ? null : UiUtils.formatRelativeTimestamp(context, notification.createdAt);
if(notification.type==Notification.Type.POLL){ if(notification.type==Notification.Type.POLL){
text=parentFragment.getString(R.string.poll_ended); text=parentFragment.getString(R.string.poll_ended);
@@ -61,7 +60,7 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
TextUtils.isEmpty(notification.account.avatar) ? session.getDefaultAvatarUrl() : TextUtils.isEmpty(notification.account.avatar) ? session.getDefaultAvatarUrl() :
GlobalUserPreferences.playGifs ? notification.account.avatar : notification.account.avatarStatic, GlobalUserPreferences.playGifs ? notification.account.avatar : notification.account.avatarStatic,
V.dp(50), V.dp(50)); V.dp(50), V.dp(50));
SpannableStringBuilder parsedName=new SpannableStringBuilder(notification.account.getDisplayName()); SpannableStringBuilder parsedName=new SpannableStringBuilder(notification.account.displayName);
HtmlParser.parseCustomEmoji(parsedName, notification.account.emojis); HtmlParser.parseCustomEmoji(parsedName, notification.account.emojis);
String str = parentFragment.getString(switch(notification.type){ String str = parentFragment.getString(switch(notification.type){
case FOLLOW -> R.string.user_followed_you; case FOLLOW -> R.string.user_followed_you;
@@ -112,8 +111,8 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
} }
public static class Holder extends StatusDisplayItem.Holder<NotificationHeaderStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<NotificationHeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final ImageView icon, avatar, deleteNotification; private final ImageView icon, avatar;
private final TextView text, timestamp; private final TextView text;
private final int selectableItemBackground; private final int selectableItemBackground;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
@@ -121,16 +120,9 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
icon=findViewById(R.id.icon); icon=findViewById(R.id.icon);
avatar=findViewById(R.id.avatar); avatar=findViewById(R.id.avatar);
text=findViewById(R.id.text); text=findViewById(R.id.text);
timestamp=findViewById(R.id.timestamp);
deleteNotification=findViewById(R.id.delete_notification);
avatar.setOutlineProvider(OutlineProviders.roundedRect(8)); avatar.setOutlineProvider(OutlineProviders.roundedRect(8));
avatar.setClipToOutline(true); avatar.setClipToOutline(true);
deleteNotification.setOnClickListener(v->UiUtils.confirmDeleteNotification(activity, item.parentFragment.getAccountID(), item.notification, ()->{
if (item.parentFragment instanceof NotificationsListFragment fragment) {
fragment.removeNotification(item.notification);
}
}));
itemView.setOnClickListener(this::onItemClick); itemView.setOnClickListener(this::onItemClick);
TypedValue outValue = new TypedValue(); TypedValue outValue = new TypedValue();
@@ -160,10 +152,9 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(NotificationHeaderStatusDisplayItem item){ public void onBind(NotificationHeaderStatusDisplayItem item){
text.setText(item.text); text.setText(item.text);
timestamp.setText(item.timestamp);
avatar.setVisibility(item.notification.type==Notification.Type.POLL ? View.GONE : View.VISIBLE); avatar.setVisibility(item.notification.type==Notification.Type.POLL ? View.GONE : View.VISIBLE);
icon.setImageResource(switch(item.notification.type){ icon.setImageResource(switch(item.notification.type){
case FAVORITE -> GlobalUserPreferences.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled; case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled; case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
case FOLLOW, FOLLOW_REQUEST -> R.drawable.ic_fluent_person_add_24_filled; case FOLLOW, FOLLOW_REQUEST -> R.drawable.ic_fluent_person_add_24_filled;
case POLL -> R.drawable.ic_fluent_poll_24_filled; case POLL -> R.drawable.ic_fluent_poll_24_filled;
@@ -174,18 +165,15 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
default -> throw new IllegalStateException("Unexpected value: "+item.notification.type); default -> throw new IllegalStateException("Unexpected value: "+item.notification.type);
}); });
icon.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(item.parentFragment.getActivity(), switch(item.notification.type){ icon.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(item.parentFragment.getActivity(), switch(item.notification.type){
case FAVORITE -> GlobalUserPreferences.likeIcon ? R.attr.colorLike : R.attr.colorFavorite; case FAVORITE -> R.attr.colorFavorite;
case REBLOG -> R.attr.colorBoost; case REBLOG -> R.attr.colorBoost;
case POLL -> R.attr.colorPoll; case POLL -> R.attr.colorPoll;
default -> android.R.attr.colorAccent; default -> android.R.attr.colorAccent;
}))); })));
deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification != null ? View.VISIBLE : View.GONE);
itemView.setBackgroundResource(item.notification.type != Notification.Type.POLL itemView.setBackgroundResource(item.notification.type != Notification.Type.POLL
&& item.notification.type != Notification.Type.REPORT ? && item.notification.type != Notification.Type.REPORT ?
selectableItemBackground : 0); selectableItemBackground : 0);
itemView.setClickable(item.notification.type != Notification.Type.POLL); itemView.setClickable(item.notification.type != Notification.Type.POLL);
itemView.setPaddingRelative(itemView.getPaddingStart(), itemView.getPaddingTop(),
GlobalUserPreferences.enableDeleteNotifications ? V.dp(4) : V.dp(16), itemView.getPaddingBottom());
} }
public void onItemClick(View v) { public void onItemClick(View v) {

View File

@@ -68,7 +68,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private final TextView text, percent; private final TextView text, percent;
private final View button; private final View button;
private final ImageView icon; private final ImageView icon;
private final Drawable progressBg; private final Drawable progressBg, progressBgInset;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_poll_option, parent); super(activity, R.layout.display_item_poll_option, parent);
@@ -77,6 +77,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
icon=findViewById(R.id.icon); icon=findViewById(R.id.icon);
button=findViewById(R.id.button); button=findViewById(R.id.button);
progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate(); progressBg=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted, activity.getTheme()).mutate();
progressBgInset=activity.getResources().getDrawable(R.drawable.bg_poll_option_voted_inset, activity.getTheme()).mutate();
itemView.setOnClickListener(this::onButtonClick); itemView.setOnClickListener(this::onButtonClick);
button.setOutlineProvider(OutlineProviders.roundedRect(20)); button.setOutlineProvider(OutlineProviders.roundedRect(20));
button.setClipToOutline(true); button.setClipToOutline(true);
@@ -92,18 +93,23 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
item.showResults ? R.drawable.ic_poll_option_button : R.drawable.ic_fluent_radio_button_24_selector item.showResults ? R.drawable.ic_poll_option_button : R.drawable.ic_fluent_radio_button_24_selector
)); ));
if(item.showResults){ if(item.showResults){
Drawable bg=progressBg; Drawable bg=item.inset ? progressBgInset : progressBg;
bg.setLevel(Math.round(10000f*item.votesFraction)); bg.setLevel(Math.round(10000f*item.votesFraction));
button.setBackground(bg); button.setBackground(bg);
itemView.setSelected(item.poll.ownVotes!=null && item.poll.ownVotes.contains(item.optionIndex)); itemView.setSelected(item.poll.ownVotes!=null && item.poll.ownVotes.contains(item.optionIndex));
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f))); percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
}else{ }else{
itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option)); itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
button.setBackgroundResource(R.drawable.bg_poll_option_clickable); button.setBackgroundResource(item.inset ? R.drawable.bg_poll_option_clickable_inset : R.drawable.bg_poll_option_clickable);
} }
if(item.inset){
text.setTextColor(itemView.getContext().getColorStateList(R.color.poll_option_text_inset));
percent.setTextColor(itemView.getContext().getColorStateList(R.color.poll_option_text_inset));
}else{
text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), android.R.attr.textColorPrimary)); text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), android.R.attr.textColorPrimary));
percent.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSecondaryContainer)); percent.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSecondaryContainer));
} }
}
@Override @Override
public void setImage(int index, Drawable image){ public void setImage(int index, Drawable image){

View File

@@ -10,8 +10,10 @@ import android.text.SpannableStringBuilder;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
@@ -38,7 +40,7 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
private StatusPrivacy visibility; private StatusPrivacy visibility;
@DrawableRes @DrawableRes
private int iconEnd; private int iconEnd;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(), fullTextEmojiHelper;
private View.OnClickListener handleClick; private View.OnClickListener handleClick;
public boolean needBottomPadding; public boolean needBottomPadding;
ReblogOrReplyLineStatusDisplayItem extra; ReblogOrReplyLineStatusDisplayItem extra;
@@ -56,13 +58,21 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
HtmlParser.parseCustomEmoji(ssb, emojis); HtmlParser.parseCustomEmoji(ssb, emojis);
this.text=ssb; this.text=ssb;
emojiHelper.setText(ssb); emojiHelper.setText(ssb);
this.fullText=fullText;
this.icon=icon; this.icon=icon;
this.status=status; this.status=status;
this.handleClick=handleClick; this.handleClick=handleClick;
TypedValue outValue = new TypedValue(); TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
updateVisibility(visibility); updateVisibility(visibility);
if (fullText != null) {
fullTextEmojiHelper = new CustomEmojiHelper();
SpannableStringBuilder fullTextSsb = new SpannableStringBuilder(fullText);
HtmlParser.parseCustomEmoji(fullTextSsb, emojis);
this.fullText=fullTextSsb;
fullTextEmojiHelper.setText(fullTextSsb);
}
} }
public void updateVisibility(StatusPrivacy visibility) { public void updateVisibility(StatusPrivacy visibility) {
@@ -82,28 +92,34 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public int getImageCount(){ public int getImageCount(){
return emojiHelper.getImageCount() + (extra!=null ? extra.emojiHelper.getImageCount() : 0); return emojiHelper.getImageCount();
} }
@Override @Override
public ImageLoaderRequest getImageRequest(int index){ public ImageLoaderRequest getImageRequest(int index){
int firstHelperCount=emojiHelper.getImageCount(); return emojiHelper.getImageRequest(index);
CustomEmojiHelper helper=index<firstHelperCount ? emojiHelper : extra.emojiHelper;
return helper.getImageRequest(firstHelperCount>0 ? index%firstHelperCount : index);
} }
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text, extraText; private final TextView text, extraText;
private final View separator; private final View separator;
private final ViewGroup parent;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_reblog_or_reply_line, parent); super(activity, R.layout.display_item_reblog_or_reply_line, parent);
this.parent = parent;
text=findViewById(R.id.text); text=findViewById(R.id.text);
extraText=findViewById(R.id.extra_text); extraText=findViewById(R.id.extra_text);
separator=findViewById(R.id.separator); separator=findViewById(R.id.separator);
if (GlobalUserPreferences.compactReblogReplyLine) {
parent.addOnLayoutChangeListener((v, l, t, right, b, ol, ot, oldRight, ob) -> {
if (right != oldRight) layoutLine();
});
}
} }
private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) { private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) {
if (item.fullText != null) text.setContentDescription(item.fullText);
text.setText(item.text); text.setText(item.text);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0); text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
text.setOnClickListener(item.handleClick); text.setOnClickListener(item.handleClick);
@@ -117,10 +133,7 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
case LOCAL -> R.string.sk_local_only; case LOCAL -> R.string.sk_local_only;
default -> 0; default -> 0;
} : 0; } : 0;
String visibilityDescription=visibilityText!=0 ? " (" + ctx.getString(visibilityText) + ")" : null; if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")");
text.setContentDescription(item.fullText==null && visibilityDescription==null ? null :
(item.fullText!=null ? item.fullText : item.text)
+ (visibilityDescription!=null ? visibilityDescription : ""));
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N) if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
UiUtils.fixCompoundDrawableTintOnAndroid6(text); UiUtils.fixCompoundDrawableTintOnAndroid6(text);
text.setCompoundDrawableTintList(text.getTextColors()); text.setCompoundDrawableTintList(text.getTextColors());
@@ -133,15 +146,31 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
extraText.setVisibility(item.extra == null ? View.GONE : View.VISIBLE); extraText.setVisibility(item.extra == null ? View.GONE : View.VISIBLE);
separator.setVisibility(item.extra == null ? View.GONE : View.VISIBLE); separator.setVisibility(item.extra == null ? View.GONE : View.VISIBLE);
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0); itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
layoutLine();
}
private void layoutLine() {
// layout line only if above header, compact and has extra
if (!GlobalUserPreferences.compactReblogReplyLine || item.extra == null) return;
itemView.measure(
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.UNSPECIFIED);
boolean isVertical = ((LinearLayout) itemView).getOrientation() == LinearLayout.VERTICAL;
extraText.setPaddingRelative(extraText.getPaddingStart(), item.extra != null && isVertical ? 0 : V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom());
separator.setVisibility(item.extra != null && !isVertical ? View.VISIBLE : View.GONE);
((LinearLayout) itemView).removeView(extraText);
if (isVertical) ((LinearLayout) itemView).addView(extraText);
else ((LinearLayout) itemView).addView(extraText, 0);
text.setText(isVertical ? item.fullText : item.text);
if (item.extra != null) {
extraText.setText(isVertical ? item.extra.fullText : item.extra.text);
}
} }
@Override @Override
public void setImage(int index, Drawable image){ public void setImage(int index, Drawable image){
int firstHelperCount=item.emojiHelper.getImageCount(); item.emojiHelper.setImageDrawable(index, image);
CustomEmojiHelper helper=index<firstHelperCount ? item.emojiHelper : item.extra.emojiHelper;
helper.setImageDrawable(firstHelperCount>0 ? index%firstHelperCount : index, image);
text.invalidate(); text.invalidate();
extraText.invalidate();
} }
@Override @Override

View File

@@ -13,8 +13,7 @@ import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
@@ -23,15 +22,14 @@ import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTabFragment; import org.joinmastodon.android.fragments.HomeTabFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment; import org.joinmastodon.android.fragments.ListTimelineFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.LegacyFilter; import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.model.FilterContext; import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterResult; import org.joinmastodon.android.model.FilterResult;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.ScheduledStatus; import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
@@ -39,6 +37,7 @@ import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.viewholders.AccountViewHolder; import org.joinmastodon.android.ui.viewholders.AccountViewHolder;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
@@ -58,10 +57,10 @@ public abstract class StatusDisplayItem{
public boolean inset; public boolean inset;
public int index; public int index;
public boolean public boolean
hasDescendantNeighbor=false, hasDescendantNeighbor = false,
hasAncestoringNeighbor=false, hasAncestoringNeighbor = false,
isMainStatus=true, isMainStatus = true,
isDirectDescendant=false; isDirectDescendant = false;
public static final int FLAG_INSET=1; public static final int FLAG_INSET=1;
public static final int FLAG_NO_FOOTER=1 << 1; public static final int FLAG_NO_FOOTER=1 << 1;
@@ -88,16 +87,6 @@ public abstract class StatusDisplayItem{
this.parentFragment=parentFragment; this.parentFragment=parentFragment;
} }
@NonNull
public String getContentID(){
if(parentFragment instanceof StatusListFragment slf){
Status s=slf.getContentStatusByID(parentID);
return s!=null ? s.id : parentID;
}else{
return parentID;
}
}
public abstract Type getType(); public abstract Type getType();
public int getImageCount(){ public int getImageCount(){
@@ -139,11 +128,11 @@ public abstract class StatusDisplayItem{
String parentID = parent.getID(); String parentID = parent.getID();
String text = threadReply ? fragment.getString(R.string.sk_show_thread) String text = threadReply ? fragment.getString(R.string.sk_show_thread)
: account == null ? fragment.getString(R.string.sk_in_reply) : account == null ? fragment.getString(R.string.sk_in_reply)
: status.reblog != null ? account.getDisplayName() : GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
: fragment.getString(R.string.in_reply_to, account.getDisplayName()); : fragment.getString(R.string.in_reply_to, account.displayName);
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread) String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
: account == null ? fragment.getString(R.string.sk_in_reply) : account == null ? fragment.getString(R.string.sk_in_reply)
: fragment.getString(R.string.in_reply_to, account.getDisplayName()); : fragment.getString(R.string.in_reply_to, account.displayName);
return new ReblogOrReplyLineStatusDisplayItem( return new ReblogOrReplyLineStatusDisplayItem(
parentID, fragment, text, account == null ? List.of() : account.emojis, parentID, fragment, text, account == null ? List.of() : account.emojis,
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText, status R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText, status
@@ -156,7 +145,7 @@ public abstract class StatusDisplayItem{
Status statusForContent=status.getContentStatus(); Status statusForContent=status.getContentStatus();
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus s ? s : null; ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null;
HeaderStatusDisplayItem header=null; HeaderStatusDisplayItem header=null;
boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts; boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts;
@@ -173,11 +162,12 @@ public abstract class StatusDisplayItem{
if(status.reblog!=null){ if(status.reblog!=null){
boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account); boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account);
String text=fragment.getString(R.string.user_boosted, status.account.getDisplayName()); String fullText = fragment.getString(R.string.user_boosted, status.account.displayName);
String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null ? status.account.displayName : fullText;
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{ items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
args.putParcelable("profileAccount", Parcels.wrap(status.account)); args.putParcelable("profileAccount", Parcels.wrap(status.account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args); Nav.go(fragment.getActivity(), ProfileFragment.class, args);
}, null, status)); }, fullText, status));
} else if (!(status.tags.isEmpty() || } else if (!(status.tags.isEmpty() ||
fragment instanceof HashtagTimelineFragment || fragment instanceof HashtagTimelineFragment ||
fragment instanceof ListTimelineFragment fragment instanceof ListTimelineFragment
@@ -201,7 +191,7 @@ public abstract class StatusDisplayItem{
.map(ReblogOrReplyLineStatusDisplayItem.class::cast) .map(ReblogOrReplyLineStatusDisplayItem.class::cast)
.findFirst(); .findFirst();
if (primaryLine.isPresent()) { if (primaryLine.isPresent() && GlobalUserPreferences.compactReblogReplyLine) {
primaryLine.get().extra = replyLine; primaryLine.get().extra = replyLine;
} else { } else {
items.add(replyLine); items.add(replyLine);
@@ -211,15 +201,14 @@ public abstract class StatusDisplayItem{
if((flags & FLAG_CHECKABLE)!=0) if((flags & FLAG_CHECKABLE)!=0)
items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null)); items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
else else
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, parentObject instanceof Notification n ? n : null, scheduledStatus)); items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, null, scheduledStatus));
} }
LegacyFilter applyingFilter=null; boolean filtered=false;
if(status.filtered!=null){ if(status.filtered!=null){
for(FilterResult filter:status.filtered){ for(FilterResult filter:status.filtered){
LegacyFilter f=filter.filter; if(filter.filter.isActive()){
if(f.isActive() && filterContext != null && f.context.contains(filterContext)){ filtered=true;
applyingFilter=f;
break; break;
} }
} }
@@ -302,7 +291,7 @@ public abstract class StatusDisplayItem{
footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID); footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID);
footer.hideCounts=hideCounts; footer.hideCounts=hideCounts;
items.add(footer); items.add(footer);
if(status.hasGapAfter!=null && !(fragment instanceof ThreadFragment)) if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
items.add(new GapStatusDisplayItem(parentID, fragment, status)); items.add(new GapStatusDisplayItem(parentID, fragment, status));
} }
int i=1; int i=1;
@@ -326,7 +315,14 @@ public abstract class StatusDisplayItem{
} }
} }
return applyingFilter==null ? items : LegacyFilter applyingFilter = null;
if (!statusForContent.filterRevealed) {
StatusFilterPredicate predicate = new StatusFilterPredicate(accountID, filterContext, FilterAction.WARN);
statusForContent.filterRevealed = predicate.test(status);
applyingFilter = predicate.getApplyingFilter();
}
return statusForContent.filterRevealed ? items :
new ArrayList<>(List.of(new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items, applyingFilter))); new ArrayList<>(List.of(new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items, applyingFilter)));
} }

View File

@@ -1,5 +1,7 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import static org.joinmastodon.android.ui.utils.UiUtils.opacityIn;
import android.app.Activity; import android.app.Activity;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@@ -28,7 +30,6 @@ import java.util.Locale;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.MovieDrawable; import me.grishka.appkit.imageloader.MovieDrawable;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class TextStatusDisplayItem extends StatusDisplayItem{ public class TextStatusDisplayItem extends StatusDisplayItem{
@@ -112,8 +113,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
}else{ }else{
text.setText(item.text); text.setText(item.text);
} }
text.setTextIsSelectable(false); text.setTextIsSelectable(item.textSelectable);
if(item.textSelectable) itemView.post(() -> text.setTextIsSelectable(true));
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
itemView.setClickable(false); itemView.setClickable(false);
itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom()); itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom());
@@ -201,7 +201,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
translationProgress=findViewById(R.id.translation_progress); translationProgress=findViewById(R.id.translation_progress);
translationButton.setOnClickListener(v->item.parentFragment.togglePostTranslation(item.status, item.parentID)); translationButton.setOnClickListener(v->item.parentFragment.togglePostTranslation(item.status, item.parentID));
} }
if(translationButton!=null) translationButton.animate().cancel();
if(item.status.translationState==Status.TranslationState.HIDDEN){ if(item.status.translationState==Status.TranslationState.HIDDEN){
if(updateText) text.setText(item.text); if(updateText) text.setText(item.text);
if(translationFooter==null) return; if(translationFooter==null) return;
@@ -210,12 +209,11 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
Translation existingTrans=item.status.getContentStatus().translation; Translation existingTrans=item.status.getContentStatus().translation;
String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null; String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null;
String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language; String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language;
Locale locale=lang!=null ? Locale.forLanguageTag(lang) : null; String displayLang=Locale.forLanguageTag(lang != null ? lang
translationButton.setText(locale!=null : AccountSessionManager.get(item.parentFragment.getAccountID()).preferences.postingDefaultLanguage).getDisplayLanguage();
? item.parentFragment.getString(R.string.translate_post, locale.getDisplayLanguage()) translationButton.setText(item.parentFragment.getString(R.string.translate_post, !displayLang.isBlank() ? displayLang : lang));
: item.parentFragment.getString(R.string.sk_translate_post)); translationButton.setEnabled(true);
translationButton.setClickable(true); translationButton.setAlpha(1);
translationButton.animate().alpha(1).setDuration(100).start();
translationInfo.setVisibility(View.GONE); translationInfo.setVisibility(View.GONE);
UiUtils.beginLayoutTransition((ViewGroup) translationButtonWrap); UiUtils.beginLayoutTransition((ViewGroup) translationButtonWrap);
}else{ }else{
@@ -223,8 +221,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
if(item.status.translationState==Status.TranslationState.SHOWN){ if(item.status.translationState==Status.TranslationState.SHOWN){
translationProgress.setVisibility(View.GONE); translationProgress.setVisibility(View.GONE);
translationButton.setText(R.string.translation_show_original); translationButton.setText(R.string.translation_show_original);
translationButton.setClickable(true); translationButton.setEnabled(true);
translationButton.animate().alpha(1).setDuration(200).start(); translationButton.setAlpha(1);
translationInfo.setVisibility(View.VISIBLE); translationInfo.setVisibility(View.VISIBLE);
translationButton.setVisibility(View.VISIBLE); translationButton.setVisibility(View.VISIBLE);
String displayLang=Locale.forLanguageTag(item.status.translation.detectedSourceLanguage).getDisplayLanguage(); String displayLang=Locale.forLanguageTag(item.status.translation.detectedSourceLanguage).getDisplayLanguage();
@@ -238,8 +236,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
} }
}else{ // LOADING }else{ // LOADING
translationProgress.setVisibility(View.VISIBLE); translationProgress.setVisibility(View.VISIBLE);
translationButton.setClickable(false); translationButton.setEnabled(false);
translationButton.animate().alpha(UiUtils.ALPHA_PRESSED).setStartDelay(50).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT).start(); translationButton.startAnimation(opacityIn);
translationInfo.setVisibility(View.INVISIBLE); translationInfo.setVisibility(View.INVISIBLE);
UiUtils.beginLayoutTransition((ViewGroup) translationButton.getParent()); UiUtils.beginLayoutTransition((ViewGroup) translationButton.getParent());
} }

View File

@@ -31,11 +31,13 @@ public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{
} }
public static class Holder extends StatusDisplayItem.Holder<WarningFilteredStatusDisplayItem>{ public static class Holder extends StatusDisplayItem.Holder<WarningFilteredStatusDisplayItem>{
public final View warningWrap;
public final TextView text; public final TextView text;
public List<StatusDisplayItem> filteredItems; public List<StatusDisplayItem> filteredItems;
public Holder(Context context, ViewGroup parent) { public Holder(Context context, ViewGroup parent) {
super(context, R.layout.display_item_filter_warning, parent); super(context, R.layout.display_item_filter_warning, parent);
warningWrap=findViewById(R.id.warning_wrap);
text=findViewById(R.id.text); text=findViewById(R.id.text);
} }
@@ -43,7 +45,11 @@ public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{
public void onBind(WarningFilteredStatusDisplayItem item) { public void onBind(WarningFilteredStatusDisplayItem item) {
filteredItems = item.filteredItems; filteredItems = item.filteredItems;
text.setText(item.parentFragment.getString(R.string.sk_filtered, item.applyingFilter.title)); text.setText(item.parentFragment.getString(R.string.sk_filtered, item.applyingFilter.title));
itemView.setOnClickListener(v->item.parentFragment.onWarningClick(this)); }
@Override
public void onClick() {
item.parentFragment.onWarningClick(this);
} }
} }
} }

View File

@@ -66,10 +66,6 @@ public class BlurhashCrossfadeDrawable extends Drawable{
public void setImageDrawable(Drawable imageDrawable){ public void setImageDrawable(Drawable imageDrawable){
this.imageDrawable=imageDrawable; this.imageDrawable=imageDrawable;
if(imageDrawable!=null){
width=imageDrawable.getIntrinsicWidth();
height=imageDrawable.getIntrinsicHeight();
}
invalidateSelf(); invalidateSelf();
} }
@@ -103,15 +99,11 @@ public class BlurhashCrossfadeDrawable extends Drawable{
@Override @Override
public int getIntrinsicWidth(){ public int getIntrinsicWidth(){
if(width==0)
return imageDrawable==null ? 1920 : imageDrawable.getIntrinsicWidth();
return width; return width;
} }
@Override @Override
public int getIntrinsicHeight(){ public int getIntrinsicHeight(){
if(height==0)
return imageDrawable==null ? 1080 : imageDrawable.getIntrinsicHeight();
return height; return height;
} }

View File

@@ -564,7 +564,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
if(player==null || !player.isPlaying()) if(player==null || !player.isPlaying())
return; return;
player.pause(); player.pause();
videoPlayPauseButton.setImageResource(R.drawable.ic_fluent_play_24_filled); videoPlayPauseButton.setImageResource(R.drawable.ic_play_24);
videoPlayPauseButton.setContentDescription(activity.getString(R.string.play)); videoPlayPauseButton.setContentDescription(activity.getString(R.string.play));
stopUpdatingVideoPosition(); stopUpdatingVideoPosition();
windowView.removeCallbacks(uiAutoHider); windowView.removeCallbacks(uiAutoHider);
@@ -575,7 +575,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
if(player==null || player.isPlaying()) if(player==null || player.isPlaying())
return; return;
player.start(); player.start();
videoPlayPauseButton.setImageResource(R.drawable.ic_fluent_pause_24_filled); videoPlayPauseButton.setImageResource(R.drawable.ic_pause_24);
videoPlayPauseButton.setContentDescription(activity.getString(R.string.pause)); videoPlayPauseButton.setContentDescription(activity.getString(R.string.pause));
startUpdatingVideoPosition(player); startUpdatingVideoPosition(player);
} }
@@ -734,18 +734,9 @@ public class PhotoViewer implements ZoomPanView.Listener{
public void onBind(Attachment item){ public void onBind(Attachment item){
super.onBind(item); super.onBind(item);
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) imageView.getLayoutParams(); FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) imageView.getLayoutParams();
Drawable currentDrawable=listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition());
if(item.hasKnownDimensions()){
params.width=item.getWidth(); params.width=item.getWidth();
params.height=item.getHeight(); params.height=item.getHeight();
}else if(currentDrawable!=null){ ViewImageLoader.load(this, listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition()), new UrlImageLoaderRequest(item.url), false);
params.width=currentDrawable.getIntrinsicWidth();
params.height=currentDrawable.getIntrinsicHeight();
}else{
params.width=1920;
params.height=1080;
}
ViewImageLoader.load(this, currentDrawable, new UrlImageLoaderRequest(item.url), false);
} }
@Override @Override
@@ -787,18 +778,9 @@ public class PhotoViewer implements ZoomPanView.Listener{
super.onBind(item); super.onBind(item);
playerReady=false; playerReady=false;
FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams(); FrameLayout.LayoutParams params=(FrameLayout.LayoutParams) wrap.getLayoutParams();
Drawable currentDrawable=listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition());
if(item.hasKnownDimensions()){
params.width=item.getWidth(); params.width=item.getWidth();
params.height=item.getHeight(); params.height=item.getHeight();
}else if(currentDrawable!=null){ wrap.setBackground(listener.getPhotoViewCurrentDrawable(getAbsoluteAdapterPosition()));
params.width=currentDrawable.getIntrinsicWidth();
params.height=currentDrawable.getIntrinsicHeight();
}else{
params.width=1920;
params.height=1080;
}
wrap.setBackground(currentDrawable);
progressBar.setVisibility(item.type==Attachment.Type.VIDEO ? View.VISIBLE : View.GONE); progressBar.setVisibility(item.type==Attachment.Type.VIDEO ? View.VISIBLE : View.GONE);
if(itemView.isAttachedToWindow()){ if(itemView.isAttachedToWindow()){
reset(); reset();
@@ -859,9 +841,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
@Override @Override
public boolean onError(MediaPlayer mp, int what, int extra){ public boolean onError(MediaPlayer mp, int what, int extra){
Log.e(TAG, "video player onError() called with: mp = ["+mp+"], what = ["+what+"], extra = ["+extra+"]"); Log.e(TAG, "video player onError() called with: mp = ["+mp+"], what = ["+what+"], extra = ["+extra+"]");
Toast.makeText(activity, R.string.error_playing_video, Toast.LENGTH_SHORT).show(); return false;
onStartSwipeToDismissTransition(0f);
return true;
} }
public void prepareAndStartPlayer(){ public void prepareAndStartPlayer(){
@@ -882,8 +862,6 @@ public class PhotoViewer implements ZoomPanView.Listener{
player.prepareAsync(); player.prepareAsync();
}catch(IOException x){ }catch(IOException x){
Log.w(TAG, "Error initializing gif player", x); Log.w(TAG, "Error initializing gif player", x);
Toast.makeText(activity, R.string.error_playing_video, Toast.LENGTH_SHORT).show();
onStartSwipeToDismissTransition(0f);
} }
} }
@@ -940,7 +918,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
@Override @Override
public void onCompletion(MediaPlayer mp){ public void onCompletion(MediaPlayer mp){
videoPlayPauseButton.setImageResource(R.drawable.ic_fluent_play_24_filled); videoPlayPauseButton.setImageResource(R.drawable.ic_play_24);
videoPlayPauseButton.setContentDescription(activity.getString(R.string.play)); videoPlayPauseButton.setContentDescription(activity.getString(R.string.play));
stopUpdatingVideoPosition(); stopUpdatingVideoPosition();
if(!uiVisible) if(!uiVisible)

View File

@@ -119,10 +119,8 @@ public class ZoomPanView extends FrameLayout implements ScaleGestureDetector.OnS
int width=right-left; int width=right-left;
int height=bottom-top; int height=bottom-top;
if(width==0 || height==0 || child.getWidth()==0 || child.getWidth()==0){ if(width==0 || height==0)
matrix.reset();
return; return;
}
float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight()); float scale=Math.min(width/(float)child.getWidth(), height/(float)child.getHeight());
minScale=scale; minScale=scale;

View File

@@ -210,7 +210,6 @@ public class HtmlParser{
} }
public static void parseCustomEmoji(SpannableStringBuilder ssb, List<Emoji> emojis){ public static void parseCustomEmoji(SpannableStringBuilder ssb, List<Emoji> emojis){
if(emojis==null) return;
Map<String, Emoji> emojiByCode = Map<String, Emoji> emojiByCode =
emojis.stream() emojis.stream()
.collect( .collect(

View File

@@ -5,7 +5,6 @@ import android.text.TextPaint;
import android.text.style.CharacterStyle; import android.text.style.CharacterStyle;
import android.view.View; import android.view.View;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
@@ -35,7 +34,7 @@ public class LinkSpan extends CharacterStyle {
@Override @Override
public void updateDrawState(TextPaint tp) { public void updateDrawState(TextPaint tp) {
tp.setColor(color=tp.linkColor); tp.setColor(color=tp.linkColor);
tp.setUnderlineText(GlobalUserPreferences.underlinedLinks); tp.setUnderlineText(true);
} }
public void onClick(Context context){ public void onClick(Context context){
@@ -46,7 +45,7 @@ public class LinkSpan extends CharacterStyle {
if(linkObject instanceof Hashtag ht) if(linkObject instanceof Hashtag ht)
UiUtils.openHashtagTimeline(context, accountID, ht); UiUtils.openHashtagTimeline(context, accountID, ht);
else else
UiUtils.openHashtagTimeline(context, accountID, link); UiUtils.openHashtagTimeline(context, accountID, text);
} }
case CUSTOM -> listener.onLinkClick(this); case CUSTOM -> listener.onLinkClick(this);
} }

View File

@@ -1,88 +0,0 @@
package org.joinmastodon.android.ui.utils;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.IntEvaluator;
import android.animation.ObjectAnimator;
import android.graphics.drawable.Drawable;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import org.joinmastodon.android.R;
import java.util.function.IntSupplier;
import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.fragments.AppKitFragment;
public class ActionModeHelper{
public static ActionMode startActionMode(AppKitFragment fragment, IntSupplier statusBarColorSupplier, ActionMode.Callback callback){
FragmentStackActivity activity=(FragmentStackActivity) fragment.getActivity();
return activity.startActionMode(new ActionMode.Callback(){
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu){
if(!callback.onCreateActionMode(mode, menu))
return false;
ObjectAnimator anim=ObjectAnimator.ofInt(activity.getWindow(), "statusBarColor", statusBarColorSupplier.getAsInt(), UiUtils.getThemeColor(activity, 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();
activity.invalidateSystemBarColors(fragment);
View fakeView=new View(activity);
// mode.setCustomView(fakeView);
// int buttonID=activity.getResources().getIdentifier("action_mode_close_button", "id", "android");
// View btn=activity.getWindow().getDecorView().findViewById(buttonID);
// if(btn!=null){
// ((ViewGroup.MarginLayoutParams)btn.getLayoutParams()).setMarginEnd(0);
// }
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu){
if(!callback.onPrepareActionMode(mode, menu))
return false;
for(int i=0;i<menu.size();i++){
Drawable icon=menu.getItem(i).getIcon();
if(icon!=null){
icon=icon.mutate();
icon.setTint(UiUtils.getThemeColor(activity, R.attr.colorM3OnPrimary));
menu.getItem(i).setIcon(icon);
}
}
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item){
return callback.onActionItemClicked(mode, item);
}
@Override
public void onDestroyActionMode(ActionMode mode){
ObjectAnimator anim=ObjectAnimator.ofInt(activity.getWindow(), "statusBarColor", UiUtils.getThemeColor(activity, R.attr.colorM3Primary), statusBarColorSupplier.getAsInt());
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){
activity.getWindow().setStatusBarColor(0);
}
});
anim.start();
activity.invalidateSystemBarColors(fragment);
callback.onDestroyActionMode(mode);
}
});
}
}

View File

@@ -1,8 +1,8 @@
package org.joinmastodon.android.ui.utils; package org.joinmastodon.android.ui.utils;
import static org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
import static org.joinmastodon.android.GlobalUserPreferences.ThemePreference; import static org.joinmastodon.android.GlobalUserPreferences.ThemePreference;
import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme; import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.*;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
@@ -11,21 +11,20 @@ import androidx.annotation.StyleRes;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import java.util.Map; import java.util.Map;
public class ColorPalette { public class ColorPalette {
public static final Map<AccountLocalPreferences.ColorPreference, ColorPalette> palettes = Map.of( public static final Map<GlobalUserPreferences.ColorPreference, ColorPalette> palettes = Map.of(
MATERIAL3, new ColorPalette(R.style.ColorPalette_Material3) ColorPreference.MATERIAL3, new ColorPalette(R.style.ColorPalette_Material3)
.dark(R.style.ColorPalette_Material3_Dark, R.style.ColorPalette_Material3_AutoLightDark), .dark(R.style.ColorPalette_Material3_Dark, R.style.ColorPalette_Material3_AutoLightDark),
PINK, new ColorPalette(R.style.ColorPalette_Pink), ColorPreference.PINK, new ColorPalette(R.style.ColorPalette_Pink),
PURPLE, new ColorPalette(R.style.ColorPalette_Purple), ColorPreference.PURPLE, new ColorPalette(R.style.ColorPalette_Purple),
GREEN, new ColorPalette(R.style.ColorPalette_Green), ColorPreference.GREEN, new ColorPalette(R.style.ColorPalette_Green),
BLUE, new ColorPalette(R.style.ColorPalette_Blue), ColorPreference.BLUE, new ColorPalette(R.style.ColorPalette_Blue),
BROWN, new ColorPalette(R.style.ColorPalette_Brown), ColorPreference.BROWN, new ColorPalette(R.style.ColorPalette_Brown),
RED, new ColorPalette(R.style.ColorPalette_Red), ColorPreference.RED, new ColorPalette(R.style.ColorPalette_Red),
YELLOW, new ColorPalette(R.style.ColorPalette_Yellow) ColorPreference.YELLOW, new ColorPalette(R.style.ColorPalette_Yellow)
); );
private @StyleRes int base; private @StyleRes int base;

View File

@@ -28,7 +28,6 @@ public class MediaAttachmentViewController{
private final Context context; private final Context context;
private boolean didClear; private boolean didClear;
private Status status; private Status status;
private Attachment attachment;
public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){ public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){
view=context.getSystemService(LayoutInflater.class).inflate(switch(type){ view=context.getSystemService(LayoutInflater.class).inflate(switch(type){
@@ -55,7 +54,6 @@ public class MediaAttachmentViewController{
public void bind(Attachment attachment, Status status){ public void bind(Attachment attachment, Status status){
this.status=status; this.status=status;
this.attachment=attachment;
crossfadeDrawable.setSize(attachment.getWidth(), attachment.getHeight()); crossfadeDrawable.setSize(attachment.getWidth(), attachment.getHeight());
crossfadeDrawable.setBlurhashDrawable(attachment.blurhashPlaceholder); crossfadeDrawable.setBlurhashDrawable(attachment.blurhashPlaceholder);
crossfadeDrawable.setCrossfadeAlpha(0f); crossfadeDrawable.setCrossfadeAlpha(0f);
@@ -78,11 +76,6 @@ public class MediaAttachmentViewController{
crossfadeDrawable.setImageDrawable(drawable); crossfadeDrawable.setImageDrawable(drawable);
if(didClear) if(didClear)
crossfadeDrawable.animateAlpha(0f); crossfadeDrawable.animateAlpha(0f);
// Make sure the image is not stretched if the server returned wrong dimensions
if(drawable!=null && (drawable.getIntrinsicWidth()!=attachment.getWidth() || drawable.getIntrinsicHeight()!=attachment.getHeight())){
photo.setImageDrawable(null);
photo.setImageDrawable(crossfadeDrawable);
}
} }
public void clearImage(){ public void clearImage(){

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