From 0be1a3076638bf789998463bfafc0437248a8889 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 21 Oct 2021 21:22:19 -0400 Subject: [PATCH] Add the ability to mute on the chat list. --- .../thoughtcrime/securesms/MuteDialog.java | 25 +++++------ .../ConversationListFragment.java | 44 +++++++++++++++++++ .../securesms/database/RecipientDatabase.java | 27 ++++++++++++ .../main/res/drawable-night/ic_mute_24.xml | 15 +++++++ .../main/res/drawable-night/ic_unmute_24.xml | 9 ++++ app/src/main/res/drawable/ic_mute_24.xml | 15 +++++++ app/src/main/res/drawable/ic_unmute_24.xml | 9 ++++ 7 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 app/src/main/res/drawable-night/ic_mute_24.xml create mode 100644 app/src/main/res/drawable-night/ic_unmute_24.xml create mode 100644 app/src/main/res/drawable/ic_mute_24.xml create mode 100644 app/src/main/res/drawable/ic_unmute_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 7541f5672..2f2ef45b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MuteDialog.java @@ -33,22 +33,19 @@ public class MuteDialog extends AlertDialog { public static void show(final Context context, final @NonNull MuteSelectionListener listener, @Nullable Runnable cancelListener) { AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context); builder.setTitle(R.string.MuteDialog_mute_notifications); - builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, final int which) { - final long muteUntil; + builder.setItems(R.array.mute_durations, (dialog, which) -> { + final long muteUntil; - switch (which) { - case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break; - case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(8); break; - case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break; - case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break; - case 4: muteUntil = Long.MAX_VALUE; break; - default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break; - } - - listener.onMuted(muteUntil); + switch (which) { + case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break; + case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(8); break; + case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break; + case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break; + case 4: muteUntil = Long.MAX_VALUE; break; + default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break; } + + listener.onMuted(muteUntil); }); if (cancelListener != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index d269e9dbf..211422d5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -74,6 +74,7 @@ import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.MainFragment; import org.thoughtcrime.securesms.MainNavigator; +import org.thoughtcrime.securesms.MuteDialog; import org.thoughtcrime.securesms.NewConversationActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.RatingManager; @@ -143,6 +144,7 @@ import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.WindowUtil; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask; +import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import org.thoughtcrime.securesms.util.views.Stub; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; @@ -158,6 +160,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import static android.app.Activity.RESULT_OK; @@ -923,6 +926,33 @@ public class ConversationListFragment extends MainFragment implements ActionMode }); } + private void handleMute(@NonNull Collection conversations) { + MuteDialog.show(requireContext(), until -> { + updateMute(conversations, until); + }); + } + + private void handleUnmute(@NonNull Collection conversations) { + updateMute(conversations, 0); + } + + private void updateMute(@NonNull Collection conversations, long until) { + SimpleProgressDialog.DismissibleDialog dialog = SimpleProgressDialog.showDelayed(requireContext(), 250, 250); + + SimpleTask.run(SignalExecutors.BOUNDED, () -> { + List recipientIds = conversations.stream() + .map(conversation -> conversation.getThreadRecord().getRecipient().live().get()) + .filter(r -> r.getMuteUntil() != until) + .map(Recipient::getId) + .collect(Collectors.toList()); + DatabaseFactory.getRecipientDatabase(requireContext()).setMuted(recipientIds, until); + return null; + }, unused -> { + endActionModeIfActive(); + dialog.dismiss(); + }); + } + private void handleSelectAllThreads() { defaultAdapter.selectAllThreads(); updateMultiSelectState(); @@ -1053,6 +1083,12 @@ public class ConversationListFragment extends MainFragment implements ActionMode } else { items.add(new ActionItem(R.drawable.ic_pin_24, R.string.ConversationListFragment_pin, () -> handlePin(Collections.singleton(conversation)))); } + + if (conversation.getThreadRecord().getRecipient().live().get().isMuted()) { + items.add(new ActionItem(R.drawable.ic_unmute_24, R.string.ConversationListFragment_unmute, () -> handleUnmute(Collections.singleton(conversation)))); + } else { + items.add(new ActionItem(R.drawable.ic_mute_24, R.string.ConversationListFragment_mute, () -> handleMute(Collections.singleton(conversation)))); + } } items.add(new ActionItem(R.drawable.ic_select_24, R.string.ConversationListFragment_select, () -> { @@ -1134,6 +1170,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode int count = defaultAdapter.getBatchSelectionIds().size(); boolean hasUnread = Stream.of(defaultAdapter.getBatchSelection()).anyMatch(conversation -> !conversation.getThreadRecord().isRead()); boolean hasUnpinned = Stream.of(defaultAdapter.getBatchSelection()).anyMatch(conversation -> !conversation.getThreadRecord().isPinned()); + boolean hasUnmuted = Stream.of(defaultAdapter.getBatchSelection()).anyMatch(conversation -> !conversation.getThreadRecord().getRecipient().live().get().isMuted()); boolean canPin = viewModel.getPinnedCount() < MAXIMUM_PINNED_CONVERSATIONS; if (actionMode != null) { @@ -1162,6 +1199,13 @@ public class ConversationListFragment extends MainFragment implements ActionMode items.add(new ActionItem(R.drawable.ic_delete_24, R.string.ConversationListFragment_delete, () -> handleDelete(defaultAdapter.getBatchSelectionIds()))); + + if (hasUnmuted) { + items.add(new ActionItem(R.drawable.ic_mute_24, R.string.ConversationListFragment_mute, () -> handleMute(defaultAdapter.getBatchSelection()))); + } else { + items.add(new ActionItem(R.drawable.ic_unmute_24, R.string.ConversationListFragment_unmute, () -> handleUnmute(defaultAdapter.getBatchSelection()))); + } + items.add(new ActionItem(R.drawable.ic_select_24, R.string.ConversationListFragment_select_all, this::handleSelectAllThreads)); bottomActionBar.setItems(items); 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 475b16f5b..67e7a7319 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -1614,6 +1614,33 @@ public class RecipientDatabase extends Database { StorageSyncHelper.scheduleSyncForDataChange(); } + public void setMuted(@NonNull Collection ids, long until) { + SQLiteDatabase db = databaseHelper.getSignalWritableDatabase(); + + db.beginTransaction(); + try { + ContentValues values = new ContentValues(); + values.put(MUTE_UNTIL, until); + + SqlUtil.Query query = SqlUtil.buildCollectionQuery(ID, ids); + db.update(TABLE_NAME, values, query.getWhere(), query.getWhereArgs()); + + for (RecipientId id : ids) { + rotateStorageId(id); + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + for (RecipientId id : ids) { + Recipient.live(id).refresh(); + } + + StorageSyncHelper.scheduleSyncForDataChange(); + } + public void setSeenFirstInviteReminder(@NonNull RecipientId id) { setInsightsBannerTier(id, InsightsBannerTier.TIER_ONE); } diff --git a/app/src/main/res/drawable-night/ic_mute_24.xml b/app/src/main/res/drawable-night/ic_mute_24.xml new file mode 100644 index 000000000..e09195a3f --- /dev/null +++ b/app/src/main/res/drawable-night/ic_mute_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-night/ic_unmute_24.xml b/app/src/main/res/drawable-night/ic_unmute_24.xml new file mode 100644 index 000000000..c1459e54f --- /dev/null +++ b/app/src/main/res/drawable-night/ic_unmute_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_mute_24.xml b/app/src/main/res/drawable/ic_mute_24.xml new file mode 100644 index 000000000..85b2fe56a --- /dev/null +++ b/app/src/main/res/drawable/ic_mute_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_unmute_24.xml b/app/src/main/res/drawable/ic_unmute_24.xml new file mode 100644 index 000000000..da453d9d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_unmute_24.xml @@ -0,0 +1,9 @@ + + +