mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-31 09:56:10 +00:00
feat: add clear messages and media dialogs and change db to not use message count == 0 in conversations
This commit is contained in:
@@ -52,9 +52,18 @@ import androidx.viewpager.widget.ViewPager;
|
|||||||
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
|
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
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.messages.control.DataExtractionNotification;
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender;
|
import org.session.libsession.messaging.sending_receiving.MessageSender;
|
||||||
import org.session.libsession.utilities.Address;
|
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.CursorRecyclerViewAdapter;
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase;
|
import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||||
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader;
|
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.database.loaders.ThreadMediaLoader;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
|
||||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
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.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import kotlin.Unit;
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for displaying media attachments in-app
|
* Activity for displaying media attachments in-app
|
||||||
*/
|
*/
|
||||||
public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity implements View.OnClickListener {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final static String TAG = MediaOverviewActivity.class.getSimpleName();
|
private final static String TAG = MediaOverviewActivity.class.getSimpleName();
|
||||||
@@ -132,6 +138,20 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
this.recipient.addListener(recipient -> {
|
this.recipient.addListener(recipient -> {
|
||||||
Util.runOnMain(() -> actionBar.setTitle(recipient.toShortString()));
|
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() {
|
public void onEnterMultiSelect() {
|
||||||
@@ -139,6 +159,18 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
viewPager.setEnabled(false);
|
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() {
|
public void onExitMultiSelect() {
|
||||||
tabLayout.setEnabled(true);
|
tabLayout.setEnabled(true);
|
||||||
viewPager.setEnabled(true);
|
viewPager.setEnabled(true);
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import network.loki.messenger.databinding.ActivityConversationSettingsBinding
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.MediaOverviewActivity
|
import org.thoughtcrime.securesms.MediaOverviewActivity
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
|
import org.thoughtcrime.securesms.conversation.settings.ClearAllMessagesDialog.Option
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import org.thoughtcrime.securesms.database.LokiThreadDatabase
|
import org.thoughtcrime.securesms.database.LokiThreadDatabase
|
||||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||||
@@ -61,6 +62,7 @@ class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View.
|
|||||||
binding.profilePictureView.root.glide = GlideApp.with(this)
|
binding.profilePictureView.root.glide = GlideApp.with(this)
|
||||||
updateRecipientDisplay()
|
updateRecipientDisplay()
|
||||||
binding.searchConversation.setOnClickListener(this)
|
binding.searchConversation.setOnClickListener(this)
|
||||||
|
binding.clearMessages.setOnClickListener(this)
|
||||||
binding.allMedia.setOnClickListener(this)
|
binding.allMedia.setOnClickListener(this)
|
||||||
binding.pinConversation.setOnClickListener(this)
|
binding.pinConversation.setOnClickListener(this)
|
||||||
binding.notificationSettings.setOnClickListener(this)
|
binding.notificationSettings.setOnClickListener(this)
|
||||||
@@ -144,6 +146,11 @@ class ConversationSettingsActivity: PassphraseRequiredActionBarActivity(), View.
|
|||||||
notificationActivityCallback.launch(viewModel.threadId)
|
notificationActivityCallback.launch(viewModel.threadId)
|
||||||
}
|
}
|
||||||
v === binding.back -> onBackPressed()
|
v === binding.back -> onBackPressed()
|
||||||
|
v === binding.clearMessages -> {
|
||||||
|
ClearAllMessagesDialog(viewModel.isUserGroupAdmin()) { option ->
|
||||||
|
viewModel.clearMessages(option == Option.FOR_EVERYONE)
|
||||||
|
}.show(supportFragmentManager, "Clear messages dialog")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,12 +32,27 @@ class ConversationSettingsViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isUserGroupAdmin(): Boolean = recipient?.let { recipient ->
|
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 localUserAddress = prefs.getLocalNumber() ?: return@let false
|
||||||
val group = storage.getGroup(recipient.address.toGroupString())
|
val group = storage.getGroup(recipient.address.toGroupString())
|
||||||
group?.admins?.contains(Address.fromSerialized(localUserAddress)) ?: false // this will have to be replaced for new closed groups
|
group?.admins?.contains(Address.fromSerialized(localUserAddress)) ?: false // this will have to be replaced for new closed groups
|
||||||
} ?: false
|
} ?: 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
|
// DI-related
|
||||||
@dagger.assisted.AssistedFactory
|
@dagger.assisted.AssistedFactory
|
||||||
interface AssistedFactory {
|
interface AssistedFactory {
|
||||||
|
|||||||
@@ -731,6 +731,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
return threadDb.getPinned(threadID)
|
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 {
|
override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri {
|
||||||
return PartAuthority.getAttachmentDataUri(attachmentId)
|
return PartAuthority.getAttachmentDataUri(attachmentId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -466,7 +466,7 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getApprovedConversationList() {
|
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 ";
|
"AND " + ARCHIVED + " = 0 ";
|
||||||
return getConversationList(where);
|
return getConversationList(where);
|
||||||
}
|
}
|
||||||
@@ -692,6 +692,8 @@ public class ThreadDatabase extends Database {
|
|||||||
deleteThread(threadId);
|
deleteThread(threadId);
|
||||||
notifyConversationListListeners();
|
notifyConversationListListeners();
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
updateThread(threadId, 0, "", null, System.currentTimeMillis(), 0, 0, 0, false, 0, 0);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -731,8 +733,9 @@ public class ThreadDatabase extends Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean deleteThreadOnEmpty(long threadId) {
|
private boolean deleteThreadOnEmpty(long threadId) {
|
||||||
Recipient threadRecipient = getRecipientForThreadId(threadId);
|
return false; // TODO: test the deletion / clearing logic here to make sure this is the desired functionality
|
||||||
return threadRecipient != null && !threadRecipient.isOpenGroupRecipient();
|
// Recipient threadRecipient = getRecipientForThreadId(threadId);
|
||||||
|
// return threadRecipient != null && !threadRecipient.isOpenGroupRecipient();
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) {
|
private @NonNull String getFormattedBodyFor(@NonNull MessageRecord messageRecord) {
|
||||||
|
|||||||
65
app/src/main/res/layout/dialog_clear_all_media.xml
Normal file
65
app/src/main/res/layout/dialog_clear_all_media.xml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:padding="@dimen/medium_spacing">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="@dimen/small_spacing"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/dialog_clear_all_media_title"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="@dimen/medium_font_size" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialogDescriptionText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:text="@string/dialog_clear_all_media_message"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="@dimen/small_font_size" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Session.Button.Dialog.DestructiveText"
|
||||||
|
android:id="@+id/clear"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="@dimen/medium_spacing"
|
||||||
|
android:text="@string/dialog_clear_all_data_clear" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Session.Button.Dialog.UnimportantText"
|
||||||
|
android:id="@+id/cancel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/dialog_clear_all_messages_cancel" />
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.Small.ThreeBounce"
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:SpinKit_Color="?colorAccent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
97
app/src/main/res/layout/dialog_clear_all_messages.xml
Normal file
97
app/src/main/res/layout/dialog_clear_all_messages.xml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:elevation="4dp"
|
||||||
|
android:padding="@dimen/medium_spacing">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="@dimen/small_spacing"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/dialog_clear_all_messages_title"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="@dimen/medium_font_size" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/close"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
android:layout_gravity="end|top"
|
||||||
|
android:layout_marginHorizontal="@dimen/small_spacing"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
app:tint="?android:textColorPrimary" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialogDescriptionText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:text="@string/dialog_clear_all_messages_message"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="@dimen/small_font_size" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Session.Button.Dialog.DestructiveText"
|
||||||
|
android:id="@+id/clear"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="@dimen/medium_spacing"
|
||||||
|
android:text="@string/dialog_clear_all_data_clear" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Session.Button.Dialog.UnimportantText"
|
||||||
|
android:id="@+id/cancel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/dialog_clear_all_messages_cancel" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Session.Button.Dialog.DestructiveText"
|
||||||
|
android:id="@+id/forEveryone"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="@dimen/medium_spacing"
|
||||||
|
android:text="@string/dialog_clear_all_messages_for_everyone" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style="@style/Widget.Session.Button.Dialog.UnimportantText"
|
||||||
|
android:id="@+id/forMe"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/dialog_clear_all_messages_for_me" />
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.Small.ThreeBounce"
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/small_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
app:SpinKit_Color="?colorAccent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -17,7 +17,17 @@
|
|||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?android:attr/actionBarSize"
|
android:layout_height="?android:attr/actionBarSize"
|
||||||
app:layout_scrollFlags="scroll|enterAlways"/>
|
app:layout_scrollFlags="scroll|enterAlways">
|
||||||
|
<TextView
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_marginHorizontal="@dimen/medium_spacing"
|
||||||
|
android:textColor="@color/destructive"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:id="@+id/clearMedia"
|
||||||
|
android:text="@string/media_overview_activity__clear_media"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.ControllableTabLayout
|
<org.thoughtcrime.securesms.components.ControllableTabLayout
|
||||||
android:id="@+id/tab_layout"
|
android:id="@+id/tab_layout"
|
||||||
|
|||||||
@@ -882,4 +882,14 @@
|
|||||||
<string name="conversation_settings_delete_group">Delete Group</string>
|
<string name="conversation_settings_delete_group">Delete Group</string>
|
||||||
<string name="image">image</string>
|
<string name="image">image</string>
|
||||||
<string name="video">video</string>
|
<string name="video">video</string>
|
||||||
|
<string name="dialog_clear_all_messages_title">Clear All Messages</string>
|
||||||
|
<string name="dialog_clear_all_media_title">Clear All Media</string>
|
||||||
|
<string name="dialog_clear_all_messages_message">Are you sure you want to clear all group messages?</string>
|
||||||
|
<string name="dialog_clear_all_media_message">Are you sure you want to clear all media and files? This will also delete all messages with attachments.</string>
|
||||||
|
<string name="dialog_clear_all_messages_local_message">Are you sure you want to clear %s messages from your device?</string>
|
||||||
|
<string name="dialog_clear_all_messages_for_me">For Me</string>
|
||||||
|
<string name="dialog_clear_all_messages_for_everyone">For Everyone</string>
|
||||||
|
<string name="dialog_clear_all_messages_clear">Clear</string>
|
||||||
|
<string name="dialog_clear_all_messages_cancel">Cancel</string>
|
||||||
|
<string name="media_overview_activity__clear_media">Clear All</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,6 +1,81 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<style name="Base.Theme.Session" parent="@style/Theme.AppCompat.DayNight">
|
<style name="Base.Theme.Session" parent="@style/Theme.AppCompat.DayNight">
|
||||||
|
|
||||||
|
<!-- Main styles -->
|
||||||
|
<item name="sessionLogoTint">@color/classic_dark_6</item>
|
||||||
|
<item name="colorPrimary">@color/classic_dark_0</item>
|
||||||
|
<item name="colorPrimaryDark">@color/classic_dark_0</item>
|
||||||
|
<item name="colorControlNormal">?android:textColorPrimary</item>
|
||||||
|
<item name="colorControlActivated">?colorAccent</item>
|
||||||
|
<item name="android:colorControlHighlight">?colorAccent</item>
|
||||||
|
<item name="android:textColorPrimary">@color/classic_dark_6</item>
|
||||||
|
<item name="android:textColorSecondary">?android:textColorPrimary</item>
|
||||||
|
<item name="android:textColorTertiary">@color/classic_dark_5</item>
|
||||||
|
<item name="android:textColor">?android:textColorPrimary</item>
|
||||||
|
<item name="android:textColorHint">@color/gray27</item>
|
||||||
|
<item name="android:windowBackground">?colorPrimary</item>
|
||||||
|
<item name="android:navigationBarColor">@color/compose_view_background</item>
|
||||||
|
<item name="dialog_background_color">@color/classic_dark_1</item>
|
||||||
|
<item name="bottomSheetDialogTheme">@style/Classic.Dark.BottomSheet</item>
|
||||||
|
<item name="actionMenuTextColor">?android:textColorPrimary</item>
|
||||||
|
<item name="popupTheme">?actionBarPopupTheme</item>
|
||||||
|
<item name="colorCellBackground">@color/classic_dark_1</item>
|
||||||
|
<item name="colorSettingsBackground">@color/classic_dark_1</item>
|
||||||
|
<item name="colorDividerBackground">@color/classic_dark_3</item>
|
||||||
|
<item name="colorCellRipple">@color/classic_dark_3</item>
|
||||||
|
<item name="actionBarPopupTheme">@style/Dark.Popup</item>
|
||||||
|
<item name="actionBarWidgetTheme">@null</item>
|
||||||
|
<item name="actionBarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
|
||||||
|
<item name="actionBarStyle">@style/Widget.Session.ActionBar</item>
|
||||||
|
<item name="prominentButtonColor">?colorAccent</item>
|
||||||
|
<item name="elementBorderColor">@color/classic_dark_3</item>
|
||||||
|
|
||||||
|
<!-- Home screen -->
|
||||||
|
<item name="searchBackgroundColor">#1B1B1B</item>
|
||||||
|
<item name="searchIconColor">#E5E5E8</item>
|
||||||
|
<item name="searchHintColor">@color/classic_dark_5</item>
|
||||||
|
<item name="searchTextColor">?android:textColorPrimary</item>
|
||||||
|
<item name="searchHighlightTint">?colorAccent</item>
|
||||||
|
<item name="home_gradient_start">#00000000</item>
|
||||||
|
<item name="home_gradient_end">@color/classic_dark_1</item>
|
||||||
|
<item name="conversation_pinned_background_color">?colorCellBackground</item>
|
||||||
|
<item name="conversation_unread_background_color">@color/classic_dark_2</item>
|
||||||
|
<item name="conversation_pinned_icon_color">@color/classic_dark_4</item>
|
||||||
|
<item name="unreadIndicatorBackgroundColor">@color/classic_dark_3</item>
|
||||||
|
<item name="unreadIndicatorTextColor">@color/classic_dark_0</item>
|
||||||
|
|
||||||
|
<!-- New conversation button -->
|
||||||
|
<item name="conversation_color_non_main">@color/classic_dark_2</item>
|
||||||
|
<item name="conversation_shadow_non_main">@color/transparent_black_30</item>
|
||||||
|
<item name="conversation_shadow_main">?colorAccent</item>
|
||||||
|
<item name="conversation_menu_background_color">@color/classic_dark_1</item>
|
||||||
|
<item name="conversation_menu_cell_color">?conversation_menu_background_color</item>
|
||||||
|
<item name="conversation_menu_border_color">@color/classic_dark_3</item>
|
||||||
|
<item name="conversationMenuSearchBackgroundColor">@color/classic_dark_0</item>
|
||||||
|
|
||||||
|
<!-- Conversation -->
|
||||||
|
<item name="message_received_background_color">@color/classic_dark_2</item>
|
||||||
|
<item name="message_received_text_color">@color/classic_dark_6</item>
|
||||||
|
<item name="message_sent_background_color">?colorAccent</item>
|
||||||
|
<item name="message_sent_text_color">@color/classic_dark_0</item>
|
||||||
|
<item name="input_bar_background">@color/classic_dark_1</item>
|
||||||
|
<item name="input_bar_text_hint">@color/classic_dark_5</item>
|
||||||
|
<item name="input_bar_text_user">@color/classic_dark_6</item>
|
||||||
|
<item name="input_bar_border">@color/classic_dark_3</item>
|
||||||
|
<item name="input_bar_button_background">@color/classic_dark_2</item>
|
||||||
|
<item name="input_bar_button_text_color">@color/classic_dark_6</item>
|
||||||
|
<item name="input_bar_button_background_opaque">@color/classic_dark_2</item>
|
||||||
|
<item name="input_bar_button_background_opaque_border">@color/classic_dark_3</item>
|
||||||
|
<item name="input_bar_lock_view_background">@color/classic_dark_2</item>
|
||||||
|
<item name="input_bar_lock_view_border">@color/classic_dark_3</item>
|
||||||
|
<item name="mention_candidates_view_background">@color/classic_dark_2</item>
|
||||||
|
<item name="mention_candidates_view_background_ripple">@color/classic_dark_3</item>
|
||||||
|
<item name="scroll_to_bottom_button_background">@color/classic_dark_1</item>
|
||||||
|
<item name="scroll_to_bottom_button_border">@color/classic_dark_3</item>
|
||||||
|
<item name="conversation_unread_count_indicator_background">@color/classic_dark_4</item>
|
||||||
|
<item name="message_selected">@color/classic_dark_1</item>
|
||||||
|
|
||||||
<item name="actionModeBackground">?colorPrimary</item>
|
<item name="actionModeBackground">?colorPrimary</item>
|
||||||
<item name="android:colorBackground">?colorPrimary</item>
|
<item name="android:colorBackground">?colorPrimary</item>
|
||||||
<item name="dialog_background_color">?colorPrimary</item>
|
<item name="dialog_background_color">?colorPrimary</item>
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.v2
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.mockito.kotlin.anyOrNull
|
||||||
|
import org.mockito.kotlin.never
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import org.mockito.kotlin.whenever
|
||||||
|
import org.session.libsession.database.StorageProtocol
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.GroupRecord
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.BaseViewModelTest
|
||||||
|
import org.thoughtcrime.securesms.conversation.settings.ConversationSettingsViewModel
|
||||||
|
|
||||||
|
class ConversationSettingsViewModelTest: BaseViewModelTest() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TEST_THREAD_ID = 1L
|
||||||
|
const val TEST_LOCAL_ID = "1234"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val mockedStorage = mock(StorageProtocol::class.java)
|
||||||
|
private val mockedPrefs = mock(TextSecurePreferences::class.java)
|
||||||
|
private val mockedRecipient = mock(Recipient::class.java)
|
||||||
|
|
||||||
|
private val viewModel = ConversationSettingsViewModel(TEST_THREAD_ID, mockedStorage, mockedPrefs)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
whenever(mockedStorage.getRecipientForThread(TEST_THREAD_ID)).thenReturn(mockedRecipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should get a mocked recipient`() {
|
||||||
|
assertEquals(mockedRecipient, viewModel.recipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should report correct pin status`() {
|
||||||
|
whenever(mockedStorage.isThreadPinned(TEST_THREAD_ID)).thenReturn(true)
|
||||||
|
val pinStatus = viewModel.isPinned()
|
||||||
|
verify(mockedStorage).isThreadPinned(TEST_THREAD_ID)
|
||||||
|
assertTrue(pinStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should auto download attachments`() {
|
||||||
|
whenever(mockedStorage.shouldAutoDownloadAttachments(mockedRecipient)).thenReturn(true)
|
||||||
|
val shouldDownload = viewModel.autoDownloadAttachments()
|
||||||
|
verify(mockedStorage).shouldAutoDownloadAttachments(mockedRecipient)
|
||||||
|
assertTrue(shouldDownload)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should not auto download if recipient null`() {
|
||||||
|
whenever(mockedStorage.getRecipientForThread(TEST_THREAD_ID)).thenReturn(null)
|
||||||
|
val shouldDownload = viewModel.autoDownloadAttachments()
|
||||||
|
verify(mockedStorage, never()).shouldAutoDownloadAttachments(anyOrNull())
|
||||||
|
assertFalse(shouldDownload)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should call storage for if user is an admin`() {
|
||||||
|
val groupAddress = Address.fromSerialized("__textsecure_group__!1234")
|
||||||
|
whenever(mockedRecipient.isClosedGroupRecipient).thenReturn(true)
|
||||||
|
whenever(mockedRecipient.address).thenReturn(groupAddress)
|
||||||
|
whenever(mockedPrefs.getLocalNumber()).thenReturn(TEST_LOCAL_ID)
|
||||||
|
val mockedGroup = mock(GroupRecord::class.java).apply {
|
||||||
|
whenever(this.admins).thenReturn(listOf(Address.fromSerialized(TEST_LOCAL_ID)))
|
||||||
|
}
|
||||||
|
whenever(mockedStorage.getGroup(groupAddress.serialize())).thenReturn(mockedGroup)
|
||||||
|
val isUserAdmin = viewModel.isUserGroupAdmin()
|
||||||
|
assertTrue(isUserAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should not call storage for group admin when we aren't in a group`() {
|
||||||
|
whenever(mockedRecipient.isClosedGroupRecipient).thenReturn(false)
|
||||||
|
val isUserAdmin = viewModel.isUserGroupAdmin()
|
||||||
|
assertFalse(isUserAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should `() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -155,6 +155,7 @@ interface StorageProtocol {
|
|||||||
fun getMessageCount(threadID: Long): Long
|
fun getMessageCount(threadID: Long): Long
|
||||||
fun setThreadPinned(threadID: Long, isPinned: Boolean)
|
fun setThreadPinned(threadID: Long, isPinned: Boolean)
|
||||||
fun isThreadPinned(threadID: Long): Boolean
|
fun isThreadPinned(threadID: Long): Boolean
|
||||||
|
fun clearMessages(threadID: Long): Boolean
|
||||||
|
|
||||||
// Contacts
|
// Contacts
|
||||||
fun getContactWithSessionID(sessionID: String): Contact?
|
fun getContactWithSessionID(sessionID: String): Contact?
|
||||||
|
|||||||
Reference in New Issue
Block a user