diff --git a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java index 3ce549f823..ab1b383a14 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java @@ -93,7 +93,7 @@ public class NewConversationActivity extends ContactSelectionActivity ContactsManagementViewModel.Factory factory = new ContactsManagementViewModel.Factory(repository); contactLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResult -> { - if (activityResult.getResultCode() == RESULT_OK) { + if (activityResult.getResultCode() != RESULT_CANCELED) { handleManualRefresh(); } }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.kt index ece1d40bfa..4555a4f0a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationData.kt @@ -24,12 +24,13 @@ data class ConversationData( data class MessageRequestData @JvmOverloads constructor( val isMessageRequestAccepted: Boolean, + val isHidden: Boolean, private val groupsInCommon: Boolean = false, val isGroup: Boolean = false ) { fun includeWarningUpdateMessage(): Boolean { - return !isMessageRequestAccepted && !groupsInCommon + return !isMessageRequestAccepted && !groupsInCommon && !isHidden } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java index 7a4130f5e5..4e33c23dd3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationDataSource.java @@ -71,6 +71,7 @@ public class ConversationDataSource implements PagedDataSource= size())) { + records.add(new InMemoryMessageRecord.RemovedContactHidden(threadId)); + } + if (showUniversalExpireTimerUpdate) { records.add(new InMemoryMessageRecord.UniversalExpireTimerUpdate(threadId)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java index f3ad76cc3f..96f224d70a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationRepository.java @@ -65,7 +65,8 @@ class ConversationRepository { long lastScrolled = metadata.getLastScrolled(); int lastScrolledPosition = 0; boolean isMessageRequestAccepted = RecipientUtil.isMessageRequestAccepted(context, threadId); - ConversationData.MessageRequestData messageRequestData = new ConversationData.MessageRequestData(isMessageRequestAccepted); + boolean isConversationHidden = RecipientUtil.isRecipientHidden(threadId); + ConversationData.MessageRequestData messageRequestData = new ConversationData.MessageRequestData(isMessageRequestAccepted, isConversationHidden); boolean showUniversalExpireTimerUpdate = false; if (lastSeen > 0) { @@ -98,7 +99,7 @@ class ConversationRepository { } else if (conversationRecipient.hasGroupsInCommon()) { recipientIsKnownOrHasGroupsInCommon = true; } - messageRequestData = new ConversationData.MessageRequestData(isMessageRequestAccepted, recipientIsKnownOrHasGroupsInCommon, isGroup); + messageRequestData = new ConversationData.MessageRequestData(isMessageRequestAccepted, isConversationHidden, recipientIsKnownOrHasGroupsInCommon, isGroup); } if (SignalStore.settings().getUniversalExpireTimer() != 0 && diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java index 7208f3defd..4d51ddebcd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java @@ -569,7 +569,11 @@ public final class ConversationListItem extends ConstraintLayout implements Bind int defaultTint = ContextCompat.getColor(context, R.color.signal_text_secondary); if (!thread.isMessageRequestAccepted()) { - return emphasisAdded(context, context.getString(R.string.ThreadRecord_message_request), defaultTint); + if (thread.isRecipientHidden()) { + return emphasisAdded(context, context.getString(R.string.ThreadRecord_hidden_recipient), defaultTint); + } else { + return emphasisAdded(context, context.getString(R.string.ThreadRecord_message_request), defaultTint); + } } else if (MessageTypes.isGroupUpdate(thread.getType())) { if (thread.getRecipient().isPushV2Group()) { return emphasisAdded(context, MessageRecord.getGv2ChangeDescription(context, thread.getBody(), null), defaultTint); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt index 58ac9a0950..1fe3b68ff9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadTable.kt @@ -1583,6 +1583,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa private fun getExtrasFor(record: MessageRecord, body: ThreadBody): Extra? { val threadRecipient = if (record.isOutgoing) record.recipient else getRecipientForThreadId(record.threadId) val messageRequestAccepted = RecipientUtil.isMessageRequestAccepted(record.threadId, threadRecipient) + val isHidden = threadRecipient?.isHidden ?: false val individualRecipientId = record.individualRecipient.id if (!messageRequestAccepted && threadRecipient != null) { @@ -1609,7 +1610,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa } } } else { - return Extra.forMessageRequest(individualRecipientId) + return Extra.forMessageRequest(individualRecipientId, isHidden) } } @@ -1772,7 +1773,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa groupAddedBy = jsonObject.getString("groupAddedBy"), individualRecipientId = jsonObject.getString("individualRecipientId")!!, bodyRanges = jsonObject.getString("bodyRanges"), - isScheduled = jsonObject.getBoolean("isScheduled") + isScheduled = jsonObject.getBoolean("isScheduled"), + isRecipientHidden = jsonObject.getBoolean("isRecipientHidden") ) } catch (exception: Exception) { null @@ -1855,7 +1857,10 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa val bodyRanges: String? = null, @field:JsonProperty @param:JsonProperty("isScheduled") - val isScheduled: Boolean = false + val isScheduled: Boolean = false, + @field:JsonProperty + @param:JsonProperty("isRecipientHidden") + val isRecipientHidden: Boolean = false ) { fun getIndividualRecipientId(): String { @@ -1879,8 +1884,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa return Extra(isRemoteDelete = true, individualRecipientId = individualRecipient.serialize()) } - fun forMessageRequest(individualRecipient: RecipientId): Extra { - return Extra(isMessageRequestAccepted = false, individualRecipientId = individualRecipient.serialize()) + fun forMessageRequest(individualRecipient: RecipientId, isHidden: Boolean = false): Extra { + return Extra(isMessageRequestAccepted = false, individualRecipientId = individualRecipient.serialize(), isRecipientHidden = isHidden) } fun forGroupMessageRequest(recipientId: RecipientId, individualRecipient: RecipientId): Extra { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/InMemoryMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/InMemoryMessageRecord.java index 48d9c5e597..b036f2ff0e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/InMemoryMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/InMemoryMessageRecord.java @@ -23,6 +23,7 @@ public class InMemoryMessageRecord extends MessageRecord { private static final int NO_GROUPS_IN_COMMON_ID = -1; private static final int UNIVERSAL_EXPIRE_TIMER_ID = -2; private static final int FORCE_BUBBLE_ID = -3; + private static final int HIDDEN_CONTACT_WARNING_ID = -4; private InMemoryMessageRecord(long id, String body, @@ -118,6 +119,29 @@ public class InMemoryMessageRecord extends MessageRecord { } } + public static final class RemovedContactHidden extends InMemoryMessageRecord { + + public RemovedContactHidden(long threadId) { + super(HIDDEN_CONTACT_WARNING_ID, "", Recipient.UNKNOWN, threadId, 0); + } + + @Override + public @Nullable UpdateDescription getUpdateDisplayBody(@NonNull Context context, @Nullable Consumer recipientClickHandler) { + return UpdateDescription.staticDescription(context.getString(R.string.ConversationUpdateItem_hidden_contact_message_to_add_back), + R.drawable.symbol_info_compact_16); + } + + @Override + public boolean isUpdate() { + return true; + } + + @Override + public boolean showActionButton() { + return false; + } + } + /** * Show temporary update message about setting the disappearing messages timer upon first message * send. diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 099d4e35e3..c6cc3eef93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -222,6 +222,11 @@ public final class ThreadRecord { else return true; } + public boolean isRecipientHidden() { + if (extra != null) return extra.isRecipientHidden(); + else return true; + } + public boolean isPinned() { return isPinned; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java index f1ed86f2f6..0c2e16161a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestRepository.java @@ -116,6 +116,8 @@ final class MessageRequestRepository { } else { if (RecipientUtil.isMessageRequestAccepted(context, threadId)) { return MessageRequestState.NONE; + } else if (RecipientUtil.isRecipientHidden(threadId)) { + return MessageRequestState.INDIVIDUAL_HIDDEN; } else { return MessageRequestState.INDIVIDUAL; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestState.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestState.java index d0d8e85b71..8604bb0e24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestState.java @@ -35,5 +35,8 @@ public enum MessageRequestState { GROUP_V2_ADD, /** A message request is needed for an individual */ - INDIVIDUAL + INDIVIDUAL, + + /** A message request is needed for an individual since they have been hidden */ + INDIVIDUAL_HIDDEN } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java index 42549d0111..a671e9ecdf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsBottomView.java @@ -139,6 +139,12 @@ public class MessageRequestsBottomView extends ConstraintLayout { setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons); accept.setText(R.string.MessageRequestBottomView_accept); break; + case INDIVIDUAL_HIDDEN: + question.setText(HtmlCompat.fromHtml(getContext().getString(R.string.MessageRequestBottomView_do_you_want_to_let_s_message_you_you_removed_them_before, + HtmlUtil.bold(recipient.getShortDisplayName(getContext()))), 0)); + setActiveInactiveGroups(normalButtons, blockedButtons, gv1MigrationButtons); + accept.setText(R.string.MessageRequestBottomView_accept); + break; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index 99ccab8d1f..944d26ebed 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -115,6 +115,7 @@ public class Recipient { private final String profileAvatar; private final ProfileAvatarFileDetails profileAvatarFileDetails; private final boolean profileSharing; + private final boolean isHidden; private final long lastProfileFetch; private final String notificationChannel; private final UnidentifiedAccessMode unidentifiedAccessMode; @@ -412,6 +413,7 @@ public class Recipient { this.profileAvatar = null; this.profileAvatarFileDetails = ProfileAvatarFileDetails.NO_DETAILS; this.profileSharing = false; + this.isHidden = false; this.lastProfileFetch = 0; this.notificationChannel = null; this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; @@ -466,6 +468,7 @@ public class Recipient { this.profileAvatar = details.profileAvatar; this.profileAvatarFileDetails = details.profileAvatarFileDetails; this.profileSharing = details.profileSharing; + this.isHidden = details.isHidden; this.lastProfileFetch = details.lastProfileFetch; this.notificationChannel = details.notificationChannel; this.unidentifiedAccessMode = details.unidentifiedAccessMode; @@ -833,6 +836,10 @@ public class Recipient { return profileSharing; } + public boolean isHidden() { + return isHidden; + } + public long getLastProfileFetchTime() { return lastProfileFetch; } @@ -1290,6 +1297,7 @@ public class Recipient { expireMessages == other.expireMessages && Objects.equals(profileAvatarFileDetails, other.profileAvatarFileDetails) && profileSharing == other.profileSharing && + isHidden == other.isHidden && forceSmsSelection == other.forceSmsSelection && Objects.equals(serviceId, other.serviceId) && Objects.equals(username, other.username) && diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java index 5a0e590fbe..5299080d80 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -63,6 +63,7 @@ public class RecipientDetails { final String profileAvatar; final ProfileAvatarFileDetails profileAvatarFileDetails; final boolean profileSharing; + final boolean isHidden; final long lastProfileFetch; final boolean systemContact; final boolean isSelf; @@ -122,6 +123,7 @@ public class RecipientDetails { this.profileAvatar = record.getProfileAvatar(); this.profileAvatarFileDetails = record.getProfileAvatarFileDetails(); this.profileSharing = record.isProfileSharing(); + this.isHidden = record.isHidden(); this.lastProfileFetch = record.getLastProfileFetch(); this.systemContact = systemContact; this.isSelf = isSelf; @@ -176,6 +178,7 @@ public class RecipientDetails { this.profileAvatar = null; this.profileAvatarFileDetails = ProfileAvatarFileDetails.NO_DETAILS; this.profileSharing = false; + this.isHidden = false; this.lastProfileFetch = 0; this.systemContact = true; this.isSelf = false; diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java index 6b840c1c9e..be60ca3e85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java @@ -198,6 +198,22 @@ public class RecipientUtil { StorageSyncHelper.scheduleSyncForDataChange(); } + @WorkerThread + public static boolean isRecipientHidden(long threadId) { + if (threadId < 0) { + return false; + } + + ThreadTable threadTable = SignalDatabase.threads(); + Recipient threadRecipient = threadTable.getRecipientForThreadId(threadId); + + if (threadRecipient == null) { + return false; + } + + return threadRecipient.isHidden(); + } + /** * If true, the new message request UI does not need to be shown, and it's safe to send read * receipts. @@ -281,7 +297,8 @@ public class RecipientUtil { threadRecipient.isProfileSharing() || threadRecipient.isSystemContact() || !threadRecipient.isRegistered() || - threadRecipient.isForceSmsSelection(); + threadRecipient.isForceSmsSelection() || + threadRecipient.isHidden(); } /** @@ -331,9 +348,11 @@ public class RecipientUtil { threadRecipient.isSystemContact() || threadRecipient.isForceSmsSelection() || !threadRecipient.isRegistered() || - hasSentMessageInThread(threadId) || - noSecureMessagesAndNoCallsInThread(threadId) || - isPreMessageRequestThread(threadId); + (!threadRecipient.isHidden() && ( + hasSentMessageInThread(threadId) || + noSecureMessagesAndNoCallsInThread(threadId) || + isPreMessageRequestThread(threadId)) + ); } @WorkerThread diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8eef2c525a..4d42585a8a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1479,6 +1479,8 @@ Delete Block Unblock + + Let %1$s message you and share your name and photo with them? You have removed this person in the past. Let %1$s message you and share your name and photo with them? They won\'t know you\'ve seen their message until you accept. Let %1$s message you and share your name and photo with them? You won\'t receive any messages until you unblock them. @@ -1992,6 +1994,8 @@ Message could not be processed Delivery issue Message Request + + You have hidden this person, message them again to add them back to your list. Photo GIF Voice Message @@ -2387,7 +2391,8 @@ Send payment Activate payments - + + You have removed this person, messaging them again will add them back to your list. Play … Pause