Add ability to hide contacts behind a feature flag.
This commit is contained in:
parent
a8a773db43
commit
04eeb434c9
19 changed files with 511 additions and 45 deletions
|
@ -259,7 +259,7 @@ class MmsDatabaseTest_stories {
|
|||
)
|
||||
|
||||
// WHEN
|
||||
val result = mms.hasSelfReplyInStory(groupStoryId)
|
||||
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
|
||||
|
||||
// THEN
|
||||
assertFalse(result)
|
||||
|
@ -284,7 +284,7 @@ class MmsDatabaseTest_stories {
|
|||
)
|
||||
|
||||
// WHEN
|
||||
val result = mms.hasSelfReplyInStory(groupStoryId)
|
||||
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
|
||||
|
||||
// THEN
|
||||
assertTrue(result)
|
||||
|
@ -309,7 +309,7 @@ class MmsDatabaseTest_stories {
|
|||
)
|
||||
|
||||
// WHEN
|
||||
val result = mms.hasSelfReplyInStory(groupStoryId)
|
||||
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
|
||||
|
||||
// THEN
|
||||
assertFalse(result)
|
||||
|
@ -337,7 +337,7 @@ class MmsDatabaseTest_stories {
|
|||
)
|
||||
|
||||
// WHEN
|
||||
val result = mms.hasSelfReplyInStory(groupStoryId)
|
||||
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
|
||||
|
||||
// THEN
|
||||
assertFalse(result)
|
||||
|
|
|
@ -18,6 +18,77 @@ class RecipientDatabaseTest {
|
|||
@get:Rule
|
||||
val harness = SignalActivityRule()
|
||||
|
||||
@Test
|
||||
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||
val hiddenRecipient = harness.others[0]
|
||||
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||
|
||||
val results = SignalDatabase.recipients.queryAllContacts("Hidden")!!
|
||||
|
||||
assertEquals(0, results.count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAHiddenRecipient_whenIGetSignalContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||
val hiddenRecipient = harness.others[0]
|
||||
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||
|
||||
val results: MutableList<RecipientId> = SignalDatabase.recipients.getSignalContacts(false)?.use {
|
||||
val ids = mutableListOf<RecipientId>()
|
||||
while (it.moveToNext()) {
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientDatabase.ID)))
|
||||
}
|
||||
|
||||
ids
|
||||
}!!
|
||||
|
||||
assertNotEquals(0, results.size)
|
||||
assertFalse(hiddenRecipient in results)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAHiddenRecipient_whenIQuerySignalContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||
val hiddenRecipient = harness.others[0]
|
||||
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||
|
||||
val results = SignalDatabase.recipients.querySignalContacts("Hidden", false)!!
|
||||
|
||||
assertEquals(0, results.count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAHiddenRecipient_whenIQueryNonGroupContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||
val hiddenRecipient = harness.others[0]
|
||||
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||
|
||||
val results = SignalDatabase.recipients.queryNonGroupContacts("Hidden", false)!!
|
||||
|
||||
assertEquals(0, results.count)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenAHiddenRecipient_whenIGetNonGroupContacts_thenIDoNotExpectHiddenToBeReturned() {
|
||||
val hiddenRecipient = harness.others[0]
|
||||
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||
|
||||
val results: MutableList<RecipientId> = SignalDatabase.recipients.getNonGroupContacts(false)?.use {
|
||||
val ids = mutableListOf<RecipientId>()
|
||||
while (it.moveToNext()) {
|
||||
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientDatabase.ID)))
|
||||
}
|
||||
|
||||
ids
|
||||
}!!
|
||||
|
||||
assertNotEquals(0, results.size)
|
||||
assertFalse(hiddenRecipient in results)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun givenABlockedRecipient_whenIQueryAllContacts_thenIDoNotExpectBlockedToBeReturned() {
|
||||
val blockedRecipient = harness.others[0]
|
||||
|
|
|
@ -144,20 +144,20 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
private MappingAdapter contactChipAdapter;
|
||||
private ContactChipViewModel contactChipViewModel;
|
||||
private LifecycleDisposable lifecycleDisposable;
|
||||
|
||||
private HeaderActionProvider headerActionProvider;
|
||||
private TextView headerActionView;
|
||||
|
||||
@Nullable private FixedViewsAdapter headerAdapter;
|
||||
@Nullable private FixedViewsAdapter footerAdapter;
|
||||
@Nullable private ListCallback listCallback;
|
||||
@Nullable private ScrollCallback scrollCallback;
|
||||
private GlideRequests glideRequests;
|
||||
private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS;
|
||||
private Set<RecipientId> currentSelection;
|
||||
private boolean isMulti;
|
||||
private boolean hideCount;
|
||||
private boolean canSelectSelf;
|
||||
@Nullable private FixedViewsAdapter headerAdapter;
|
||||
@Nullable private FixedViewsAdapter footerAdapter;
|
||||
@Nullable private ListCallback listCallback;
|
||||
@Nullable private ScrollCallback scrollCallback;
|
||||
@Nullable private OnItemLongClickListener onItemLongClickListener;
|
||||
private GlideRequests glideRequests;
|
||||
private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS;
|
||||
private Set<RecipientId> currentSelection;
|
||||
private boolean isMulti;
|
||||
private boolean hideCount;
|
||||
private boolean canSelectSelf;
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
|
@ -206,6 +206,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
if (getParentFragment() instanceof HeaderActionProvider) {
|
||||
headerActionProvider = (HeaderActionProvider) getParentFragment();
|
||||
}
|
||||
|
||||
if (context instanceof OnItemLongClickListener) {
|
||||
onItemLongClickListener = (OnItemLongClickListener) context;
|
||||
}
|
||||
|
||||
if (getParentFragment() instanceof OnItemLongClickListener) {
|
||||
onItemLongClickListener = (OnItemLongClickListener) getParentFragment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -720,6 +728,15 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemLongClick(ContactSelectionListItem item) {
|
||||
if (onItemLongClickListener != null) {
|
||||
return onItemLongClickListener.onLongClick(item);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean selectionHardLimitReached() {
|
||||
|
@ -850,6 +867,10 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
|||
@NonNull HeaderAction getHeaderAction();
|
||||
}
|
||||
|
||||
public interface OnItemLongClickListener {
|
||||
boolean onLongClick(ContactSelectionListItem contactSelectionListItem);
|
||||
}
|
||||
|
||||
public interface AbstractContactsCursorLoaderFactoryProvider {
|
||||
@NonNull AbstractContactsCursorLoader.Factory get();
|
||||
}
|
||||
|
|
|
@ -20,11 +20,27 @@ import android.content.Intent;
|
|||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.menu.ActionItem;
|
||||
import org.thoughtcrime.securesms.components.menu.SignalContextMenu;
|
||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||
import org.thoughtcrime.securesms.contacts.management.ContactsManagementRepository;
|
||||
import org.thoughtcrime.securesms.contacts.management.ContactsManagementViewModel;
|
||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
|
@ -33,32 +49,57 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable;
|
||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Activity container for starting a new conversation.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
public class NewConversationActivity extends ContactSelectionActivity
|
||||
implements ContactSelectionListFragment.ListCallback
|
||||
implements ContactSelectionListFragment.ListCallback, ContactSelectionListFragment.OnItemLongClickListener
|
||||
{
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Log.tag(NewConversationActivity.class);
|
||||
|
||||
private ContactsManagementViewModel viewModel;
|
||||
private ActivityResultLauncher<Intent> contactLauncher;
|
||||
|
||||
private final LifecycleDisposable disposables = new LifecycleDisposable();
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, boolean ready) {
|
||||
super.onCreate(bundle, ready);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.NewConversationActivity__new_message);
|
||||
|
||||
disposables.bindTo(this);
|
||||
|
||||
ContactsManagementRepository repository = new ContactsManagementRepository(this);
|
||||
ContactsManagementViewModel.Factory factory = new ContactsManagementViewModel.Factory(repository);
|
||||
|
||||
contactLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> {
|
||||
if (activityResult.getResultCode() == RESULT_OK) {
|
||||
handleManualRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
viewModel = new ViewModelProvider(this, factory).get(ContactsManagementViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,10 +161,18 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||
super.onOptionsItemSelected(item);
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home: super.onBackPressed(); return true;
|
||||
case R.id.menu_refresh: handleManualRefresh(); return true;
|
||||
case R.id.menu_new_group: handleCreateGroup(); return true;
|
||||
case R.id.menu_invite: handleInvite(); return true;
|
||||
case android.R.id.home:
|
||||
super.onBackPressed();
|
||||
return true;
|
||||
case R.id.menu_refresh:
|
||||
handleManualRefresh();
|
||||
return true;
|
||||
case R.id.menu_new_group:
|
||||
handleCreateGroup();
|
||||
return true;
|
||||
case R.id.menu_invite:
|
||||
handleInvite();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -162,4 +211,143 @@ public class NewConversationActivity extends ContactSelectionActivity
|
|||
handleCreateGroup();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(ContactSelectionListItem contactSelectionListItem) {
|
||||
RecipientId recipientId = contactSelectionListItem.getRecipientId().orElse(null);
|
||||
if (recipientId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<ActionItem> actions = generateContextualActionsForRecipient(recipientId);
|
||||
if (actions.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
new SignalContextMenu.Builder(contactSelectionListItem, (ViewGroup) contactSelectionListItem.getRootView())
|
||||
.preferredVerticalPosition(SignalContextMenu.VerticalPosition.BELOW)
|
||||
.preferredHorizontalPosition(SignalContextMenu.HorizontalPosition.START)
|
||||
.offsetX((int) DimensionUnit.DP.toPixels(12))
|
||||
.offsetY((int) DimensionUnit.DP.toPixels(12))
|
||||
.show(actions);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private @NonNull List<ActionItem> generateContextualActionsForRecipient(@NonNull RecipientId recipientId) {
|
||||
Recipient recipient = Recipient.resolved(recipientId);
|
||||
|
||||
return Stream.of(
|
||||
createMessageActionItem(recipient),
|
||||
createAudioCallActionItem(recipient),
|
||||
createVideoCallActionItem(recipient),
|
||||
createRemoveActionItem(recipient),
|
||||
createBlockActionItem(recipient)
|
||||
).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private @NonNull ActionItem createMessageActionItem(@NonNull Recipient recipient) {
|
||||
return new ActionItem(
|
||||
R.drawable.ic_message_24,
|
||||
getString(R.string.NewConversationActivity__message),
|
||||
R.color.signal_colorOnSurface,
|
||||
() -> startActivity(ConversationIntents.createBuilder(this, recipient.getId(), -1L).build())
|
||||
);
|
||||
}
|
||||
|
||||
private @Nullable ActionItem createAudioCallActionItem(@NonNull Recipient recipient) {
|
||||
if (recipient.isSelf() || recipient.isGroup()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ActionItem(
|
||||
R.drawable.ic_phone_right_24,
|
||||
getString(R.string.NewConversationActivity__audio_call),
|
||||
R.color.signal_colorOnSurface,
|
||||
() -> CommunicationActions.startVoiceCall(this, recipient)
|
||||
);
|
||||
}
|
||||
|
||||
private @Nullable ActionItem createVideoCallActionItem(@NonNull Recipient recipient) {
|
||||
if (recipient.isSelf() || recipient.isMmsGroup()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ActionItem(
|
||||
R.drawable.ic_video_call_24,
|
||||
getString(R.string.NewConversationActivity__video_call),
|
||||
R.color.signal_colorOnSurface,
|
||||
() -> CommunicationActions.startVideoCall(this, recipient)
|
||||
);
|
||||
}
|
||||
|
||||
private @Nullable ActionItem createRemoveActionItem(@NonNull Recipient recipient) {
|
||||
if (!FeatureFlags.hideContacts() || recipient.isSelf() || recipient.isGroup()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ActionItem(
|
||||
R.drawable.ic_minus_circle_20, // TODO [alex] -- correct asset
|
||||
getString(R.string.NewConversationActivity__remove),
|
||||
R.color.signal_colorOnSurface,
|
||||
() -> {
|
||||
if (recipient.isSystemContact()) {
|
||||
displayIsInSystemContactsDialog(recipient);
|
||||
} else {
|
||||
displayRemovalDialog(recipient);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("CodeBlock2Expr")
|
||||
private @Nullable ActionItem createBlockActionItem(@NonNull Recipient recipient) {
|
||||
if (recipient.isSelf()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ActionItem(
|
||||
R.drawable.ic_block_tinted_24,
|
||||
getString(R.string.NewConversationActivity__block),
|
||||
R.color.signal_colorError,
|
||||
() -> BlockUnblockDialog.showBlockFor(this,
|
||||
this.getLifecycle(),
|
||||
recipient,
|
||||
() -> {
|
||||
disposables.add(viewModel.blockContact(recipient).subscribe(() -> {
|
||||
displaySnackbar(R.string.NewConversationActivity__s_has_been_removed);
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private void displayIsInSystemContactsDialog(@NonNull Recipient recipient) {
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(getString(R.string.NewConversationActivity__unable_to_remove_s, recipient.getShortDisplayName(this)))
|
||||
.setMessage(R.string.NewConversationActivity__this_person_is_saved_to_your)
|
||||
.setPositiveButton(R.string.NewConversationActivity__view_contact,
|
||||
(dialog, which) -> contactLauncher.launch(new Intent(Intent.ACTION_VIEW, recipient.getContactUri()))
|
||||
)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void displayRemovalDialog(@NonNull Recipient recipient) {
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(getString(R.string.NewConversationActivity__remove_s, recipient.getShortDisplayName(this)))
|
||||
.setMessage(R.string.NewConversationActivity__you_wont_see_this_person)
|
||||
.setPositiveButton(R.string.NewConversationActivity__remove,
|
||||
(dialog, which) -> {
|
||||
disposables.add(viewModel.hideContact(recipient).subscribe(() -> {
|
||||
displaySnackbar(R.string.NewConversationActivity__s_has_been_removed);
|
||||
}));
|
||||
}
|
||||
)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void displaySnackbar(@StringRes int message) {
|
||||
Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,6 +130,14 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
|||
itemView.setOnClickListener(v -> {
|
||||
if (clickListener != null) clickListener.onItemClick(getView());
|
||||
});
|
||||
|
||||
itemView.setOnLongClickListener(v -> {
|
||||
if (clickListener != null) {
|
||||
return clickListener.onItemLongClick(getView());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ContactSelectionListItem getView() {
|
||||
|
@ -435,5 +443,6 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
|||
|
||||
public interface ItemClickListener {
|
||||
void onItemClick(ContactSelectionListItem item);
|
||||
boolean onItemLongClick(ContactSelectionListItem item);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package org.thoughtcrime.securesms.contacts.management
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.CheckResult
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil
|
||||
|
||||
class ContactsManagementRepository(context: Context) {
|
||||
private val context = context.applicationContext
|
||||
|
||||
@CheckResult
|
||||
fun blockContact(recipient: Recipient): Completable {
|
||||
return Completable.fromAction {
|
||||
if (recipient.isDistributionList) {
|
||||
error("Blocking a distribution list makes no sense")
|
||||
} else if (recipient.isGroup) {
|
||||
RecipientUtil.block(context, recipient)
|
||||
} else {
|
||||
RecipientUtil.blockNonGroup(context, recipient)
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun hideContact(recipient: Recipient): Completable {
|
||||
return Completable.fromAction {
|
||||
if (recipient.isGroup || recipient.isDistributionList || recipient.isSelf) {
|
||||
error("Cannot hide groups, self, or distribution lists.")
|
||||
}
|
||||
|
||||
SignalDatabase.recipients.markHidden(recipient.id)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.thoughtcrime.securesms.contacts.management
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
class ContactsManagementViewModel(private val repository: ContactsManagementRepository) : ViewModel() {
|
||||
|
||||
@CheckResult
|
||||
fun hideContact(recipient: Recipient): Completable {
|
||||
return repository.hideContact(recipient).observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun blockContact(recipient: Recipient): Completable {
|
||||
return repository.blockContact(recipient).observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
class Factory(private val repository: ContactsManagementRepository) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(ContactsManagementViewModel(repository)) as T
|
||||
}
|
||||
}
|
||||
}
|
|
@ -184,6 +184,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
private const val IDENTITY_KEY = "identity_key"
|
||||
private const val NEEDS_PNI_SIGNATURE = "needs_pni_signature"
|
||||
private const val UNREGISTERED_TIMESTAMP = "unregistered_timestamp"
|
||||
private const val HIDDEN = "hidden"
|
||||
|
||||
@JvmField
|
||||
val CREATE_TABLE =
|
||||
|
@ -243,7 +244,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
$PNI_COLUMN TEXT DEFAULT NULL,
|
||||
$DISTRIBUTION_LIST_ID INTEGER DEFAULT NULL,
|
||||
$NEEDS_PNI_SIGNATURE INTEGER DEFAULT 0,
|
||||
$UNREGISTERED_TIMESTAMP INTEGER DEFAULT 0
|
||||
$UNREGISTERED_TIMESTAMP INTEGER DEFAULT 0,
|
||||
$HIDDEN INTEGER DEFAULT 0
|
||||
)
|
||||
""".trimIndent()
|
||||
|
||||
|
@ -304,7 +306,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
CUSTOM_CHAT_COLORS_ID,
|
||||
BADGES,
|
||||
DISTRIBUTION_LIST_ID,
|
||||
NEEDS_PNI_SIGNATURE
|
||||
NEEDS_PNI_SIGNATURE,
|
||||
HIDDEN
|
||||
)
|
||||
|
||||
private val ID_PROJECTION = arrayOf(ID)
|
||||
|
@ -386,7 +389,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
$TABLE_NAME.$REGISTERED = ${RegisteredState.NOT_REGISTERED.id} AND
|
||||
$TABLE_NAME.$SEEN_INVITE_REMINDER < ${InsightsBannerTier.TIER_TWO.id} AND
|
||||
${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.HAS_SENT} AND
|
||||
${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.DATE} > ?
|
||||
${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.DATE} > ? AND
|
||||
$TABLE_NAME.$HIDDEN = 0
|
||||
ORDER BY ${ThreadDatabase.TABLE_NAME}.${ThreadDatabase.DATE} DESC LIMIT 50
|
||||
"""
|
||||
}
|
||||
|
@ -1820,8 +1824,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
|
||||
fun getSimilarRecipientIds(recipient: Recipient): List<RecipientId> {
|
||||
val projection = SqlUtil.buildArgs(ID, "COALESCE(NULLIF($SYSTEM_JOINED_NAME, ''), NULLIF($PROFILE_JOINED_NAME, '')) AS checked_name")
|
||||
val where = "checked_name = ?"
|
||||
val arguments = SqlUtil.buildArgs(recipient.profileName.toString())
|
||||
val where = "checked_name = ? AND $HIDDEN = ?"
|
||||
val arguments = SqlUtil.buildArgs(recipient.profileName.toString(), 0)
|
||||
|
||||
readableDatabase.query(TABLE_NAME, projection, where, arguments, null, null, null).use { cursor ->
|
||||
if (cursor == null || cursor.count == 0) {
|
||||
|
@ -1881,10 +1885,31 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
}
|
||||
}
|
||||
|
||||
fun markHidden(id: RecipientId) {
|
||||
val contentValues = contentValuesOf(
|
||||
HIDDEN to 1,
|
||||
PROFILE_SHARING to 0
|
||||
)
|
||||
|
||||
val updated = writableDatabase.update(TABLE_NAME, contentValues, "$ID_WHERE AND $GROUP_TYPE = ?", SqlUtil.buildArgs(id, GroupType.NONE.id)) > 0
|
||||
if (updated) {
|
||||
rotateStorageId(id)
|
||||
ApplicationDependencies.getDatabaseObserver().notifyRecipientChanged(id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
} else {
|
||||
Log.w(TAG, "Failed to hide recipient $id")
|
||||
}
|
||||
}
|
||||
|
||||
fun setProfileSharing(id: RecipientId, enabled: Boolean) {
|
||||
val contentValues = ContentValues(1).apply {
|
||||
put(PROFILE_SHARING, if (enabled) 1 else 0)
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
contentValues.put(HIDDEN, 0)
|
||||
}
|
||||
|
||||
val profiledUpdated = update(id, contentValues)
|
||||
|
||||
if (profiledUpdated && enabled) {
|
||||
|
@ -2961,7 +2986,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
fun getRegistered(): List<RecipientId> {
|
||||
val results: MutableList<RecipientId> = LinkedList()
|
||||
|
||||
readableDatabase.query(TABLE_NAME, ID_PROJECTION, "$REGISTERED = ?", arrayOf("1"), null, null, null).use { cursor ->
|
||||
readableDatabase.query(TABLE_NAME, ID_PROJECTION, "$REGISTERED = ? and $HIDDEN = ?", arrayOf("1", "0"), null, null, null).use { cursor ->
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
results.add(RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ID))))
|
||||
}
|
||||
|
@ -3127,7 +3152,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
val query = SqlUtil.buildCaseInsensitiveGlobPattern(inputQuery)
|
||||
val selection =
|
||||
"""
|
||||
$BLOCKED = ? AND
|
||||
$BLOCKED = ? AND $HIDDEN = ? AND
|
||||
(
|
||||
$SORT_NAME GLOB ? OR
|
||||
$USERNAME GLOB ? OR
|
||||
|
@ -3135,7 +3160,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
$EMAIL GLOB ?
|
||||
)
|
||||
""".trimIndent()
|
||||
val args = SqlUtil.buildArgs("0", query, query, query, query)
|
||||
val args = SqlUtil.buildArgs(0, 0, query, query, query, query)
|
||||
return readableDatabase.query(TABLE_NAME, SEARCH_PROJECTION, selection, args, null, null, null)
|
||||
}
|
||||
|
||||
|
@ -3323,9 +3348,11 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
|
||||
if (Util.hasItems(idsToUpdate)) {
|
||||
val query = SqlUtil.buildSingleCollectionQuery(ID, idsToUpdate)
|
||||
val values = ContentValues(1).apply {
|
||||
put(PROFILE_SHARING, 1)
|
||||
}
|
||||
|
||||
val values = contentValuesOf(
|
||||
PROFILE_SHARING to 1,
|
||||
HIDDEN to 0
|
||||
)
|
||||
|
||||
writableDatabase.update(TABLE_NAME, values, query.where, query.whereArgs)
|
||||
|
||||
|
@ -3588,6 +3615,10 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
MENTION_SETTING to if (primaryRecord.mentionSetting != MentionSetting.ALWAYS_NOTIFY) primaryRecord.mentionSetting.id else secondaryRecord.mentionSetting.id
|
||||
)
|
||||
|
||||
if (primaryRecord.profileSharing || secondaryRecord.profileSharing) {
|
||||
uuidValues.put(HIDDEN, 0)
|
||||
}
|
||||
|
||||
if (primaryRecord.profileKey != null) {
|
||||
updateProfileValuesForMerge(uuidValues, primaryRecord)
|
||||
} else if (secondaryRecord.profileKey != null) {
|
||||
|
@ -3657,6 +3688,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
put(BLOCKED, if (contact.isBlocked) "1" else "0")
|
||||
put(MUTE_UNTIL, contact.muteUntil)
|
||||
put(STORAGE_SERVICE_ID, Base64.encodeBytes(contact.id.raw))
|
||||
put(HIDDEN, contact.isHidden)
|
||||
|
||||
if (contact.hasUnknownFields()) {
|
||||
put(STORAGE_PROTO, Base64.encodeBytes(Objects.requireNonNull(contact.serializeUnknownFields())))
|
||||
|
@ -3908,7 +3940,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
extras = getExtras(cursor),
|
||||
hasGroupsInCommon = cursor.requireBoolean(GROUPS_IN_COMMON),
|
||||
badges = parseBadgeList(cursor.requireBlob(BADGES)),
|
||||
needsPniSignature = cursor.requireBoolean(NEEDS_PNI_SIGNATURE)
|
||||
needsPniSignature = cursor.requireBoolean(NEEDS_PNI_SIGNATURE),
|
||||
isHidden = cursor.requireBoolean(HIDDEN)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -4187,6 +4220,9 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
stringBuilder.append(FILTER_BLOCKED)
|
||||
args.add(0)
|
||||
|
||||
stringBuilder.append(FILTER_HIDDEN)
|
||||
args.add(0)
|
||||
|
||||
if (excludeGroups) {
|
||||
stringBuilder.append(FILTER_GROUPS)
|
||||
}
|
||||
|
@ -4204,6 +4240,7 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
const val FILTER_GROUPS = " AND $GROUP_ID IS NULL"
|
||||
const val FILTER_ID = " AND $ID != ?"
|
||||
const val FILTER_BLOCKED = " AND $BLOCKED = ?"
|
||||
const val FILTER_HIDDEN = " AND $HIDDEN = ?"
|
||||
const val NON_SIGNAL_CONTACT = "$REGISTERED != ? AND $SYSTEM_CONTACT_URI NOT NULL AND ($PHONE NOT NULL OR $EMAIL NOT NULL)"
|
||||
const val QUERY_NON_SIGNAL_CONTACT = "$NON_SIGNAL_CONTACT AND ($PHONE GLOB ? OR $EMAIL GLOB ? OR $SYSTEM_JOINED_NAME GLOB ?)"
|
||||
const val SIGNAL_CONTACT = "$REGISTERED = ? AND (NULLIF($SYSTEM_JOINED_NAME, '') NOT NULL OR $PROFILE_SHARING = ?) AND ($SORT_NAME NOT NULL OR $USERNAME NOT NULL)"
|
||||
|
@ -4217,7 +4254,8 @@ open class RecipientDatabase(context: Context, databaseHelper: SignalDatabase) :
|
|||
*/
|
||||
internal object Capabilities {
|
||||
const val BIT_LENGTH = 2
|
||||
// const val GROUPS_V2 = 0
|
||||
|
||||
// const val GROUPS_V2 = 0
|
||||
const val GROUPS_V1_MIGRATION = 1
|
||||
const val SENDER_KEY = 2
|
||||
const val ANNOUNCEMENT_GROUPS = 3
|
||||
|
|
|
@ -11,13 +11,14 @@ import org.thoughtcrime.securesms.database.helpers.migration.V153_MyStoryMigrati
|
|||
import org.thoughtcrime.securesms.database.helpers.migration.V154_PniSignaturesMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V155_SmsExporterMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V156_RecipientUnregisteredTimestampMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V157_RecipeintHiddenMigration
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
*/
|
||||
object SignalDatabaseMigrations {
|
||||
|
||||
const val DATABASE_VERSION = 156
|
||||
const val DATABASE_VERSION = 157
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
@ -52,6 +53,10 @@ object SignalDatabaseMigrations {
|
|||
if (oldVersion < 156) {
|
||||
V156_RecipientUnregisteredTimestampMigration.migrate(context, db, oldVersion, newVersion)
|
||||
}
|
||||
|
||||
if (oldVersion < 157) {
|
||||
V157_RecipeintHiddenMigration.migrate(context, db, oldVersion, newVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
object V157_RecipeintHiddenMigration : SignalDatabaseMigration {
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL("ALTER TABLE recipient ADD COLUMN hidden INTEGER DEFAULT 0")
|
||||
}
|
||||
}
|
|
@ -85,7 +85,8 @@ data class RecipientRecord(
|
|||
val hasGroupsInCommon: Boolean,
|
||||
val badges: List<Badge>,
|
||||
@get:JvmName("needsPniSignature")
|
||||
val needsPniSignature: Boolean
|
||||
val needsPniSignature: Boolean,
|
||||
val isHidden: Boolean
|
||||
) {
|
||||
|
||||
fun getDefaultSubscriptionId(): Optional<Int> {
|
||||
|
|
|
@ -197,8 +197,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
|||
long muteUntil = remote.getMuteUntil();
|
||||
boolean hideStory = remote.shouldHideStory();
|
||||
long unregisteredTimestamp = remote.getUnregisteredTimestamp();
|
||||
boolean matchesRemote = doParamsMatch(remote, unknownFields, serviceId, pni, e164, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp);
|
||||
boolean matchesLocal = doParamsMatch(local, unknownFields, serviceId, pni, e164, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp);
|
||||
boolean hidden = remote.isHidden();
|
||||
boolean matchesRemote = doParamsMatch(remote, unknownFields, serviceId, pni, e164, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden);
|
||||
boolean matchesLocal = doParamsMatch(local, unknownFields, serviceId, pni, e164, givenName, familyName, profileKey, username, identityState, identityKey, blocked, profileSharing, archived, forcedUnread, muteUntil, hideStory, unregisteredTimestamp, hidden);
|
||||
|
||||
if (matchesRemote) {
|
||||
return remote;
|
||||
|
@ -221,6 +222,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
|||
.setMuteUntil(muteUntil)
|
||||
.setHideStory(hideStory)
|
||||
.setUnregisteredTimestamp(unregisteredTimestamp)
|
||||
.setHidden(hidden)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
@ -264,7 +266,8 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
|||
boolean forcedUnread,
|
||||
long muteUntil,
|
||||
boolean hideStory,
|
||||
long unregisteredTimestamp)
|
||||
long unregisteredTimestamp,
|
||||
boolean hidden)
|
||||
{
|
||||
return Arrays.equals(contact.serializeUnknownFields(), unknownFields) &&
|
||||
Objects.equals(contact.getServiceId(), serviceId) &&
|
||||
|
@ -282,6 +285,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
|||
contact.isForcedUnread() == forcedUnread &&
|
||||
contact.getMuteUntil() == muteUntil &&
|
||||
contact.shouldHideStory() == hideStory &&
|
||||
contact.getUnregisteredTimestamp() == unregisteredTimestamp;
|
||||
contact.getUnregisteredTimestamp() == unregisteredTimestamp &&
|
||||
contact.isHidden() == hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ public final class StorageSyncModels {
|
|||
.setMuteUntil(recipient.getMuteUntil())
|
||||
.setHideStory(hideStory)
|
||||
.setUnregisteredTimestamp(recipient.getSyncExtras().getUnregisteredTimestamp())
|
||||
.setHidden(recipient.isHidden())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@ public final class FeatureFlags {
|
|||
private static final String SMS_EXPORTER = "android.sms.exporter";
|
||||
private static final String CDS_V2_COMPAT = "android.cdsV2Compat.3";
|
||||
public static final String STORIES_LOCALE = "android.stories.locale";
|
||||
private static final String HIDE_CONTACTS = "android.hide.contacts";
|
||||
|
||||
/**
|
||||
* We will only store remote values for flags in this set. If you want a flag to be controllable
|
||||
|
@ -163,7 +164,8 @@ public final class FeatureFlags {
|
|||
CDS_V2_LOAD_TEST,
|
||||
SMS_EXPORTER,
|
||||
CDS_V2_COMPAT,
|
||||
STORIES_LOCALE
|
||||
STORIES_LOCALE,
|
||||
HIDE_CONTACTS
|
||||
);
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -588,6 +590,16 @@ public final class FeatureFlags {
|
|||
return getBoolean(CDS_V2_COMPAT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not users can hide contacts.
|
||||
*
|
||||
* WARNING: This feature is intended to be enabled in tandem with other clients, as it modifies contact records.
|
||||
* Here be dragons.
|
||||
*/
|
||||
public static boolean hideContacts() {
|
||||
return getBoolean(HIDE_CONTACTS, false);
|
||||
}
|
||||
|
||||
/** Only for rendering debug info. */
|
||||
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
|
||||
return new TreeMap<>(REMOTE_VALUES);
|
||||
|
|
|
@ -4185,6 +4185,30 @@
|
|||
|
||||
<!-- NewConversationActivity -->
|
||||
<string name="NewConversationActivity__new_message">New message</string>
|
||||
<!-- Context menu item message -->
|
||||
<string name="NewConversationActivity__message">Message</string>
|
||||
<!-- Context menu item audio call -->
|
||||
<string name="NewConversationActivity__audio_call">Audio call</string>
|
||||
<!-- Context menu item video call -->
|
||||
<string name="NewConversationActivity__video_call">Video call</string>
|
||||
<!-- Context menu item remove -->
|
||||
<string name="NewConversationActivity__remove">Remove</string>
|
||||
<!-- Context menu item block -->
|
||||
<string name="NewConversationActivity__block">Block</string>
|
||||
<!-- Dialog title when removing a contact -->
|
||||
<string name="NewConversationActivity__remove_s">Remove %1$s?</string>
|
||||
<!-- Dialog message when removing a contact -->
|
||||
<string name="NewConversationActivity__you_wont_see_this_person">You won\'t see this person when searching. You\'ll get a message request if they message you in the future.</string>
|
||||
<!-- Snackbar message after removing a contact -->
|
||||
<string name="NewConversationActivity__s_has_been_removed">%1$s has been removed</string>
|
||||
<!-- Snackbar message after blocking a contact -->
|
||||
<string name="NewConversationActivity__s_has_been_blocked">%1$s has been blocked</string>
|
||||
<!-- Dialog title when remove target contact is in system contacts -->
|
||||
<string name="NewConversationActivity__unable_to_remove_s">Unable to remove %1$s</string>
|
||||
<!-- Dialog message when remove target contact is in system contacts -->
|
||||
<string name="NewConversationActivity__this_person_is_saved_to_your">This person is saved to your device\'s Contacts. Delete them from your Contacts and try again.</string>
|
||||
<!-- Dialog action to view contact when they can't be removed otherwise -->
|
||||
<string name="NewConversationActivity__view_contact">View contact</string>
|
||||
|
||||
<!-- ContactFilterView -->
|
||||
<string name="ContactFilterView__search_name_or_number">Search name or number</string>
|
||||
|
|
|
@ -22,7 +22,8 @@ class ContactSearchSelectionBuilderTest {
|
|||
|
||||
Assert.assertTrue(result.where.contains(RecipientDatabase.ContactSearchSelection.SIGNAL_CONTACT))
|
||||
Assert.assertTrue(result.where.contains(RecipientDatabase.ContactSearchSelection.FILTER_BLOCKED))
|
||||
Assert.assertArrayEquals(SqlUtil.buildArgs(RecipientDatabase.RegisteredState.REGISTERED.id, 1, 0), result.args)
|
||||
Assert.assertTrue(result.where.contains(RecipientDatabase.ContactSearchSelection.FILTER_HIDDEN))
|
||||
Assert.assertArrayEquals(SqlUtil.buildArgs(RecipientDatabase.RegisteredState.REGISTERED.id, 1, 0, 0), result.args)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -48,10 +49,12 @@ class ContactSearchSelectionBuilderTest {
|
|||
Assert.assertTrue(result.where.contains(RecipientDatabase.ContactSearchSelection.NON_SIGNAL_CONTACT))
|
||||
Assert.assertTrue(result.where.contains(RecipientDatabase.ContactSearchSelection.FILTER_GROUPS))
|
||||
Assert.assertTrue(result.where.contains(RecipientDatabase.ContactSearchSelection.FILTER_BLOCKED))
|
||||
Assert.assertTrue(result.where.contains(RecipientDatabase.ContactSearchSelection.FILTER_HIDDEN))
|
||||
Assert.assertArrayEquals(
|
||||
SqlUtil.buildArgs(
|
||||
RecipientDatabase.RegisteredState.REGISTERED.id, 1,
|
||||
RecipientDatabase.RegisteredState.REGISTERED.id,
|
||||
0,
|
||||
0
|
||||
),
|
||||
result.args
|
||||
|
|
|
@ -147,7 +147,8 @@ object RecipientDatabaseTestUtils {
|
|||
extras,
|
||||
hasGroupsInCommon,
|
||||
badges,
|
||||
false
|
||||
needsPniSignature = false,
|
||||
isHidden = false
|
||||
),
|
||||
participants,
|
||||
isReleaseChannel
|
||||
|
|
|
@ -133,6 +133,10 @@ public final class SignalContactRecord implements SignalRecord {
|
|||
diff.add("UnregisteredTimestamp");
|
||||
}
|
||||
|
||||
if (isHidden() != that.isHidden()) {
|
||||
diff.add("Hidden");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
|
||||
diff.add("UnknownFields");
|
||||
}
|
||||
|
@ -215,6 +219,10 @@ public final class SignalContactRecord implements SignalRecord {
|
|||
return proto.getUnregisteredAtTimestamp();
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return proto.getHidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the same record, but stripped of the PNI field. Only used while PNP is in development.
|
||||
*/
|
||||
|
@ -331,6 +339,11 @@ public final class SignalContactRecord implements SignalRecord {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setHidden(boolean hidden) {
|
||||
builder.setHidden(hidden);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static ContactRecord.Builder parseUnknowns(byte[] serializedUnknowns) {
|
||||
try {
|
||||
return ContactRecord.parseFrom(serializedUnknowns).toBuilder();
|
||||
|
|
|
@ -87,7 +87,8 @@ message ContactRecord {
|
|||
uint64 mutedUntilTimestamp = 13;
|
||||
bool hideStory = 14;
|
||||
uint64 unregisteredAtTimestamp = 16;
|
||||
// NEXT ID: 17
|
||||
bool hidden = 19;
|
||||
// NEXT ID: 20
|
||||
}
|
||||
|
||||
message GroupV1Record {
|
||||
|
|
Loading…
Add table
Reference in a new issue