Merge remote-tracking branch 'megalodon_main/main'

# Conflicts:
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsDisplayFragment.java
This commit is contained in:
LucasGGamerM
2023-10-12 21:25:16 -03:00
15 changed files with 198 additions and 112 deletions

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android; package org.joinmastodon.android;
import static org.joinmastodon.android.api.MastodonAPIController.gson; import static org.joinmastodon.android.api.MastodonAPIController.gson;
import static org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference.MATERIAL3;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@@ -13,6 +14,7 @@ import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.api.session.AccountLocalPreferences; import org.joinmastodon.android.api.session.AccountLocalPreferences;
import org.joinmastodon.android.api.session.AccountLocalPreferences.ColorPreference;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.ContentType;
@@ -62,6 +64,7 @@ public class GlobalUserPreferences{
public static boolean overlayMedia; public static boolean overlayMedia;
public static boolean showSuicideHelp; public static boolean showSuicideHelp;
public static boolean underlinedLinks; public static boolean underlinedLinks;
public static ColorPreference color;
// MOSHIDON // MOSHIDON
public static boolean showDividers; public static boolean showDividers;
@@ -138,6 +141,7 @@ public class GlobalUserPreferences{
overlayMedia=prefs.getBoolean("overlayMedia", false); overlayMedia=prefs.getBoolean("overlayMedia", false);
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true); showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
underlinedLinks=prefs.getBoolean("underlinedLinks", true); underlinedLinks=prefs.getBoolean("underlinedLinks", true);
color=ColorPreference.valueOf(prefs.getString("color", MATERIAL3.name()));
// MOSHIDON // MOSHIDON
uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false); uniformNotificationIcon=prefs.getBoolean("uniformNotificationIcon", false);
@@ -171,8 +175,6 @@ public class GlobalUserPreferences{
int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE); int migrationLevel=prefs.getInt("migrationLevel", BuildConfig.VERSION_CODE);
if(migrationLevel < 61) if(migrationLevel < 61)
migrateToUpstreamVersion61(); migrateToUpstreamVersion61();
if(migrationLevel < 102)
migrateToVersion102();
if(migrationLevel < BuildConfig.VERSION_CODE) if(migrationLevel < BuildConfig.VERSION_CODE)
prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply(); prefs.edit().putInt("migrationLevel", BuildConfig.VERSION_CODE).apply();
} }
@@ -216,6 +218,7 @@ public class GlobalUserPreferences{
.putBoolean("overlayMedia", overlayMedia) .putBoolean("overlayMedia", overlayMedia)
.putBoolean("showSuicideHelp", showSuicideHelp) .putBoolean("showSuicideHelp", showSuicideHelp)
.putBoolean("underlinedLinks", underlinedLinks) .putBoolean("underlinedLinks", underlinedLinks)
.putString("color", color.name())
// MOSHIDON // MOSHIDON
.putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies) .putBoolean("defaultToUnlistedReplies", defaultToUnlistedReplies)
@@ -258,25 +261,6 @@ public class GlobalUserPreferences{
//region preferences migrations //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(){ private static void migrateToUpstreamVersion61(){
Log.d(TAG, "Migrating preferences to upstream version 61!!"); Log.d(TAG, "Migrating preferences to upstream version 61!!");

View File

@@ -26,6 +26,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.function.Consumer; import java.util.function.Consumer;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
@@ -69,7 +70,7 @@ public class CacheController{
Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class); Status status=MastodonAPIController.gson.fromJson(cursor.getString(0), Status.class);
status.postprocess(); status.postprocess();
int flags=cursor.getInt(1); int flags=cursor.getInt(1);
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0); status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0) ? status.id : null;
newMaxID=status.id; newMaxID=status.id;
result.add(status); result.add(status);
}while(cursor.moveToNext()); }while(cursor.moveToNext());
@@ -113,7 +114,7 @@ public class CacheController{
values.put("id", s.id); values.put("id", s.id);
values.put("json", MastodonAPIController.gson.toJson(s)); values.put("json", MastodonAPIController.gson.toJson(s));
int flags=0; int flags=0;
if(s.hasGapAfter) if(Objects.equals(s.hasGapAfter, s.id))
flags|=POST_FLAG_GAP_AFTER; flags|=POST_FLAG_GAP_AFTER;
values.put("flags", flags); values.put("flags", flags);
values.put("time", s.createdAt.getEpochSecond()); values.put("time", s.createdAt.getEpochSecond());

View File

@@ -10,6 +10,7 @@ import androidx.annotation.StringRes;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.ContentType;
import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Emoji;
@@ -84,7 +85,7 @@ public class AccountLocalPreferences{
keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false); keepOnlyLatestNotification=prefs.getBoolean("keepOnlyLatestNotification", false);
emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma()); emojiReactionsEnabled=prefs.getBoolean("emojiReactionsEnabled", session.getInstance().isPresent() && session.getInstance().get().isAkkoma());
showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name())); showEmojiReactions=ShowEmojiReactions.valueOf(prefs.getString("showEmojiReactions", ShowEmojiReactions.HIDE_EMPTY.name()));
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.MATERIAL3.name())); color=prefs.contains("color") ? ColorPreference.valueOf(prefs.getString("color", null)) : null;
likeIcon=prefs.getBoolean("likeIcon", false); likeIcon=prefs.getBoolean("likeIcon", false);
recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>()); recentCustomEmoji=fromJson(prefs.getString("recentCustomEmoji", null), recentCustomEmojiType, new ArrayList<>());
@@ -101,6 +102,10 @@ public class AccountLocalPreferences{
prefs.edit().putLong("notificationsPauseTime", time).apply(); prefs.edit().putLong("notificationsPauseTime", time).apply();
} }
public ColorPreference getCurrentColor(){
return color!=null ? color : GlobalUserPreferences.color!=null ? GlobalUserPreferences.color : ColorPreference.MATERIAL3;
}
public void save(){ public void save(){
prefs.edit() prefs.edit()
.putBoolean("interactionCounts", showInteractionCounts) .putBoolean("interactionCounts", showInteractionCounts)
@@ -124,7 +129,7 @@ public class AccountLocalPreferences{
.putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification) .putBoolean("keepOnlyLatestNotification", keepOnlyLatestNotification)
.putBoolean("emojiReactionsEnabled", emojiReactionsEnabled) .putBoolean("emojiReactionsEnabled", emojiReactionsEnabled)
.putString("showEmojiReactions", showEmojiReactions.name()) .putString("showEmojiReactions", showEmojiReactions.name())
.putString("color", color.name()) .putString("color", color!=null ? color.name() : null)
.putBoolean("likeIcon", likeIcon) .putBoolean("likeIcon", likeIcon)
.putString("recentCustomEmoji", gson.toJson(recentCustomEmoji)) .putString("recentCustomEmoji", gson.toJson(recentCustomEmoji))

View File

@@ -270,6 +270,7 @@ public class AccountSession{
if(s!=null && s.filtered!=null){ if(s!=null && s.filtered!=null){
localPreferences.serverSideFiltersSupported=true; localPreferences.serverSideFiltersSupported=true;
localPreferences.save(); localPreferences.save();
break;
} }
} }
@@ -279,9 +280,17 @@ public class AccountSession{
if(filterStatusContainingObject(o, extractor, context, profile)){ if(filterStatusContainingObject(o, extractor, context, profile)){
Status s=extractor.apply(o); Status s=extractor.apply(o);
removeUs.add(o); removeUs.add(o);
if(s!=null && s.hasGapAfter && i > 0){ if(s!=null && s.hasGapAfter!=null && i>0){
Status prev=extractor.apply(objects.get(i - 1)); // oops, we're about to remove an item that has a gap after...
if(prev!=null) prev.hasGapAfter=true; // 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;
}
}
} }
} }
} }

