mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-30 23:56:27 +00:00
Add mentions unread counter.
This commit is contained in:
committed by
Greyson Parrelli
parent
3c90dfa660
commit
06eadd0c15
@@ -30,7 +30,7 @@ public interface BindableConversationItem extends Unbindable {
|
||||
@NonNull Set<ConversationMessage> batchSelected,
|
||||
@NonNull Recipient recipients,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseHighlight);
|
||||
boolean pulseMention);
|
||||
|
||||
ConversationMessage getConversationMessage();
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ public class ConversationItemThumbnail extends FrameLayout {
|
||||
private ConversationItemFooter footer;
|
||||
private CornerMask cornerMask;
|
||||
private Outliner outliner;
|
||||
private Outliner pulseOutliner;
|
||||
private boolean borderless;
|
||||
|
||||
public ConversationItemThumbnail(Context context) {
|
||||
@@ -80,6 +81,14 @@ public class ConversationItemThumbnail extends FrameLayout {
|
||||
outliner.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
if (pulseOutliner != null) {
|
||||
pulseOutliner.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPulseOutliner(@NonNull Outliner outliner) {
|
||||
this.pulseOutliner = outliner;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public final class ConversationScrollToView extends FrameLayout {
|
||||
|
||||
private final TextView unreadCount;
|
||||
private final ImageView scrollButton;
|
||||
|
||||
public ConversationScrollToView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ConversationScrollToView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public ConversationScrollToView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
inflate(context, R.layout.conversation_scroll_to, this);
|
||||
|
||||
unreadCount = findViewById(R.id.conversation_scroll_to_count);
|
||||
scrollButton = findViewById(R.id.conversation_scroll_to_button);
|
||||
|
||||
if (attrs != null) {
|
||||
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ConversationScrollToView);
|
||||
Drawable src = array.getDrawable(R.styleable.ConversationScrollToView_cstv_scroll_button_src);
|
||||
|
||||
scrollButton.setImageDrawable(src);
|
||||
|
||||
array.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(@Nullable OnClickListener l) {
|
||||
scrollButton.setOnClickListener(l);
|
||||
}
|
||||
|
||||
public void setUnreadCount(int unreadCount) {
|
||||
this.unreadCount.setText(formatUnreadCount(unreadCount));
|
||||
this.unreadCount.setVisibility(unreadCount > 0 ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
private @NonNull CharSequence formatUnreadCount(int unreadCount) {
|
||||
return unreadCount > 999 ? "999+" : String.valueOf(unreadCount);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,14 @@ public class Outliner {
|
||||
outlinePaint.setColor(color);
|
||||
}
|
||||
|
||||
public void setStrokeWidth(float pixels) {
|
||||
outlinePaint.setStrokeWidth(pixels);
|
||||
}
|
||||
|
||||
public void setAlpha(int alpha) {
|
||||
outlinePaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas) {
|
||||
draw(canvas, 0, canvas.getWidth(), canvas.getHeight(), 0);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@@ -96,7 +95,7 @@ public class ConversationAdapter
|
||||
private final MessageDigest digest;
|
||||
|
||||
private String searchQuery;
|
||||
private ConversationMessage recordToPulseHighlight;
|
||||
private ConversationMessage recordToPulse;
|
||||
private View headerView;
|
||||
private View footerView;
|
||||
|
||||
@@ -228,10 +227,10 @@ public class ConversationAdapter
|
||||
selected,
|
||||
recipient,
|
||||
searchQuery,
|
||||
conversationMessage == recordToPulseHighlight);
|
||||
conversationMessage == recordToPulse);
|
||||
|
||||
if (conversationMessage == recordToPulseHighlight) {
|
||||
recordToPulseHighlight = null;
|
||||
if (conversationMessage == recordToPulse) {
|
||||
recordToPulse = null;
|
||||
}
|
||||
break;
|
||||
case MESSAGE_TYPE_HEADER:
|
||||
@@ -384,13 +383,13 @@ public class ConversationAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* Momentarily highlights a row at the requested position.
|
||||
* Momentarily highlights a mention at the requested position.
|
||||
*/
|
||||
void pulseHighlightItem(int position) {
|
||||
void pulseAtPosition(int position) {
|
||||
if (position >= 0 && position < getItemCount()) {
|
||||
int correctedPosition = isHeaderPosition(position) ? position + 1 : position;
|
||||
|
||||
recordToPulseHighlight = getItem(correctedPosition);
|
||||
recordToPulse = getItem(correctedPosition);
|
||||
notifyItemChanged(correctedPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.ConversationScrollToView;
|
||||
import org.thoughtcrime.securesms.components.ConversationTypingView;
|
||||
import org.thoughtcrime.securesms.components.TooltipPopup;
|
||||
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
|
||||
@@ -77,6 +78,7 @@ import org.thoughtcrime.securesms.conversation.ConversationAdapter.StickyHeaderV
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
@@ -133,7 +135,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@@ -161,14 +162,22 @@ public class ConversationFragment extends LoggingFragment {
|
||||
private ConversationTypingView typingView;
|
||||
private UnknownSenderView unknownSenderView;
|
||||
private View composeDivider;
|
||||
private View scrollToBottomButton;
|
||||
private ConversationScrollToView scrollToBottomButton;
|
||||
private ConversationScrollToView scrollToMentionButton;
|
||||
private TextView scrollDateHeader;
|
||||
private ConversationBannerView conversationBanner;
|
||||
private ConversationBannerView emptyConversationBanner;
|
||||
private MessageRequestViewModel messageRequestViewModel;
|
||||
private MessageCountsViewModel messageCountsViewModel;
|
||||
private ConversationViewModel conversationViewModel;
|
||||
private SnapToTopDataObserver snapToTopDataObserver;
|
||||
private MarkReadHelper markReadHelper;
|
||||
private Animation scrollButtonInAnimation;
|
||||
private Animation mentionButtonInAnimation;
|
||||
private Animation scrollButtonOutAnimation;
|
||||
private Animation mentionButtonOutAnimation;
|
||||
private OnScrollListener conversationScrollListener;
|
||||
private int pulsePosition = -1;
|
||||
|
||||
public static void prepare(@NonNull Context context) {
|
||||
FrameLayout parent = new FrameLayout(context);
|
||||
@@ -191,13 +200,13 @@ public class ConversationFragment extends LoggingFragment {
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
|
||||
list = ViewUtil.findById(view, android.R.id.list);
|
||||
composeDivider = ViewUtil.findById(view, R.id.compose_divider);
|
||||
scrollToBottomButton = ViewUtil.findById(view, R.id.scroll_to_bottom_button);
|
||||
scrollDateHeader = ViewUtil.findById(view, R.id.scroll_date_header);
|
||||
emptyConversationBanner = ViewUtil.findById(view, R.id.empty_conversation_banner);
|
||||
list = view.findViewById(android.R.id.list);
|
||||
composeDivider = view.findViewById(R.id.compose_divider);
|
||||
|
||||
scrollToBottomButton.setOnClickListener(v -> scrollToBottom());
|
||||
scrollToBottomButton = view.findViewById(R.id.scroll_to_bottom);
|
||||
scrollToMentionButton = view.findViewById(R.id.scroll_to_mention);
|
||||
scrollDateHeader = view.findViewById(R.id.scroll_date_header);
|
||||
emptyConversationBanner = view.findViewById(R.id.empty_conversation_banner);
|
||||
|
||||
final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true);
|
||||
list.setHasFixedSize(false);
|
||||
@@ -222,7 +231,9 @@ public class ConversationFragment extends LoggingFragment {
|
||||
|
||||
setupListLayoutListeners();
|
||||
|
||||
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
|
||||
this.messageCountsViewModel = ViewModelProviders.of(requireActivity()).get(MessageCountsViewModel.class);
|
||||
this.conversationViewModel = ViewModelProviders.of(requireActivity(), new ConversationViewModel.Factory()).get(ConversationViewModel.class);
|
||||
|
||||
conversationViewModel.getMessages().observe(this, list -> {
|
||||
if (getListAdapter() != null && !list.getDataSource().isInvalid()) {
|
||||
Log.i(TAG, "submitList");
|
||||
@@ -233,6 +244,25 @@ public class ConversationFragment extends LoggingFragment {
|
||||
});
|
||||
conversationViewModel.getConversationMetadata().observe(this, this::presentConversationMetadata);
|
||||
|
||||
conversationViewModel.getShowMentionsButton().observe(this, shouldShow -> {
|
||||
if (shouldShow) {
|
||||
ViewUtil.animateIn(scrollToMentionButton, mentionButtonInAnimation);
|
||||
} else {
|
||||
ViewUtil.animateOut(scrollToMentionButton, mentionButtonOutAnimation, View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
conversationViewModel.getShowScrollToBottom().observe(this, shouldShow -> {
|
||||
if (shouldShow) {
|
||||
ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation);
|
||||
} else {
|
||||
ViewUtil.animateOut(scrollToBottomButton, scrollButtonOutAnimation, View.INVISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
scrollToBottomButton.setOnClickListener(v -> scrollToBottom());
|
||||
scrollToMentionButton.setOnClickListener(v -> scrollToNextMention());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -268,6 +298,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
public void onActivityCreated(Bundle bundle) {
|
||||
super.onActivityCreated(bundle);
|
||||
|
||||
initializeScrollButtonAnimations();
|
||||
initializeResources();
|
||||
initializeMessageRequestViewModel();
|
||||
initializeListAdapter();
|
||||
@@ -426,9 +457,16 @@ public class ConversationFragment extends LoggingFragment {
|
||||
this.markReadHelper = new MarkReadHelper(threadId, requireContext());
|
||||
|
||||
conversationViewModel.onConversationDataAvailable(threadId, startingPosition);
|
||||
messageCountsViewModel.setThreadId(threadId);
|
||||
|
||||
OnScrollListener scrollListener = new ConversationScrollListener(getActivity());
|
||||
list.addOnScrollListener(scrollListener);
|
||||
messageCountsViewModel.getUnreadMessagesCount().observe(getViewLifecycleOwner(), scrollToBottomButton::setUnreadCount);
|
||||
messageCountsViewModel.getUnreadMentionsCount().observe(getViewLifecycleOwner(), count -> {
|
||||
scrollToMentionButton.setUnreadCount(count);
|
||||
conversationViewModel.setHasUnreadMentions(count > 0);
|
||||
});
|
||||
|
||||
conversationScrollListener = new ConversationScrollListener(requireContext());
|
||||
list.addOnScrollListener(conversationScrollListener);
|
||||
|
||||
if (oldThreadId != threadId) {
|
||||
ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(oldThreadId).removeObservers(this);
|
||||
@@ -566,6 +604,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
|
||||
snapToTopDataObserver.requestScrollPosition(0);
|
||||
conversationViewModel.onConversationDataAvailable(threadId, -1);
|
||||
messageCountsViewModel.setThreadId(threadId);
|
||||
initializeListAdapter();
|
||||
}
|
||||
}
|
||||
@@ -655,6 +694,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
if (threadDeleted) {
|
||||
threadId = -1;
|
||||
conversationViewModel.clearThreadId();
|
||||
messageCountsViewModel.clearThreadId();
|
||||
listener.setThreadId(threadId);
|
||||
}
|
||||
}
|
||||
@@ -695,6 +735,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
if (threadDeleted) {
|
||||
threadId = -1;
|
||||
conversationViewModel.clearThreadId();
|
||||
messageCountsViewModel.clearThreadId();
|
||||
listener.setThreadId(threadId);
|
||||
}
|
||||
}
|
||||
@@ -920,6 +961,8 @@ public class ConversationFragment extends LoggingFragment {
|
||||
}
|
||||
|
||||
listener.onCursorChanged();
|
||||
|
||||
conversationScrollListener.onScrolled(list, 0, 0);
|
||||
};
|
||||
|
||||
int lastSeenPosition = adapter.getAdapterPositionForMessagePosition(conversation.getLastSeenPosition());
|
||||
@@ -931,7 +974,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
snapToTopDataObserver.buildScrollPosition(conversation.getJumpToPosition())
|
||||
.withOnScrollRequestComplete(() -> {
|
||||
afterScroll.run();
|
||||
getListAdapter().pulseHighlightItem(conversation.getJumpToPosition());
|
||||
getListAdapter().pulseAtPosition(conversation.getJumpToPosition());
|
||||
})
|
||||
.submit();
|
||||
} else if (conversation.isMessageRequestAccepted()) {
|
||||
@@ -969,27 +1012,40 @@ public class ConversationFragment extends LoggingFragment {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("CodeBlock2Expr")
|
||||
public void jumpToMessage(@NonNull RecipientId author, long timestamp, @Nullable Runnable onMessageNotFound) {
|
||||
SimpleTask.run(getLifecycle(), () -> {
|
||||
return DatabaseFactory.getMmsSmsDatabase(getContext())
|
||||
.getMessagePositionInConversation(threadId, timestamp, author);
|
||||
}, p -> moveToMessagePosition(p + (isTypingIndicatorShowing() ? 1 : 0), onMessageNotFound));
|
||||
}, p -> moveToPosition(p + (isTypingIndicatorShowing() ? 1 : 0), onMessageNotFound));
|
||||
}
|
||||
|
||||
private void moveToMessagePosition(int position, @Nullable Runnable onMessageNotFound) {
|
||||
private void moveToPosition(int position, @Nullable Runnable onMessageNotFound) {
|
||||
conversationViewModel.onConversationDataAvailable(threadId, position);
|
||||
snapToTopDataObserver.buildScrollPosition(position)
|
||||
.withOnPerformScroll(((layoutManager, p) ->
|
||||
list.post(() -> {
|
||||
layoutManager.scrollToPosition(p);
|
||||
getListAdapter().pulseHighlightItem(position);
|
||||
})
|
||||
list.post(() -> {
|
||||
if (Math.abs(layoutManager.findFirstVisibleItemPosition() - p) < SCROLL_ANIMATION_THRESHOLD) {
|
||||
View child = layoutManager.findViewByPosition(position);
|
||||
|
||||
if (child != null && layoutManager.isViewPartiallyVisible(child, true, false)) {
|
||||
getListAdapter().pulseAtPosition(position);
|
||||
} else {
|
||||
pulsePosition = position;
|
||||
}
|
||||
|
||||
list.smoothScrollToPosition(p);
|
||||
} else {
|
||||
layoutManager.scrollToPosition(p);
|
||||
getListAdapter().pulseAtPosition(position);
|
||||
}
|
||||
})
|
||||
))
|
||||
.withOnInvalidPosition(() -> {
|
||||
if (onMessageNotFound != null) {
|
||||
onMessageNotFound.run();
|
||||
}
|
||||
Log.w(TAG, "[moveToMessagePosition] Tried to navigate to message, but it wasn't found.");
|
||||
Log.w(TAG, "[moveToMentionPosition] Tried to navigate to mention, but it wasn't found.");
|
||||
})
|
||||
.submit();
|
||||
}
|
||||
@@ -1008,6 +1064,48 @@ public class ConversationFragment extends LoggingFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeScrollButtonAnimations() {
|
||||
scrollButtonInAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_scale_in);
|
||||
scrollButtonOutAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_scale_out);
|
||||
|
||||
mentionButtonInAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_scale_in);
|
||||
mentionButtonOutAnimation = AnimationUtils.loadAnimation(requireContext(), R.anim.fade_scale_out);
|
||||
|
||||
scrollButtonInAnimation.setDuration(100);
|
||||
scrollButtonOutAnimation.setDuration(50);
|
||||
|
||||
mentionButtonInAnimation.setDuration(100);
|
||||
mentionButtonOutAnimation.setDuration(50);
|
||||
}
|
||||
|
||||
private void scrollToNextMention() {
|
||||
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
|
||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(ApplicationDependencies.getApplication());
|
||||
return mmsDatabase.getOldestUnreadMentionDetails(threadId);
|
||||
}, (pair) -> {
|
||||
if (pair != null) {
|
||||
jumpToMessage(pair.first, pair.second, () -> {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void postMarkAsReadRequest() {
|
||||
if (getListAdapter().hasNoConversationMessages()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int position = getListLayoutManager().findFirstVisibleItemPosition();
|
||||
if (position >= (isTypingIndicatorShowing() ? 1 : 0)) {
|
||||
ConversationMessage item = getListAdapter().getItem(position);
|
||||
if (item != null) {
|
||||
long timestamp = item.getMessageRecord()
|
||||
.getDateReceived();
|
||||
|
||||
markReadHelper.onViewsRevealed(timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ConversationFragmentListener {
|
||||
void setThreadId(long threadId);
|
||||
void handleReplyMessage(ConversationMessage conversationMessage);
|
||||
@@ -1026,47 +1124,40 @@ public class ConversationFragment extends LoggingFragment {
|
||||
|
||||
private class ConversationScrollListener extends OnScrollListener {
|
||||
|
||||
private final Animation scrollButtonInAnimation;
|
||||
private final Animation scrollButtonOutAnimation;
|
||||
private final ConversationDateHeader conversationDateHeader;
|
||||
|
||||
private boolean wasAtBottom = true;
|
||||
private boolean wasAtZoomScrollHeight = false;
|
||||
private long lastPositionId = -1;
|
||||
|
||||
ConversationScrollListener(@NonNull Context context) {
|
||||
this.scrollButtonInAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_in);
|
||||
this.scrollButtonOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_out);
|
||||
this.conversationDateHeader = new ConversationDateHeader(context, scrollDateHeader);
|
||||
|
||||
this.scrollButtonInAnimation.setDuration(100);
|
||||
this.scrollButtonOutAnimation.setDuration(50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull final RecyclerView rv, final int dx, final int dy) {
|
||||
boolean currentlyAtBottom = isAtBottom();
|
||||
boolean currentlyAtBottom = !rv.canScrollVertically(1);
|
||||
boolean currentlyAtZoomScrollHeight = isAtZoomScrollHeight();
|
||||
int positionId = getHeaderPositionId();
|
||||
|
||||
if (currentlyAtBottom && !wasAtBottom) {
|
||||
ViewUtil.fadeOut(composeDivider, 50, View.INVISIBLE);
|
||||
ViewUtil.animateOut(scrollToBottomButton, scrollButtonOutAnimation, View.INVISIBLE);
|
||||
} else if (!currentlyAtBottom && wasAtBottom) {
|
||||
ViewUtil.fadeIn(composeDivider, 500);
|
||||
}
|
||||
|
||||
if (currentlyAtZoomScrollHeight && !wasAtZoomScrollHeight) {
|
||||
ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation);
|
||||
if (currentlyAtBottom) {
|
||||
conversationViewModel.setShowScrollButtons(false);
|
||||
} else if (currentlyAtZoomScrollHeight) {
|
||||
conversationViewModel.setShowScrollButtons(true);
|
||||
}
|
||||
|
||||
if (positionId != lastPositionId) {
|
||||
bindScrollHeader(conversationDateHeader, positionId);
|
||||
}
|
||||
|
||||
wasAtBottom = currentlyAtBottom;
|
||||
wasAtZoomScrollHeight = currentlyAtZoomScrollHeight;
|
||||
lastPositionId = positionId;
|
||||
wasAtBottom = currentlyAtBottom;
|
||||
lastPositionId = positionId;
|
||||
|
||||
postMarkAsReadRequest();
|
||||
}
|
||||
@@ -1077,22 +1168,10 @@ public class ConversationFragment extends LoggingFragment {
|
||||
conversationDateHeader.show();
|
||||
} else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
conversationDateHeader.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void postMarkAsReadRequest() {
|
||||
if (getListAdapter().hasNoConversationMessages()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int position = getListLayoutManager().findFirstVisibleItemPosition();
|
||||
if (position >= (isTypingIndicatorShowing() ? 1 : 0)) {
|
||||
ConversationMessage item = getListAdapter().getItem(position);
|
||||
if (item != null) {
|
||||
long timestamp = item.getMessageRecord()
|
||||
.getDateReceived();
|
||||
|
||||
markReadHelper.onViewsRevealed(timestamp);
|
||||
if (pulsePosition != -1) {
|
||||
getListAdapter().pulseAtPosition(pulsePosition);
|
||||
pulsePosition = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1175,7 +1254,7 @@ public class ConversationFragment extends LoggingFragment {
|
||||
.getQuotedMessagePosition(threadId,
|
||||
messageRecord.getQuote().getId(),
|
||||
messageRecord.getQuote().getAuthor());
|
||||
}, p -> moveToMessagePosition(p + (isTypingIndicatorShowing() ? 1 : 0), () -> {
|
||||
}, p -> moveToPosition(p + (isTypingIndicatorShowing() ? 1 : 0), () -> {
|
||||
Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_no_longer_available, Toast.LENGTH_SHORT).show();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
@@ -119,6 +120,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -151,6 +153,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
private boolean groupThread;
|
||||
private LiveRecipient recipient;
|
||||
private GlideRequests glideRequests;
|
||||
private ValueAnimator pulseOutlinerAlphaAnimator;
|
||||
|
||||
protected ConversationItemBodyBubble bodyBubble;
|
||||
protected View reply;
|
||||
@@ -167,8 +170,10 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
private ViewGroup container;
|
||||
protected ReactionsConversationView reactionsView;
|
||||
|
||||
private @NonNull Set<ConversationMessage> batchSelected = new HashSet<>();
|
||||
private @NonNull Outliner outliner = new Outliner();
|
||||
private @NonNull Set<ConversationMessage> batchSelected = new HashSet<>();
|
||||
private @NonNull Outliner outliner = new Outliner();
|
||||
private @NonNull Outliner pulseOutliner = new Outliner();
|
||||
private @NonNull List<Outliner> outliners = new ArrayList<>(2);
|
||||
private LiveRecipient conversationRecipient;
|
||||
private Stub<ConversationItemThumbnail> mediaThumbnailStub;
|
||||
private Stub<AudioView> audioViewStub;
|
||||
@@ -249,7 +254,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
@NonNull Set<ConversationMessage> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseHighlight)
|
||||
boolean pulse)
|
||||
{
|
||||
if (this.recipient != null) this.recipient.removeForeverObserver(this);
|
||||
if (this.conversationRecipient != null) this.conversationRecipient.removeForeverObserver(this);
|
||||
@@ -271,9 +276,9 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
setGutterSizes(messageRecord, groupThread);
|
||||
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread);
|
||||
setInteractionState(conversationMessage, pulseHighlight);
|
||||
setBodyText(messageRecord, searchQuery);
|
||||
setBubbleState(messageRecord);
|
||||
setInteractionState(conversationMessage, pulse);
|
||||
setStatusIcons(messageRecord);
|
||||
setContactPhoto(recipient.get());
|
||||
setGroupMessageStatus(messageRecord, recipient.get());
|
||||
@@ -387,6 +392,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
if (conversationRecipient != null) {
|
||||
conversationRecipient.removeForeverObserver(this);
|
||||
}
|
||||
cancelPulseOutlinerAnimation();
|
||||
}
|
||||
|
||||
public ConversationMessage getConversationMessage() {
|
||||
@@ -411,7 +417,21 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
}
|
||||
|
||||
outliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_sent_text_secondary_color));
|
||||
bodyBubble.setOutliner(shouldDrawBodyBubbleOutline(messageRecord) ? outliner : null);
|
||||
|
||||
pulseOutliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_mention_pulse_color));
|
||||
pulseOutliner.setStrokeWidth(ViewUtil.dpToPx(4));
|
||||
|
||||
outliners.clear();
|
||||
if (shouldDrawBodyBubbleOutline(messageRecord)) {
|
||||
outliners.add(outliner);
|
||||
}
|
||||
outliners.add(pulseOutliner);
|
||||
|
||||
bodyBubble.setOutliners(outliners);
|
||||
|
||||
if (mediaThumbnailStub.resolved()) {
|
||||
mediaThumbnailStub.get().setPulseOutliner(pulseOutliner);
|
||||
}
|
||||
|
||||
if (audioViewStub.resolved()) {
|
||||
setAudioViewTint(messageRecord);
|
||||
@@ -432,14 +452,14 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
}
|
||||
}
|
||||
|
||||
private void setInteractionState(ConversationMessage conversationMessage, boolean pulseHighlight) {
|
||||
private void setInteractionState(ConversationMessage conversationMessage, boolean pulseMention) {
|
||||
if (batchSelected.contains(conversationMessage)) {
|
||||
setBackgroundResource(R.drawable.conversation_item_background);
|
||||
setSelected(true);
|
||||
} else if (pulseHighlight) {
|
||||
setBackgroundResource(R.drawable.conversation_item_background_animated);
|
||||
setSelected(true);
|
||||
postDelayed(() -> setSelected(false), 500);
|
||||
} else if (pulseMention) {
|
||||
setBackground(null);
|
||||
setSelected(false);
|
||||
startPulseOutlinerAnimation();
|
||||
} else {
|
||||
setSelected(false);
|
||||
}
|
||||
@@ -462,6 +482,28 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
}
|
||||
}
|
||||
|
||||
private void startPulseOutlinerAnimation() {
|
||||
pulseOutlinerAlphaAnimator = ValueAnimator.ofInt(0, 0x66, 0).setDuration(600);
|
||||
pulseOutlinerAlphaAnimator.addUpdateListener(animator -> {
|
||||
pulseOutliner.setAlpha((Integer) animator.getAnimatedValue());
|
||||
bodyBubble.invalidate();
|
||||
|
||||
if (mediaThumbnailStub.resolved()) {
|
||||
mediaThumbnailStub.get().invalidate();
|
||||
}
|
||||
});
|
||||
pulseOutlinerAlphaAnimator.start();
|
||||
}
|
||||
|
||||
private void cancelPulseOutlinerAnimation() {
|
||||
if (pulseOutlinerAlphaAnimator != null) {
|
||||
pulseOutlinerAlphaAnimator.cancel();
|
||||
pulseOutlinerAlphaAnimator = null;
|
||||
}
|
||||
|
||||
pulseOutliner.setAlpha(0);
|
||||
}
|
||||
|
||||
private boolean shouldDrawBodyBubbleOutline(MessageRecord messageRecord) {
|
||||
boolean isIncomingViewedOnce = !messageRecord.isOutgoing() && isViewOnceMessage(messageRecord) && ViewOnceUtil.isViewed((MmsMessageRecord) messageRecord);
|
||||
return isIncomingViewedOnce || messageRecord.isRemoteDelete();
|
||||
@@ -1097,33 +1139,41 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
||||
if (current.isOutgoing()) {
|
||||
background = R.drawable.message_bubble_background_sent_alone;
|
||||
outliner.setRadius(bigRadius);
|
||||
pulseOutliner.setRadius(bigRadius);
|
||||
} else {
|
||||
background = R.drawable.message_bubble_background_received_alone;
|
||||
outliner.setRadius(bigRadius);
|
||||
pulseOutliner.setRadius(bigRadius);
|
||||
}
|
||||
} else if (isStartOfMessageCluster(current, previous, isGroupThread)) {
|
||||
if (current.isOutgoing()) {
|
||||
background = R.drawable.message_bubble_background_sent_start;
|
||||
outliner.setRadii(bigRadius, bigRadius, smallRadius, bigRadius);
|
||||
pulseOutliner.setRadii(bigRadius, bigRadius, smallRadius, bigRadius);
|
||||
} else {
|
||||
background = R.drawable.message_bubble_background_received_start;
|
||||
outliner.setRadii(bigRadius, bigRadius, bigRadius, smallRadius);
|
||||
pulseOutliner.setRadii(bigRadius, bigRadius, bigRadius, smallRadius);
|
||||
}
|
||||
} else if (isEndOfMessageCluster(current, next, isGroupThread)) {
|
||||
if (current.isOutgoing()) {
|
||||
background = R.drawable.message_bubble_background_sent_end;
|
||||
outliner.setRadii(bigRadius, smallRadius, bigRadius, bigRadius);
|
||||
pulseOutliner.setRadii(bigRadius, smallRadius, bigRadius, bigRadius);
|
||||
} else {
|
||||
background = R.drawable.message_bubble_background_received_end;
|
||||
outliner.setRadii(smallRadius, bigRadius, bigRadius, bigRadius);
|
||||
pulseOutliner.setRadii(smallRadius, bigRadius, bigRadius, bigRadius);
|
||||
}
|
||||
} else {
|
||||
if (current.isOutgoing()) {
|
||||
background = R.drawable.message_bubble_background_sent_middle;
|
||||
outliner.setRadii(bigRadius, smallRadius, smallRadius, bigRadius);
|
||||
pulseOutliner.setRadii(bigRadius, smallRadius, smallRadius, bigRadius);
|
||||
} else {
|
||||
background = R.drawable.message_bubble_background_received_middle;
|
||||
outliner.setRadii(smallRadius, bigRadius, bigRadius, smallRadius);
|
||||
pulseOutliner.setRadii(smallRadius, bigRadius, bigRadius, smallRadius);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,18 @@ import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.Outliner;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ConversationItemBodyBubble extends LinearLayout {
|
||||
|
||||
@Nullable private Outliner outliner;
|
||||
@Nullable private List<Outliner> outliners = Collections.emptyList();
|
||||
@Nullable private OnSizeChangedListener sizeChangedListener;
|
||||
|
||||
public ConversationItemBodyBubble(Context context) {
|
||||
@@ -26,8 +31,8 @@ public class ConversationItemBodyBubble extends LinearLayout {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setOutliner(@Nullable Outliner outliner) {
|
||||
this.outliner = outliner;
|
||||
public void setOutliners(@NonNull List<Outliner> outliners) {
|
||||
this.outliners = outliners;
|
||||
}
|
||||
|
||||
public void setOnSizeChangedListener(@Nullable OnSizeChangedListener listener) {
|
||||
@@ -38,9 +43,11 @@ public class ConversationItemBodyBubble extends LinearLayout {
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (outliner == null) return;
|
||||
if (Util.isEmpty(outliners)) return;
|
||||
|
||||
outliner.draw(canvas, 0, getMeasuredWidth(), getMeasuredHeight(), 0);
|
||||
for (Outliner outliner : outliners) {
|
||||
outliner.draw(canvas, 0, getMeasuredWidth(), getMeasuredHeight(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -93,7 +93,7 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||
@NonNull Set<ConversationMessage> batchSelected,
|
||||
@NonNull Recipient conversationRecipient,
|
||||
@Nullable String searchQuery,
|
||||
boolean pulseUpdate)
|
||||
boolean pulseMention)
|
||||
{
|
||||
this.batchSelected = batchSelected;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.Application;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
@@ -14,7 +13,6 @@ import androidx.paging.DataSource;
|
||||
import androidx.paging.LivePagedListBuilder;
|
||||
import androidx.paging.PagedList;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
@@ -38,6 +36,8 @@ class ConversationViewModel extends ViewModel {
|
||||
private final LiveData<PagedList<ConversationMessage>> messages;
|
||||
private final LiveData<ConversationData> conversationMetadata;
|
||||
private final Invalidator invalidator;
|
||||
private final MutableLiveData<Boolean> showScrollButtons;
|
||||
private final MutableLiveData<Boolean> hasUnreadMentions;
|
||||
|
||||
private int jumpToPosition;
|
||||
|
||||
@@ -48,6 +48,8 @@ class ConversationViewModel extends ViewModel {
|
||||
this.recentMedia = new MutableLiveData<>();
|
||||
this.threadId = new MutableLiveData<>();
|
||||
this.invalidator = new Invalidator();
|
||||
this.showScrollButtons = new MutableLiveData<>(false);
|
||||
this.hasUnreadMentions = new MutableLiveData<>(false);
|
||||
|
||||
LiveData<ConversationData> metadata = Transformations.switchMap(threadId, thread -> {
|
||||
LiveData<ConversationData> conversationData = conversationRepository.getConversationData(thread, jumpToPosition);
|
||||
@@ -109,6 +111,22 @@ class ConversationViewModel extends ViewModel {
|
||||
this.threadId.postValue(-1L);
|
||||
}
|
||||
|
||||
@NonNull LiveData<Boolean> getShowScrollToBottom() {
|
||||
return Transformations.distinctUntilChanged(showScrollButtons);
|
||||
}
|
||||
|
||||
@NonNull LiveData<Boolean> getShowMentionsButton() {
|
||||
return Transformations.distinctUntilChanged(LiveDataUtil.combineLatest(showScrollButtons, hasUnreadMentions, (a, b) -> a && b));
|
||||
}
|
||||
|
||||
void setHasUnreadMentions(boolean hasUnreadMentions) {
|
||||
this.hasUnreadMentions.setValue(hasUnreadMentions);
|
||||
}
|
||||
|
||||
void setShowScrollButtons(boolean showScrollButtons) {
|
||||
this.showScrollButtons.setValue(showScrollButtons);
|
||||
}
|
||||
|
||||
@NonNull LiveData<List<Media>> getRecentMedia() {
|
||||
return recentMedia;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class MessageCountsViewModel extends ViewModel {
|
||||
|
||||
private static final Executor EXECUTOR = new SerialMonoLifoExecutor(SignalExecutors.BOUNDED);
|
||||
|
||||
private final Application context;
|
||||
private final MutableLiveData<Long> threadId = new MutableLiveData<>(-1L);
|
||||
private final LiveData<Pair<Integer, Integer>> unreadCounts;
|
||||
|
||||
private ContentObserver observer;
|
||||
|
||||
public MessageCountsViewModel() {
|
||||
this.context = ApplicationDependencies.getApplication();
|
||||
this.unreadCounts = Transformations.switchMap(Transformations.distinctUntilChanged(threadId), id -> {
|
||||
|
||||
MutableLiveData<Pair<Integer, Integer>> counts = new MutableLiveData<>(new Pair<>(0, 0));
|
||||
|
||||
if (id == -1L) {
|
||||
return counts;
|
||||
}
|
||||
|
||||
observer = new ContentObserver(null) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
EXECUTOR.execute(() -> {
|
||||
counts.postValue(getCounts(context, id));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
observer.onChange(false);
|
||||
|
||||
context.getContentResolver().registerContentObserver(DatabaseContentProviders.Conversation.getUriForThread(id), true, observer);
|
||||
|
||||
return counts;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void setThreadId(long threadId) {
|
||||
this.threadId.setValue(threadId);
|
||||
}
|
||||
|
||||
void clearThreadId() {
|
||||
this.threadId.postValue(-1L);
|
||||
}
|
||||
|
||||
@NonNull LiveData<Integer> getUnreadMessagesCount() {
|
||||
return Transformations.map(unreadCounts, Pair::first);
|
||||
}
|
||||
|
||||
@NonNull LiveData<Integer> getUnreadMentionsCount() {
|
||||
return Transformations.map(unreadCounts, Pair::second);
|
||||
}
|
||||
|
||||
private Pair<Integer, Integer> getCounts(@NonNull Context context, long threadId) {
|
||||
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
|
||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
int unreadCount = mmsSmsDatabase.getUnreadCount(threadId);
|
||||
int unreadMentionCount = mmsDatabase.getUnreadMentionCount(threadId);
|
||||
|
||||
return new Pair<>(unreadCount, unreadMentionCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
if (observer != null) {
|
||||
context.getContentResolver().unregisterContentObserver(observer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ import org.thoughtcrime.securesms.revealable.ViewOnceExpirationInfo;
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
@@ -746,6 +747,36 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return expiring;
|
||||
}
|
||||
|
||||
public @Nullable Pair<RecipientId, Long> getOldestUnreadMentionDetails(long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
String[] projection = new String[]{RECIPIENT_ID,DATE_RECEIVED};
|
||||
String selection = THREAD_ID + " = ? AND " + READ + " = 0 AND " + MENTIONS_SELF + " = 1";
|
||||
String[] args = SqlUtil.buildArgs(threadId);
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, projection, selection, args, null, null, DATE_RECEIVED + " ASC", "1")) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return new Pair<>(RecipientId.from(CursorUtil.requireString(cursor, RECIPIENT_ID)), CursorUtil.requireLong(cursor, DATE_RECEIVED));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getUnreadMentionCount(long threadId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
String[] projection = new String[]{"COUNT(*)"};
|
||||
String selection = THREAD_ID + " = ? AND " + READ + " = 0 AND " + MENTIONS_SELF + " = 1";
|
||||
String[] args = SqlUtil.buildArgs(threadId);
|
||||
|
||||
try (Cursor cursor = database.query(TABLE_NAME, projection, selection, args, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void updateMessageBody(long messageId, String body) {
|
||||
long type = 0;
|
||||
|
||||
|
||||
@@ -387,6 +387,7 @@ public class ThreadDatabase extends Database {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
notifyConversationListeners(new HashSet<>(threadIds));
|
||||
notifyConversationListListeners();
|
||||
return Util.concatenatedList(smsRecords, mmsRecords);
|
||||
}
|
||||
|
||||
@@ -249,11 +249,6 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
||||
|
||||
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
|
||||
|
||||
if (isVisible) {
|
||||
List<MarkedMessageInfo> messageIds = threads.setRead(threadId, false);
|
||||
MarkReadReceiver.process(context, messageIds);
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isNotificationsEnabled(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user