diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index 8a855fd03..2f62ba21c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -1,6 +1,7 @@ package org.joinmastodon.android; import static org.joinmastodon.android.api.MastodonAPIController.gson; +import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.MATERIAL3; import android.content.Context; import android.content.SharedPreferences; @@ -13,6 +14,7 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import org.joinmastodon.android.api.session.AccountLocalPreferences; +import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference; import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.model.ContentType; @@ -62,6 +64,7 @@ public class GlobalUserPreferences{ public static boolean overlayMedia; public static boolean showSuicideHelp; public static boolean underlinedLinks; + public static ColorPreference color; // MOSHIDON public static boolean showDividers; @@ -138,6 +141,7 @@ public class GlobalUserPreferences{ overlayMedia=prefs.getBoolean("overlayMedia", false); showSuicideHelp=prefs.getBoolean("showSuicideHelp", true); underlinedLinks=prefs.getBoolean("underlinedLinks", true); + color=ColorPreference.valueOf(prefs.getString("color", MATERIAL3.name())); // MOSHIDON uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false); @@ -171,8 +175,6 @@ public class GlobalUserPreferences{ 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(); } @@ -216,6 +218,7 @@ public class GlobalUserPreferences{ .putBoolean("overlayMedia", overlayMedia) .putBoolean("showSuicideHelp", showSuicideHelp) .putBoolean("underlinedLinks", underlinedLinks) + .putString("color", color.name()) // MOSHIDON .putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies) @@ -258,25 +261,6 @@ public class GlobalUserPreferences{ //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!!"); diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java b/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java index cc65eb682..0a743a13a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import me.grishka.appkit.api.Callback; @@ -69,7 +70,7 @@ public class CacheController{ Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class); status.postprocess(); int flags=cursor.getInt(1); - status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0); + status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0) ? status.id : null; newMaxID=status.id; result.add(status); }while(cursor.moveToNext()); @@ -113,7 +114,7 @@ public class CacheController{ values.put("id", s.id); values.put("json", MastodonAPIController.gson.toJson(s)); int flags=0; - if(s.hasGapAfter) + if(Objects.equals(s.hasGapAfter, s.id)) flags|=POST_FLAG_GAP_AFTER; values.put("flags", flags); values.put("time", s.createdAt.getEpochSecond()); diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java index 9f419f228..20a12e69e 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountLocalPreferences.java @@ -10,6 +10,7 @@ import androidx.annotation.StringRes; import com.google.gson.reflect.TypeToken; +import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.R; import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.Emoji; @@ -84,7 +85,7 @@ 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())); + color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null; likeIcon=prefs.getBoolean("likeIcon", false); recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>()); @@ -101,6 +102,10 @@ public class AccountLocalPreferences{ prefs.edit().putLong("notificationsPauseTime", time).apply(); } + public ColorPreference getCurrentColor(){ + return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3; + } + public void save(){ prefs.edit() .putBoolean("interactionCounts", showInteractionCounts) @@ -124,7 +129,7 @@ public class AccountLocalPreferences{ .putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification) .putBoolean("emojiReactionsEnabled", emojiReactionsEnabled) .putString("showEmojiReactions", showEmojiReactions.name()) - .putString("color", color.name()) + .putString("color", color!=null ? color.name() : null) .putBoolean("likeIcon", likeIcon) .putString("recentCustomEmoji", gson.toJson(recentCustomEmoji)) diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java index 225e6bc4c..e75b5c6d4 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java @@ -270,6 +270,7 @@ public class AccountSession{ if(s!=null && s.filtered!=null){ localPreferences.serverSideFiltersSupported=true; localPreferences.save(); + break; } } @@ -279,9 +280,17 @@ public class AccountSession{ 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; + if(s!=null && s.hasGapAfter!=null && i>0){ + // oops, we're about to remove an item that has a gap after... + // gotta find the previous status that's not also about to be removed + for(int j=i-1; j>=0; j--){ + T p=objects.get(j); + Status prev=extractor.apply(objects.get(j)); + if(prev!=null && !removeUs.contains(p)){ + prev.hasGapAfter=s.hasGapAfter; + break; + } + } } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java index 317c5d6a7..c578452eb 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -38,7 +38,6 @@ import org.joinmastodon.android.model.Translation; import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; -import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; @@ -641,21 +640,21 @@ public abstract class BaseStatusListFragment exten public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) { Status status = holder.getItem().status; - MediaGridStatusDisplayItem.Holder mediaGrid = findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class); - if (mediaGrid != null) { - if (!status.sensitiveRevealed) mediaGrid.revealSensitive(); - else mediaGrid.hideSensitive(); - } else { - // media grid's methods normally change the status' state - we still want to be able - // to do this if the media grid is not bound, tho - so, doing it ourselves here - status.sensitiveRevealed = !status.sensitiveRevealed; - } if(holder.getItem().hasVisibilityToggle) holder.animateVisibilityToggle(false); + MediaGridStatusDisplayItem.Holder mediaGrid=findHolderOfType(holder.getItemID(), MediaGridStatusDisplayItem.Holder.class); + if(mediaGrid!=null){ + if(!status.sensitiveRevealed) mediaGrid.revealSensitive(); + else mediaGrid.hideSensitive(); + }else{ + status.sensitiveRevealed=false; + notifyItemChangedAfter(holder.getItem(), MediaGridStatusDisplayItem.class); + } } public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) { - HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); - if(header != null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true); + HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); + if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true); + else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class); } protected void toggleSpoiler(Status status, String itemID){ @@ -664,8 +663,8 @@ public abstract class BaseStatusListFragment exten status.sensitiveRevealed = false; SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class); - if(spoiler!=null) - spoiler.rebind(); + if(spoiler!=null) spoiler.rebind(); + else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class); SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class)); int index=displayItems.indexOf(spoilerItem); @@ -677,30 +676,29 @@ public abstract class BaseStatusListFragment exten adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size()); } - TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); - if(text!=null) - adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset()); + notifyItemChanged(itemID, TextStatusDisplayItem.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); - if(header!=null) - header.rebind(); + if(header!=null) header.rebind(); + else notifyItemChanged(itemID, HeaderStatusDisplayItem.class); list.invalidateItemDecorations(); } public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) { - if (holder.getItem().status.textExpandable != expandable && list != null) { - holder.getItem().status.textExpandable = expandable; - HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); - if (header != null) header.rebind(); + Status s=holder.getItem().status; + if(s.textExpandable!=expandable && list!=null) { + s.textExpandable=expandable; + HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); + if(header!=null) header.bindCollapseButton(); } } public void onToggleExpanded(Status status, String itemID) { status.textExpanded = !status.textExpanded; - TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); + notifyItemChanged(itemID, TextStatusDisplayItem.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); - if (text != null) text.rebind(); - if (header != null) header.rebind(); + if(header!=null) header.animateExpandToggle(); + else notifyItemChanged(itemID, HeaderStatusDisplayItem.class); } public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){} @@ -759,9 +757,61 @@ public abstract class BaseStatusListFragment exten return null; } + /** + * Use this as a fallback if findHolderOfType fails to find the ViewHolder. + * It might still be bound but off-screen and therefore not a child of the RecyclerView - + * resulting in the ViewHolder displaying an outdated state once scrolled back into view. + */ + protected int notifyItemChanged(String id, Class type){ + boolean encounteredParent=false; + for(int i=0; i int notifyItemChangedAfter(StatusDisplayItem afterThis, Class type){ + int startIndex=displayItems.indexOf(afterThis); + if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem"); + String parentID=afterThis.parentID; + for(int i=startIndex; i int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class type){ + int startIndex=displayItems.indexOf(beforeThis); + if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem"); + String parentID=beforeThis.parentID; + for(int i=startIndex; i>=0; i--){ + StatusDisplayItem item=displayItems.get(i); + if(!parentID.equals(item.parentID)) break; // didn't find anything + if(type.isInstance(item)){ + // found it + adapter.notifyItemChanged(i); + return i; + } + } + return -1; + } + @Nullable protected > H findHolderOfType(String id, Class type){ - for(int i=0;i itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder)) return type.cast(holder); @@ -891,6 +941,8 @@ public abstract class BaseStatusListFragment exten if(text!=null){ text.updateTranslation(true); imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition()); + }else{ + notifyItemChanged(itemID, TextStatusDisplayItem.class); } } @@ -902,6 +954,8 @@ public abstract class BaseStatusListFragment exten TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); if(text!=null){ text.updateTranslation(true); + }else{ + notifyItemChanged(itemID, TextStatusDisplayItem.class); } new M3AlertDialogBuilder(getActivity()) .setTitle(R.string.error) @@ -918,6 +972,8 @@ public abstract class BaseStatusListFragment exten if(text!=null){ text.updateTranslation(true); imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition()); + }else{ + notifyItemChanged(itemID, TextStatusDisplayItem.class); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeImageDescriptionFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeImageDescriptionFragment.java index ef03a45ac..733bc0815 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeImageDescriptionFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeImageDescriptionFragment.java @@ -63,7 +63,8 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp accountID=getArguments().getString("account"); attachmentID=getArguments().getString("attachment"); themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark); - ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().color).apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK); + ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().getCurrentColor()) + .apply(themeWrapper, GlobalUserPreferences.ThemePreference.DARK); setTitle(R.string.add_alt_text); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java index 51f79de7f..4af622235 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java @@ -140,7 +140,7 @@ public class HomeTimelineFragment extends StatusListFragment { if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post }else{ - result.get(result.size()-1).hasGapAfter=true; + last.hasGapAfter=last.id; toAdd=result; } List existingIds=data.stream().map(Status::getID).collect(Collectors.toList()); @@ -176,10 +176,10 @@ public class HomeTimelineFragment extends StatusListFragment { gap.loading=true; dataLoading=true; - String maxID = null; - String minID = null; + String maxID=null; + String minID=null; if (downwards) { - maxID = item.getItemID(); + maxID=item.getItem().getMaxID(); } else { int gapPos=displayItems.indexOf(gap); StatusDisplayItem nextItem=displayItems.get(gapPos + 1); @@ -196,12 +196,13 @@ public class HomeTimelineFragment extends StatusListFragment { int gapPos=displayItems.indexOf(gap); if(gapPos==-1) return; + AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext()); if(result.isEmpty()){ displayItems.remove(gapPos); adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos); Status gapStatus=getStatusByID(gap.parentID); if(gapStatus!=null){ - gapStatus.hasGapAfter=false; + gapStatus.hasGapAfter=null; AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false); } }else{ @@ -214,7 +215,7 @@ public class HomeTimelineFragment extends StatusListFragment { idsBelowGap.add(s.id); }else if(s.id.equals(gap.parentID)){ belowGap=true; - s.hasGapAfter=false; + s.hasGapAfter=null; AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false); }else{ gapPostIndex++; @@ -227,7 +228,8 @@ public class HomeTimelineFragment extends StatusListFragment { break; } if(endIndex==result.size()){ - result.get(result.size()-1).hasGapAfter=true; + Status last=result.get(result.size()-1); + last.hasGapAfter=last.id; }else{ result=result.subList(0, endIndex); } @@ -272,7 +274,7 @@ public class HomeTimelineFragment extends StatusListFragment { .filter(s->Objects.equals(s.id, gap.parentID)) .findFirst(); if (gapStatus.isPresent()) { - gapStatus.get().hasGapAfter=false; + gapStatus.get().hasGapAfter=null; AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false); } targetList.clear(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java index 34e65cdfa..dabbf898d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java @@ -104,8 +104,8 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{ else selectedIDs.add(id); CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class); - if(holder!=null) - holder.rebind(); + if(holder!=null) holder.rebind(); + else notifyItemChanged(id, CheckableHeaderStatusDisplayItem.class); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java index 9ab227b73..447ca82aa 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java @@ -1,6 +1,7 @@ package org.joinmastodon.android.fragments.settings; import android.app.Activity; +import android.app.AlertDialog; import android.graphics.Bitmap; import android.graphics.Canvas; import android.os.Build; @@ -29,6 +30,8 @@ import org.joinmastodon.android.ui.views.TextInputFrameLayout; import java.util.Arrays; import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.IntStream; import me.grishka.appkit.FragmentStackActivity; @@ -152,19 +155,11 @@ public class SettingsDisplayFragment extends BaseSettingsFragment{ }; } - private @StringRes int getColorPaletteValue(){ - 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; - 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; - }; + private String getColorPaletteValue(){ + ColorPreference color=AccountSessionManager.get(accountID).getLocalPreferences().color; + return color==null + ? getString(R.string.sk_settings_color_palette_default, getString(GlobalUserPreferences.color.getName())) + : getString(color.getName()); } private String getPublishButtonText() { @@ -220,26 +215,40 @@ public class SettingsDisplayFragment extends BaseSettingsFragment{ } private void onColorClick(){ - int selected=lp.color.ordinal(); + boolean multiple=AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1; + int indexOffset=multiple ? 1 : 0; + int selected=lp.color==null ? 0 : lp.color.ordinal() + indexOffset; int[] newSelected={selected}; - String[] names=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).toArray(String[]::new); - new M3AlertDialogBuilder(getActivity()) + List items=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).collect(Collectors.toList()); + if(multiple) + items.add(0, getString(R.string.sk_settings_color_palette_default, items.get(GlobalUserPreferences.color.ordinal()))); + + Consumer save=(asDefault)->{ + boolean defaultSelected=multiple && newSelected[0]==0; + ColorPreference pref=defaultSelected ? null : ColorPreference.values()[newSelected[0]-indexOffset]; + if(pref!=lp.color){ + ColorPreference prev=lp.color; + lp.color=asDefault ? null : pref; + lp.save(); + if((asDefault || !multiple) && pref!=null){ + GlobalUserPreferences.color=pref; + GlobalUserPreferences.save(); + } + colorItem.subtitle=getColorPaletteValue(); + rebindItem(colorItem); + if(prev==null && pref!=null) restartActivityToApplyNewTheme(); + else maybeApplyNewThemeRightNow(null, prev, null); + } + }; + + AlertDialog.Builder alert=new M3AlertDialogBuilder(getActivity()) .setTitle(R.string.sk_settings_color_palette) - .setSingleChoiceItems(names, + .setSingleChoiceItems(items.toArray(String[]::new), selected, (dlg, item)->newSelected[0]=item) - .setPositiveButton(R.string.ok, (dlg, item)->{ - ColorPreference pref=ColorPreference.values()[newSelected[0]]; - if(pref!=lp.color){ - ColorPreference prev=lp.color; - lp.color=pref; - GlobalUserPreferences.save(); - colorItem.subtitleRes=getColorPaletteValue(); - rebindItem(colorItem); - maybeApplyNewThemeRightNow(null, prev, null); - } - }) - .setNegativeButton(R.string.cancel, null) - .show(); + .setPositiveButton(R.string.ok, (dlg, item)->save.accept(false)) + .setNegativeButton(R.string.cancel, null); + if(multiple) alert.setNeutralButton(R.string.sk_set_as_default, (dlg, item)->save.accept(true)); + alert.show(); } private void onPublishTextClick(){ @@ -284,14 +293,14 @@ public class SettingsDisplayFragment extends BaseSettingsFragment{ 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=lp.color; + if(prevColor==null) prevColor=lp.getCurrentColor(); boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK || (prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive()); boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK || (GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive()); boolean isNewBlack=GlobalUserPreferences.trueBlackTheme; - if(isCurrentDark!=isNewDark || prevColor!=lp.color || (isNewDark && prevTrueBlack!=isNewBlack)){ + if(isCurrentDark!=isNewDark || prevColor!=lp.getCurrentColor() || (isNewDark && prevTrueBlack!=isNewBlack)){ restartActivityToApplyNewTheme(); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java index 40c6a3df0..6b569729a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java @@ -105,7 +105,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{ public transient boolean spoilerRevealed; public transient boolean sensitiveRevealed; public transient boolean textExpanded, textExpandable; - public transient boolean hasGapAfter; + public transient String hasGapAfter; private transient String strippedText; public transient TranslationState translationState=TranslationState.HIDDEN; public transient Translation translation; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GapStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GapStatusDisplayItem.java index 9615b2b33..718263dd9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GapStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/GapStatusDisplayItem.java @@ -25,6 +25,10 @@ public class GapStatusDisplayItem extends StatusDisplayItem{ this.status=status; } + public String getMaxID(){ + return status.hasGapAfter; + } + @Override public Type getType(){ return Type.GAP; @@ -54,6 +58,8 @@ public class GapStatusDisplayItem extends StatusDisplayItem{ if(!item.loading){ progressBottom.setVisibility(View.GONE); progressTop.setVisibility(View.GONE); + textTop.setAlpha(1); + textBottom.setAlpha(1); } top.setClickable(!item.loading); bottom.setClickable(!item.loading); diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index 50d60bb1a..16a71814d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -381,21 +381,32 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ markAsRead.setVisibility(View.GONE); } - if (item.status == null || !item.status.textExpandable) { - collapseBtn.setVisibility(View.GONE); - } else { - String collapseText = item.parentFragment.getString(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand); - collapseBtn.setVisibility(item.status.textExpandable ? View.VISIBLE : View.GONE); - collapseBtn.setContentDescription(collapseText); - if (GlobalUserPreferences.reduceMotion) collapseBtnIcon.setScaleY(item.status.textExpanded ? -1 : 1); - else collapseBtnIcon.animate().scaleY(item.status.textExpanded ? -1 : 1).start(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) collapseBtn.setTooltipText(collapseText); - } + bindCollapseButton(); itemView.setPaddingRelative(itemView.getPaddingStart(), itemView.getPaddingTop(), item.inset ? V.dp(10) : V.dp(4), itemView.getPaddingBottom()); } + public void bindCollapseButton(){ + boolean expandable=item.status!=null && item.status.textExpandable; + collapseBtn.setVisibility(expandable ? View.VISIBLE : View.GONE); + if(expandable) { + bindCollapseButtonText(); + collapseBtnIcon.setScaleY(item.status.textExpanded ? -1 : 1); + } + } + + private void bindCollapseButtonText(){ + String collapseText = item.parentFragment.getString(item.status.textExpanded ? R.string.sk_collapse : R.string.sk_expand); + collapseBtn.setContentDescription(collapseText); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) collapseBtn.setTooltipText(collapseText); + } + + public void animateExpandToggle(){ + bindCollapseButtonText(); + collapseBtnIcon.animate().scaleY(item.status.textExpanded ? -1 : 1).start(); + } + public void animateVisibilityToggle(boolean visible){ visibility.animate() .alpha(visible ? 1 : 0) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 0ddf6a9b9..4dd3099ea 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -302,7 +302,7 @@ public abstract class StatusDisplayItem{ footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID); footer.hideCounts=hideCounts; items.add(footer); - if(status.hasGapAfter && !(fragment instanceof ThreadFragment)) + if(status.hasGapAfter!=null && !(fragment instanceof ThreadFragment)) items.add(new GapStatusDisplayItem(parentID, fragment, status)); } int i=1; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index 41739c791..7c49d66b7 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -1032,8 +1032,8 @@ public class UiUtils { default -> R.style.Theme_Mastodon_AutoLightDark; }); - AccountLocalPreferences prefs=session != null ? session.getLocalPreferences() : null; - AccountLocalPreferences.ColorPreference color=prefs != null ? prefs.color : AccountLocalPreferences.ColorPreference.MATERIAL3; + AccountLocalPreferences prefs=session!=null ? session.getLocalPreferences() : null; + AccountLocalPreferences.ColorPreference color=prefs!=null ? prefs.getCurrentColor() : AccountLocalPreferences.ColorPreference.MATERIAL3; ColorPalette palette = ColorPalette.palettes.get(color); if (palette != null) palette.apply(context, theme); diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 6a62111d3..67bb61eb5 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -51,6 +51,7 @@ Posts Post notifications Color palette + Default (%s) System Pink Purple @@ -409,4 +410,5 @@ Use heart as favorite icon Recently used Underlined links + Set as default \ No newline at end of file