View File

@@ -38,7 +38,6 @@ import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.BetterItemAnimator;
import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.EmojiReactionsStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
@@ -641,21 +640,21 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) { public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
Status status = holder.getItem().status; Status status = holder.getItem().status;
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); 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) { public void onSensitiveRevealed(MediaGridStatusDisplayItem.Holder holder) {
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header != null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true); if(header!=null && header.getItem().hasVisibilityToggle) header.animateVisibilityToggle(true);
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
} }
protected void toggleSpoiler(Status status, String itemID){ protected void toggleSpoiler(Status status, String itemID){
@@ -664,8 +663,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
status.sensitiveRevealed = false; status.sensitiveRevealed = false;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class); SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null) if(spoiler!=null) spoiler.rebind();
spoiler.rebind(); else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class)); SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
int index=displayItems.indexOf(spoilerItem); int index=displayItems.indexOf(spoilerItem);
@@ -677,30 +676,29 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size()); adapter.notifyItemRangeRemoved(index+1, spoilerItem.contentItems.size());
} }
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); notifyItemChanged(itemID, TextStatusDisplayItem.class);
if(text!=null)
adapter.notifyItemChanged(text.getAbsoluteAdapterPosition()-getMainAdapterOffset());
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if(header!=null) if(header!=null) header.rebind();
header.rebind(); else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
list.invalidateItemDecorations(); list.invalidateItemDecorations();
} }
public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) { public void onEnableExpandable(TextStatusDisplayItem.Holder holder, boolean expandable) {
if (holder.getItem().status.textExpandable != expandable && list != null) { Status s=holder.getItem().status;
holder.getItem().status.textExpandable = expandable; if(s.textExpandable!=expandable && list!=null) {
HeaderStatusDisplayItem.Holder header = findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class); s.textExpandable=expandable;
if (header != null) header.rebind(); HeaderStatusDisplayItem.Holder header=findHolderOfType(holder.getItemID(), HeaderStatusDisplayItem.Holder.class);
if(header!=null) header.bindCollapseButton();
} }
} }
public void onToggleExpanded(Status status, String itemID) { public void onToggleExpanded(Status status, String itemID) {
status.textExpanded = !status.textExpanded; status.textExpanded = !status.textExpanded;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); notifyItemChanged(itemID, TextStatusDisplayItem.class);
HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class); HeaderStatusDisplayItem.Holder header=findHolderOfType(itemID, HeaderStatusDisplayItem.Holder.class);
if (text != null) text.rebind(); if(header!=null) header.animateExpandToggle();
if (header != null) header.rebind(); else notifyItemChanged(itemID, HeaderStatusDisplayItem.class);
} }
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){} public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
@@ -759,9 +757,61 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return null; return null;
} }
/**
* Use this as a fallback if findHolderOfType fails to find the ViewHolder.
* It might still be bound but off-screen and therefore not a child of the RecyclerView -
* resulting in the ViewHolder displaying an outdated state once scrolled back into view.
*/
protected <I extends StatusDisplayItem> int notifyItemChanged(String id, Class<I> type){
boolean encounteredParent=false;
for(int i=0; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
boolean idEquals=id.equals(item.parentID);
if(!encounteredParent && idEquals) encounteredParent=true; // reached top of the parent
else if(encounteredParent && !idEquals) break; // passed by bottom of the parent. man muss ja wissen wann schluss is
if(idEquals && type.isInstance(item)){
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedAfter(StatusDisplayItem afterThis, Class<I> type){
int startIndex=displayItems.indexOf(afterThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedAfter didn't find the passed StatusDisplayItem");
String parentID=afterThis.parentID;
for(int i=startIndex; i<displayItems.size(); i++){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
protected <I extends StatusDisplayItem> int notifyItemChangedBefore(StatusDisplayItem beforeThis, Class<I> type){
int startIndex=displayItems.indexOf(beforeThis);
if(startIndex == -1) throw new IllegalStateException("notifyItemChangedBefore didn't find the passed StatusDisplayItem");
String parentID=beforeThis.parentID;
for(int i=startIndex; i>=0; i--){
StatusDisplayItem item=displayItems.get(i);
if(!parentID.equals(item.parentID)) break; // didn't find anything
if(type.isInstance(item)){
// found it
adapter.notifyItemChanged(i);
return i;
}
}
return -1;
}
@Nullable @Nullable
protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){ protected <I extends StatusDisplayItem, H extends StatusDisplayItem.Holder<I>> H findHolderOfType(String id, Class<H> type){
for(int i=0;i<list.getChildCount();i++){ for(int i=0; i<list.getChildCount(); i++){
RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i)); RecyclerView.ViewHolder holder=list.getChildViewHolder(list.getChildAt(i));
if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder)) if(holder instanceof StatusDisplayItem.Holder<?> itemHolder && itemHolder.getItemID().equals(id) && type.isInstance(holder))
return type.cast(holder); return type.cast(holder);
@@ -891,6 +941,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(text!=null){ if(text!=null){
text.updateTranslation(true); text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition()); imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
} }
} }
@@ -902,6 +954,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){ if(text!=null){
text.updateTranslation(true); text.updateTranslation(true);
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
} }
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error) .setTitle(R.string.error)
@@ -918,6 +972,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(text!=null){ if(text!=null){
text.updateTranslation(true); text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition()); imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
} }
} }

