Merge remote-tracking branch 'megalodon_main/main'
# Conflicts: # mastodon/build.gradle # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java # mastodon/src/main/res/values-ar/strings_sk.xml # mastodon/src/main/res/values-fr-rFR/strings_sk.xml # mastodon/src/main/res/values-ja-rJP/strings_sk.xml # metadata/ar/short_description.txt
This commit is contained in:
@@ -26,6 +26,8 @@ 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";
|
||||
|
||||
|
||||
@@ -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(false);
|
||||
registerAllAccountsForPush(true); // TODO: revert this before release
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "tryRegisterFCM: no token found or app was updated. Trying to get push token...");
|
||||
|
||||
@@ -622,7 +622,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
}
|
||||
}
|
||||
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){}
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){}
|
||||
|
||||
public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){
|
||||
int startPos = warning.getAbsoluteAdapterPosition();
|
||||
|
||||
@@ -13,25 +13,24 @@ import org.joinmastodon.android.api.requests.markers.SaveMarkers;
|
||||
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
|
||||
import org.joinmastodon.android.api.session.AccountLocalPreferences;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCreatedEvent;
|
||||
import org.joinmastodon.android.model.CacheablePaginatedResponse;
|
||||
import org.joinmastodon.android.model.FilterContext;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.TimelineMarkers;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.utils.StatusFilterPredicate;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.grishka.appkit.api.Callback;
|
||||
import me.grishka.appkit.api.ErrorResponse;
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HomeTimelineFragment extends StatusListFragment {
|
||||
private HomeTabFragment parent;
|
||||
@@ -176,15 +175,23 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){
|
||||
if(dataLoading)
|
||||
return;
|
||||
item.getItem().loading=true;
|
||||
V.setVisibilityAnimated(item.progress, View.VISIBLE);
|
||||
V.setVisibilityAnimated(item.text, View.GONE);
|
||||
GapStatusDisplayItem gap=item.getItem();
|
||||
gap.loading=true;
|
||||
dataLoading=true;
|
||||
currentRequest=new GetHomeTimeline(item.getItemID(), null, 20, null, getLocalPrefs().timelineReplyVisibility)
|
||||
|
||||
String maxID = null;
|
||||
String minID = null;
|
||||
if (downwards) {
|
||||
maxID = item.getItemID();
|
||||
} else {
|
||||
int gapPos=displayItems.indexOf(gap);
|
||||
StatusDisplayItem nextItem=displayItems.get(gapPos + 1);
|
||||
minID=nextItem.parentID;
|
||||
}
|
||||
currentRequest=new GetHomeTimeline(maxID, minID, 20, null, getLocalPrefs().timelineReplyVisibility)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
@@ -204,52 +211,96 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||
}
|
||||
}else{
|
||||
Set<String> idsBelowGap=new HashSet<>();
|
||||
boolean belowGap=false;
|
||||
int gapPostIndex=0;
|
||||
for(Status s:data){
|
||||
if(belowGap){
|
||||
idsBelowGap.add(s.id);
|
||||
}else if(s.id.equals(gap.parentID)){
|
||||
belowGap=true;
|
||||
s.hasGapAfter=false;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||
}else{
|
||||
gapPostIndex++;
|
||||
if(downwards) {
|
||||
Set<String> idsBelowGap=new HashSet<>();
|
||||
boolean belowGap=false;
|
||||
int gapPostIndex=0;
|
||||
for(Status s:data){
|
||||
if(belowGap){
|
||||
idsBelowGap.add(s.id);
|
||||
}else if(s.id.equals(gap.parentID)){
|
||||
belowGap=true;
|
||||
s.hasGapAfter=false;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(s), false);
|
||||
}else{
|
||||
gapPostIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
int endIndex=0;
|
||||
for(Status s:result){
|
||||
endIndex++;
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
}
|
||||
if(endIndex==result.size()){
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
}else{
|
||||
result=result.subList(0, endIndex);
|
||||
}
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
targetList.clear();
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
if(typeFilterPredicate(s) && filterPredicate.test(s)){
|
||||
int endIndex=0;
|
||||
for(Status s:result){
|
||||
endIndex++;
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
}
|
||||
if(endIndex==result.size()){
|
||||
result.get(result.size()-1).hasGapAfter=true;
|
||||
}else{
|
||||
result=result.subList(0, endIndex);
|
||||
}
|
||||
AccountSessionManager.get(accountID).filterStatuses(result, FilterContext.HOME);
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
targetList.clear();
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
targetList.addAll(buildDisplayItems(s));
|
||||
insertedPosts.add(s);
|
||||
}
|
||||
if(targetList.isEmpty()){
|
||||
// oops. We didn't add new posts, but at least we know there are none.
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
}else{
|
||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||
}
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
} else {
|
||||
String aboveGapID = gap.parentID;
|
||||
int gapPostIndex = 0;
|
||||
for (;gapPostIndex<data.size();gapPostIndex++){
|
||||
if (Objects.equals(aboveGapID, data.get(gapPostIndex).id)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// find if there's an overlap between the new data and the current data
|
||||
int indexOfGapInResponse = 0;
|
||||
for (;indexOfGapInResponse<result.size();indexOfGapInResponse++){
|
||||
if (Objects.equals(aboveGapID, result.get(indexOfGapInResponse).id)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// there is an overlap between new and current data
|
||||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
if(indexOfGapInResponse<result.size()){
|
||||
result=result.subList(indexOfGapInResponse+1,result.size());
|
||||
Optional<Status> gapStatus=data.stream()
|
||||
.filter(s->Objects.equals(s.id, gap.parentID))
|
||||
.findFirst();
|
||||
if (gapStatus.isPresent()) {
|
||||
gapStatus.get().hasGapAfter=false;
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus.get()), false);
|
||||
}
|
||||
targetList.clear();
|
||||
} else {
|
||||
gap.loading=false;
|
||||
}
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
for(Status s:result){
|
||||
targetList.addAll(buildDisplayItems(s));
|
||||
insertedPosts.add(s);
|
||||
}
|
||||
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, FilterContext.HOME);
|
||||
if(targetList.isEmpty()){
|
||||
// oops. We didn't add new posts, but at least we know there are none.
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
}else{
|
||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||
}
|
||||
list.scrollToPosition(getMainAdapterOffset()+gapPos+targetList.size());
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
}
|
||||
AccountSessionManager.get(accountID).filterStatuses(insertedPosts, getFilterContext());
|
||||
if(targetList.isEmpty()){
|
||||
// oops. We didn't add new posts, but at least we know there are none.
|
||||
adapter.notifyItemRemoved(getMainAdapterOffset()+gapPos);
|
||||
}else{
|
||||
adapter.notifyItemChanged(getMainAdapterOffset()+gapPos);
|
||||
adapter.notifyItemRangeInserted(getMainAdapterOffset()+gapPos+1, targetList.size()-1);
|
||||
}
|
||||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(insertedPosts, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
||||
}
|
||||
}
|
||||
String sep = getString(R.string.sk_separator);
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null));
|
||||
items.add(0, new ReblogOrReplyLineStatusDisplayItem(s.id, this, action+" "+sep+" "+date, Collections.emptyList(), 0, null, null, s));
|
||||
items.add(1, new DummyStatusDisplayItem(s.id, this));
|
||||
}
|
||||
return items;
|
||||
|
||||
@@ -83,7 +83,7 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
||||
reportStatus=Parcels.unwrap(getArguments().getParcelable("status"));
|
||||
if(reportStatus!=null){
|
||||
Status hiddenStatus=reportStatus.clone();
|
||||
hiddenStatus.spoilerText=getString(R.string.post_hidden);
|
||||
if(hiddenStatus.spoilerText==null) hiddenStatus.spoilerText=getString(R.string.post_hidden);
|
||||
onDataLoaded(Collections.singletonList(hiddenStatus));
|
||||
setTitle(R.string.report_title_post);
|
||||
}else{
|
||||
@@ -168,17 +168,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
||||
((UsableRecyclerView)list).setIncludeMarginsInItemHitbox(false);
|
||||
|
||||
if(reportStatus!=null){
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
|
||||
outRect.left=V.dp(16);
|
||||
outRect.right=V.dp(16);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||
private Paint paint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
{
|
||||
@@ -222,10 +211,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
||||
if(holder instanceof StatusDisplayItem.Holder<?>){
|
||||
outRect.left=outRect.right=V.dp(16);
|
||||
}
|
||||
int index=holder.getAbsoluteAdapterPosition()-mergeAdapter.getPositionForAdapter(adapter);
|
||||
if(index==displayItems.size()){
|
||||
outRect.top=V.dp(32);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -251,18 +236,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onModifyItemViewHolder(BindableViewHolder<StatusDisplayItem> holder){
|
||||
if((Object)holder instanceof MediaGridStatusDisplayItem.Holder h){
|
||||
View layout=h.getLayout();
|
||||
layout.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
layout.setClipToOutline(true);
|
||||
View overlay=h.getSensitiveOverlay();
|
||||
overlay.setOutlineProvider(OutlineProviders.roundedRect(8));
|
||||
overlay.setClipToOutline(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putRelationship(String id, Relationship rel){
|
||||
super.putRelationship(id, rel);
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.joinmastodon.android.utils.MastodonLanguage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
|
||||
private ListItem<Void> languageItem;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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;
|
||||
|
||||
@@ -8,14 +8,21 @@ import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
// Mind the gap!
|
||||
public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean loading;
|
||||
private Status status;
|
||||
|
||||
public GapStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment){
|
||||
public GapStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status){
|
||||
super(parentID, parentFragment);
|
||||
this.status=status;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -24,25 +31,53 @@ public class GapStatusDisplayItem extends StatusDisplayItem{
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<GapStatusDisplayItem>{
|
||||
public final ProgressBar progress;
|
||||
public final TextView text;
|
||||
public final ProgressBar progressTop, progressBottom;
|
||||
public final TextView textTop, gap, textBottom;
|
||||
public final View top, bottom;
|
||||
|
||||
public Holder(Context context, ViewGroup parent){
|
||||
super(context, R.layout.display_item_gap, parent);
|
||||
progress=findViewById(R.id.progress);
|
||||
text=findViewById(R.id.text);
|
||||
itemView.setForeground(new SawtoothTearDrawable(context));
|
||||
progressTop=findViewById(R.id.progress_top);
|
||||
progressBottom=findViewById(R.id.progress_bottom);
|
||||
textTop=findViewById(R.id.text_top);
|
||||
textBottom=findViewById(R.id.text_bottom);
|
||||
top=findViewById(R.id.top);
|
||||
top.setOnClickListener(this::onViewClick);
|
||||
bottom=findViewById(R.id.bottom);
|
||||
bottom.setOnClickListener(this::onViewClick);
|
||||
gap=findViewById(R.id.gap);
|
||||
gap.setForeground(new SawtoothTearDrawable(context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(GapStatusDisplayItem item){
|
||||
text.setVisibility(item.loading ? View.GONE : View.VISIBLE);
|
||||
progress.setVisibility(item.loading ? View.VISIBLE : View.GONE);
|
||||
if(!item.loading){
|
||||
progressBottom.setVisibility(View.GONE);
|
||||
progressTop.setVisibility(View.GONE);
|
||||
}
|
||||
top.setClickable(!item.loading);
|
||||
bottom.setClickable(!item.loading);
|
||||
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
|
||||
Instant dateBelow=next instanceof HeaderStatusDisplayItem h ? h.status.createdAt
|
||||
: next instanceof ReblogOrReplyLineStatusDisplayItem l ? l.status.createdAt
|
||||
: null;
|
||||
String text=dateBelow!=null && item.status.createdAt!=null && dateBelow.isBefore(item.status.createdAt)
|
||||
? UiUtils.formatPeriodBetween(item.parentFragment.getContext(), dateBelow, item.status.createdAt)
|
||||
: null;
|
||||
gap.setText(text);
|
||||
int p=text==null ? V.dp(6) : V.dp(20);
|
||||
gap.setPadding(p, p, p, p);
|
||||
}
|
||||
|
||||
private void onViewClick(View v){
|
||||
if(item.loading) return;
|
||||
boolean isTop=v==top;
|
||||
(isTop ? textTop : textBottom).startAnimation(UiUtils.opacityOut);
|
||||
V.setVisibilityAnimated((isTop ? progressTop : progressBottom), View.VISIBLE);
|
||||
item.parentFragment.onGapClick(this, isTop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(){
|
||||
item.parentFragment.onGapClick(this);
|
||||
}
|
||||
public void onClick(){}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.StatusPrivacy;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
@@ -43,18 +44,20 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
|
||||
public boolean needBottomPadding;
|
||||
ReblogOrReplyLineStatusDisplayItem extra;
|
||||
CharSequence fullText;
|
||||
Status status;
|
||||
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick) {
|
||||
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text);
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, Status status) {
|
||||
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text, status);
|
||||
}
|
||||
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText) {
|
||||
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, CharSequence fullText, Status status) {
|
||||
super(parentID, parentFragment);
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(text);
|
||||
HtmlParser.parseCustomEmoji(ssb, emojis);
|
||||
this.text=ssb;
|
||||
emojiHelper.setText(ssb);
|
||||
this.icon=icon;
|
||||
this.status=status;
|
||||
this.handleClick=handleClick;
|
||||
TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
||||
|
||||
@@ -54,7 +54,7 @@ import me.grishka.appkit.views.UsableRecyclerView;
|
||||
public abstract class StatusDisplayItem{
|
||||
public final String parentID;
|
||||
public final BaseStatusListFragment<?> parentFragment;
|
||||
public boolean inset, insetPadding=true;
|
||||
public boolean inset;
|
||||
public int index;
|
||||
public boolean
|
||||
hasDescendantNeighbor = false,
|
||||
@@ -136,7 +136,7 @@ public abstract class StatusDisplayItem{
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
return new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText, status
|
||||
);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ public abstract class StatusDisplayItem{
|
||||
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
|
||||
args.putParcelable("profileAccount", Parcels.wrap(status.account));
|
||||
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
|
||||
}, fullText));
|
||||
}, fullText, status));
|
||||
} else if (!(status.tags.isEmpty() ||
|
||||
fragment instanceof HashtagTimelineFragment ||
|
||||
fragment instanceof ListTimelineFragment
|
||||
@@ -187,7 +187,7 @@ public abstract class StatusDisplayItem{
|
||||
i -> {
|
||||
args.putString("hashtag", hashtag.name);
|
||||
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
|
||||
}
|
||||
}, status
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ public abstract class StatusDisplayItem{
|
||||
footer.hideCounts=hideCounts;
|
||||
items.add(footer);
|
||||
if(status.hasGapAfter && !(fragment instanceof ThreadFragment))
|
||||
items.add(new GapStatusDisplayItem(parentID, fragment));
|
||||
items.add(new GapStatusDisplayItem(parentID, fragment, status));
|
||||
}
|
||||
int i=1;
|
||||
boolean inset=(flags & FLAG_INSET)!=0;
|
||||
@@ -406,10 +406,11 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
|
||||
public Optional<StatusDisplayItem> getDisplayItemOffset(int offset){
|
||||
int nextPos=getAbsoluteAdapterPosition() + offset;
|
||||
List<StatusDisplayItem> displayItems=item.parentFragment.getDisplayItems();
|
||||
return displayItems.size() > nextPos
|
||||
? Optional.of(displayItems.get(nextPos))
|
||||
int thisPos=displayItems.indexOf(item);
|
||||
int offsetPos=thisPos + offset;
|
||||
return displayItems.size() > offsetPos && thisPos >= 0 && offsetPos >= 0
|
||||
? Optional.of(displayItems.get(offsetPos))
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ import me.grishka.appkit.utils.V;
|
||||
public class SawtoothTearDrawable extends Drawable{
|
||||
private final Paint topPaint, bottomPaint;
|
||||
|
||||
private static final int TOP_SAWTOOTH_HEIGHT=5;
|
||||
private static final int BOTTOM_SAWTOOTH_HEIGHT=3;
|
||||
private static final int STROKE_WIDTH=2;
|
||||
private static final int TOP_SAWTOOTH_HEIGHT=4;
|
||||
private static final int BOTTOM_SAWTOOTH_HEIGHT=4;
|
||||
private static final int STROKE_WIDTH=1;
|
||||
private static final int SAWTOOTH_PERIOD=14;
|
||||
|
||||
public SawtoothTearDrawable(Context context){
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.widget.TextView;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.model.TimelineDefinition;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -44,8 +45,8 @@ public class DiscoverInfoBannerHelper{
|
||||
banner=((Activity)list.getContext()).getLayoutInflater().inflate(R.layout.discover_info_banner, list, false);
|
||||
TextView text=banner.findViewById(R.id.banner_text);
|
||||
text.setText(switch(type){
|
||||
case TRENDING_POSTS -> list.getResources().getString(R.string.trending_posts_info_banner);
|
||||
case TRENDING_LINKS -> list.getResources().getString(R.string.trending_links_info_banner);
|
||||
case TRENDING_POSTS -> list.getResources().getString(R.string.sk_trending_posts_info_banner);
|
||||
case TRENDING_LINKS -> list.getResources().getString(R.string.sk_trending_links_info_banner);
|
||||
case FEDERATED_TIMELINE -> list.getResources().getString(R.string.sk_federated_timeline_info_banner);
|
||||
case POST_NOTIFICATIONS -> list.getResources().getString(R.string.sk_notify_posts_info_banner);
|
||||
case BUBBLE_TIMELINE -> list.getResources().getString(R.string.sk_bubble_timeline_info_banner);
|
||||
@@ -57,8 +58,10 @@ public class DiscoverInfoBannerHelper{
|
||||
case TRENDING_POSTS -> R.drawable.ic_fluent_arrow_trending_24_regular;
|
||||
case TRENDING_LINKS -> R.drawable.ic_fluent_news_24_regular;
|
||||
case ACCOUNTS -> R.drawable.ic_fluent_people_add_24_regular;
|
||||
// no icon because those are displayed as timelines - with icon in top left
|
||||
case LOCAL_TIMELINE, FEDERATED_TIMELINE, BUBBLE_TIMELINE, POST_NOTIFICATIONS -> 0;
|
||||
case LOCAL_TIMELINE -> TimelineDefinition.LOCAL_TIMELINE.getDefaultIcon().iconRes;
|
||||
case FEDERATED_TIMELINE -> TimelineDefinition.FEDERATED_TIMELINE.getDefaultIcon().iconRes;
|
||||
case BUBBLE_TIMELINE -> TimelineDefinition.BUBBLE_TIMELINE.getDefaultIcon().iconRes;
|
||||
case POST_NOTIFICATIONS -> TimelineDefinition.POSTS_TIMELINE.getDefaultIcon().iconRes;
|
||||
});
|
||||
adapter.addAdapter(new SingleViewRecyclerAdapter(banner));
|
||||
}
|
||||
|
||||
@@ -84,24 +84,19 @@ public class InsetStatusItemDecoration extends RecyclerView.ItemDecoration{
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||
List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||
// List<StatusDisplayItem> displayItems=listFragment.getDisplayItems();
|
||||
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
|
||||
if(holder instanceof StatusDisplayItem.Holder<?> sdi){
|
||||
boolean inset=sdi.getItem().inset;
|
||||
int pos=holder.getAbsoluteAdapterPosition();
|
||||
// int pos=holder.getAbsoluteAdapterPosition();
|
||||
if(inset){
|
||||
boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
int pad;
|
||||
// boolean topSiblingInset=pos>0 && displayItems.get(pos-1).inset;
|
||||
// boolean bottomSiblingInset=pos<displayItems.size()-1 && displayItems.get(pos+1).inset;
|
||||
// if(holder instanceof MediaGridStatusDisplayItem.Holder || holder instanceof LinkCardStatusDisplayItem.Holder)
|
||||
pad=V.dp(16);
|
||||
// else
|
||||
// pad=V.dp(12);
|
||||
boolean insetPadding=((StatusDisplayItem.Holder<?>) holder).getItem().insetPadding;
|
||||
if(insetPadding)
|
||||
outRect.left=pad;
|
||||
if(insetPadding)
|
||||
outRect.right=pad;
|
||||
int pad=V.dp(16);
|
||||
// else pad=V.dp(12);
|
||||
outRect.left=pad;
|
||||
outRect.right=pad;
|
||||
|
||||
// had to comment this out because animations with offsets aren't handled properly.
|
||||
// can be worked around by manually applying top margins to items
|
||||
|
||||
@@ -54,6 +54,8 @@ import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
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;
|
||||
@@ -97,7 +99,6 @@ import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
import org.joinmastodon.android.fragments.HashtagTimelineFragment;
|
||||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsServerAboutFragment;
|
||||
import org.joinmastodon.android.fragments.settings.SettingsServerFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.AccountField;
|
||||
@@ -175,6 +176,19 @@ public class UiUtils {
|
||||
private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
|
||||
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() {
|
||||
}
|
||||
|
||||
@@ -194,28 +208,33 @@ public class UiUtils {
|
||||
}
|
||||
|
||||
public static String formatRelativeTimestamp(Context context, Instant instant) {
|
||||
long t = instant.toEpochMilli();
|
||||
long now = System.currentTimeMillis();
|
||||
return formatPeriodBetween(context, instant, null);
|
||||
}
|
||||
|
||||
public static String formatPeriodBetween(Context context, Instant since, Instant until) {
|
||||
boolean ago = until == null;
|
||||
long t = since.toEpochMilli();
|
||||
long now = ago ? System.currentTimeMillis() : until.toEpochMilli();
|
||||
long diff = now - t;
|
||||
if(diff<1000L){
|
||||
return context.getString(R.string.time_now);
|
||||
}else if(diff<60_000L){
|
||||
return context.getString(R.string.time_seconds_ago_short, diff/1000L);
|
||||
return context.getString(ago ? R.string.time_seconds_ago_short : R.string.sk_time_seconds, diff/1000L);
|
||||
}else if(diff<3600_000L){
|
||||
return context.getString(R.string.time_minutes_ago_short, diff/60_000L);
|
||||
return context.getString(ago ? R.string.time_minutes_ago_short : R.string.sk_time_minutes, diff/60_000L);
|
||||
}else if(diff<3600_000L*24L){
|
||||
return context.getString(R.string.time_hours_ago_short, diff/3600_000L);
|
||||
return context.getString(ago ? R.string.time_hours_ago_short : R.string.sk_time_hours, diff/3600_000L);
|
||||
} else {
|
||||
int days = (int) (diff / (3600_000L * 24L));
|
||||
if (days > 30) {
|
||||
ZonedDateTime dt = instant.atZone(ZoneId.systemDefault());
|
||||
if (ago && days > 30) {
|
||||
ZonedDateTime dt = since.atZone(ZoneId.systemDefault());
|
||||
if (dt.getYear() == ZonedDateTime.now().getYear()) {
|
||||
return DATE_FORMATTER_SHORT.format(dt);
|
||||
} else {
|
||||
return DATE_FORMATTER_SHORT_WITH_YEAR.format(dt);
|
||||
}
|
||||
}
|
||||
return context.getString(R.string.time_days_ago_short, days);
|
||||
return context.getString(ago ? R.string.time_days_ago_short : R.string.sk_time_days, days);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user