Update how we mark messages as read.

master
Alex Hart 2020-08-04 14:13:59 -03:00 committed by Greyson Parrelli
parent fdf4ad9543
commit 393e54ce91
9 changed files with 142 additions and 34 deletions

View File

@ -521,7 +521,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
}
ApplicationDependencies.getMessageNotifier().setVisibleThread(threadId);
markThreadAsRead();
}
@Override
@ -2285,21 +2284,6 @@ public class ConversationActivity extends PassphraseRequiredActivity
: MediaConstraints.getMmsMediaConstraints(sendButton.getSelectedTransport().getSimSubscriptionId().or(-1));
}
private void markThreadAsRead() {
new AsyncTask<Long, Void, Void>() {
@Override
protected Void doInBackground(Long... params) {
Context context = ConversationActivity.this;
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(params[0], false);
ApplicationDependencies.getMessageNotifier().updateNotification(context);
MarkReadReceiver.process(context, messageIds);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId);
}
private void markLastSeen() {
new AsyncTask<Long, Void, Void>() {
@Override

View File

@ -253,7 +253,9 @@ public class ConversationAdapter
protected @Nullable ConversationMessage getItem(int position) {
position = hasHeader() ? position - 1 : position;
if (position < fastRecords.size()) {
if (position == -1) {
return null;
} else if (position < fastRecords.size()) {
return fastRecords.get(position);
} else {
int correctedPosition = position - fastRecords.size();
@ -307,6 +309,10 @@ public class ConversationAdapter
viewHolder.setText(viewHolder.itemView.getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1)));
}
boolean hasNoConversationMessages() {
return super.getItemCount() + fastRecords.size() == 0;
}
/**
* The presence of a header may throw off the position you'd like to jump to. This will return
* an adjusted message position based on adapter state.

View File

@ -129,9 +129,9 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
@SuppressLint("StaticFieldLeak")
@ -166,6 +166,7 @@ public class ConversationFragment extends LoggingFragment {
private MessageRequestViewModel messageRequestViewModel;
private ConversationViewModel conversationViewModel;
private SnapToTopDataObserver snapToTopDataObserver;
private MarkReadHelper markReadHelper;
public static void prepare(@NonNull Context context) {
FrameLayout parent = new FrameLayout(context);
@ -420,6 +421,7 @@ public class ConversationFragment extends LoggingFragment {
this.recipient = Recipient.live(getActivity().getIntent().getParcelableExtra(ConversationActivity.RECIPIENT_EXTRA));
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
this.unknownSenderView = new UnknownSenderView(getActivity(), recipient.get(), threadId, () -> clearHeaderIfNotTyping(getListAdapter()));
this.markReadHelper = new MarkReadHelper(threadId, requireContext());
conversationViewModel.onConversationDataAvailable(threadId, startingPosition);
@ -658,6 +660,7 @@ public class ConversationFragment extends LoggingFragment {
if (threadDeleted) {
threadId = -1;
conversationViewModel.clearThreadId();
listener.setThreadId(threadId);
}
}
@ -697,6 +700,7 @@ public class ConversationFragment extends LoggingFragment {
if (threadDeleted) {
threadId = -1;
conversationViewModel.clearThreadId();
listener.setThreadId(threadId);
}
}
@ -1070,6 +1074,8 @@ public class ConversationFragment extends LoggingFragment {
wasAtBottom = currentlyAtBottom;
wasAtZoomScrollHeight = currentlyAtZoomScrollHeight;
lastPositionId = positionId;
postMarkAsReadRequest();
}
@Override
@ -1081,6 +1087,23 @@ public class ConversationFragment extends LoggingFragment {
}
}
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);
}
}
}
private boolean isAtZoomScrollHeight() {
return getListLayoutManager().findFirstCompletelyVisibleItemPosition() > 4;
}

View File

@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.conversation;
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;
@ -94,6 +96,7 @@ class ConversationViewModel extends ViewModel {
mediaRepository.getMediaInBucket(context, Media.ALL_MEDIA_BUCKET_ID, recentMedia::postValue);
}
@MainThread
void onConversationDataAvailable(long threadId, int startingPosition) {
Log.d(TAG, "[onConversationDataAvailable] threadId: " + threadId + ", startingPosition: " + startingPosition);
this.jumpToPosition = startingPosition;
@ -101,6 +104,11 @@ class ConversationViewModel extends ViewModel {
this.threadId.setValue(threadId);
}
void clearThreadId() {
this.jumpToPosition = -1;
this.threadId.postValue(-1L);
}
@NonNull LiveData<List<Media>> getRecentMedia() {
return recentMedia;
}

View File

@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.conversation;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.util.Debouncer;
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import java.util.List;
import java.util.concurrent.Executor;
class MarkReadHelper {
private static final String TAG = Log.tag(MarkReadHelper.class);
private static final long DEBOUNCE_TIMEOUT = 100;
private static final Executor EXECUTOR = new SerialMonoLifoExecutor(SignalExecutors.BOUNDED);
private final long threadId;
private final Context context;
private final Debouncer debouncer = new Debouncer(DEBOUNCE_TIMEOUT);
private long latestTimestamp;
MarkReadHelper(long threadId, @NonNull Context context) {
this.threadId = threadId;
this.context = context.getApplicationContext();
}
public void onViewsRevealed(long timestamp) {
if (timestamp <= latestTimestamp) {
return;
}
latestTimestamp = timestamp;
debouncer.publish(() -> {
EXECUTOR.execute(() -> {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
List<MessagingDatabase.MarkedMessageInfo> infos = threadDatabase.setReadSince(threadId, false, timestamp);
Log.d(TAG, "Marking " + infos.size() + " messages as read.");
ApplicationDependencies.getMessageNotifier().updateNotification(context);
MarkReadReceiver.process(context, infos);
});
});
}
}

View File

@ -46,6 +46,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
protected abstract String getTableName();
protected abstract String getTypeField();
protected abstract String getDateSentColumnName();
protected abstract String getDateReceivedColumnName();
public abstract void markExpireStarted(long messageId);
public abstract void markExpireStarted(long messageId, long startTime);
@ -144,12 +145,16 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
return String.format(Locale.ENGLISH, "(%s OR %s) AND %s", isSent, isReceived, isSecure);
}
public void setReactionsSeen(long threadId) {
public void setReactionsSeen(long threadId, long sinceTimestamp) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
String whereClause = THREAD_ID + " = ? AND " + REACTIONS_UNREAD + " = ?";
String[] whereArgs = new String[]{String.valueOf(threadId), "1"};
if (sinceTimestamp > -1) {
whereClause += " AND " + getDateReceivedColumnName() + " <= " + sinceTimestamp;
}
values.put(REACTIONS_UNREAD, 0);
values.put(REACTIONS_LAST_SEEN, System.currentTimeMillis());

View File

@ -248,6 +248,11 @@ public class MmsDatabase extends MessagingDatabase {
return DATE_SENT;
}
@Override
protected String getDateReceivedColumnName() {
return DATE_RECEIVED;
}
@Override
protected String getTypeField() {
return MESSAGE_BOX;
@ -632,12 +637,14 @@ public class MmsDatabase extends MessagingDatabase {
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)});
}
public List<MarkedMessageInfo> setMessagesRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
public List<MarkedMessageInfo> setMessagesReadSince(long threadId, long sinceTimestamp) {
if (sinceTimestamp == -1) {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
} else {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0 AND " + DATE_RECEIVED + " <= ?", new String[]{String.valueOf(threadId), String.valueOf(sinceTimestamp)});
}
}
public List<MarkedMessageInfo> setEntireThreadRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ?", new String[] {String.valueOf(threadId)});
}

View File

@ -150,6 +150,11 @@ public class SmsDatabase extends MessagingDatabase {
return DATE_SENT;
}
@Override
protected String getDateReceivedColumnName() {
return DATE_RECEIVED;
}
@Override
protected String getTypeField() {
return TYPE;
@ -509,8 +514,12 @@ public class SmsDatabase extends MessagingDatabase {
return setMessagesRead(THREAD_ID + " = ?", new String[] {String.valueOf(threadId)});
}
public List<MarkedMessageInfo> setMessagesRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
public List<MarkedMessageInfo> setMessagesReadSince(long threadId, long sinceTimestamp) {
if (sinceTimestamp == -1) {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
} else {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0 AND " + DATE_RECEIVED + " <= ?", new String[] {String.valueOf(threadId),String.valueOf(sinceTimestamp)});
}
}
public List<MarkedMessageInfo> setAllMessagesRead() {

View File

@ -340,10 +340,18 @@ public class ThreadDatabase extends Database {
}
public List<MarkedMessageInfo> setRead(long threadId, boolean lastSeen) {
return setRead(Collections.singletonList(threadId), lastSeen);
return setReadInternal(Collections.singletonList(threadId), lastSeen, -1);
}
public List<MarkedMessageInfo> setReadSince(long threadId, boolean lastSeen, long sinceTimestamp) {
return setReadInternal(Collections.singletonList(threadId), lastSeen, sinceTimestamp);
}
public List<MarkedMessageInfo> setRead(Collection<Long> threadIds, boolean lastSeen) {
return setReadInternal(threadIds, lastSeen, -1);
}
private List<MarkedMessageInfo> setReadInternal(Collection<Long> threadIds, boolean lastSeen, long sinceTimestamp) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
List<MarkedMessageInfo> smsRecords = new LinkedList<>();
@ -354,20 +362,23 @@ public class ThreadDatabase extends Database {
try {
ContentValues contentValues = new ContentValues(2);
contentValues.put(READ, ReadStatus.READ.serialize());
contentValues.put(UNREAD_COUNT, 0);
if (lastSeen) {
contentValues.put(LAST_SEEN, System.currentTimeMillis());
contentValues.put(LAST_SEEN, sinceTimestamp == -1 ? System.currentTimeMillis() : sinceTimestamp);
}
for (long threadId : threadIds) {
smsRecords.addAll(DatabaseFactory.getSmsDatabase(context).setMessagesReadSince(threadId, sinceTimestamp));
mmsRecords.addAll(DatabaseFactory.getMmsDatabase(context).setMessagesReadSince(threadId, sinceTimestamp));
DatabaseFactory.getSmsDatabase(context).setReactionsSeen(threadId, sinceTimestamp);
DatabaseFactory.getMmsDatabase(context).setReactionsSeen(threadId, sinceTimestamp);
int unreadCount = DatabaseFactory.getMmsSmsDatabase(context).getUnreadCount(threadId);
contentValues.put(UNREAD_COUNT, unreadCount);
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[]{threadId + ""});
smsRecords.addAll(DatabaseFactory.getSmsDatabase(context).setMessagesRead(threadId));
mmsRecords.addAll(DatabaseFactory.getMmsDatabase(context).setMessagesRead(threadId));
DatabaseFactory.getSmsDatabase(context).setReactionsSeen(threadId);
DatabaseFactory.getMmsDatabase(context).setReactionsSeen(threadId);
}
db.setTransactionSuccessful();