View File

@@ -63,7 +63,8 @@ public class ComposeImageDescriptionFragment extends MastodonToolbarFragment imp
accountID=getArguments().getString("account"); accountID=getArguments().getString("account");
attachmentID=getArguments().getString("attachment"); attachmentID=getArguments().getString("attachment");
themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark); themeWrapper=new ContextThemeWrapper(activity, R.style.Theme_Mastodon_Dark);
ColorPalette.palettes.get(AccountSessionManager.get(accountID).getLocalPreferences().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); setTitle(R.string.add_alt_text);
} }

View File

@@ -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 if(!data.isEmpty() && last.id.equals(data.get(0).id)){ // This part intersects with the existing one
toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post toAdd=new ArrayList<>(result.subList(0, result.size()-1)); // Remove the already known last post
}else{ }else{
result.get(result.size()-1).hasGapAfter=true; last.hasGapAfter=last.id;
toAdd=result; toAdd=result;
} }
List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList()); List<String> existingIds=data.stream().map(Status::getID).collect(Collectors.toList());
@@ -176,10 +176,10 @@ public class HomeTimelineFragment extends StatusListFragment {
gap.loading=true; gap.loading=true;
dataLoading=true; dataLoading=true;
String maxID = null; String maxID=null;
String minID = null; String minID=null;
if (downwards) { if (downwards) {
maxID = item.getItemID(); maxID=item.getItem().getMaxID();
} else { } else {
int gapPos=displayItems.indexOf(gap); int gapPos=displayItems.indexOf(gap);
StatusDisplayItem nextItem=displayItems.get(gapPos + 1); StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
@@ -196,12 +196,13 @@ public class HomeTimelineFragment extends StatusListFragment {
int gapPos=displayItems.indexOf(gap); int gapPos=displayItems.indexOf(gap);
if(gapPos==-1) if(gapPos==-1)
return; return;
AccountSessionManager.get(accountID).filterStatuses(result, getFilterContext());
if(result.isEmpty()){ if(result.isEmpty()){
displayItems.remove(gapPos); displayItems.remove(gapPos);
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos); adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
Status gapStatus=getStatusByID(gap.parentID); Status gapStatus=getStatusByID(gap.parentID);
if(gapStatus!=null){ if(gapStatus!=null){
gapStatus.hasGapAfter=false; gapStatus.hasGapAfter=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
} }
}else{ }else{
@@ -214,7 +215,7 @@ public class HomeTimelineFragment extends StatusListFragment {
idsBelowGap.add(s.id); idsBelowGap.add(s.id);
}else if(s.id.equals(gap.parentID)){ }else if(s.id.equals(gap.parentID)){
belowGap=true; belowGap=true;
s.hasGapAfter=false; s.hasGapAfter=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
}else{ }else{
gapPostIndex++; gapPostIndex++;
@@ -227,7 +228,8 @@ public class HomeTimelineFragment extends StatusListFragment {
break; break;
} }
if(endIndex==result.size()){ if(endIndex==result.size()){
result.get(result.size()-1).hasGapAfter=true; Status last=result.get(result.size()-1);
last.hasGapAfter=last.id;
}else{ }else{
result=result.subList(0, endIndex); result=result.subList(0, endIndex);
} }
@@ -272,7 +274,7 @@ public class HomeTimelineFragment extends StatusListFragment {
.filter(s->Objects.equals(s.id, gap.parentID)) .filter(s->Objects.equals(s.id, gap.parentID))
.findFirst(); .findFirst();
if (gapStatus.isPresent()) { if (gapStatus.isPresent()) {
gapStatus.get().hasGapAfter=false; gapStatus.get().hasGapAfter=null;
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false); AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
} }
targetList.clear(); targetList.clear();

View File

@@ -104,8 +104,8 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
else else
selectedIDs.add(id); selectedIDs.add(id);
CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class); CheckableHeaderStatusDisplayItem.Holder holder=findHolderOfType(id, CheckableHeaderStatusDisplayItem.Holder.class);
if(holder!=null) if(holder!=null) holder.rebind();
holder.rebind(); else notifyItemChanged(id, CheckableHeaderStatusDisplayItem.class);
} }
@Override @Override

View File

@@ -1,6 +1,7 @@
package org.joinmastodon.android.fragments.settings; package org.joinmastodon.android.fragments.settings;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.os.Build; import android.os.Build;
@@ -29,6 +30,8 @@ import org.joinmastodon.android.ui.views.TextInputFrameLayout;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
@@ -152,19 +155,11 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
}; };
} }
private @StringRes int getColorPaletteValue(){ private String getColorPaletteValue(){
return switch(AccountSessionManager.get(accountID).getLocalPreferences().color){ ColorPreference color=AccountSessionManager.get(accountID).getLocalPreferences().color;
case MATERIAL3 -> R.string.sk_color_palette_material3; return color==null
case PINK -> R.string.sk_color_palette_pink; ? getString(R.string.sk_settings_color_palette_default, getString(GlobalUserPreferences.color.getName()))
case PURPLE -> R.string.sk_color_palette_purple; : getString(color.getName());
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 getPublishButtonText() { private String getPublishButtonText() {
@@ -220,26 +215,40 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
} }
private void onColorClick(){ 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}; int[] newSelected={selected};
String[] names=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).toArray(String[]::new); List<String> items=Arrays.stream(ColorPreference.values()).map(ColorPreference::getName).map(this::getString).collect(Collectors.toList());
new M3AlertDialogBuilder(getActivity()) if(multiple)
items.add(0, getString(R.string.sk_settings_color_palette_default, items.get(GlobalUserPreferences.color.ordinal())));
Consumer<Boolean> save=(asDefault)->{
boolean defaultSelected=multiple && newSelected[0]==0;
ColorPreference pref=defaultSelected ? null : ColorPreference.values()[newSelected[0]-indexOffset];
if(pref!=lp.color){
ColorPreference prev=lp.color;
lp.color=asDefault ? null : pref;
lp.save();
if((asDefault || !multiple) && pref!=null){
GlobalUserPreferences.color=pref;
GlobalUserPreferences.save();
}
colorItem.subtitle=getColorPaletteValue();
rebindItem(colorItem);
if(prev==null && pref!=null) restartActivityToApplyNewTheme();
else maybeApplyNewThemeRightNow(null, prev, null);
}
};
AlertDialog.Builder alert=new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_settings_color_palette) .setTitle(R.string.sk_settings_color_palette)
.setSingleChoiceItems(names, .setSingleChoiceItems(items.toArray(String[]::new),
selected, (dlg, item)->newSelected[0]=item) selected, (dlg, item)->newSelected[0]=item)
.setPositiveButton(R.string.ok, (dlg, item)->{ .setPositiveButton(R.string.ok, (dlg, item)->save.accept(false))
ColorPreference pref=ColorPreference.values()[newSelected[0]]; .setNegativeButton(R.string.cancel, null);
if(pref!=lp.color){ if(multiple) alert.setNeutralButton(R.string.sk_set_as_default, (dlg, item)->save.accept(true));
ColorPreference prev=lp.color; alert.show();
lp.color=pref;
GlobalUserPreferences.save();
colorItem.subtitleRes=getColorPaletteValue();
rebindItem(colorItem);
maybeApplyNewThemeRightNow(null, prev, null);
}
})
.setNegativeButton(R.string.cancel, null)
.show();
} }
private void onPublishTextClick(){ private void onPublishTextClick(){
@@ -284,14 +293,14 @@ public class SettingsDisplayFragment extends BaseSettingsFragment<Void>{
private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, ColorPreference prevColor, Boolean prevTrueBlack){ private void maybeApplyNewThemeRightNow(GlobalUserPreferences.ThemePreference prevTheme, ColorPreference prevColor, Boolean prevTrueBlack){
if(prevTheme==null) prevTheme=GlobalUserPreferences.theme; if(prevTheme==null) prevTheme=GlobalUserPreferences.theme;
if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme; if(prevTrueBlack==null) prevTrueBlack=GlobalUserPreferences.trueBlackTheme;
if(prevColor==null) prevColor=lp.color; if(prevColor==null) prevColor=lp.getCurrentColor();
boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK || boolean isCurrentDark=prevTheme==GlobalUserPreferences.ThemePreference.DARK ||
(prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive()); (prevTheme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK || boolean isNewDark=GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK ||
(GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive()); (GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.AUTO && Build.VERSION.SDK_INT>=30 && getResources().getConfiguration().isNightModeActive());
boolean isNewBlack=GlobalUserPreferences.trueBlackTheme; boolean isNewBlack=GlobalUserPreferences.trueBlackTheme;
if(isCurrentDark!=isNewDark || prevColor!=lp.color || (isNewDark && prevTrueBlack!=isNewBlack)){ if(isCurrentDark!=isNewDark || prevColor!=lp.getCurrentColor() || (isNewDark && prevTrueBlack!=isNewBlack)){
restartActivityToApplyNewTheme(); restartActivityToApplyNewTheme();
} }
} }

View File

@@ -105,7 +105,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient boolean spoilerRevealed; public transient boolean spoilerRevealed;
public transient boolean sensitiveRevealed; public transient boolean sensitiveRevealed;
public transient boolean textExpanded, textExpandable; public transient boolean textExpanded, textExpandable;
public transient boolean hasGapAfter; public transient String hasGapAfter;
private transient String strippedText; private transient String strippedText;
public transient TranslationState translationState=TranslationState.HIDDEN; public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation; public transient Translation translation;

View File

@@ -25,6 +25,10 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
this.status=status; this.status=status;
} }
public String getMaxID(){
return status.hasGapAfter;
}
@Override @Override
public Type getType(){ public Type getType(){
return Type.GAP; return Type.GAP;
@@ -54,6 +58,8 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
if(!item.loading){ if(!item.loading){
progressBottom.setVisibility(View.GONE); progressBottom.setVisibility(View.GONE);
progressTop.setVisibility(View.GONE); progressTop.setVisibility(View.GONE);
textTop.setAlpha(1);
textBottom.setAlpha(1);
} }
top.setClickable(!item.loading); top.setClickable(!item.loading);
bottom.setClickable(!item.loading); bottom.setClickable(!item.loading);

View File

@@ -381,21 +381,32 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
markAsRead.setVisibility(View.GONE); markAsRead.setVisibility(View.GONE);
} }
if (item.status == null || !item.status.textExpandable) { bindCollapseButton();
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);
}
itemView.setPaddingRelative(itemView.getPaddingStart(), itemView.getPaddingTop(), itemView.setPaddingRelative(itemView.getPaddingStart(), itemView.getPaddingTop(),
item.inset ? V.dp(10) : V.dp(4), itemView.getPaddingBottom()); 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){ public void animateVisibilityToggle(boolean visible){
visibility.animate() visibility.animate()
.alpha(visible ? 1 : 0) .alpha(visible ? 1 : 0)

View File

@@ -302,7 +302,7 @@ public abstract class StatusDisplayItem{
footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID); footer=new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID);
footer.hideCounts=hideCounts; footer.hideCounts=hideCounts;
items.add(footer); items.add(footer);
if(status.hasGapAfter && !(fragment instanceof ThreadFragment)) if(status.hasGapAfter!=null && !(fragment instanceof ThreadFragment))
items.add(new GapStatusDisplayItem(parentID, fragment, status)); items.add(new GapStatusDisplayItem(parentID, fragment, status));
} }
int i=1; int i=1;

