From b95cb2bedbdc79d4189fbfd6f3818dc5619515a1 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 20 Jul 2021 16:06:59 +1000 Subject: [PATCH 1/5] feat: add notification types for all / mentions only or none with UI and push logic --- .../thoughtcrime/securesms/MuteDialog.java | 4 +- .../v2/menus/ConversationMenuHelper.kt | 13 ++++- .../v2/utilities/NotificationUtils.kt | 21 +++++++++ .../securesms/database/RecipientDatabase.java | 41 ++++++++++++---- .../database/helpers/SQLCipherOpenHelper.java | 21 +++++---- .../home/ConversationOptionsBottomSheet.kt | 33 ++++++++++--- .../securesms/home/HomeActivity.kt | 47 +++++++++++++++++-- .../notifications/DefaultMessageNotifier.java | 21 ++++++--- .../ic_outline_notification_important_24.xml | 10 ++++ .../ic_outline_notifications_active_24.xml | 10 ++++ .../fragment_conversation_bottom_sheet.xml | 27 +++++++++++ .../menu/menu_conversation_closed_group.xml | 4 ++ .../res/menu/menu_conversation_open_group.xml | 4 ++ app/src/main/res/values/arrays.xml | 7 ++- app/src/main/res/values/strings.xml | 5 ++ .../utilities/recipients/Recipient.java | 44 +++++++++++------ .../recipients/RecipientProvider.java | 17 +++---- 17 files changed, 270 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt create mode 100644 app/src/main/res/drawable/ic_outline_notification_important_24.xml create mode 100644 app/src/main/res/drawable/ic_outline_notifications_active_24.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java b/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java index 33aace5123..acca9f8375 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms; import android.content.Context; import android.content.DialogInterface; + import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -33,11 +34,10 @@ public class MuteDialog extends AlertDialog { final long muteUntil; switch (which) { - case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break; case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2); break; case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break; case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break; - case 4: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365); break; + case 4: muteUntil = Long.MAX_VALUE; break; default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index 93278cb6a6..713add9277 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -36,13 +36,14 @@ import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.* +import org.thoughtcrime.securesms.contacts.SelectContactsActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.groups.EditClosedGroupActivity import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey -import org.thoughtcrime.securesms.contacts.SelectContactsActivity -import org.thoughtcrime.securesms.util.getColorWithID import org.thoughtcrime.securesms.util.BitmapUtil +import org.thoughtcrime.securesms.util.getColorWithID import java.io.IOException object ConversationMenuHelper { @@ -152,6 +153,7 @@ object ConversationMenuHelper { R.id.menu_invite_to_open_group -> { inviteContacts(context, thread) } R.id.menu_unmute_notifications -> { unmute(context, thread) } R.id.menu_mute_notifications -> { mute(context, thread) } + R.id.menu_notification_settings -> { setNotifyType(context, thread) } } return true } @@ -325,4 +327,11 @@ object ConversationMenuHelper { DatabaseFactory.getRecipientDatabase(context).setMuted(thread, until) } } + + private fun setNotifyType(context: Context, thread: Recipient) { + NotificationUtils.showNotifyDialog(context, thread) { notifyType -> + DatabaseFactory.getRecipientDatabase(context).setNotifyType(thread, notifyType) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt new file mode 100644 index 0000000000..dbbcfb51ef --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/NotificationUtils.kt @@ -0,0 +1,21 @@ +package org.thoughtcrime.securesms.conversation.v2.utilities + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import network.loki.messenger.R +import org.session.libsession.utilities.recipients.Recipient + +object NotificationUtils { + fun showNotifyDialog(context: Context, thread: Recipient, notifyTypeHandler: (Int)->Unit) { + val notifyTypes = context.resources.getStringArray(R.array.notify_types) + val currentSelected = thread.notifyType + + AlertDialog.Builder(context) + .setSingleChoiceItems(notifyTypes,currentSelected) { d, newSelection -> + notifyTypeHandler(newSelection) + d.dismiss() + } + .setTitle(R.string.RecipientPreferenceActivity_notification_settings) + .show() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 287a6d365c..8d7cedb61b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -10,17 +10,18 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteDatabase; -import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; -import org.session.libsession.utilities.MaterialColor; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.Recipient.*; -import org.session.libsignal.utilities.Base64; +import org.session.libsession.utilities.MaterialColor; import org.session.libsession.utilities.Util; - -import org.session.libsignal.utilities.guava.Optional; +import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; +import org.session.libsession.utilities.recipients.Recipient.RegisteredState; +import org.session.libsession.utilities.recipients.Recipient.UnidentifiedAccessMode; +import org.session.libsignal.utilities.Base64; import org.session.libsignal.utilities.Log; +import org.session.libsignal.utilities.guava.Optional; +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import java.io.Closeable; import java.io.IOException; @@ -55,13 +56,14 @@ public class RecipientDatabase extends Database { private static final String NOTIFICATION_CHANNEL = "notification_channel"; private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode"; private static final String FORCE_SMS_SELECTION = "force_sms_selection"; + private static final String NOTIFY_TYPE = "notify_type"; // all, mentions only, none private static final String[] RECIPIENT_PROJECTION = new String[] { BLOCK, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED, PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI, SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL, UNIDENTIFIED_ACCESS_MODE, - FORCE_SMS_SELECTION, + FORCE_SMS_SELECTION, NOTIFY_TYPE, }; static final List TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) @@ -95,6 +97,15 @@ public class RecipientDatabase extends Database { UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " + FORCE_SMS_SELECTION + " INTEGER DEFAULT 0);"; + public static String getCreateNotificationTypeCommand() { + return "ALTER TABLE "+ TABLE_NAME + " " + + "ADD COLUMN " + NOTIFY_TYPE + " INTEGER DEFAULT 0;"; + } + + public static final int NOTIFY_TYPE_ALL = 0; + public static final int NOTIFY_TYPE_MENTIONS = 1; + public static final int NOTIFY_TYPE_NONE = 2; + public RecipientDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); } @@ -131,6 +142,7 @@ public class RecipientDatabase extends Database { int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(VIBRATE)); int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE)); long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL)); + int notifyType = cursor.getInt(cursor.getColumnIndexOrThrow(NOTIFY_TYPE)); String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR)); int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID)); int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(EXPIRE_MESSAGES)); @@ -167,6 +179,7 @@ public class RecipientDatabase extends Database { } return Optional.of(new RecipientSettings(blocked, muteUntil, + notifyType, Recipient.VibrateState.fromId(messageVibrateState), Recipient.VibrateState.fromId(callVibrateState), Util.uri(messageRingtone), Util.uri(callRingtone), @@ -214,6 +227,18 @@ public class RecipientDatabase extends Database { recipient.resolve().setMuted(until); } + /** + * + * @param recipient to modify notifications for + * @param notifyType the new notification type {@link #NOTIFY_TYPE_ALL}, {@link #NOTIFY_TYPE_MENTIONS} or {@link #NOTIFY_TYPE_NONE} + */ + public void setNotifyType(@NonNull Recipient recipient, int notifyType) { + ContentValues values = new ContentValues(); + values.put(NOTIFY_TYPE, notifyType); + updateOrInsert(recipient.getAddress(), values); + recipient.resolve().setNotifyType(notifyType); + } + public void setExpireMessages(@NonNull Recipient recipient, int expiration) { recipient.setExpireMessages(expiration); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 8c0763d233..efdf55c0b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -10,26 +10,26 @@ import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabaseHook; import net.sqlcipher.database.SQLiteOpenHelper; +import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.crypto.DatabaseSecret; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DraftDatabase; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupReceiptDatabase; import org.thoughtcrime.securesms.database.JobDatabase; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.PushDatabase; -import org.thoughtcrime.securesms.database.RecipientDatabase; -import org.thoughtcrime.securesms.database.SearchDatabase; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.database.LokiAPIDatabase; import org.thoughtcrime.securesms.database.LokiBackupFilesDatabase; import org.thoughtcrime.securesms.database.LokiMessageDatabase; import org.thoughtcrime.securesms.database.LokiThreadDatabase; import org.thoughtcrime.securesms.database.LokiUserDatabase; +import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.database.PushDatabase; +import org.thoughtcrime.securesms.database.RecipientDatabase; +import org.thoughtcrime.securesms.database.SearchDatabase; import org.thoughtcrime.securesms.database.SessionContactDatabase; import org.thoughtcrime.securesms.database.SessionJobDatabase; +import org.thoughtcrime.securesms.database.SmsDatabase; +import org.thoughtcrime.securesms.database.ThreadDatabase; public class SQLCipherOpenHelper extends SQLiteOpenHelper { @@ -58,9 +58,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV24 = 45; private static final int lokiV25 = 46; private static final int lokiV26 = 47; + private static final int lokiV27 = 48; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV26; + private static final int DATABASE_VERSION = lokiV27; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -296,6 +297,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand()); } + if (oldVersion < lokiV27) { + db.execSQL(RecipientDatabase.getCreateNotificationTypeCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt index 7b454d33c7..b82737932e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationOptionsBottomSheet.kt @@ -1,16 +1,17 @@ package org.thoughtcrime.securesms.home import android.os.Bundle -import com.google.android.material.bottomsheet.BottomSheetDialogFragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible +import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.android.synthetic.main.fragment_conversation_bottom_sheet.* import network.loki.messenger.R import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.util.UiModeUtilities -public class ConversationOptionsBottomSheet : BottomSheetDialogFragment() { +public class ConversationOptionsBottomSheet : BottomSheetDialogFragment(), View.OnClickListener { //FIXME AC: Supplying a recipient directly into the field from an activity // is not the best idea. It doesn't survive configuration change. @@ -22,11 +23,25 @@ public class ConversationOptionsBottomSheet : BottomSheetDialogFragment() { var onBlockTapped: (() -> Unit)? = null var onUnblockTapped: (() -> Unit)? = null var onDeleteTapped: (() -> Unit)? = null + var onNotificationTapped: (() -> Unit)? = null + var onSetMuteTapped: ((Boolean) -> Unit)? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_conversation_bottom_sheet, container, false) } + override fun onClick(v: View?) { + when (v) { + detailsTextView -> onViewDetailsTapped?.invoke() + blockTextView -> onBlockTapped?.invoke() + unblockTextView -> onUnblockTapped?.invoke() + deleteTextView -> onDeleteTapped?.invoke() + notificationsTextView -> onNotificationTapped?.invoke() + unMuteNotificationsTextView -> onSetMuteTapped?.invoke(false) + muteNotificationsTextView -> onSetMuteTapped?.invoke(true) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) if (!this::recipient.isInitialized) { return dismiss() } @@ -34,13 +49,19 @@ public class ConversationOptionsBottomSheet : BottomSheetDialogFragment() { detailsTextView.visibility = View.VISIBLE unblockTextView.visibility = if (recipient.isBlocked) View.VISIBLE else View.GONE blockTextView.visibility = if (recipient.isBlocked) View.GONE else View.VISIBLE - detailsTextView.setOnClickListener { onViewDetailsTapped?.invoke() } - blockTextView.setOnClickListener { onBlockTapped?.invoke() } - unblockTextView.setOnClickListener { onUnblockTapped?.invoke() } + detailsTextView.setOnClickListener(this) + blockTextView.setOnClickListener(this) + unblockTextView.setOnClickListener(this) } else { detailsTextView.visibility = View.GONE } - deleteTextView.setOnClickListener { onDeleteTapped?.invoke() } + unMuteNotificationsTextView.isVisible = recipient.isMuted && !recipient.isLocalNumber + muteNotificationsTextView.isVisible = !recipient.isMuted && !recipient.isLocalNumber + unMuteNotificationsTextView.setOnClickListener(this) + muteNotificationsTextView.setOnClickListener(this) + notificationsTextView.isVisible = recipient.isGroupRecipient + notificationsTextView.setOnClickListener(this) + deleteTextView.setOnClickListener(this) } override fun onStart() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 6c6fd8e1ed..6d42d80de8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -35,8 +35,10 @@ import org.session.libsession.utilities.Util import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.toHexString import org.thoughtcrime.securesms.ApplicationContext +import org.thoughtcrime.securesms.MuteDialog import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord @@ -44,14 +46,12 @@ import org.thoughtcrime.securesms.dms.CreatePrivateChatActivity import org.thoughtcrime.securesms.groups.CreateClosedGroupActivity import org.thoughtcrime.securesms.groups.JoinPublicChatActivity import org.thoughtcrime.securesms.groups.OpenGroupManager -import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities -import org.thoughtcrime.securesms.util.* -import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.onboarding.SeedActivity +import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate import org.thoughtcrime.securesms.preferences.SettingsActivity -import org.thoughtcrime.securesms.util.IP2Country +import org.thoughtcrime.securesms.util.* import java.io.IOException import java.util.* @@ -257,6 +257,16 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis bottomSheet.dismiss() deleteConversation(thread) } + bottomSheet.onSetMuteTapped = { muted -> + bottomSheet.dismiss() + setConversationMuted(thread, muted) + } + bottomSheet.onNotificationTapped = { + bottomSheet.dismiss() + NotificationUtils.showNotifyDialog(this, thread.recipient) { notifyType -> + setNotifyType(thread, notifyType) + } + } bottomSheet.show(supportFragmentManager, bottomSheet.tag) } @@ -292,6 +302,35 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis }.show() } + private fun setConversationMuted(thread: ThreadRecord, isMuted: Boolean) { + if (!isMuted) { + ThreadUtils.queue { + DatabaseFactory.getRecipientDatabase(this).setMuted(thread.recipient, 0) + Util.runOnMain { + recyclerView.adapter!!.notifyDataSetChanged() + } + } + } else { + MuteDialog.show(this) { until: Long -> + ThreadUtils.queue { + DatabaseFactory.getRecipientDatabase(this).setMuted(thread.recipient, until) + Util.runOnMain { + recyclerView.adapter!!.notifyDataSetChanged() + } + } + } + } + } + + private fun setNotifyType(thread: ThreadRecord, newNotifyType: Int) { + ThreadUtils.queue { + DatabaseFactory.getRecipientDatabase(this).setNotifyType(thread.recipient, newNotifyType) + Util.runOnMain { + recyclerView.adapter!!.notifyDataSetChanged() + } + } + } + private fun deleteConversation(thread: ThreadRecord) { val threadID = thread.threadId val recipient = thread.recipient diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index 33f25c96b4..6e0fbbd183 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -41,26 +41,26 @@ import androidx.core.app.NotificationManagerCompat; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.utilities.Contact; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsignal.utilities.Util; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; +import org.session.libsignal.utilities.Util; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.contactshare.ContactUtil; - import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; +import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MmsSmsDatabase; +import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.util.SessionMetaProtocol; -import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities; import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.util.SessionMetaProtocol; import org.thoughtcrime.securesms.util.SpanUtil; import java.util.HashSet; @@ -493,7 +493,16 @@ public class DefaultMessageNotifier implements MessageNotifier { } if (threadRecipients == null || !threadRecipients.isMuted()) { - notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck)); + if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { + // check if mentioned here + if (body.toString().contains("@"+TextSecurePreferences.getLocalNumber(context))) { + notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck)); + } + } else if (threadRecipients != null && threadRecipients.notifyType == RecipientDatabase.NOTIFY_TYPE_NONE) { + // do nothing, no notifications + } else { + notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, slideDeck)); + } } } diff --git a/app/src/main/res/drawable/ic_outline_notification_important_24.xml b/app/src/main/res/drawable/ic_outline_notification_important_24.xml new file mode 100644 index 0000000000..5a4d7814d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_notification_important_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_notifications_active_24.xml b/app/src/main/res/drawable/ic_outline_notifications_active_24.xml new file mode 100644 index 0000000000..0a327617e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_notifications_active_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_conversation_bottom_sheet.xml b/app/src/main/res/layout/fragment_conversation_bottom_sheet.xml index 4cd4708573..ad1044a539 100644 --- a/app/src/main/res/layout/fragment_conversation_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_conversation_bottom_sheet.xml @@ -32,6 +32,33 @@ android:visibility="gone" tools:visibility="visible" /> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_conversation_open_group.xml b/app/src/main/res/menu/menu_conversation_open_group.xml index 6ff025aadb..3a022a8384 100644 --- a/app/src/main/res/menu/menu_conversation_open_group.xml +++ b/app/src/main/res/menu/menu_conversation_open_group.xml @@ -6,4 +6,8 @@ android:title="@string/ConversationActivity_invite_to_open_group" android:id="@+id/menu_invite_to_open_group" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 172aefd74b..1ab8459684 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -187,7 +187,7 @@ @string/arrays__mute_for_two_hours @string/arrays__mute_for_one_day @string/arrays__mute_for_seven_days - @string/arrays__mute_for_one_year + @string/arrays__mute_forever @@ -244,5 +244,10 @@ 1 2 + + @string/notify_type_all + @string/notify_type_mentions + @string/notify_type_none + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index db2edb6196..ac6ad0b8f4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -247,6 +247,7 @@ Unblock this contact? You will once again be able to receive messages and calls from this contact. Unblock + Notification Settings Image @@ -481,6 +482,7 @@ Mute for 1 day Mute for 7 days Mute for 1 year + Mute forever Settings default Enabled @@ -888,4 +890,7 @@ Warning This is your recovery phrase. If you send it to someone they\'ll have full access to your account. Send + All + Mentions + None diff --git a/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java b/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java index 3903908f3f..5b31ded3c5 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java +++ b/libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java @@ -26,29 +26,28 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.annimon.stream.function.Consumer; -import com.esotericsoftware.kryo.util.Null; import org.greenrobot.eventbus.EventBus; -import org.session.libsession.database.StorageProtocol; -import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.avatars.TransparentContactPhoto; -import org.session.libsession.messaging.contacts.Contact; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.GroupRecord; -import org.session.libsession.utilities.recipients.RecipientProvider.RecipientDetails; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.MaterialColor; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.guava.Optional; import org.session.libsession.avatars.ContactColors; import org.session.libsession.avatars.ContactPhoto; import org.session.libsession.avatars.GroupRecordContactPhoto; import org.session.libsession.avatars.ProfileContactPhoto; import org.session.libsession.avatars.SystemContactPhoto; -import org.session.libsession.utilities.ProfilePictureModifiedEvent; +import org.session.libsession.avatars.TransparentContactPhoto; +import org.session.libsession.database.StorageProtocol; +import org.session.libsession.messaging.MessagingModuleConfiguration; +import org.session.libsession.messaging.contacts.Contact; +import org.session.libsession.utilities.Address; import org.session.libsession.utilities.FutureTaskListener; +import org.session.libsession.utilities.GroupRecord; import org.session.libsession.utilities.ListenableFutureTask; +import org.session.libsession.utilities.MaterialColor; +import org.session.libsession.utilities.ProfilePictureModifiedEvent; +import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.recipients.RecipientProvider.RecipientDetails; +import org.session.libsignal.utilities.Log; +import org.session.libsignal.utilities.guava.Optional; import java.util.Collections; import java.util.HashSet; @@ -80,6 +79,7 @@ public class Recipient implements RecipientModifiedListener { private @Nullable Uri messageRingtone = null; private @Nullable Uri callRingtone = null; public long mutedUntil = 0; + public int notifyType = 0; private boolean blocked = false; private VibrateState messageVibrate = VibrateState.DEFAULT; private VibrateState callVibrate = VibrateState.DEFAULT; @@ -249,6 +249,7 @@ public class Recipient implements RecipientModifiedListener { this.messageRingtone = details.messageRingtone; this.callRingtone = details.callRingtone; this.mutedUntil = details.mutedUntil; + this.notifyType = details.notifyType; this.blocked = details.blocked; this.messageVibrate = details.messageVibrateState; this.callVibrate = details.callVibrateState; @@ -547,6 +548,14 @@ public class Recipient implements RecipientModifiedListener { notifyListeners(); } + public void setNotifyType(int notifyType) { + synchronized (this) { + this.notifyType = notifyType; + } + + notifyListeners(); + } + public synchronized boolean isBlocked() { return blocked; } @@ -769,6 +778,7 @@ public class Recipient implements RecipientModifiedListener { public static class RecipientSettings { private final boolean blocked; private final long muteUntil; + private final int notifyType; private final VibrateState messageVibrateState; private final VibrateState callVibrateState; private final Uri messageRingtone; @@ -790,6 +800,7 @@ public class Recipient implements RecipientModifiedListener { private final boolean forceSmsSelection; public RecipientSettings(boolean blocked, long muteUntil, + int notifyType, @NonNull VibrateState messageVibrateState, @NonNull VibrateState callVibrateState, @Nullable Uri messageRingtone, @@ -812,6 +823,7 @@ public class Recipient implements RecipientModifiedListener { { this.blocked = blocked; this.muteUntil = muteUntil; + this.notifyType = notifyType; this.messageVibrateState = messageVibrateState; this.callVibrateState = callVibrateState; this.messageRingtone = messageRingtone; @@ -845,6 +857,10 @@ public class Recipient implements RecipientModifiedListener { return muteUntil; } + public int getNotifyType() { + return notifyType; + } + public @NonNull VibrateState getMessageVibrateState() { return messageVibrateState; } diff --git a/libsession/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java b/libsession/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java index 8bbabae97a..f87906e4f9 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java +++ b/libsession/src/main/java/org/session/libsession/utilities/recipients/RecipientProvider.java @@ -23,19 +23,20 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.session.libsession.R; import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsignal.utilities.guava.Optional; -import org.session.libsession.utilities.MaterialColor; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.GroupRecord; +import org.session.libsession.utilities.ListenableFutureTask; +import org.session.libsession.utilities.MaterialColor; +import org.session.libsession.utilities.SoftHashMap; +import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; import org.session.libsession.utilities.recipients.Recipient.RegisteredState; import org.session.libsession.utilities.recipients.Recipient.UnidentifiedAccessMode; import org.session.libsession.utilities.recipients.Recipient.VibrateState; -import org.session.libsession.utilities.ListenableFutureTask; -import org.session.libsession.utilities.SoftHashMap; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; +import org.session.libsignal.utilities.guava.Optional; import java.util.HashMap; import java.util.LinkedList; @@ -44,8 +45,6 @@ import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; -import org.session.libsession.R; - class RecipientProvider { @SuppressWarnings("unused") @@ -168,6 +167,7 @@ class RecipientProvider { @Nullable final Uri messageRingtone; @Nullable final Uri callRingtone; final long mutedUntil; + final int notifyType; @Nullable final VibrateState messageVibrateState; @Nullable final VibrateState callVibrateState; final boolean blocked; @@ -197,6 +197,7 @@ class RecipientProvider { this.messageRingtone = settings != null ? settings.getMessageRingtone() : null; this.callRingtone = settings != null ? settings.getCallRingtone() : null; this.mutedUntil = settings != null ? settings.getMuteUntil() : 0; + this.notifyType = settings != null ? settings.getNotifyType() : 0; this.messageVibrateState = settings != null ? settings.getMessageVibrateState() : null; this.callVibrateState = settings != null ? settings.getCallVibrateState() : null; this.blocked = settings != null && settings.isBlocked(); From 7f047f1c2b3695edc54e997a8e53b1048cb6f3e8 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 20 Jul 2021 17:34:07 +1000 Subject: [PATCH 2/5] feat: add mention only icon, trying to figure out non-repeating push notifications --- .../securesms/database/ThreadDatabase.java | 16 ++++++++-------- .../securesms/home/ConversationView.kt | 12 ++++++++++-- .../notifications/DefaultMessageNotifier.java | 10 +++++++++- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 16f1f64d4a..9852fdd2e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -30,18 +30,18 @@ import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteDatabase; -import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.GroupRecord; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; +import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.DelimiterUtil; +import org.session.libsession.utilities.DistributionTypes; +import org.session.libsession.utilities.GroupRecord; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.Recipient.RecipientSettings; +import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.Pair; import org.session.libsignal.utilities.guava.Optional; -import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.contactshare.ContactUtil; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -49,9 +49,9 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord; -import org.thoughtcrime.securesms.util.SessionMetaProtocol; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; +import org.thoughtcrime.securesms.util.SessionMetaProtocol; import java.io.Closeable; import java.util.HashMap; @@ -651,7 +651,7 @@ public class ThreadDatabase extends Database { groupRecord = Optional.absent(); } - Recipient recipient = Recipient.from(context, address, settings, groupRecord, true); + Recipient recipient = Recipient.from(context, address, settings, groupRecord, false); String body = cursor.getString(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index 57681963c1..4d404bc5e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -14,9 +14,10 @@ import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.view_conversation.view.* import network.loki.messenger.R import org.session.libsession.utilities.recipients.Recipient -import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities.populateUserPublicKeyCacheIfNeeded import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions +import org.thoughtcrime.securesms.database.RecipientDatabase +import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.util.DateUtils import java.util.* @@ -59,7 +60,14 @@ class ConversationView : LinearLayout { val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() conversationViewDisplayNameTextView.text = senderDisplayName timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), thread.date) - muteIndicatorImageView.visibility = if (thread.recipient.isMuted) VISIBLE else GONE + val recipient = thread.recipient + muteIndicatorImageView.isVisible = recipient.isMuted || recipient.notifyType != RecipientDatabase.NOTIFY_TYPE_ALL + val drawableRes = if (recipient.isMuted || recipient.notifyType == RecipientDatabase.NOTIFY_TYPE_NONE) { + R.drawable.ic_outline_notifications_off_24 + } else { + R.drawable.ic_outline_notification_important_24 + } + muteIndicatorImageView.setImageResource(drawableRes) val rawSnippet = thread.getDisplayBody(context) val snippet = highlightMentions(rawSnippet, thread.threadId, context) snippetTextView.text = snippet diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index 6e0fbbd183..e06a1c153c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -238,6 +238,14 @@ public class DefaultMessageNotifier implements MessageNotifier { return; } + if (recipients != null && recipients.notifyType != RecipientDatabase.NOTIFY_TYPE_ALL) { + // if mentions and no mention then return + if (recipients.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { + + } + return; + } + if (isVisible) { sendInThreadNotification(context, threads.getRecipientForThreadId(threadId)); } else if (!homeScreenVisible) { @@ -280,7 +288,7 @@ public class DefaultMessageNotifier implements MessageNotifier { } sendMultipleThreadNotification(context, notificationState, signal); - } else { + } else if (notificationState.getMessageCount() > 0){ sendSingleThreadNotification(context, notificationState, signal, false); } From ff853e01b42f218dd02337734a426b18162ed62c Mon Sep 17 00:00:00 2001 From: Harris Date: Wed, 21 Jul 2021 13:58:07 +1000 Subject: [PATCH 3/5] fix: notifications deduplicate based on last message ID, ConversationActivityV2.kt updates notification by thread ID --- .../conversation/v2/ConversationActivityV2.kt | 24 ++++++++--- .../AbstractNotificationBuilder.java | 24 +++++++---- .../notifications/DefaultMessageNotifier.java | 40 ++++++++----------- .../SingleRecipientNotificationBuilder.java | 20 +++++++--- 4 files changed, 67 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index f57726bcb2..b85759f314 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -24,7 +24,6 @@ import android.widget.Toast import androidx.annotation.DimenRes import androidx.appcompat.app.AlertDialog import androidx.core.view.children -import androidx.core.view.get import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider @@ -53,17 +52,13 @@ import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.messages.control.DataExtractionNotification import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage -import org.session.libsession.messaging.messages.visible.LinkPreview.Companion.from import org.session.libsession.messaging.messages.visible.OpenGroupInvitation -import org.session.libsession.messaging.messages.visible.Quote.Companion.from import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel -import org.session.libsession.messaging.utilities.UpdateMessageData -import org.session.libsession.messaging.utilities.UpdateMessageData.Companion.fromJSON import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.MediaTypes @@ -116,6 +111,23 @@ import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.util.* import java.util.* import java.util.concurrent.ExecutionException +import kotlin.collections.List +import kotlin.collections.Set +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.filter +import kotlin.collections.find +import kotlin.collections.first +import kotlin.collections.forEach +import kotlin.collections.indices +import kotlin.collections.isNotEmpty +import kotlin.collections.iterator +import kotlin.collections.listOf +import kotlin.collections.mutableListOf +import kotlin.collections.mutableMapOf +import kotlin.collections.set +import kotlin.collections.sortedBy +import kotlin.collections.toTypedArray import kotlin.math.* // Some things that seemingly belong to the input bar (e.g. the voice message recording UI) are actually @@ -502,7 +514,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } else { MarkReadReceiver.process(this, messages) } - ApplicationContext.getInstance(this).messageNotifier.updateNotification(this) + ApplicationContext.getInstance(this).messageNotifier.updateNotification(this, threadID) } override fun inputBarHeightChanged(newValue: Int) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index 89296691fa..a359596c91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -4,18 +4,21 @@ import android.app.Notification; import android.content.Context; import android.graphics.Color; import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; +import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.TextUtils; -import network.loki.messenger.R; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; + import org.session.libsession.utilities.NotificationPrivacyPreference; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.Recipient.*; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.Recipient.VibrateState; + +import network.loki.messenger.R; public abstract class AbstractNotificationBuilder extends NotificationCompat.Builder { @@ -26,10 +29,11 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui protected Context context; protected NotificationPrivacyPreference privacy; + protected final Bundle extras; public AbstractNotificationBuilder(Context context, NotificationPrivacyPreference privacy) { super(context); - + extras = new Bundle(); this.context = context; this.privacy = privacy; @@ -97,4 +101,10 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui return text.length() <= MAX_DISPLAY_LENGTH ? text : text.subSequence(0, MAX_DISPLAY_LENGTH); } + + @Override + public Notification build() { + addExtras(extras); + return super.build(); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index e06a1c153c..bdcf916dd4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -87,6 +87,7 @@ public class DefaultMessageNotifier implements MessageNotifier { private static final String TAG = DefaultMessageNotifier.class.getSimpleName(); public static final String EXTRA_REMOTE_REPLY = "extra_remote_reply"; + public static final String LATEST_MESSAGE_ID_TAG = "extra_latest_message_id"; private static final int FOREGROUND_ID = 313399; private static final int SUMMARY_NOTIFICATION_ID = 1338; @@ -238,14 +239,6 @@ public class DefaultMessageNotifier implements MessageNotifier { return; } - if (recipients != null && recipients.notifyType != RecipientDatabase.NOTIFY_TYPE_ALL) { - // if mentions and no mention then return - if (recipients.notifyType == RecipientDatabase.NOTIFY_TYPE_MENTIONS) { - - } - return; - } - if (isVisible) { sendInThreadNotification(context, threads.getRecipientForThreadId(threadId)); } else if (!homeScreenVisible) { @@ -280,16 +273,9 @@ public class DefaultMessageNotifier implements MessageNotifier { lastAudibleNotification = System.currentTimeMillis(); } - if (notificationState.hasMultipleThreads()) { - if (Build.VERSION.SDK_INT >= 23) { - for (long threadId : notificationState.getThreads()) { - sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true); - } - } - - sendMultipleThreadNotification(context, notificationState, signal); - } else if (notificationState.getMessageCount() > 0){ - sendSingleThreadNotification(context, notificationState, signal, false); + boolean hasMultipleThreads = notificationState.hasMultipleThreads(); + for (long threadId : notificationState.getThreads()) { + sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), signal, hasMultipleThreads); } cancelOrphanedNotifications(context, notificationState); @@ -320,7 +306,20 @@ public class DefaultMessageNotifier implements MessageNotifier { List notifications = notificationState.getNotifications(); Recipient recipient = notifications.get(0).getRecipient(); int notificationId = (int) (SUMMARY_NOTIFICATION_ID + (bundled ? notifications.get(0).getThreadId() : 0)); + String messageIdTag = String.valueOf(notifications.get(0).getId()); + NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); + for (StatusBarNotification notification: notificationManager.getActiveNotifications()) { + + if (messageIdTag.equals(notification.getNotification().extras.getString(LATEST_MESSAGE_ID_TAG))) { + return; + } + } + + long timestamp = notifications.get(0).getTimestamp(); + if (timestamp != 0) builder.setWhen(timestamp); + + builder.putStringExtra(LATEST_MESSAGE_ID_TAG, messageIdTag); builder.setThread(notifications.get(0).getRecipient()); builder.setMessageCount(notificationState.getMessageCount()); @@ -333,11 +332,6 @@ public class DefaultMessageNotifier implements MessageNotifier { builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY); builder.setAutoCancel(true); - long timestamp = notifications.get(0).getTimestamp(); - if (timestamp != 0) builder.setWhen(timestamp); - - long threadID = notifications.get(0).getThreadId(); - ReplyMethod replyMethod = ReplyMethod.forRecipient(context, recipient); boolean canReply = SessionMetaProtocol.canUserReplyToNotification(recipient); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 80dd7d2602..7bd4ef4998 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -15,33 +15,38 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.text.SpannableStringBuilder; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat.Action; import androidx.core.app.RemoteInput; + import com.bumptech.glide.load.engine.DiskCacheStrategy; + import org.session.libsession.avatars.ContactColors; import org.session.libsession.avatars.ContactPhoto; import org.session.libsession.avatars.GeneratedContactPhoto; import org.session.libsession.messaging.contacts.Contact; +import org.session.libsession.utilities.NotificationPrivacyPreference; +import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.SessionContactDatabase; -import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; -import org.session.libsession.utilities.NotificationPrivacyPreference; -import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator; import org.thoughtcrime.securesms.util.BitmapUtil; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; + import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; + import network.loki.messenger.R; public class SingleRecipientNotificationBuilder extends AbstractNotificationBuilder { @@ -58,6 +63,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil { super(context, privacy); + setSmallIcon(R.drawable.ic_notification); setColor(context.getResources().getColor(R.color.textsecure_primary)); setCategory(NotificationCompat.CATEGORY_MESSAGE); @@ -198,6 +204,10 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil return R.string.MessageNotifier_reply; } + public void putStringExtra(String key, String value) { + extras.putString(key,value); + } + public void addMessageBody(@NonNull Recipient threadRecipient, @NonNull Recipient individualRecipient, @Nullable CharSequence messageBody) From 7ef9fb2b28ab20d0779c01829c428c90728932fb Mon Sep 17 00:00:00 2001 From: Harris Date: Wed, 21 Jul 2021 15:25:48 +1000 Subject: [PATCH 4/5] feat: added notification mentions vector image --- .../securesms/home/ConversationView.kt | 2 +- .../res/drawable/ic_notifications_mentions.xml | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_notifications_mentions.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index 4d404bc5e5..9454070e77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -65,7 +65,7 @@ class ConversationView : LinearLayout { val drawableRes = if (recipient.isMuted || recipient.notifyType == RecipientDatabase.NOTIFY_TYPE_NONE) { R.drawable.ic_outline_notifications_off_24 } else { - R.drawable.ic_outline_notification_important_24 + R.drawable.ic_notifications_mentions } muteIndicatorImageView.setImageResource(drawableRes) val rawSnippet = thread.getDisplayBody(context) diff --git a/app/src/main/res/drawable/ic_notifications_mentions.xml b/app/src/main/res/drawable/ic_notifications_mentions.xml new file mode 100644 index 0000000000..c078377a5a --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_mentions.xml @@ -0,0 +1,15 @@ + + + + From 716dbccb9fdd211c0d0ad94571d805f43804d9e6 Mon Sep 17 00:00:00 2001 From: Harris Date: Mon, 26 Jul 2021 09:37:39 +1000 Subject: [PATCH 5/5] feat: add muted forever to ConversationActivityV2.kt --- .../conversation/v2/ConversationActivityV2.kt | 20 +++++-------------- app/src/main/res/values/strings.xml | 1 + 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index b85759f314..656da5fd66 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -111,23 +111,9 @@ import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.util.* import java.util.* import java.util.concurrent.ExecutionException -import kotlin.collections.List -import kotlin.collections.Set import kotlin.collections.component1 import kotlin.collections.component2 -import kotlin.collections.filter -import kotlin.collections.find -import kotlin.collections.first -import kotlin.collections.forEach -import kotlin.collections.indices -import kotlin.collections.isNotEmpty -import kotlin.collections.iterator -import kotlin.collections.listOf -import kotlin.collections.mutableListOf -import kotlin.collections.mutableMapOf import kotlin.collections.set -import kotlin.collections.sortedBy -import kotlin.collections.toTypedArray import kotlin.math.* // Some things that seemingly belong to the input bar (e.g. the voice message recording UI) are actually @@ -732,7 +718,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe muteIconImageView.isVisible = thread.isMuted conversationSubtitleView.isVisible = true if (thread.isMuted) { - conversationSubtitleView.text = getString(R.string.ConversationActivity_muted_until_date, DateUtils.getFormattedDateTime(thread.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault())) + if (thread.mutedUntil != Long.MAX_VALUE) { + conversationSubtitleView.text = getString(R.string.ConversationActivity_muted_until_date, DateUtils.getFormattedDateTime(thread.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault())) + } else { + conversationSubtitleView.text = getString(R.string.ConversationActivity_muted_forever) + } } else if (thread.isGroupRecipient) { val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) if (openGroup != null) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ac6ad0b8f4..585aec9f28 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,6 +75,7 @@ Message Compose Muted until %1$s + Muted forever %1$d members Community Guidelines