feat: Add the option to mark a conversation as read (#816)

* feat: Add the option to mark a conversation as read

Fixes #789

* Extract and share logic to mark all as read

* Fix merge conflict issues
This commit is contained in:
ceokot 2022-01-16 19:02:39 +02:00 committed by GitHub
parent c113a447cf
commit c2657bb785
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 55 additions and 15 deletions

View file

@ -124,7 +124,6 @@ import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.mms.VideoSlide import org.thoughtcrime.securesms.mms.VideoSlide
import org.thoughtcrime.securesms.notifications.MarkReadReceiver
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.util.ActivityDispatcher import org.thoughtcrime.securesms.util.ActivityDispatcher
import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.DateUtils
@ -310,7 +309,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId) ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId)
markAllAsRead() threadDb.markAllAsRead(viewModel.threadId, viewModel.recipient.isOpenGroupRecipient)
} }
override fun onPause() { override fun onPause() {
@ -555,18 +554,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
} }
private fun markAllAsRead() {
val messages = threadDb.setRead(viewModel.threadId, true)
if (viewModel.recipient.isGroupRecipient) {
for (message in messages) {
MarkReadReceiver.scheduleDeletion(this, message.expirationInfo)
}
} else {
MarkReadReceiver.process(this, messages)
}
ApplicationContext.getInstance(this).messageNotifier.updateNotification(this, false, 0)
}
override fun inputBarHeightChanged(newValue: Int) { override fun inputBarHeightChanged(newValue: Int) {
@Suppress("NAME_SHADOWING") val newValue = max(newValue, resources.getDimension(R.dimen.input_bar_height).roundToInt()) @Suppress("NAME_SHADOWING") val newValue = max(newValue, resources.getDimension(R.dimen.input_bar_height).roundToInt())
// 36 DP is the exact height of the typing indicator view. It's also exactly 18 * 2, and 18 is the large message // 36 DP is the exact height of the typing indicator view. It's also exactly 18 * 2, and 18 is the large message

View file

@ -45,6 +45,7 @@ import org.session.libsession.utilities.recipients.Recipient.RecipientSettings;
import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.Pair; import org.session.libsignal.utilities.Pair;
import org.session.libsignal.utilities.guava.Optional; import org.session.libsignal.utilities.guava.Optional;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
@ -55,6 +56,7 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.util.SessionMetaProtocol; import org.thoughtcrime.securesms.util.SessionMetaProtocol;
import java.io.Closeable; import java.io.Closeable;
@ -593,6 +595,18 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
public void markAllAsRead(long threadId, boolean isGroupRecipient) {
List<MarkedMessageInfo> messages = setRead(threadId, true);
if (isGroupRecipient) {
for (MarkedMessageInfo message: messages) {
MarkReadReceiver.scheduleDeletion(context, message.getExpirationInfo());
}
} else {
MarkReadReceiver.process(context, messages);
}
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, false, 0);
}
private boolean deleteThreadOnEmpty(long threadId) { private boolean deleteThreadOnEmpty(long threadId) {
Recipient threadRecipient = getRecipientForThreadId(threadId); Recipient threadRecipient = getRecipientForThreadId(threadId);
return threadRecipient != null && !threadRecipient.isOpenGroupRecipient(); return threadRecipient != null && !threadRecipient.isOpenGroupRecipient();

View file

@ -24,6 +24,7 @@ class ConversationOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClick
var onBlockTapped: (() -> Unit)? = null var onBlockTapped: (() -> Unit)? = null
var onUnblockTapped: (() -> Unit)? = null var onUnblockTapped: (() -> Unit)? = null
var onDeleteTapped: (() -> Unit)? = null var onDeleteTapped: (() -> Unit)? = null
var onMarkAllAsReadTapped: (() -> Unit)? = null
var onNotificationTapped: (() -> Unit)? = null var onNotificationTapped: (() -> Unit)? = null
var onSetMuteTapped: ((Boolean) -> Unit)? = null var onSetMuteTapped: ((Boolean) -> Unit)? = null
@ -40,6 +41,7 @@ class ConversationOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClick
binding.blockTextView -> onBlockTapped?.invoke() binding.blockTextView -> onBlockTapped?.invoke()
binding.unblockTextView -> onUnblockTapped?.invoke() binding.unblockTextView -> onUnblockTapped?.invoke()
binding.deleteTextView -> onDeleteTapped?.invoke() binding.deleteTextView -> onDeleteTapped?.invoke()
binding.markAllAsReadTextView -> onMarkAllAsReadTapped?.invoke()
binding.notificationsTextView -> onNotificationTapped?.invoke() binding.notificationsTextView -> onNotificationTapped?.invoke()
binding.unMuteNotificationsTextView -> onSetMuteTapped?.invoke(false) binding.unMuteNotificationsTextView -> onSetMuteTapped?.invoke(false)
binding.muteNotificationsTextView -> onSetMuteTapped?.invoke(true) binding.muteNotificationsTextView -> onSetMuteTapped?.invoke(true)
@ -67,6 +69,8 @@ class ConversationOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClick
binding.notificationsTextView.isVisible = recipient.isGroupRecipient && !recipient.isMuted binding.notificationsTextView.isVisible = recipient.isGroupRecipient && !recipient.isMuted
binding.notificationsTextView.setOnClickListener(this) binding.notificationsTextView.setOnClickListener(this)
binding.deleteTextView.setOnClickListener(this) binding.deleteTextView.setOnClickListener(this)
binding.markAllAsReadTextView.isVisible = thread.unreadCount > 0
binding.markAllAsReadTextView.setOnClickListener(this)
binding.pinTextView.isVisible = !thread.isPinned binding.pinTextView.isVisible = !thread.isPinned
binding.unpinTextView.isVisible = thread.isPinned binding.unpinTextView.isVisible = thread.isPinned
binding.pinTextView.setOnClickListener(this) binding.pinTextView.setOnClickListener(this)

View file

@ -34,6 +34,7 @@ import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.ProfilePictureModifiedEvent import org.session.libsession.utilities.ProfilePictureModifiedEvent
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.ThreadUtils
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.MuteDialog import org.thoughtcrime.securesms.MuteDialog
@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.groups.JoinPublicChatActivity
import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.groups.OpenGroupManager
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.notifications.MarkReadReceiver
import org.thoughtcrime.securesms.onboarding.SeedActivity import org.thoughtcrime.securesms.onboarding.SeedActivity
import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate
import org.thoughtcrime.securesms.preferences.SettingsActivity import org.thoughtcrime.securesms.preferences.SettingsActivity
@ -296,6 +298,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
bottomSheet.dismiss() bottomSheet.dismiss()
setConversationPinned(thread.threadId, false) setConversationPinned(thread.threadId, false)
} }
bottomSheet.onMarkAllAsReadTapped = {
bottomSheet.dismiss()
markAllAsRead(thread)
}
bottomSheet.show(supportFragmentManager, bottomSheet.tag) bottomSheet.show(supportFragmentManager, bottomSheet.tag)
} }
@ -369,6 +375,12 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis
} }
} }
private fun markAllAsRead(thread: ThreadRecord) {
ThreadUtils.queue {
threadDb.markAllAsRead(thread.threadId, thread.recipient.isOpenGroupRecipient)
}
}
private fun deleteConversation(thread: ThreadRecord) { private fun deleteConversation(thread: ThreadRecord) {
val threadID = thread.threadId val threadID = thread.threadId
val recipient = thread.recipient val recipient = thread.recipient

View file

@ -70,12 +70,13 @@ public class MarkReadReceiver extends BroadcastReceiver {
public static void process(@NonNull Context context, @NonNull List<MarkedMessageInfo> markedReadMessages) { public static void process(@NonNull Context context, @NonNull List<MarkedMessageInfo> markedReadMessages) {
if (markedReadMessages.isEmpty()) return; if (markedReadMessages.isEmpty()) return;
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) return;
for (MarkedMessageInfo messageInfo : markedReadMessages) { for (MarkedMessageInfo messageInfo : markedReadMessages) {
scheduleDeletion(context, messageInfo.getExpirationInfo()); scheduleDeletion(context, messageInfo.getExpirationInfo());
} }
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) return;
Map<Address, List<SyncMessageId>> addressMap = Stream.of(markedReadMessages) Map<Address, List<SyncMessageId>> addressMap = Stream.of(markedReadMessages)
.map(MarkedMessageInfo::getSyncMessageId) .map(MarkedMessageInfo::getSyncMessageId)
.collect(Collectors.groupingBy(SyncMessageId::getAddress)); .collect(Collectors.groupingBy(SyncMessageId::getAddress));

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,18l-6,0l-4,4V4c0,-1.1 0.9,-2 2,-2h16c1.1,0 2,0.9 2,2v7l-2,0V4H4v12l8,0V18zM23,14.34l-1.41,-1.41l-4.24,4.24l-2.12,-2.12l-1.41,1.41L17.34,20L23,14.34z" />
</vector>

View file

@ -75,6 +75,14 @@
android:visibility="gone" android:visibility="gone"
/> />
<TextView
android:id="@+id/markAllAsReadTextView"
style="@style/BottomSheetActionItem"
android:drawableStart="?attr/menu_mark_all_as_read"
android:text="@string/mark_all_as_read"
android:visibility="gone"
/>
<TextView <TextView
android:id="@+id/deleteTextView" android:id="@+id/deleteTextView"
style="@style/BottomSheetActionItem" style="@style/BottomSheetActionItem"

View file

@ -121,6 +121,7 @@
<attr name="menu_info_icon" format="reference" /> <attr name="menu_info_icon" format="reference" />
<attr name="menu_pin_icon" format="reference" /> <attr name="menu_pin_icon" format="reference" />
<attr name="menu_unpin_icon" format="reference" /> <attr name="menu_unpin_icon" format="reference" />
<attr name="menu_mark_all_as_read" format="reference" />
<attr name="pref_icon_tint" format="color"/> <attr name="pref_icon_tint" format="color"/>

View file

@ -904,5 +904,6 @@
<string name="dialog_share_logs_explanation">Would you like to export your application logs to be able to share for troubleshooting?</string> <string name="dialog_share_logs_explanation">Would you like to export your application logs to be able to share for troubleshooting?</string>
<string name="conversation_pin">Pin</string> <string name="conversation_pin">Pin</string>
<string name="conversation_unpin">Unpin</string> <string name="conversation_unpin">Unpin</string>
<string name="mark_all_as_read">Mark all as read</string>
</resources> </resources>

View file

@ -78,6 +78,7 @@
<item name="menu_info_icon">@drawable/ic_baseline_info_24</item> <item name="menu_info_icon">@drawable/ic_baseline_info_24</item>
<item name="menu_pin_icon">@drawable/ic_outline_pin_24</item> <item name="menu_pin_icon">@drawable/ic_outline_pin_24</item>
<item name="menu_unpin_icon">@drawable/ic_outline_pin_off_24</item> <item name="menu_unpin_icon">@drawable/ic_outline_pin_off_24</item>
<item name="menu_mark_all_as_read">@drawable/ic_outline_mark_chat_read_24</item>
<item name="conversation_emoji_toggle">@drawable/ic_emoji_filled_keyboard_24</item> <item name="conversation_emoji_toggle">@drawable/ic_emoji_filled_keyboard_24</item>
<item name="conversation_sticker_toggle">@drawable/ic_sticker_filled_keyboard_24</item> <item name="conversation_sticker_toggle">@drawable/ic_sticker_filled_keyboard_24</item>

View file

@ -121,6 +121,7 @@
<attr name="menu_info_icon" format="reference" /> <attr name="menu_info_icon" format="reference" />
<attr name="menu_pin_icon" format="reference" /> <attr name="menu_pin_icon" format="reference" />
<attr name="menu_unpin_icon" format="reference" /> <attr name="menu_unpin_icon" format="reference" />
<attr name="menu_mark_all_as_read" format="reference" />
<attr name="pref_icon_tint" format="color"/> <attr name="pref_icon_tint" format="color"/>