View File

@@ -1032,8 +1032,8 @@ public class UiUtils {
default -> R.style.Theme_Mastodon_AutoLightDark; default -> R.style.Theme_Mastodon_AutoLightDark;
}); });
AccountLocalPreferences prefs=session != null ? session.getLocalPreferences() : null; AccountLocalPreferences prefs=session!=null ? session.getLocalPreferences() : null;
AccountLocalPreferences.ColorPreference color=prefs != null ? prefs.color : AccountLocalPreferences.ColorPreference.MATERIAL3; AccountLocalPreferences.ColorPreference color=prefs!=null ? prefs.getCurrentColor() : AccountLocalPreferences.ColorPreference.MATERIAL3;
ColorPalette palette = ColorPalette.palettes.get(color); ColorPalette palette = ColorPalette.palettes.get(color);
if (palette != null) palette.apply(context, theme); if (palette != null) palette.apply(context, theme);

View File

@@ -51,6 +51,7 @@
<string name="sk_notification_type_status">Posts</string> <string name="sk_notification_type_status">Posts</string>
<string name="sk_notification_type_posts">Post notifications</string> <string name="sk_notification_type_posts">Post notifications</string>
<string name="sk_settings_color_palette">Color palette</string> <string name="sk_settings_color_palette">Color palette</string>
<string name="sk_settings_color_palette_default">Default (%s)</string>
<string name="sk_color_palette_material3">System</string> <string name="sk_color_palette_material3">System</string>
<string name="sk_color_palette_pink">Pink</string> <string name="sk_color_palette_pink">Pink</string>
<string name="sk_color_palette_purple">Purple</string> <string name="sk_color_palette_purple">Purple</string>
@@ -409,4 +410,5 @@
<string name="sk_settings_like_icon">Use heart as favorite icon</string> <string name="sk_settings_like_icon">Use heart as favorite icon</string>
<string name="sk_recently_used">Recently used</string> <string name="sk_recently_used">Recently used</string>
<string name="sk_settings_underlined_links">Underlined links</string> <string name="sk_settings_underlined_links">Underlined links</string>
<string name="sk_set_as_default">Set as default</string>
</resources> </resources>