From ca200c4f2e56bb802ef1a82bf9adfeddf32b098a Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 14 Nov 2022 17:22:00 +1100 Subject: [PATCH] feat: add clear messages and media dialogs and change db to not use message count == 0 in conversations --- .../securesms/MediaOverviewActivity.java | 42 +++++++- .../settings/ClearAllMediaDialog.kt | 33 +++++++ .../settings/ClearAllMessagesDialog.kt | 53 ++++++++++ .../settings/ConversationSettingsActivity.kt | 7 ++ .../settings/ConversationSettingsViewModel.kt | 17 +++- .../securesms/database/Storage.kt | 10 ++ .../securesms/database/ThreadDatabase.java | 9 +- .../res/layout/dialog_clear_all_media.xml | 65 +++++++++++++ .../res/layout/dialog_clear_all_messages.xml | 97 +++++++++++++++++++ .../res/layout/media_overview_activity.xml | 12 ++- app/src/main/res/values/strings.xml | 10 ++ app/src/main/res/values/themes.xml | 75 ++++++++++++++ .../v2/ConversationSettingsViewModelTest.kt | 95 ++++++++++++++++++ .../libsession/database/StorageProtocol.kt | 1 + 14 files changed, 516 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ClearAllMediaDialog.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ClearAllMessagesDialog.kt create mode 100644 app/src/main/res/layout/dialog_clear_all_media.xml create mode 100644 app/src/main/res/layout/dialog_clear_all_messages.xml create mode 100644 app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationSettingsViewModelTest.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java index 53a909c5a..561b03672 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -52,9 +52,18 @@ import androidx.viewpager.widget.ViewPager; import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager; import com.google.android.material.tabs.TabLayout; +import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.messages.control.DataExtractionNotification; import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.utilities.Address; +import org.session.libsession.utilities.GroupRecord; +import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.ViewUtil; +import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.task.ProgressDialogAsyncTask; +import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.conversation.settings.ClearAllMediaDialog; import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter; import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader; @@ -62,25 +71,22 @@ import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.Buc import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.permissions.Permissions; -import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.StickyHeaderDecoration; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.task.ProgressDialogAsyncTask; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import kotlin.Unit; import network.loki.messenger.R; /** * Activity for displaying media attachments in-app */ -public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { +public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity implements View.OnClickListener { @SuppressWarnings("unused") private final static String TAG = MediaOverviewActivity.class.getSimpleName(); @@ -132,6 +138,20 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { this.recipient.addListener(recipient -> { Util.runOnMain(() -> actionBar.setTitle(recipient.toShortString())); }); + View clearButton = toolbar.findViewById(R.id.clearMedia); + if (!this.recipient.isClosedGroupRecipient()) { + clearButton.setVisibility(View.GONE); + } else { + String userPublicKey = TextSecurePreferences.getLocalNumber(this); + GroupRecord groupRecord = MessagingModuleConfiguration.getShared().getStorage().getGroup(this.recipient.getAddress().toGroupString()); + if (userPublicKey == null || groupRecord == null) { + clearButton.setVisibility(View.GONE); + } else { + boolean isUserAdmin = groupRecord.getAdmins().contains(Address.fromSerialized(userPublicKey)); + clearButton.setVisibility(isUserAdmin ? View.VISIBLE : View.GONE); + clearButton.setOnClickListener(this); + } + } } public void onEnterMultiSelect() { @@ -139,6 +159,18 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { viewPager.setEnabled(false); } + @Override + public void onClick(View v) { + if (v.getId() == R.id.clearMedia) { + FragmentManager fm = getSupportFragmentManager(); + ClearAllMediaDialog dialog = new ClearAllMediaDialog(() -> { + Log.d("Loki", "Clear all the media"); + return Unit.INSTANCE; + }); + dialog.show(fm, "ClearAllMedia"); + } + } + public void onExitMultiSelect() { tabLayout.setEnabled(true); viewPager.setEnabled(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ClearAllMediaDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ClearAllMediaDialog.kt new file mode 100644 index 000000000..9c5048ce1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ClearAllMediaDialog.kt @@ -0,0 +1,33 @@ +package org.thoughtcrime.securesms.conversation.settings + +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.app.AlertDialog +import network.loki.messenger.databinding.DialogClearAllMediaBinding +import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog + +class ClearAllMediaDialog(private val callback: ()->Unit): BaseDialog(), View.OnClickListener { + + private lateinit var binding: DialogClearAllMediaBinding + + override fun setContentView(builder: AlertDialog.Builder) { + super.setContentView(builder) + binding = DialogClearAllMediaBinding.inflate(LayoutInflater.from(requireContext())) + with (binding) { + clear.setOnClickListener(this@ClearAllMediaDialog) + cancel.setOnClickListener(this@ClearAllMediaDialog) + } + builder.setView(binding.root) + } + + override fun onClick(v: View) { + when { + v === binding.cancel -> dismiss() + v === binding.clear -> { + callback() + dismiss() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ClearAllMessagesDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ClearAllMessagesDialog.kt new file mode 100644 index 000000000..1824001bc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ClearAllMessagesDialog.kt @@ -0,0 +1,53 @@ +package org.thoughtcrime.securesms.conversation.settings + +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import network.loki.messenger.databinding.DialogClearAllMessagesBinding +import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog + +class ClearAllMessagesDialog(private val isUserAdmin: Boolean, private val callback: (Option) -> Unit): BaseDialog(), View.OnClickListener { + + enum class Option { + FOR_ME, + FOR_EVERYONE + } + + private lateinit var binding: DialogClearAllMessagesBinding + + override fun setContentView(builder: AlertDialog.Builder) { + super.setContentView(builder) + binding = DialogClearAllMessagesBinding.inflate(LayoutInflater.from(requireContext())) + with (binding) { + forEveryone.isVisible = isUserAdmin + forEveryone.setOnClickListener(this@ClearAllMessagesDialog) + forMe.isVisible = isUserAdmin + forMe.setOnClickListener(this@ClearAllMessagesDialog) + close.isVisible = isUserAdmin + close.setOnClickListener(this@ClearAllMessagesDialog) + + cancel.isVisible = !isUserAdmin + cancel.setOnClickListener(this@ClearAllMessagesDialog) + clear.isVisible = !isUserAdmin + clear.setOnClickListener(this@ClearAllMessagesDialog) + } + builder.setView(binding.root) + } + + override fun onClick(v: View) { + when { + v === binding.cancel || + v === binding.close -> dismiss() + v === binding.forMe || + v === binding.clear -> { + callback(Option.FOR_ME) + dismiss() + } + v === binding.forEveryone -> { + callback(Option.FOR_EVERYONE) + dismiss() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivity.kt index 0fe8b021f..a548a81ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivity.kt @@ -11,6 +11,7 @@ import network.loki.messenger.databinding.ActivityConversationSettingsBinding import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.MediaOverviewActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity +import org.thoughtcrime.securesms.conversation.settings.ClearAllMessagesDialog.Option import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.ThreadDatabase @@ -61,6 +62,7 @@ class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View. binding.profilePictureView.root.glide = GlideApp.with(this) updateRecipientDisplay() binding.searchConversation.setOnClickListener(this) + binding.clearMessages.setOnClickListener(this) binding.allMedia.setOnClickListener(this) binding.pinConversation.setOnClickListener(this) binding.notificationSettings.setOnClickListener(this) @@ -144,6 +146,11 @@ class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View. notificationActivityCallback.launch(viewModel.threadId) } v === binding.back -> onBackPressed() + v === binding.clearMessages -> { + ClearAllMessagesDialog(viewModel.isUserGroupAdmin()) { option -> + viewModel.clearMessages(option == Option.FOR_EVERYONE) + }.show(supportFragmentManager, "Clear messages dialog") + } } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsViewModel.kt index 083161c5e..c1bc18174 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsViewModel.kt @@ -32,12 +32,27 @@ class ConversationSettingsViewModel( } fun isUserGroupAdmin(): Boolean = recipient?.let { recipient -> - if (!recipient.isGroupRecipient) return@let false + if (!recipient.isClosedGroupRecipient) return@let false val localUserAddress = prefs.getLocalNumber() ?: return@let false val group = storage.getGroup(recipient.address.toGroupString()) group?.admins?.contains(Address.fromSerialized(localUserAddress)) ?: false // this will have to be replaced for new closed groups } ?: false + fun clearMessages(forAll: Boolean) { + if (forAll && !isUserGroupAdmin()) return + + if (!forAll) { + viewModelScope.launch { + storage.clearMessages(threadId) + } + } else { + // do a send message here and on success do a clear messages + viewModelScope.launch { + storage.clearMessages(threadId) + } + } + } + // DI-related @dagger.assisted.AssistedFactory interface AssistedFactory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index f2fb35aab..1bb866128 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -731,6 +731,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return threadDb.getPinned(threadID) } + override fun clearMessages(threadID: Long): Boolean { + val smsDb = DatabaseComponent.get(context).smsDatabase() + val mmsDb = DatabaseComponent.get(context).mmsDatabase() + val threadDb = DatabaseComponent.get(context).threadDatabase() + smsDb.deleteThread(threadID) + mmsDb.deleteThread(threadID) // threadDB update called from within + threadDb.update(threadID, false) + return true + } + override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentDataUri(attachmentId) } 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 6b892bdef..58a92fdf0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -466,7 +466,7 @@ public class ThreadDatabase extends Database { } public Cursor getApprovedConversationList() { - String where = "((" + MESSAGE_COUNT + " != 0 AND (" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%')) OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " + + String where = "((" + HAS_SENT + " = 1 OR " + RecipientDatabase.APPROVED + " = 1 OR "+ GroupDatabase.TABLE_NAME +"."+GROUP_ID+" LIKE '"+CLOSED_GROUP_PREFIX+"%') OR " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " LIKE '" + OPEN_GROUP_PREFIX + "%') " + "AND " + ARCHIVED + " = 0 "; return getConversationList(where); } @@ -692,6 +692,8 @@ public class ThreadDatabase extends Database { deleteThread(threadId); notifyConversationListListeners(); return true; + } else { + updateThread(threadId, 0, "", null, System.currentTimeMillis(), 0, 0, 0, false, 0, 0); } return false; } @@ -731,8 +733,9 @@ public class ThreadDatabase extends Database { } private boolean deleteThreadOnEmpty(long threadId) { - Recipient threadRecipient = getRecipientForThreadId(threadId); - return threadRecipient != null && !threadRecipient.isOpenGroupRecipient(); + return false; // TODO: test the deletion / clearing logic here to make sure this is the desired functionality +// Recipient threadRecipient = getRecipientForThreadId(threadId); +// return threadRecipient != null && !threadRecipient.isOpenGroupRecipient(); } private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) { diff --git a/app/src/main/res/layout/dialog_clear_all_media.xml b/app/src/main/res/layout/dialog_clear_all_media.xml new file mode 100644 index 000000000..b81c8cadd --- /dev/null +++ b/app/src/main/res/layout/dialog_clear_all_media.xml @@ -0,0 +1,65 @@ + + + + + + + + + +