Refresh message timestamp every minute (#1113)

* Refresh message timestamp every minute

* Refresh timeline adapter every minute via differ
This commit is contained in:
pandasoft0 2019-03-16 16:38:29 +03:00 committed by Konrad Pozniak
parent d0f7f6f83c
commit 7680b1b529
5 changed files with 182 additions and 104 deletions

View file

@ -44,7 +44,9 @@ import at.connyduck.sparkbutton.SparkEventListener;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder { public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
public static class Key{
public static final String KEY_CREATED = "created";
}
private TextView displayName; private TextView displayName;
private TextView username; private TextView username;
private ImageButton replyButton; private ImageButton replyButton;
@ -538,45 +540,60 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener, protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
boolean mediaPreviewEnabled) { boolean mediaPreviewEnabled) {
setDisplayName(status.getUserFullName(), status.getAccountEmojis()); this.setupWithStatus(status,listener,mediaPreviewEnabled,null);
setUsername(status.getNickname()); }
setCreatedAt(status.getCreatedAt()); protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
setIsReply(status.getInReplyToId() != null); boolean mediaPreviewEnabled, @Nullable Object payloads) {
setAvatar(status.getAvatar(), status.getRebloggedAvatar()); if (payloads == null) {
setReblogged(status.isReblogged()); setDisplayName(status.getUserFullName(), status.getAccountEmojis());
setFavourited(status.isFavourited()); setUsername(status.getNickname());
List<Attachment> attachments = status.getAttachments(); setCreatedAt(status.getCreatedAt());
boolean sensitive = status.isSensitive(); setIsReply(status.getInReplyToId() != null);
if (mediaPreviewEnabled) { setAvatar(status.getAvatar(), status.getRebloggedAvatar());
setMediaPreviews(attachments, sensitive, listener, status.isShowingContent()); setReblogged(status.isReblogged());
setFavourited(status.isFavourited());
List<Attachment> attachments = status.getAttachments();
boolean sensitive = status.isSensitive();
if (mediaPreviewEnabled) {
setMediaPreviews(attachments, sensitive, listener, status.isShowingContent());
if (attachments.size() == 0) { if (attachments.size() == 0) {
hideSensitiveMediaWarning();
}
// Hide the unused label.
mediaLabel.setVisibility(View.GONE);
} else {
setMediaLabel(attachments, sensitive, listener);
// Hide all unused views.
mediaPreviews[0].setVisibility(View.GONE);
mediaPreviews[1].setVisibility(View.GONE);
mediaPreviews[2].setVisibility(View.GONE);
mediaPreviews[3].setVisibility(View.GONE);
hideSensitiveMediaWarning(); hideSensitiveMediaWarning();
} }
// Hide the unused label.
mediaLabel.setVisibility(View.GONE); setupButtons(listener, status.getSenderId());
} else { setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
setMediaLabel(attachments, sensitive, listener);
// Hide all unused views. setSpoilerAndContent(status.isExpanded(), status.getContent(), status.getSpoilerText(), status.getMentions(), status.getStatusEmojis(), listener);
mediaPreviews[0].setVisibility(View.GONE);
mediaPreviews[1].setVisibility(View.GONE); setContentDescription(status);
mediaPreviews[2].setVisibility(View.GONE); // Workaround for RecyclerView 1.0.0 / androidx.core 1.0.0
mediaPreviews[3].setVisibility(View.GONE); // RecyclerView tries to set AccessibilityDelegateCompat to null
hideSensitiveMediaWarning(); // but ViewCompat code replaces is with the default one. RecyclerView never
// fetches another one from its delegate because it checks that it's set so we remove it
// and let RecyclerView ask for a new delegate.
itemView.setAccessibilityDelegate(null);
} }
else{
if (payloads instanceof List)
for (Object item:(List)payloads) {
if (Key.KEY_CREATED.equals(item)){
setCreatedAt(status.getCreatedAt());
}
}
setupButtons(listener, status.getSenderId()); }
setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
setSpoilerAndContent(status.isExpanded(), status.getContent(), status.getSpoilerText(), status.getMentions(), status.getStatusEmojis(), listener);
setContentDescription(status);
// Workaround for RecyclerView 1.0.0 / androidx.core 1.0.0
// RecyclerView tries to set AccessibilityDelegateCompat to null
// but ViewCompat code replaces is with the default one. RecyclerView never
// fetches another one from its delegate because it checks that it's set so we remove it
// and let RecyclerView ask for a new delegate.
itemView.setAccessibilityDelegate(null);
} }

View file

