Merge remote-tracking branch 'megalodon_main/main'

# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/CustomEmojiPopupKeyboard.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/NotificationHeaderStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/ColorPalette.java
#	mastodon/src/main/res/color/bookmark_icon.xml
#	mastodon/src/main/res/color/boost_icon.xml
#	mastodon/src/main/res/color/favorite_icon.xml
#	mastodon/src/main/res/layout/display_item_footer.xml
#	mastodon/src/main/res/layout/tab_bar.xml
#	mastodon/src/main/res/values/palettes.xml
#	mastodon/src/main/res/values/styles.xml
#	metadata/uk/changelogs/51.txt
#	metadata/uk/changelogs/56.txt
#	metadata/uk/changelogs/61.txt
#	metadata/uk/full_description.txt
This commit is contained in:
LucasGGamerM
2023-10-12 14:58:23 -03:00
241 changed files with 1110 additions and 1636 deletions

View File

@@ -281,7 +281,7 @@ public class AudioPlayerService extends Service{
if(playerReady){
boolean isPlaying=player.isPlaying();
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_pause_24 : R.drawable.ic_play_24),
bldr.addAction(new Notification.Action.Builder(Icon.createWithResource(this, isPlaying ? R.drawable.ic_fluent_pause_24_filled : R.drawable.ic_fluent_play_24_filled),
getString(isPlaying ? R.string.pause : R.string.play),
PendingIntent.getBroadcast(this, 2, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_IMMUTABLE))
.build());

View File

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

View File

@@ -26,8 +26,6 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import androidx.annotation.StringRes;
public class GlobalUserPreferences{
private static final String TAG="GlobalUserPreferences";
@@ -58,12 +56,12 @@ public class GlobalUserPreferences{
public static boolean allowRemoteLoading;
public static boolean forwardReportDefault;
public static AutoRevealMode autoRevealEqualSpoilers;
public static ColorPreference color;
public static boolean disableM3PillActiveIndicator;
public static boolean showNavigationLabels;
public static boolean displayPronounsInTimelines, displayPronounsInThreads, displayPronounsInUserListings;
public static boolean overlayMedia;
public static boolean showSuicideHelp;
public static boolean underlinedLinks;
// MOSHIDON
public static boolean showDividers;
@@ -133,12 +131,13 @@ public class GlobalUserPreferences{
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
disableM3PillActiveIndicator=prefs.getBoolean("disableM3PillActiveIndicator", false);
showNavigationLabels=prefs.getBoolean("showNavigationLabels", true);
showNavigationLabels=prefs.getBoolean("showNavigationLabels", false);
displayPronounsInTimelines=prefs.getBoolean("displayPronounsInTimelines", true);
displayPronounsInThreads=prefs.getBoolean("displayPronounsInThreads", true);
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
overlayMedia=prefs.getBoolean("overlayMedia", false);
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
underlinedLinks=prefs.getBoolean("underlinedLinks", true);
// MOSHIDON
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
@@ -169,18 +168,13 @@ public class GlobalUserPreferences{
.apply();
}
try {
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.MATERIAL3.name()));
}else{
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PURPLE.name()));
}
} catch (IllegalArgumentException|ClassCastException ignored) {
// invalid color name or color was previously saved as integer
color=ColorPreference.PURPLE;
}
if(prefs.getInt("migrationLevel", 0) < 61) migrateToUpstreamVersion61();
int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE);
if(migrationLevel < 61)
migrateToUpstreamVersion61();
if(migrationLevel < 102)
migrateToVersion102();
if(migrationLevel < BuildConfig.VERSION_CODE)
prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply();
}
public static void save(){
@@ -211,7 +205,6 @@ public class GlobalUserPreferences{
.putBoolean("spectatorMode", spectatorMode)
.putBoolean("autoHideFab", autoHideFab)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putString("color", color.name())
.putBoolean("allowRemoteLoading", allowRemoteLoading)
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
.putBoolean("forwardReportDefault", forwardReportDefault)
@@ -222,6 +215,7 @@ public class GlobalUserPreferences{
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
.putBoolean("overlayMedia", overlayMedia)
.putBoolean("showSuicideHelp", showSuicideHelp)
.putBoolean("underlinedLinks", underlinedLinks)
// MOSHIDON
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
@@ -240,11 +234,49 @@ public class GlobalUserPreferences{
.putBoolean("showPostsWithoutAlt", showPostsWithoutAlt)
.putBoolean("showMediaPreview", showMediaPreview)
.putInt("theme", theme.ordinal())
.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 migrateToVersion102(){
Log.d(TAG, "Migrating preferences to version 102!! (copy current theme to local preferences)");
SharedPreferences prefs=getPrefs();
// only migrate if global prefs even contains a color (but like.. it should)
if(prefs.contains("color")){
AccountSessionManager asm=AccountSessionManager.getInstance();
for(AccountSession session : asm.getLoggedInAccounts()){
if(session.getRawLocalPreferences().contains("color")) continue;
AccountLocalPreferences localPrefs=session.getLocalPreferences();
localPrefs.color=AccountLocalPreferences.ColorPreference.valueOf(prefs.getString(
"color", AccountLocalPreferences.ColorPreference.MATERIAL3.name()
));
localPrefs.save();
}
prefs.edit().remove("color").apply();
}
}
private static void migrateToUpstreamVersion61(){
Log.d(TAG, "Migrating preferences to upstream version 61!!");
@@ -291,53 +323,8 @@ public class GlobalUserPreferences{
localPrefs.save();
}
prefs.edit().putInt("migrationLevel", 61).apply();
}
public enum ColorPreference{
MATERIAL3,
PURPLE,
PINK,
GREEN,
BLUE,
BROWN,
RED,
YELLOW,
NORD,
WHITE;
//endregion
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
case NORD -> R.string.mo_color_palette_nord;
case WHITE -> R.string.mo_color_palette_black_and_white;
};
}
}
public enum ThemePreference{
AUTO,
LIGHT,
DARK
}
public enum AutoRevealMode {
NEVER,
THREADS,
DISCUSSIONS
}
public enum PrefixRepliesMode {
NEVER,
ALWAYS,
TO_OTHERS
}
}

View File

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

View File

@@ -28,6 +28,7 @@ import org.joinmastodon.android.api.requests.statuses.CreateStatus;
import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.Account;
@@ -164,6 +165,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
private void notify(Context context, PushNotification pn, String accountID, org.joinmastodon.android.model.Notification notification){
NotificationManager nm=context.getSystemService(NotificationManager.class);
AccountSession session=AccountSessionManager.get(accountID);
AccountLocalPreferences lp=session.getLocalPreferences();
Account self=session.self;
String accountName="@"+self.username+"@"+AccountSessionManager.getInstance().getAccount(accountID).domain;
Notification.Builder builder;
@@ -218,7 +220,7 @@ public class PushNotificationReceiver extends BroadcastReceiver{
if (!GlobalUserPreferences.uniformNotificationIcon) {
builder.setSmallIcon(switch (pn.notificationType) {
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
case FAVORITE -> lp.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_24_filled;
case REBLOG -> R.drawable.ic_fluent_arrow_repeat_all_24_filled;
case FOLLOW -> R.drawable.ic_fluent_person_add_24_filled;
case MENTION -> R.drawable.ic_fluent_mention_24_filled;

View File

@@ -74,7 +74,6 @@ public class CacheController{
result.add(status);
}while(cursor.moveToNext());
String _newMaxID=newMaxID;
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
uiHandler.post(()->callback.onSuccess(new CacheablePaginatedResponse<>(result, _newMaxID, true)));
return;
}
@@ -86,9 +85,7 @@ public class CacheController{
.setCallback(new Callback<>(){
@Override
public void onSuccess(List<Status> result){
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));
callback.onSuccess(new CacheablePaginatedResponse<>(result, result.isEmpty() ? null : result.get(result.size()-1).id, false));
putHomeTimeline(result, maxID==null);
}

View File

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

View File

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

View File

@@ -6,10 +6,14 @@ import static org.joinmastodon.android.api.MastodonAPIController.gson;
import android.content.SharedPreferences;
import androidx.annotation.StringRes;
import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.model.TimelineDefinition;
@@ -41,16 +45,19 @@ public class AccountLocalPreferences{
public String publishButtonText;
public String timelineReplyVisibility; // akkoma-only
public boolean keepOnlyLatestNotification;
public boolean emojiReactionsEnabled;
public ShowEmojiReactions showEmojiReactions;
public ColorPreference color;
public boolean likeIcon;
public ArrayList<Emoji> recentCustomEmoji;
private final static Type recentLanguagesType = new TypeToken<ArrayList<String>>() {}.getType();
private final static Type timelinesType = new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
private final static Type recentLanguagesType=new TypeToken<ArrayList<String>>() {}.getType();
private final static Type timelinesType=new TypeToken<ArrayList<TimelineDefinition>>() {}.getType();
private final static Type recentCustomEmojiType=new TypeToken<ArrayList<Emoji>>() {}.getType();
// MOSHIDON
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
public Map<String, Integer> recentEmojis;
// private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
// public Map<String, Integer> recentEmojis;
private final static Type notificationFiltersType = new TypeToken<PushSubscription.Alerts>() {}.getType();
public PushSubscription.Alerts notificationFilters;
@@ -77,9 +84,12 @@ public class AccountLocalPreferences{
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.MATERIAL3.name()));
likeIcon=prefs.getBoolean("likeIcon", false);
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
// MOSHIDON
recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
// recentEmojis=fromJson(prefs.getString("recentEmojis", "{}"), recentEmojisType, new HashMap<>());
notificationFilters=fromJson(prefs.getString("notificationFilters", gson.toJson(PushSubscription.Alerts.ofAll())), notificationFiltersType, PushSubscription.Alerts.ofAll());
}
@@ -114,13 +124,40 @@ public class AccountLocalPreferences{
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
.putString("showEmojiReactions", showEmojiReactions.name())
.putString("color", color.name())
.putBoolean("likeIcon", likeIcon)
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))
// MOSHIDON
.putString("recentEmojis", gson.toJson(recentEmojis))
// .putString("recentEmojis", gson.toJson(recentEmojis))
.putString("notificationFilters", gson.toJson(notificationFilters))
.apply();
}
public enum ColorPreference{
MATERIAL3,
PINK,
PURPLE,
GREEN,
BLUE,
BROWN,
RED,
YELLOW;
public @StringRes int getName() {
return switch(this){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
case GREEN -> R.string.sk_color_palette_green;
case BLUE -> R.string.sk_color_palette_blue;
case BROWN -> R.string.sk_color_palette_brown;
case RED -> R.string.sk_color_palette_red;
case YELLOW -> R.string.sk_color_palette_yellow;
};
}
}
public enum ShowEmojiReactions{
HIDE_EMPTY,
ONLY_OPENED,

View File

@@ -40,7 +40,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -255,52 +254,62 @@ public class AccountSession{
filterStatusContainingObjects(objects, extractor, context, null);
}
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
Predicate<Status> statusIsOnOwnProfile = (s) -> self != null && profile != null && s.account != 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);
}
if(getLocalPreferences().serverSideFiltersSupported){
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
objects.removeIf(o->{
Status s=extractor.apply(o);
if(s==null)
return false;
if(s.filtered==null)
return false;
// don't hide own posts in own profile
if (statusIsOnOwnProfile.test(s))
return false;
for(FilterResult filter:s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;
}
return false;
});
return;
}
if(wordFilters==null)
return;
for(T obj:objects){
private boolean isFilteredType(Status s){
return (!localPreferences.showReplies && s.inReplyToId != null)
|| (!localPreferences.showBoosts && s.reblog != null);
}
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){
getLocalPreferences().serverSideFiltersSupported=true;
getLocalPreferences().save();
return;
localPreferences.serverSideFiltersSupported=true;
localPreferences.save();
}
}
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){
List<T> removeUs=new ArrayList<>();
for(int i=0; i<objects.size(); i++){
T o=objects.get(i);
if(filterStatusContainingObject(o, extractor, context, profile)){
Status s=extractor.apply(o);
removeUs.add(o);
if(s!=null && s.hasGapAfter && i > 0){
Status prev=extractor.apply(objects.get(i - 1));
if(prev!=null) prev.hasGapAfter=true;
}
}
}
objects.removeAll(removeUs);
}
public <T> boolean filterStatusContainingObject(T object, Function<T, Status> extractor, FilterContext context, Account profile){
Status s=extractor.apply(object);
if(s==null)
return false;
// don't hide own posts in own profile
if(statusIsOnOwnProfile(s, profile))
return false;
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
return true;
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
if(localPreferences.serverSideFiltersSupported){
for(FilterResult filter : s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;
}
}else if(wordFilters!=null){
for(LegacyFilter filter : wordFilters){
if(filter.context.contains(context) && filter.matches(s) && filter.isActive())
return true;
}
return false;
});
}
return false;
}
public void updateAccountInfo(){

View File

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

View File

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

View File

@@ -9,7 +9,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -100,6 +99,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected TypedObjectPool<MediaGridStatusDisplayItem.GridItemType, PreviewlessMediaAttachmentViewController> previewlessAttachmentViewsPool=new TypedObjectPool<>(this::makeNewPreviewlessMediaAttachmentView);
protected boolean currentlyScrolling;
protected String maxID;
public BaseStatusListFragment(){
super(20);
@@ -166,6 +166,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
protected String getMaxID(){
if(refreshing) return null;
if(maxID!=null) return maxID;
if(!preloadedData.isEmpty())
return preloadedData.get(preloadedData.size()-1).getID();
else if(!data.isEmpty())
@@ -174,6 +176,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null;
}
protected boolean applyMaxID(List<Status> result){
boolean empty=result.isEmpty();
if(!empty) maxID=result.get(result.size()-1).id;
return !empty;
}
protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
protected abstract void addAccountToKnown(T s);
@@ -642,12 +650,12 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
// to do this if the media grid is not bound, tho - so, doing it ourselves here
status.sensitiveRevealed = !status.sensitiveRevealed;
}
holder.rebind();
if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false);
}
public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header != null) header.rebind();
if(header != null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true);
}
protected void toggleSpoiler(Status status, String itemID){
@@ -695,15 +703,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
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 onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
@@ -937,7 +936,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(getContext()==null) return;
super.onDataLoaded(d, more);
// more available, but the page isn't even full yet? seems wrong, let's load some more
if(more && d.size() < itemsPerPage){
if(more && data.size() < itemsPerPage){
preloader.onScrolledToLastItem();
}
}

View File

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

View File

@@ -177,7 +177,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
private int charCount, charLimit, trimmedCharCount;
private Button publishButton, languageButton, scheduleTimeBtn;
private PopupMenu languagePopup, contentTypePopup, visibilityPopup, draftOptionsPopup;
private PopupMenu contentTypePopup, visibilityPopup, draftOptionsPopup;
private ImageButton publishButtonRelocated, mediaBtn, pollBtn, emojiBtn, spoilerBtn, draftsBtn, scheduleDraftDismiss, contentTypeBtn;
private View sensitiveBtn;
private TextView replyText;
@@ -313,7 +313,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override
public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
creatingView=true;
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), customEmojis, instanceDomain, getAccountID());
emojiKeyboard=new CustomEmojiPopupKeyboard(getActivity(), accountID, customEmojis, instanceDomain);
emojiKeyboard.setListener(new CustomEmojiPopupKeyboard.Listener(){
@Override
public void onEmojiSelected(Emoji emoji){

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,6 +29,7 @@ import org.joinmastodon.android.api.requests.tags.SetTagFollowed;
import org.joinmastodon.android.api.requests.timelines.GetHashtagTimeline;
import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterKeyword;
import org.joinmastodon.android.model.Hashtag;
@@ -153,11 +154,14 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetHashtagTimeline(hashtagName, offset==0 ? null : getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
currentRequest=new GetHashtagTimeline(hashtagName, getMaxID(), null, count, any, all, none, localOnly, getLocalPrefs().timelineReplyVisibility)
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<Status> result){
onDataLoaded(result, !result.isEmpty());
if(getActivity()==null) return;
boolean more=applyMaxID(result);
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
onDataLoaded(result, more);
}
})
.exec(accountID);
@@ -285,6 +289,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
followMenuItem=optionsMenu.findItem(R.id.follow_hashtag);
pinMenuItem=optionsMenu.findItem(R.id.pin);
followMenuItem.setVisible(toolbarContentVisible);
pinMenuItem.setShowAsAction(toolbarContentVisible ? MenuItem.SHOW_AS_ACTION_NEVER : MenuItem.SHOW_AS_ACTION_ALWAYS);
super.updatePinButton(pinMenuItem);
muteMenuItem = optionsMenu.findItem(R.id.mute_hashtag);
@@ -339,7 +344,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment{
}
private void updateHeader(){
if(hashtag==null)
if(hashtag==null || getActivity()==null)
return;
if(hashtag.history!=null && !hashtag.history.isEmpty()){

View File

@@ -291,7 +291,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
new GetAnnouncements(false).setCallback(new Callback<>() {
@Override
public void onSuccess(List<Announcement> result) {
if (getActivity() == null) return;
if(getActivity()==null) return;
if (result.stream().anyMatch(a -> !a.read)) {
announcementsBadged = true;
announcements.setVisible(false);
@@ -385,7 +385,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
}
private void updateOverflowMenu() {
if (getActivity() == null) return;
if(getActivity()==null) return;
Menu m = overflowPopup.getMenu();
m.clear();
overflowPopup.inflate(R.menu.home_overflow);

View File

@@ -11,7 +11,6 @@ import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.api.requests.markers.SaveMarkers;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.CacheablePaginatedResponse;
import org.joinmastodon.android.model.FilterContext;
@@ -20,6 +19,7 @@ import org.joinmastodon.android.model.TimelineMarkers;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -49,16 +49,6 @@ public class HomeTimelineFragment extends StatusListFragment {
loadData();
}
private boolean typeFilterPredicate(Status s) {
AccountLocalPreferences lp=getLocalPrefs();
return (lp.showReplies || s.inReplyToId == null) &&
(lp.showBoosts || s.reblog == null);
}
private List<Status> filterPosts(List<Status> items) {
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
}
@Override
protected void doLoadData(int offset, int count){
AccountSessionManager.getInstance()
@@ -66,10 +56,11 @@ public class HomeTimelineFragment extends StatusListFragment {
.getHomeTimeline(offset>0 ? maxID : null, count, refreshing, new SimpleCallback<>(this){
@Override
public void onSuccess(CacheablePaginatedResponse<List<Status>> result){
if (getActivity() == null) return;
List<Status> filteredItems = filterPosts(result.items);
if(getActivity()==null) return;
boolean empty=result.items.isEmpty();
maxID=result.maxID;
onDataLoaded(filteredItems, !result.items.isEmpty());
AccountSessionManager.get(accountID).filterStatuses(result.items, getFilterContext());
onDataLoaded(result.items, !empty);
if(result.isFromCache())
loadNewPosts();
}
@@ -142,23 +133,26 @@ public class HomeTimelineFragment extends StatusListFragment {
public void onSuccess(List<Status> result){
currentRequest=null;
dataLoading=false;
result = filterPosts(result);
if(result.isEmpty() || getActivity()==null)
return;
Status last=result.get(result.size()-1);
List<Status> toAdd;
if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one
toAdd=result.subList(0, result.size()-1); // Remove the already known last post
toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post
}else{
result.get(result.size()-1).hasGapAfter=true;
toAdd=result;
}
List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList());
toAdd.removeIf(s->existingIds.contains(s.getID()));
List<Status> toAddUnfiltered=new ArrayList<>(toAdd);
AccountSessionManager.get(accountID).filterStatuses(toAdd, getFilterContext());
if(!toAdd.isEmpty()){
prependItems(toAdd, true);
if (parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAdd, false);
if(parent != null && GlobalUserPreferences.showNewPostsButton) parent.showNewPostsButton();
}
if(toAddUnfiltered.isEmpty())
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(toAddUnfiltered, false);
}
@Override

View File

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

View File

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

View File

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

View File

@@ -178,6 +178,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
maxID=result.maxID;
onDataLoaded(result.items.stream().filter(n->n.type!=null).collect(Collectors.toList()), !result.items.isEmpty());
if(bannerHelper!=null) bannerHelper.onBannerBecameVisible();
reloadingFromCache=false;
if (getParentFragment() instanceof NotificationsFragment nf) {
nf.updateMarkAllReadButton();

View File

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

View File

@@ -138,6 +138,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ImageView avatar;
private CoverImageView cover;
private View avatarBorder;
private View usernameWrap;
private TextView name, username, bio, followersCount, followersLabel, followingCount, followingLabel;
private ImageView lockIcon, botIcon;
private ProgressBarButton actionButton, notifyButton;
@@ -241,6 +242,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
cover=content.findViewById(R.id.cover);
avatarBorder=content.findViewById(R.id.avatar_border);
name=content.findViewById(R.id.name);
usernameWrap=content.findViewById(R.id.username_wrap);
username=content.findViewById(R.id.username);
lockIcon=content.findViewById(R.id.lock_icon);
botIcon=content.findViewById(R.id.bot_icon);
@@ -563,7 +565,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(Account result){
if (getActivity() == null) return;
if(getActivity()==null) return;
onAccountLoaded(result);
}
})
@@ -984,7 +986,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
private void updateRelationship(){
if (getActivity() == null) return;
if(getActivity()==null) return;
invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
@@ -1272,6 +1274,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
imm.hideSoftInputFromWindow(content.getWindowToken(), 0);
V.setVisibilityAnimated(fab, View.VISIBLE);
bindHeaderView();
V.setVisibilityAnimated(fab, View.VISIBLE);
}
private void saveAndExitEditMode(){
@@ -1286,7 +1289,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
savingEdits=false;
account=result;
AccountSessionManager.getInstance().updateAccountInfo(accountID, account);
if (getActivity() == null) return;
if(getActivity()==null) return;
exitEditMode();
setActionProgressVisible(false);
}

View File

@@ -130,7 +130,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
else
nextMaxID=null;
if (getActivity() == null) return;
if(getActivity()==null) return;
onDataLoaded(result, nextMaxID!=null);
}
})

View File

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

View File

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

View File

@@ -45,10 +45,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
boolean isMainThreadStatus = this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id);
int flags = 0;
AccountLocalPreferences lp=getLocalPrefs();
if (GlobalUserPreferences.spectatorMode)
if(GlobalUserPreferences.spectatorMode)
flags |= StatusDisplayItem.FLAG_NO_FOOTER;
if (!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
if(!lp.emojiReactionsEnabled || lp.showEmojiReactions==ONLY_OPENED)
flags |= StatusDisplayItem.FLAG_NO_EMOJI_REACTIONS;
if(GlobalUserPreferences.translateButtonOpenedOnly)
flags |= StatusDisplayItem.FLAG_NO_TRANSLATE;
if(!GlobalUserPreferences.showMediaPreview)
flags |= StatusDisplayItem.FLAG_NO_MEDIA_PREVIEW;
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), isMainThreadStatus ? 0 : flags);

View File

@@ -35,7 +35,6 @@ import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import org.parceler.Parcels;
import java.util.ArrayDeque;
@@ -221,8 +220,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
// TODO: figure out how this code works
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
result.descendants=filterStatuses(result.descendants);
result.ancestors=filterStatuses(result.ancestors);
filterStatuses(result.descendants);
filterStatuses(result.ancestors);
restoreStatusStates(result.descendants, oldData);
restoreStatusStates(result.ancestors, oldData);
@@ -358,11 +357,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
.collect(Collectors.toList());
}
private List<Status> filterStatuses(List<Status> statuses){
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext());
return statuses.stream()
.filter(statusFilterPredicate)
.collect(Collectors.toList());
private void filterStatuses(List<Status> statuses){
AccountSessionManager.get(accountID).filterStatuses(statuses, getFilterContext());
}
@Override

View File

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

View File

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

View File

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

View File

@@ -77,7 +77,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
.setCallback(new SimpleCallback<>(this){
@Override
public void onSuccess(List<FollowSuggestion> result){
if (getActivity() == null) return;
if(getActivity()==null) return;
onDataLoaded(result.stream().map(fs->new AccountWrapper(fs.account)).collect(Collectors.toList()), false);
loadRelationships();
}
@@ -112,7 +112,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public void onSuccess(List<Relationship> result){
relationshipsRequest=null;
relationships=result.stream().collect(Collectors.toMap(rel->rel.id, Function.identity()));
if (getActivity() == null) return;
if(getActivity()==null) return;
if(list==null)
return;
for(int i=0;i<list.getChildCount();i++){

View File

@@ -97,8 +97,6 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop,
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){
@Override
public void onPageSelected(int position){
if(position==0)
return;
Fragment _page=getFragmentForPage(position);
if(_page instanceof BaseRecyclerFragment<?> page){
if(!page.loaded && !page.isDataLoading())

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,7 +97,7 @@ public class FilterWordsFragment extends BaseSettingsFragment<FilterKeyword> imp
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
fab=view.findViewById(R.id.fab);
fab.setImageResource(R.drawable.ic_add_24px);
fab.setImageResource(R.drawable.ic_fluent_add_24_regular);
fab.setContentDescription(getString(R.string.add_muted_word));
fab.setOnClickListener(v->onFabClick());
}

View File

@@ -18,6 +18,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusDisplaySettingsChangedEvent;
@@ -38,7 +39,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
private CheckableListItem<Void> revealCWsItem, hideSensitiveMediaItem, interactionCountsItem, emojiInNamesItem;
// MEGALODON
private CheckableListItem<Void> trueBlackModeItem, marqueeItem, disableSwipeItem, reduceMotionItem, altIndicatorItem, noAltIndicatorItem, collapsePostsItem, spectatorModeItem, hideFabItem, translateOpenedItem, disablePillItem, showNavigationLabelsItem;
private CheckableListItem<Void> trueBlackModeItem, marqueeItem, disableSwipeItem, reduceMotionItem, altIndicatorItem, noAltIndicatorItem, collapsePostsItem, spectatorModeItem, hideFabItem, translateOpenedItem, disablePillItem, showNavigationLabelsItem, likeIconItem, underlinedLinksItem;
private ListItem<Void> colorItem, publishTextItem, autoRevealCWsItem;
private CheckableListItem<Void> pronounsInUserListingsItem, pronounsInTimelinesItem, pronounsInThreadsItem;
@@ -76,6 +77,8 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
spectatorModeItem=new CheckableListItem<>(R.string.sk_settings_hide_interaction, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.spectatorMode, R.drawable.ic_fluent_star_off_24_regular, ()->toggleCheckableItem(spectatorModeItem)),
hideFabItem=new CheckableListItem<>(R.string.sk_settings_hide_fab, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.autoHideFab, R.drawable.ic_fluent_edit_24_regular, ()->toggleCheckableItem(hideFabItem)),
translateOpenedItem=new CheckableListItem<>(R.string.sk_settings_translate_only_opened, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.translateButtonOpenedOnly, R.drawable.ic_fluent_translate_24_regular, ()->toggleCheckableItem(translateOpenedItem)),
likeIconItem=new CheckableListItem<>(R.string.sk_settings_like_icon, 0, CheckableListItem.Style.SWITCH, lp.likeIcon, R.drawable.ic_fluent_heart_24_regular, ()->toggleCheckableItem(likeIconItem)),
underlinedLinksItem=new CheckableListItem<>(R.string.sk_settings_underlined_links, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.underlinedLinks, R.drawable.ic_fluent_text_underline_24_regular, ()->toggleCheckableItem(underlinedLinksItem)),
showPostDividersItem=new CheckableListItem<>(R.string.mo_enable_dividers, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showDividers, R.drawable.ic_fluent_timeline_24_regular, ()->toggleCheckableItem(showPostDividersItem)),
disablePillItem=new CheckableListItem<>(R.string.sk_disable_pill_shaped_active_indicator, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.disableM3PillActiveIndicator, R.drawable.ic_fluent_pill_24_regular, ()->toggleCheckableItem(disablePillItem)),
showNavigationLabelsItem=new CheckableListItem<>(R.string.sk_settings_show_labels_in_navigation_bar, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.showNavigationLabels, R.drawable.ic_fluent_tag_24_regular, ()->toggleCheckableItem(showNavigationLabelsItem), true),
@@ -106,6 +109,8 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
boolean restartPlease=
GlobalUserPreferences.disableM3PillActiveIndicator!=disablePillItem.checked ||
GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked ||
lp.likeIcon!=likeIconItem.checked;
GlobalUserPreferences.showNavigationLabels!=showNavigationLabelsItem.checked ||
GlobalUserPreferences.showMediaPreview !=showMediaPreviewItem.checked ||
GlobalUserPreferences.showDividers!=showPostDividersItem.checked;
@@ -113,6 +118,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
lp.hideSensitiveMedia=hideSensitiveMediaItem.checked;
lp.showInteractionCounts=interactionCountsItem.checked;
lp.customEmojiInNames=emojiInNamesItem.checked;
lp.likeIcon=likeIconItem.checked;
lp.save();
GlobalUserPreferences.toolbarMarquee=marqueeItem.checked;
GlobalUserPreferences.relocatePublishButton=relocatePublishButtonItem.checked;
@@ -126,6 +132,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
GlobalUserPreferences.spectatorMode=spectatorModeItem.checked;
GlobalUserPreferences.autoHideFab=hideFabItem.checked;
GlobalUserPreferences.translateButtonOpenedOnly=translateOpenedItem.checked;
GlobalUserPreferences.underlinedLinks=underlinedLinksItem.checked;
GlobalUserPreferences.showDividers=showPostDividersItem.checked;
GlobalUserPreferences.disableM3PillActiveIndicator=disablePillItem.checked;
GlobalUserPreferences.showNavigationLabels=showNavigationLabelsItem.checked;
@@ -147,7 +154,7 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
}
private @StringRes int getColorPaletteValue(){
return switch(GlobalUserPreferences.color){
return switch(AccountSessionManager.get(accountID).getLocalPreferences().color){
case MATERIAL3 -> R.string.sk_color_palette_material3;
case PINK -> R.string.sk_color_palette_pink;
case PURPLE -> R.string.sk_color_palette_purple;
@@ -214,18 +221,18 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
}
private void onColorClick(){
int selected=GlobalUserPreferences.color.ordinal();
int selected=lp.color.ordinal();
int[] newSelected={selected};
String[] names=Arrays.stream(GlobalUserPreferences.ColorPreference.values()).map(GlobalUserPreferences.ColorPreference::getName).map(this::getString).toArray(String[]::new);
String[] names=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).toArray(String[]::new);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.settings_theme)
.setTitle(R.string.sk_settings_color_palette)
.setSingleChoiceItems(names,
selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{
GlobalUserPreferences.ColorPreference pref=GlobalUserPreferences.ColorPreference.values()[newSelected[0]];
if(pref!=GlobalUserPreferences.color){
GlobalUserPreferences.ColorPreference prev=GlobalUserPreferences.color;
GlobalUserPreferences.color=pref;
ColorPreference pref=ColorPreference.values()[newSelected[0]];
if(pref!=lp.color){
ColorPreference prev=lp.color;
lp.color=pref;
GlobalUserPreferences.save();
colorItem.subtitleRes=getColorPaletteValue();
rebindItem(colorItem);
@@ -275,17 +282,17 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
.show();
}
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, GlobalUserPreferences.ColorPreference prevColor, Boolean prevTrueBlack){
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, ColorPreference prevColor, Boolean prevTrueBlack){
if(prevTheme==null) prevTheme=GlobalUserPreferences.theme;
if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme;
if(prevColor==null) prevColor=GlobalUserPreferences.color;
if(prevColor==null) prevColor=lp.color;
boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK ||
(prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK ||
(GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewBlack=GlobalUserPreferences.trueBlackTheme;
if(isCurrentDark!=isNewDark || prevColor!=GlobalUserPreferences.color || (isNewDark && prevTrueBlack!=isNewBlack)){
if(isCurrentDark!=isNewDark || prevColor!=lp.color || (isNewDark && prevTrueBlack!=isNewBlack)){
restartActivityToApplyNewTheme();
}
}

View File

@@ -55,7 +55,7 @@ public class SettingsFiltersFragment extends BaseSettingsFragment<Filter>{
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
adapter.addAdapter(super.getAdapter());
adapter.addAdapter(new GenericListItemsAdapter<>(Collections.singletonList(
new ListItem<Void>(R.string.settings_add_filter, 0, R.drawable.ic_add_24px, this::onAddFilterClick)
new ListItem<Void>(R.string.settings_add_filter, 0, R.drawable.ic_fluent_add_24_regular, this::onAddFilterClick)
)));
return adapter;
}

View File

@@ -64,7 +64,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(account.domain);
if (!instance.isAkkoma())
data.add(2, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
data.add(3, new ListItem<>(R.string.settings_filters, 0, R.drawable.ic_fluent_filter_24_regular, this::onFiltersClick));
if(BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.equals("appcenterPrivateBeta")){
data.add(0, new ListItem<>("Debug settings", null, R.drawable.ic_fluent_wrench_screwdriver_24_regular, ()->Nav.go(getActivity(), SettingsDebugFragment.class, makeFragmentArgs()), null, 0, true));
@@ -163,7 +163,7 @@ public class SettingsMainFragment extends BaseSettingsFragment<Void>{
.setMessage(getString(R.string.confirm_log_out, session.getFullUsername()))
.setPositiveButton(R.string.log_out, (dialog, which)->account.logOut(getActivity(), ()->{
loggedOut=true;
((MainActivity)getActivity()).restartHomeFragment();
((MainActivity)getActivity()).restartActivity();
}))
.setNegativeButton(R.string.cancel, null)
.show();

View File

@@ -18,8 +18,8 @@ public class SettingsPrivacyFragment extends BaseSettingsFragment<Void>{
setTitle(R.string.settings_privacy);
Account self=AccountSessionManager.get(accountID).self;
onDataLoaded(List.of(
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_thumbs_up_down_24px, ()->toggleCheckableItem(discoverableItem)),
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))
discoverableItem=new CheckableListItem<>(R.string.settings_discoverable, 0, CheckableListItem.Style.SWITCH, self.discoverable, R.drawable.ic_fluent_thumb_like_dislike_24_regular, ()->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, ()->toggleCheckableItem(indexableItem))
));
if(self.source.indexable==null)
indexableItem.isEnabled=false;

View File

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

View File

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

View File

@@ -148,8 +148,7 @@ public class AccountSwitcherSheet extends BottomSheet{
private void logOut(String accountID){
AccountSessionManager.get(accountID).logOut(activity, ()->{
dismiss();
((MainActivity)activity).restartHomeFragment();
((MainActivity)activity).restartActivity();
});
}
@@ -318,14 +317,14 @@ public class AccountSwitcherSheet extends BottomSheet{
@Override
public void onClick(){
setOnDismissListener(null);
dismiss();
if (onClick != null) {
dismiss();
onClick.accept(item.getID(), false);
return;
}
if(AccountSessionManager.getInstance().tryGetAccount(item.getID())!=null){
AccountSessionManager.getInstance().setLastActiveAccountID(item.getID());
((MainActivity)activity).restartHomeFragment();
((MainActivity)activity).restartActivity();
}
}

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -14,6 +14,8 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.StatusEditHistoryFragment;
@@ -75,6 +77,9 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(ExtendedFooterStatusDisplayItem item){
Status s=item.status;
AccountSession session=AccountSessionManager.get(item.accountID);
boolean like=session!=null && session.getLocalPreferences().likeIcon;
favorites.setCompoundDrawablesRelativeWithIntrinsicBounds(like ? 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));
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);

View File

@@ -1,12 +1,10 @@
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.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
@@ -25,6 +23,7 @@ import android.view.animation.AnimationSet;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
@@ -65,6 +64,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
private final TextView replies, boosts, favorites;
private final View reply, boost, favorite, share, bookmark;
private final ImageView favIcon;
private static final Animation opacityOut, opacityIn;
private static AnimationSet animSet;
@@ -74,7 +74,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private final Runnable longClickRunnable = () -> {
longClickPerformed = touchingView != null && touchingView.performLongClick();
if (longClickPerformed && touchingView != null) {
touchingView.startAnimation(opacityIn);
UiUtils.opacityIn(touchingView);
touchingView.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
}
};
@@ -88,27 +88,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
};
private static final float ALPHA_PRESSED=0.55f;
static {
opacityOut = new AlphaAnimation(1, ALPHA_PRESSED);
opacityOut.setDuration(300);
opacityOut.setInterpolator(CubicBezierInterpolator.DEFAULT);
opacityOut.setFillAfter(true);
opacityIn = new AlphaAnimation(ALPHA_PRESSED, 1);
opacityIn.setDuration(400);
opacityIn.setInterpolator(CubicBezierInterpolator.DEFAULT);
Animation spin = new RotateAnimation(0, 360,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
animSet = new AnimationSet(true);
animSet.setInterpolator(CubicBezierInterpolator.DEFAULT);
animSet.addAnimation(spin);
animSet.addAnimation(opacityIn);
animSet.setDuration(400);
}
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_footer, parent);
@@ -121,6 +100,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
favorite=findViewById(R.id.favorite_btn);
share=findViewById(R.id.share_btn);
bookmark=findViewById(R.id.bookmark_btn);
favIcon=findViewById(R.id.favorite_icon);
reply.setOnTouchListener(this::onButtonTouch);
reply.setOnClickListener(this::onReplyClick);
@@ -164,6 +144,16 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
boolean condenseBottom = !item.isMainStatus && item.hasDescendantNeighbor &&
!nextIsWarning;
AccountSession session=AccountSessionManager.get(item.accountID);
boolean like=session!=null && session.getLocalPreferences().likeIcon;
ColorStateList color=item.parentFragment.getResources().getColorStateList(
like ? R.color.like_icon : R.color.favorite_icon, item.parentFragment.getContext().getTheme()
);
favIcon.setImageResource(like ? 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();
params.setMargins(params.leftMargin, params.topMargin, params.rightMargin,
condenseBottom ? V.dp(-5) : 0);
@@ -192,7 +182,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
if (!longClickPerformed) v.animate().scaleX(1).scaleY(1).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(150).start();
if (disabled) return true;
if (action == MotionEvent.ACTION_UP && !longClickPerformed) v.performClick();
else if (!longClickPerformed) v.startAnimation(opacityIn);
else if (!longClickPerformed) UiUtils.opacityIn(v);
} else if (action == MotionEvent.ACTION_DOWN) {
longClickPerformed = false;
touchingView = v;
@@ -200,7 +190,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
if (disabled) return true;
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
v.startAnimation(opacityOut);
UiUtils.opacityOut(v);
}
return true;
}
@@ -219,7 +209,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
);
return;
}
v.startAnimation(opacityIn);
UiUtils.opacityIn(v);
Bundle args=new Bundle();
args.putString("account", item.accountID);
args.putParcelable("replyTo", Parcels.wrap(item.status));
@@ -243,7 +233,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
private void onBoostClick(View v){
if (GlobalUserPreferences.confirmBoost) {
v.startAnimation(opacityIn);
UiUtils.opacityIn(v);
onBoostLongClick(v);
return;
}
@@ -266,7 +256,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void boostConsumer(View v, Status r) {
v.startAnimation(opacityIn);
UiUtils.opacityIn(v);
bindText(boosts, r.reblogsCount);
}
@@ -277,7 +267,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
AccountSession session = AccountSessionManager.getInstance().getAccount(item.accountID);
Consumer<StatusPrivacy> doReblog = (visibility) -> {
v.startAnimation(opacityOut);
UiUtils.opacityOut(v);
if(item.status.isRemote){
UiUtils.lookupStatus(v.getContext(),
item.status, item.accountID, null,
@@ -343,7 +333,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
menu.findViewById(R.id.quote).setOnClickListener(c->{
dialog.dismiss();
v.startAnimation(opacityIn);
UiUtils.opacityIn(v);
Bundle args=new Bundle();
args.putString("account", item.accountID);
AccountSession accountSession=AccountSessionManager.getInstance().getAccount(item.accountID);
@@ -407,7 +397,8 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
R.string.sk_favorite_as,
R.string.sk_favorited_as,
R.string.sk_already_favorited,
R.drawable.ic_fluent_star_28_regular
AccountSessionManager.get(item.accountID).getLocalPreferences().likeIcon ?
R.drawable.ic_fluent_heart_28_regular : R.drawable.ic_fluent_star_28_regular
);
return true;
}
@@ -431,7 +422,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
bookmark.setSelected(!item.status.bookmarked);
vibrateForAction(bookmark, !item.status.bookmarked);
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked, r->{
v.startAnimation(opacityIn);
UiUtils.opacityIn(v);
});
}
@@ -439,7 +430,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickInteractAs(v.getContext(),
item.accountID, item.status,
s -> s.bookmarked,
s -> s.bookmarked,w
(ic, status, consumer) -> ic.setBookmarked(status, true, consumer),
R.string.sk_bookmark_as,
R.string.sk_bookmarked_as,
@@ -450,7 +441,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
}
private void onShareClick(View v){
v.startAnimation(opacityIn);
UiUtils.opacityIn(v);
Intent intent=new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, item.status.url);

View File

@@ -72,7 +72,7 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
private void onViewClick(View v){
if(item.loading) return;
boolean isTop=v==top;
(isTop ? textTop : textBottom).startAnimation(UiUtils.opacityOut);
UiUtils.opacityOut(isTop ? textTop : textBottom);
V.setVisibilityAnimated((isTop ? progressTop : progressBottom), View.VISIBLE);
item.parentFragment.onGapClick(this, isTop);
}

View File

@@ -68,6 +68,7 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class HeaderStatusDisplayItem extends StatusDisplayItem{
@@ -78,7 +79,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private SpannableStringBuilder parsedName;
public final Status status;
private boolean hasVisibilityToggle;
public boolean hasVisibilityToggle;
boolean needBottomPadding;
private CharSequence extraText;
private Notification notification;
@@ -298,17 +299,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
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")
@Override
public void onBind(HeaderStatusDisplayItem item){
@@ -335,23 +325,13 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
botIcon.setColorFilter(username.getCurrentTextColor());
deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification!=null && !item.inset ? View.VISIBLE : View.GONE);
visibility.setVisibility(item.hasVisibilityToggle ? View.VISIBLE : View.GONE);
if (item.hasVisibilityToggle){
boolean hidden = !item.status.sensitiveRevealed || (item.status.hasSpoiler() && !item.status.spoilerRevealed);
// doing this because V.setVisibilityAnimated ignores changes between INVISIBLE and GONE
int newVis=hidden ? View.INVISIBLE : View.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);
boolean visible = item.status.sensitiveRevealed && (!item.status.hasSpoiler() || item.status.spoilerRevealed);
visibility.setAlpha(visible ? 1 : 0f);
visibility.setScaleY(visible ? 1 : 0.8f);
visibility.setScaleX(visible ? 1 : 0.8f);
visibility.setEnabled(visible);
}
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
if(TextUtils.isEmpty(item.extraText)){
@@ -416,6 +396,16 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
item.inset ? V.dp(10) : V.dp(4), itemView.getPaddingBottom());
}
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
public void setImage(int index, Drawable drawable){
if(index>0){

View File

@@ -1,7 +1,6 @@
package org.joinmastodon.android.ui.displayitems;
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 android.annotation.SuppressLint;
@@ -9,7 +8,6 @@ import android.app.Activity;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.TypedValue;
@@ -20,9 +18,11 @@ import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
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.NotificationsListFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Notification;
@@ -46,11 +46,15 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
private final String accountID;
private final CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private final CharSequence text;
private final CharSequence timestamp;
private final AccountLocalPreferences lp;
public NotificationHeaderStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Notification notification, String accountID){
super(parentID, parentFragment);
this.notification=notification;
this.accountID=accountID;
this.timestamp=notification.createdAt==null ? null : UiUtils.formatRelativeTimestamp(context, notification.createdAt);
this.lp=AccountSessionManager.get(accountID).getLocalPreferences();
if(notification.type==Notification.Type.POLL){
text=parentFragment.getString(R.string.poll_ended);
@@ -111,17 +115,25 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<NotificationHeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final ImageView icon, avatar;
private final TextView text;
private final ImageView icon, avatar, deleteNotification;
private final TextView text, timestamp;
private final int selectableItemBackground;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_notification_header, parent);
icon=findViewById(R.id.icon);
avatar=findViewById(R.id.avatar);
text=findViewById(R.id.text);
timestamp=findViewById(R.id.timestamp);
deleteNotification=findViewById(R.id.delete_notification);
avatar.setOutlineProvider(OutlineProviders.roundedRect(8));
avatar.setClipToOutline(true);
deleteNotification.setOnClickListener(v->UiUtils.confirmDeleteNotification(activity, item.parentFragment.getAccountID(), item.notification, ()->{
if (item.parentFragment instanceof NotificationsListFragment fragment) {
fragment.removeNotification(item.notification);
}
}));
icon.setOnClickListener(this::onItemClick);
avatar.setOnClickListener(this::onItemClick);
@@ -149,9 +161,10 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(NotificationHeaderStatusDisplayItem item){
text.setText(item.text);
timestamp.setText(item.timestamp);
avatar.setVisibility(item.notification.type==Notification.Type.POLL ? View.GONE : View.VISIBLE);
icon.setImageResource(switch(item.notification.type){
case FAVORITE -> R.drawable.ic_fluent_star_24_filled;
case FAVORITE -> item.lp.likeIcon ? R.drawable.ic_fluent_heart_24_filled : R.drawable.ic_fluent_star_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 POLL -> R.drawable.ic_fluent_poll_24_filled;
@@ -162,11 +175,12 @@ public class NotificationHeaderStatusDisplayItem extends StatusDisplayItem{
default -> throw new IllegalStateException("Unexpected value: "+item.notification.type);
});
icon.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(item.parentFragment.getActivity(), switch(item.notification.type){
case FAVORITE -> R.attr.colorFavorite;
case FAVORITE -> item.lp.likeIcon ? R.attr.colorLike : R.attr.colorFavorite;
case REBLOG -> R.attr.colorBoost;
case POLL -> R.attr.colorPoll;
default -> android.R.attr.colorAccent;
})));
deleteNotification.setVisibility(GlobalUserPreferences.enableDeleteNotifications && item.notification != null ? View.VISIBLE : View.GONE);
itemView.setBackgroundResource(0);
}

View File

@@ -69,7 +69,7 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private final TextView text, percent;
private final View button;
private final ImageView icon;
private final Drawable progressBg, progressBgInset;
private final Drawable progressBg;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_poll_option, parent);
@@ -78,7 +78,6 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
icon=findViewById(R.id.icon);
button=findViewById(R.id.button);
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);
button.setOutlineProvider(OutlineProviders.roundedRect(20));
button.setClipToOutline(true);
@@ -94,22 +93,17 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
item.showResults ? R.drawable.ic_poll_option_button : R.drawable.ic_fluent_radio_button_24_selector
));
if(item.showResults){
Drawable bg=item.inset ? progressBgInset : progressBg;
Drawable bg=progressBg;
bg.setLevel(Math.round(10000f*item.votesFraction));
button.setBackground(bg);
itemView.setSelected(item.poll.ownVotes!=null && item.poll.ownVotes.contains(item.optionIndex));
percent.setText(String.format(Locale.getDefault(), "%d%%", Math.round(item.votesFraction*100f)));
}else{
itemView.setSelected(item.poll.selectedOptions!=null && item.poll.selectedOptions.contains(item.option));
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));
percent.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSecondaryContainer));
button.setBackgroundResource(R.drawable.bg_poll_option_clickable);
}
text.setTextColor(UiUtils.getThemeColor(itemView.getContext(), android.R.attr.textColorPrimary));
percent.setTextColor(UiUtils.getThemeColor(itemView.getContext(), R.attr.colorM3OnSecondaryContainer));
}
@Override

View File

@@ -30,6 +30,7 @@ import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.FilterAction;
import org.joinmastodon.android.model.FilterContext;
import org.joinmastodon.android.model.FilterResult;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status;
@@ -147,7 +148,7 @@ public abstract class StatusDisplayItem{
Status statusForContent=status.getContentStatus();
Bundle args=new Bundle();
args.putString("account", accountID);
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null;
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus s ? s : null;
HeaderStatusDisplayItem header=null;
boolean hideCounts=!AccountSessionManager.get(accountID).getLocalPreferences().showInteractionCounts;
@@ -206,14 +207,15 @@ public abstract class StatusDisplayItem{
if((flags & FLAG_CHECKABLE)!=0)
items.add(header=new CheckableHeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
else
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, null, scheduledStatus));
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, parentObject instanceof Notification n ? n : null, scheduledStatus));
}
boolean filtered=false;
LegacyFilter applyingFilter=null;
if(status.filtered!=null){
for(FilterResult filter:status.filtered){
if(filter.filter.isActive()){
filtered=true;
LegacyFilter f=filter.filter;
if(f.isActive() && filterContext != null && f.context.contains(filterContext)){
applyingFilter=f;
break;
}
}
@@ -276,14 +278,14 @@ public abstract class StatusDisplayItem{
contentItems.add(new AudioStatusDisplayItem(parentID, fragment, statusForContent, att));
}
if(att.type==Attachment.Type.UNKNOWN){
contentItems.add(new FileStatusDisplayItem(parentID, fragment, att, statusForContent));
contentItems.add(new FileStatusDisplayItem(parentID, fragment, att));
}
}
if(statusForContent.poll!=null){
buildPollItems(parentID, fragment, statusForContent.poll, contentItems, statusForContent);
buildPollItems(parentID, fragment, statusForContent.poll, contentItems);
}
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty()){
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent, (flags & FLAG_NO_MEDIA_PREVIEW)==0));
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
}
if(contentItems!=items && statusForContent.spoilerRevealed){
items.addAll(contentItems);
@@ -324,18 +326,11 @@ public abstract class StatusDisplayItem{
}
}
LegacyFilter applyingFilter = null;
if (!statusForContent.filterRevealed) {
StatusFilterPredicate predicate = new StatusFilterPredicate(accountID, filterContext, FilterAction.WARN);
statusForContent.filterRevealed = predicate.test(status);
applyingFilter = predicate.getApplyingFilter();
}
// Hide statuses that have a filter action of hide
if(!new StatusFilterPredicate(accountID, filterContext, FilterAction.HIDE).test(status))
return new ArrayList<StatusDisplayItem>() ;
return statusForContent.filterRevealed ? items :
return applyingFilter==null ? items :
new ArrayList<>(List.of(new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items, applyingFilter)));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
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.trueBlackTheme;
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.*;
import android.content.Context;
import android.content.res.Resources;
@@ -11,23 +11,25 @@ import androidx.annotation.StyleRes;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import java.util.Map;
public class ColorPalette {
public static final Map<GlobalUserPreferences.ColorPreference, ColorPalette> palettes = Map.of(
ColorPreference.MATERIAL3, new ColorPalette(R.style.ColorPalette_Material3)
public static final Map<AccountLocalPreferences.ColorPreference, ColorPalette> palettes = Map.of(
MATERIAL3, new ColorPalette(R.style.ColorPalette_Material3)
.dark(R.style.ColorPalette_Material3_Dark, R.style.ColorPalette_Material3_AutoLightDark),
ColorPreference.PINK, new ColorPalette(R.style.ColorPalette_Pink),
ColorPreference.PURPLE, new ColorPalette(R.style.ColorPalette_Purple),
ColorPreference.GREEN, new ColorPalette(R.style.ColorPalette_Green),
ColorPreference.BLUE, new ColorPalette(R.style.ColorPalette_Blue),
ColorPreference.BROWN, new ColorPalette(R.style.ColorPalette_Brown),
ColorPreference.RED, new ColorPalette(R.style.ColorPalette_Red),
ColorPreference.YELLOW, new ColorPalette(R.style.ColorPalette_Yellow),
ColorPreference.NORD, new ColorPalette(R.style.ColorPalette_Nord),
ColorPreference.WHITE, new ColorPalette(R.style.ColorPalette_White)
);
PINK, new ColorPalette(R.style.ColorPalette_Pink),
PURPLE, new ColorPalette(R.style.ColorPalette_Purple),
GREEN, new ColorPalette(R.style.ColorPalette_Green),
BLUE, new ColorPalette(R.style.ColorPalette_Blue),
BROWN, new ColorPalette(R.style.ColorPalette_Brown),
RED, new ColorPalette(R.style.ColorPalette_Red),
YELLOW, new ColorPalette(R.style.ColorPalette_Yellow)
NORD, new ColorPalette(R.style.ColorPalette_Nord),
WHITE, new ColorPalette(R.style.ColorPalette_White)
);
private @StyleRes int base;
private @StyleRes int autoDark;

View File

@@ -28,6 +28,7 @@ public class MediaAttachmentViewController{
private final Context context;
private boolean didClear;
private Status status;
private Attachment attachment;
public MediaAttachmentViewController(Context context, MediaGridStatusDisplayItem.GridItemType type){
view=context.getSystemService(LayoutInflater.class).inflate(switch(type){
@@ -54,6 +55,7 @@ public class MediaAttachmentViewController{
public void bind(Attachment attachment, Status status){
this.status=status;
this.attachment=attachment;
crossfadeDrawable.setSize(attachment.getWidth(), attachment.getHeight());
crossfadeDrawable.setBlurhashDrawable(attachment.blurhashPlaceholder);
crossfadeDrawable.setCrossfadeAlpha(0f);
@@ -76,6 +78,11 @@ public class MediaAttachmentViewController{
crossfadeDrawable.setImageDrawable(drawable);
if(didClear)
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(){

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.ui.utils;
import static android.view.Menu.NONE;
import static org.joinmastodon.android.GlobalUserPreferences.ThemePreference.*;
import static org.joinmastodon.android.GlobalUserPreferences.theme;
import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme;
@@ -53,9 +54,8 @@ import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.webkit.MimeTypeMap;
import android.widget.Button;
import android.widget.ImageView;
@@ -86,6 +86,7 @@ import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
import org.joinmastodon.android.api.requests.statuses.SetStatusPinned;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusMuteChangedEvent;
@@ -178,17 +179,6 @@ public class UiUtils {
public static int MAX_WIDTH, SCROLL_TO_TOP_DELTA;
public static final float ALPHA_PRESSED=0.55f;
public static final Animation opacityOut, opacityIn;
static {
opacityOut = new AlphaAnimation(1, ALPHA_PRESSED);
opacityOut.setDuration(300);
opacityOut.setInterpolator(CubicBezierInterpolator.DEFAULT);
opacityOut.setFillAfter(true);
opacityIn = new AlphaAnimation(ALPHA_PRESSED, 1);
opacityIn.setDuration(400);
opacityIn.setInterpolator(CubicBezierInterpolator.DEFAULT);
}
private UiUtils() {
}
@@ -1032,14 +1022,20 @@ public class UiUtils {
}
public static void setUserPreferredTheme(Context context) {
context.setTheme(switch (theme) {
setUserPreferredTheme(context, null);
}
public static void setUserPreferredTheme(Context context, @Nullable AccountSession session) {
context.setTheme(switch(theme) {
case LIGHT -> R.style.Theme_Mastodon_Light;
case DARK -> R.style.Theme_Mastodon_Dark;
default -> R.style.Theme_Mastodon_AutoLightDark;
});
ColorPalette palette = ColorPalette.palettes.get(GlobalUserPreferences.color);
if (palette != null) palette.apply(context);
AccountLocalPreferences prefs=session != null ? session.getLocalPreferences() : null;
AccountLocalPreferences.ColorPreference color=prefs != null ? prefs.color : AccountLocalPreferences.ColorPreference.MATERIAL3;
ColorPalette palette = ColorPalette.palettes.get(color);
if (palette != null) palette.apply(context, theme);
Resources res = context.getResources();
MAX_WIDTH = (int) res.getDimension(R.dimen.layout_max_width);
@@ -1056,9 +1052,9 @@ public class UiUtils {
}
public static boolean isDarkTheme() {
if (theme == GlobalUserPreferences.ThemePreference.AUTO)
if (theme == AUTO)
return (MastodonApp.context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
return theme == GlobalUserPreferences.ThemePreference.DARK;
return theme == DARK;
}
public static Optional<Pair<String, Optional<String>>> parseFediverseHandle(String maybeFediHandle) {
@@ -1801,4 +1797,16 @@ public class UiUtils {
.filter(Objects::nonNull)
.findFirst();
}
public static void opacityIn(View v){
v.animate().alpha(1).setDuration(400).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
}
public static void opacityOut(View v){
opacityOut(v, ALPHA_PRESSED).start();
}
public static ViewPropertyAnimator opacityOut(View v, float alpha){
return v.animate().alpha(alpha).setDuration(300).setInterpolator(CubicBezierInterpolator.DEFAULT);
}
}

View File

@@ -18,6 +18,7 @@ import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.ui.DividerItemDecoration;
@@ -66,7 +67,7 @@ public class ComposeLanguageAlertViewController{
.collect(Collectors.toList());
if(!TextUtils.isEmpty(preferred)){
MastodonLanguage lang = resolver.fromOrFallback(preferred);
MastodonLanguage lang=resolver.fromOrFallback(preferred);
specialLocales.add(new SpecialLocaleInfo(
lang,
lang.getDisplayName(context),
@@ -75,7 +76,7 @@ public class ComposeLanguageAlertViewController{
}
if(!Locale.getDefault().getLanguage().equals(preferred)){
MastodonLanguage lang = resolver.getDefault();
MastodonLanguage lang=resolver.getDefault();
specialLocales.add(new SpecialLocaleInfo(
lang,
lang.getDisplayName(context),
@@ -91,8 +92,18 @@ public class ComposeLanguageAlertViewController{
detectLanguage(detected, postText);
}
if (session!=null && session.getLocalPreferences().bottomEncoding) {
specialLocales.add(new SpecialLocaleInfo(null, "\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48", "bottom"));
AccountLocalPreferences lp=session==null ? null : session.getLocalPreferences();
if(lp!=null){
for(String tag : lp.recentLanguages){
if(specialLocales.stream().anyMatch(l->l.language!=null && l.language.languageTag!=null
&& l.language.languageTag.equals(tag))) continue;
resolver.from(tag).ifPresent(lang->specialLocales.add(new SpecialLocaleInfo(
lang, lang.getDisplayName(context), null
)));
}
if(lp.bottomEncoding) {
specialLocales.add(new SpecialLocaleInfo(null, "\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48", "bottom"));
}
}
if(previouslySelected!=null){
@@ -323,7 +334,7 @@ public class ComposeLanguageAlertViewController{
public void onClick(){
selectItem(getAbsoluteAdapterPosition());
selectedLocale=item.language;
selectedEncoding = item.title.equals("bottom") ? "bottom" : null;
selectedEncoding=item.title != null && item.title.equals("bottom") ? "bottom" : null;
}
@Override

View File

@@ -1,9 +1,11 @@
package org.joinmastodon.android.ui.views;
import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
@@ -45,9 +47,7 @@ public class TabBar extends LinearLayout{
listener.accept(v.getId());
if(v.getId()==selectedTabID)
return;
findViewById(selectedTabID).setSelected(false);
v.setSelected(true);
selectedTabID=v.getId();
selectTab(v.getId());
}
private boolean onChildLongClick(View v){
@@ -60,8 +60,17 @@ public class TabBar extends LinearLayout{
}
public void selectTab(int id){
findViewById(selectedTabID).setSelected(false);
toggleSelected(selectedTabID, false);
selectedTabID=id;
findViewById(selectedTabID).setSelected(true);
toggleSelected(id, true);
}
private void toggleSelected(int selectedTabID, boolean selected){
LinearLayout tab=findViewById(selectedTabID);
tab.setSelected(selected);
View v=tab.findViewWithTag("label");
if(v instanceof TextView text){
text.setTypeface(Typeface.create(text.getTypeface(), selected ? Typeface.BOLD : Typeface.NORMAL));
}
}
}

File diff suppressed because one or more lines are too long