implement bidirectional missing posts gap
This commit is contained in:
parent
de5165434d
commit
efa003a9a5
|
@ -34,7 +34,6 @@ public class GlobalUserPreferences{
|
|||
public static boolean useCustomTabs;
|
||||
public static boolean altTextReminders, confirmUnfollow, confirmBoost, confirmDeletePost;
|
||||
public static ThemePreference theme;
|
||||
public static LoadMissingPostsPreference loadMissingPosts;
|
||||
|
||||
// MEGALODON
|
||||
public static boolean trueBlackTheme;
|
||||
|
@ -124,7 +123,6 @@ public class GlobalUserPreferences{
|
|||
displayPronounsInUserListings=prefs.getBoolean("displayPronounsInUserListings", true);
|
||||
overlayMedia=prefs.getBoolean("overlayMedia", false);
|
||||
showSuicideHelp=prefs.getBoolean("showSuicideHelp", true);
|
||||
loadMissingPosts=LoadMissingPostsPreference.values()[prefs.getInt("loadMissingItems", 0)];
|
||||
|
||||
if (prefs.contains("prefixRepliesWithRe")) {
|
||||
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
||||
|
@ -184,7 +182,6 @@ public class GlobalUserPreferences{
|
|||
.putBoolean("displayPronounsInUserListings", displayPronounsInUserListings)
|
||||
.putBoolean("overlayMedia", overlayMedia)
|
||||
.putBoolean("showSuicideHelp", showSuicideHelp)
|
||||
.putInt("loadMissingItems", loadMissingPosts.ordinal())
|
||||
.apply();
|
||||
}
|
||||
|
||||
|
@ -279,16 +276,4 @@ public class GlobalUserPreferences{
|
|||
ALWAYS,
|
||||
TO_OTHERS
|
||||
}
|
||||
|
||||
public enum LoadMissingPostsPreference{
|
||||
NEWEST_FIRST(R.string.sk_load_missing_posts_newest_first), // Downwards, default
|
||||
OLDEST_FIRST(R.string.sk_load_missing_posts_oldest_first); // Upwards
|
||||
|
||||
@StringRes
|
||||
public int labelRes;
|
||||
|
||||
LoadMissingPostsPreference(@StringRes int labelRes){
|
||||
this.labelRes=labelRes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -617,7 +617,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();
|
||||
|
|
|
@ -8,23 +8,18 @@ import android.view.View;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
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.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -36,7 +31,6 @@ 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;
|
||||
|
@ -181,19 +175,16 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item){
|
||||
public void onGapClick(GapStatusDisplayItem.Holder item, boolean downwards){
|
||||
if(dataLoading)
|
||||
return;
|
||||
GapStatusDisplayItem gap=item.getItem();
|
||||
gap.loading=true;
|
||||
V.setVisibilityAnimated(item.progress, View.VISIBLE);
|
||||
V.setVisibilityAnimated(item.text, View.GONE);
|
||||
dataLoading=true;
|
||||
|
||||
GlobalUserPreferences.LoadMissingPostsPreference preference = GlobalUserPreferences.loadMissingPosts;
|
||||
String maxID = null;
|
||||
String minID = null;
|
||||
if (preference==GlobalUserPreferences.LoadMissingPostsPreference.NEWEST_FIRST) {
|
||||
if (downwards) {
|
||||
maxID = item.getItemID();
|
||||
} else {
|
||||
int gapPos=displayItems.indexOf(gap);
|
||||
|
@ -220,7 +211,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().putHomeTimeline(Collections.singletonList(gapStatus), false);
|
||||
}
|
||||
}else{
|
||||
if(preference==GlobalUserPreferences.LoadMissingPostsPreference.NEWEST_FIRST) {
|
||||
if(downwards) {
|
||||
Set<String> idsBelowGap=new HashSet<>();
|
||||
boolean belowGap=false;
|
||||
int gapPostIndex=0;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.util.stream.IntStream;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> implements HasAccountID{
|
||||
private ListItem<Void> languageItem, loadMissingPostsItem;
|
||||
private ListItem<Void> languageItem;
|
||||
private CheckableListItem<Void> altTextItem, playGifsItem, customTabsItem, confirmUnfollowItem, confirmBoostItem, confirmDeleteItem;
|
||||
private MastodonLanguage postLanguage;
|
||||
private ComposeLanguageAlertViewController.SelectedOption newPostLanguage;
|
||||
|
@ -46,7 +46,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
|||
|
||||
List<ListItem<Void>> items = new ArrayList<>(List.of(
|
||||
languageItem=new ListItem<>(getString(R.string.default_post_language), postLanguage!=null ? postLanguage.getDisplayName(getContext()) : null, R.drawable.ic_fluent_local_language_24_regular, this::onDefaultLanguageClick),
|
||||
loadMissingPostsItem=new ListItem<>(R.string.sk_settings_load_missing_posts, GlobalUserPreferences.loadMissingPosts.labelRes, R.drawable.ic_fluent_arrow_maximize_vertical_24_regular, this::onLoadMissingPostsClick),
|
||||
altTextItem=new CheckableListItem<>(R.string.settings_alt_text_reminders, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.altTextReminders, R.drawable.ic_fluent_image_alt_text_24_regular, ()->toggleCheckableItem(altTextItem)),
|
||||
playGifsItem=new CheckableListItem<>(R.string.settings_gif, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.playGifs, R.drawable.ic_fluent_gif_24_regular, ()->toggleCheckableItem(playGifsItem)),
|
||||
overlayMediaItem=new CheckableListItem<>(R.string.sk_settings_continues_playback, R.string.sk_settings_continues_playback_summary, CheckableListItem.Style.SWITCH, GlobalUserPreferences.overlayMedia, R.drawable.ic_fluent_play_circle_hint_24_regular, ()->toggleCheckableItem(overlayMediaItem)),
|
||||
|
@ -129,27 +128,6 @@ public class SettingsBehaviorFragment extends BaseSettingsFragment<Void> impleme
|
|||
.show();
|
||||
}
|
||||
|
||||
private void onLoadMissingPostsClick(){
|
||||
GlobalUserPreferences.LoadMissingPostsPreference[] values=GlobalUserPreferences.LoadMissingPostsPreference.values();
|
||||
int selected=GlobalUserPreferences.loadMissingPosts.ordinal();
|
||||
int[] newSelected={selected};
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.sk_settings_load_missing_posts)
|
||||
.setSingleChoiceItems(Stream.of(values).map(pref->getString(pref.labelRes)).toArray(String[]::new),
|
||||
selected, (dlg, item)->newSelected[0]=item)
|
||||
.setPositiveButton(R.string.ok, (dlg, item)->{
|
||||
GlobalUserPreferences.LoadMissingPostsPreference pref=values[newSelected[0]];
|
||||
if(pref!=GlobalUserPreferences.loadMissingPosts){
|
||||
GlobalUserPreferences.loadMissingPosts=pref;
|
||||
GlobalUserPreferences.save();
|
||||
loadMissingPostsItem.subtitleRes=pref.labelRes;
|
||||
rebindItem(loadMissingPostsItem);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void onReplyVisibilityClick(){
|
||||
AccountLocalPreferences lp=getLocalPrefs();
|
||||
int selected=lp.timelineReplyVisibility==null ? 2 : switch(lp.timelineReplyVisibility){
|
||||
|
|
|
@ -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;
|
||||
|
@ -12,8 +15,6 @@ import android.view.View;
|
|||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
@ -56,7 +57,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||
public static class Holder extends StatusDisplayItem.Holder<FooterStatusDisplayItem>{
|
||||
private final TextView replies, boosts, favorites;
|
||||
private final View reply, boost, favorite, share, bookmark;
|
||||
private static final Animation opacityOut, opacityIn;
|
||||
|
||||
private View touchingView = null;
|
||||
private boolean longClickPerformed = false;
|
||||
|
@ -77,18 +77,6 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
|||
}
|
||||
};
|
||||
|
||||
private static final float ALPHA_PRESSED=0.55f;
|
||||
|
||||
static {
|
||||
opacityOut = new AlphaAnimation(1, ALPHA_PRESSED);
|
||||
opacityOut.setDuration(300);
|
||||
opacityOut.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
opacityOut.setFillAfter(true);
|
||||
opacityIn = new AlphaAnimation(ALPHA_PRESSED, 1);
|
||||
opacityIn.setDuration(400);
|
||||
opacityIn.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
||||
}
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(activity, R.layout.display_item_footer, parent);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -135,7 +135,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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -167,7 +167,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
|
||||
|
@ -183,7 +183,7 @@ public abstract class StatusDisplayItem{
|
|||
i -> {
|
||||
args.putString("hashtag", hashtag.name);
|
||||
Nav.go(fragment.getActivity(), HashtagTimelineFragment.class, args);
|
||||
}
|
||||
}, status
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -294,7 +294,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;
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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;
|
||||
|
@ -96,7 +98,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;
|
||||
|
@ -173,6 +174,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() {
|
||||
}
|
||||
|
||||
|
@ -192,28 +206,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<color android:color="?colorM3SurfaceVariant"/>
|
||||
<color android:color="?colorM3SecondaryContainer"/>
|
||||
</item>
|
||||
<item android:drawable="?android:selectableItemBackground"/>
|
||||
</layer-list>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:left="-1dp" android:right="-1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?colorM3Surface" />
|
||||
<stroke android:color="?colorM3OutlineVariant" android:width="1dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||
<path android:pathData="M15.794 8.733c0.286 0.3 0.274 0.774-0.026 1.06l-5.25 5.001c-0.29 0.276-0.745 0.276-1.035 0l-5.25-5c-0.3-0.287-0.312-0.761-0.026-1.061 0.286-0.3 0.76-0.312 1.06-0.026l4.734 4.508 4.733-4.508c0.3-0.286 0.774-0.274 1.06 0.026zm0-4c0.286 0.3 0.274 0.774-0.026 1.06l-5.25 5.001c-0.29 0.276-0.745 0.276-1.035 0l-5.25-5c-0.3-0.287-0.312-0.761-0.026-1.061 0.286-0.3 0.76-0.312 1.06-0.026l4.734 4.509 4.733-4.51c0.3-0.285 0.774-0.273 1.06 0.027z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
|
@ -0,0 +1,3 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="20dp" android:height="20dp" android:viewportWidth="20" android:viewportHeight="20">
|
||||
<path android:pathData="M4.207 15.267c-0.286-0.3-0.274-0.774 0.026-1.06l5.25-5.002c0.29-0.275 0.745-0.275 1.034 0l5.25 5.002c0.3 0.286 0.312 0.76 0.026 1.06-0.286 0.3-0.76 0.312-1.06 0.026L10 10.784l-4.733 4.51c-0.3 0.285-0.774 0.273-1.06-0.027zm0-4.998c-0.286-0.3-0.274-0.775 0.026-1.06l5.25-5.002c0.29-0.276 0.745-0.276 1.034 0l5.25 5.001c0.3 0.286 0.312 0.76 0.026 1.06-0.286 0.3-0.76 0.312-1.06 0.026L10 5.786l-4.733 4.508c-0.3 0.286-0.774 0.275-1.06-0.025z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
|
@ -1,24 +1,79 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="75dp"
|
||||
android:background="@drawable/bg_timeline_gap">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/bg_timeline_gap_border">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
<FrameLayout
|
||||
android:id="@+id/top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:textAppearance="@style/m3_body_large"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:text="@string/load_missing_posts"/>
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_top"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</FrameLayout>
|
||||
<TextView
|
||||
android:id="@+id/text_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:drawablePadding="16dp"
|
||||
android:drawableEnd="@drawable/ic_fluent_chevron_double_down_20_filled"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:text="@string/sk_load_missing_posts_below"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="20dp"
|
||||
android:gravity="center"
|
||||
android:textStyle="italic"
|
||||
android:textColor="?colorM3Primary"
|
||||
android:textAppearance="@style/m3_label_large"
|
||||
android:background="@drawable/bg_timeline_gap"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:background="?android:selectableItemBackground">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bottom"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_bottom"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:drawablePadding="16dp"
|
||||
android:drawableEnd="@drawable/ic_fluent_chevron_double_up_20_filled"
|
||||
android:textAppearance="@style/m3_title_medium"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:text="@string/sk_load_missing_posts_above"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -388,4 +388,10 @@
|
|||
<string name="sk_settings_load_missing_posts">Load missing posts behavior</string>
|
||||
<string name="sk_load_missing_posts_newest_first">Newest first (downwards)</string>
|
||||
<string name="sk_load_missing_posts_oldest_first">Oldest first (upwards)</string>
|
||||
<string name="sk_load_missing_posts_above">Load newer posts</string>
|
||||
<string name="sk_load_missing_posts_below">Load older posts</string>
|
||||
<string name="sk_time_seconds">%d seconds</string>
|
||||
<string name="sk_time_minutes">%d minutes</string>
|
||||
<string name="sk_time_hours">%d hours</string>
|
||||
<string name="sk_time_days">%d days</string>
|
||||
</resources>
|
Loading…
Reference in New Issue