@ -125,74 +125,75 @@ class StatusDetailedViewHolder extends StatusBaseViewHolder {
@Override @Override
protected void setupWithStatus(final StatusViewData.Concrete status, final StatusActionListener listener, protected void setupWithStatus(final StatusViewData.Concrete status, final StatusActionListener listener,
boolean mediaPreviewEnabled) { boolean mediaPreviewEnabled, @Nullable Object payloads) {
super.setupWithStatus(status, listener, mediaPreviewEnabled); super.setupWithStatus(status, listener, mediaPreviewEnabled,payloads);
if (payloads==null) {
setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener);
setReblogAndFavCount(status.getReblogsCount(), status.getFavouritesCount(), listener); setApplication(status.getApplication());
setApplication(status.getApplication());
View.OnLongClickListener longClickListener = view -> { View.OnLongClickListener longClickListener = view -> {
TextView textView = (TextView) view; TextView textView = (TextView) view;
ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) view.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("toot", textView.getText()); ClipData clip = ClipData.newPlainText("toot", textView.getText());
clipboard.setPrimaryClip(clip); clipboard.setPrimaryClip(clip);
Toast.makeText(view.getContext(), R.string.copy_to_clipboard_success, Toast.LENGTH_SHORT).show(); Toast.makeText(view.getContext(), R.string.copy_to_clipboard_success, Toast.LENGTH_SHORT).show();
return true; return true;
}; };
content.setOnLongClickListener(longClickListener); content.setOnLongClickListener(longClickListener);
contentWarningDescription.setOnLongClickListener(longClickListener); contentWarningDescription.setOnLongClickListener(longClickListener);
if (status.getAttachments().size() == 0 && status.getCard() != null && !TextUtils.isEmpty(status.getCard().getUrl())) { if (status.getAttachments().size() == 0 && status.getCard() != null && !TextUtils.isEmpty(status.getCard().getUrl())) {
final Card card = status.getCard(); final Card card = status.getCard();
cardView.setVisibility(View.VISIBLE); cardView.setVisibility(View.VISIBLE);
cardTitle.setText(card.getTitle()); cardTitle.setText(card.getTitle());
cardDescription.setText(card.getDescription()); cardDescription.setText(card.getDescription());
cardUrl.setText(card.getUrl()); cardUrl.setText(card.getUrl());
if (card.getWidth() > 0 && card.getHeight() > 0 && !TextUtils.isEmpty(card.getImage())) { if (card.getWidth() > 0 && card.getHeight() > 0 && !TextUtils.isEmpty(card.getImage())) {
cardImage.setVisibility(View.VISIBLE); cardImage.setVisibility(View.VISIBLE);
if (card.getWidth() > card.getHeight()) {
cardView.setOrientation(LinearLayout.VERTICAL);
cardImage.getLayoutParams().height = cardImage.getContext().getResources()
.getDimensionPixelSize(R.dimen.card_image_vertical_height);
cardImage.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
cardView.setOrientation(LinearLayout.HORIZONTAL);
cardImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
cardImage.getLayoutParams().width = cardImage.getContext().getResources()
.getDimensionPixelSize(R.dimen.card_image_horizontal_width);
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
}
cardView.setClipToOutline(true);
Picasso.with(cardImage.getContext())
.load(card.getImage())
.fit()
.centerCrop()
.into(cardImage);
if (card.getWidth() > card.getHeight()) {
cardView.setOrientation(LinearLayout.VERTICAL);
cardImage.getLayoutParams().height = cardImage.getContext().getResources()
.getDimensionPixelSize(R.dimen.card_image_vertical_height);
cardImage.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
} else { } else {
cardView.setOrientation(LinearLayout.HORIZONTAL); cardImage.setVisibility(View.GONE);
cardImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
cardImage.getLayoutParams().width = cardImage.getContext().getResources()
.getDimensionPixelSize(R.dimen.card_image_horizontal_width);
cardInfo.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
cardInfo.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
} }
cardView.setClipToOutline(true); cardView.setOnClickListener(v -> LinkHelper.openLink(card.getUrl(), v.getContext()));
Picasso.with(cardImage.getContext())
.load(card.getImage())
.fit()
.centerCrop()
.into(cardImage);
} else { } else {
cardImage.setVisibility(View.GONE); cardView.setVisibility(View.GONE);
} }
cardView.setOnClickListener(v -> LinkHelper.openLink(card.getUrl(), v.getContext())); setStatusVisibility(status.getVisibility());
} else {
cardView.setVisibility(View.GONE);
} }
setStatusVisibility(status.getVisibility());
} }
private void setStatusVisibility(Status.Visibility visibility) { private void setStatusVisibility(Status.Visibility visibility) {

View file

@ -75,22 +75,26 @@ public class StatusViewHolder extends StatusBaseViewHolder {
@Override @Override
protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener, protected void setupWithStatus(StatusViewData.Concrete status, final StatusActionListener listener,
boolean mediaPreviewEnabled) { boolean mediaPreviewEnabled, @Nullable Object payloads) {
if(status == null) { if (status == null || payloads==null) {
showContent(false); if (status == null) {
} else { showContent(false);
showContent(true);
setupCollapsedState(status, listener);
super.setupWithStatus(status, listener, mediaPreviewEnabled);
String rebloggedByDisplayName = status.getRebloggedByUsername();
if (rebloggedByDisplayName == null) {
hideRebloggedByDisplayName();
} else { } else {
setRebloggedByDisplayName(rebloggedByDisplayName); showContent(true);
} setupCollapsedState(status, listener);
super.setupWithStatus(status, listener, mediaPreviewEnabled, null);
rebloggedBar.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition())); String rebloggedByDisplayName = status.getRebloggedByUsername();
if (rebloggedByDisplayName == null) {
hideRebloggedByDisplayName();
} else {
setRebloggedByDisplayName(rebloggedByDisplayName);
}
rebloggedBar.setOnClickListener(v -> listener.onOpenReblog(getAdapterPosition()));}
}
else{
super.setupWithStatus(status, listener, mediaPreviewEnabled, payloads);
} }
} }

View file

@ -16,11 +16,14 @@
package com.keylesspalace.tusky.adapter; package com.keylesspalace.tusky.adapter;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import java.util.List;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.viewdata.StatusViewData; import com.keylesspalace.tusky.viewdata.StatusViewData;
@ -70,17 +73,25 @@ public final class TimelineAdapter extends RecyclerView.Adapter {
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
bindViewHolder(viewHolder,position,null);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @NonNull List payloads) {
bindViewHolder(viewHolder,position,payloads);
}
private void bindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position, @Nullable List payloads){
StatusViewData status = dataSource.getItemAt(position); StatusViewData status = dataSource.getItemAt(position);
if (status instanceof StatusViewData.Placeholder) { if (status instanceof StatusViewData.Placeholder) {
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder; PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(statusListener, ((StatusViewData.Placeholder) status).isLoading()); holder.setup(statusListener, ((StatusViewData.Placeholder) status).isLoading());
} else { } else if (status instanceof StatusViewData.Concrete) {
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
holder.setupWithStatus((StatusViewData.Concrete) status, holder.setupWithStatus((StatusViewData.Concrete)status,statusListener, mediaPreviewEnabled,payloads!=null&&!payloads.isEmpty()?payloads.get(0):null);
statusListener, mediaPreviewEnabled);
} }
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return dataSource.getItemCount(); return dataSource.getItemCount();

View file

@ -28,9 +28,11 @@ import android.widget.ProgressBar;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import io.reactivex.Observable;
import com.keylesspalace.tusky.AccountListActivity; import com.keylesspalace.tusky.AccountListActivity;
import com.keylesspalace.tusky.BaseActivity; import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder;
import com.keylesspalace.tusky.adapter.TimelineAdapter; import com.keylesspalace.tusky.adapter.TimelineAdapter;
import com.keylesspalace.tusky.appstore.BlockEvent; import com.keylesspalace.tusky.appstore.BlockEvent;
import com.keylesspalace.tusky.appstore.EventHub; import com.keylesspalace.tusky.appstore.EventHub;
@ -63,10 +65,12 @@ import com.keylesspalace.tusky.view.EndlessOnScrollListener;
import com.keylesspalace.tusky.viewdata.StatusViewData; import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -1266,7 +1270,48 @@ public class TimelineFragment extends SFragment implements
@Override @Override
public boolean areContentsTheSame(StatusViewData oldItem, @NonNull StatusViewData newItem) { public boolean areContentsTheSame(StatusViewData oldItem, @NonNull StatusViewData newItem) {
return oldItem.deepEquals(newItem); return false; //Items are different always. It allows to refresh timestamp on every view holder update
}
@Nullable
@Override
public Object getChangePayload(@NonNull StatusViewData oldItem, @NonNull StatusViewData newItem) {
if (oldItem.deepEquals(newItem)){
//If items are equal - update timestamp only
List<String> payload = new ArrayList<>();
payload.add(StatusBaseViewHolder.Key.KEY_CREATED);
return payload;
}
else
// If items are different - update a whole view holder
return null;
} }
}; };
@Override
public void onResume() {
super.onResume();
startUpdateTimestamp();
}
/**
* Start to update adapter every minute to refresh timestamp
* If setting absoluteTimeView is false
* Auto dispose observable on pause
*/
private void startUpdateTimestamp() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
boolean useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false);
if (!useAbsoluteTime) {
Observable.interval(1, TimeUnit.MINUTES)
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(from(this, Lifecycle.Event.ON_PAUSE)))
.subscribe(
interval -> updateAdapter()
);
}
}
} }