mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-11 19:07:40 +00:00
feat: Add conversation filtering for message requests (#830)
* feat: Message requests * Apply contact sync message * Filter based on message requests toggle * Add message requests screen * Implement message requests screen * Handle message request buttons * Handle approval syncing * Display message request response * Display pending message request * Display pending message request * Add approval migrations * Send message request response * Fix conversation filters * Add approval migration * Handle message request response * Update message request response proto * Update message request response handling * Refresh message requests * Show message request banner on new message request * Message request item layout tweaks * Fix latest unapproved conversation query * Handle sent message request responses on restore * QA feedback tweaks * Remove send limit on message requests * Config message handling tweaks * Reverse conversation upon message request approval * Remove read receipts, delete declined conversations * Fix contact filtering in config messages * Fix message request order and handle deletion * Fix message request snippet on home screen * Refresh message request list after decline or clearing all * Fix message request reversal * Fix message request notifications * Disable media buttons for message requests * Hide message request banner after reading * Refresh message request banner
This commit is contained in:
@@ -8,6 +8,7 @@ import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob
|
||||
import org.session.libsession.messaging.jobs.Job
|
||||
import org.session.libsession.messaging.jobs.MessageSendJob
|
||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||
import org.session.libsession.messaging.messages.visible.Attachment
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||
@@ -156,4 +157,5 @@ interface StorageProtocol {
|
||||
*/
|
||||
fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long?
|
||||
fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long)
|
||||
fun insertMessageRequestResponse(response: MessageRequestResponse)
|
||||
}
|
||||
|
@@ -60,19 +60,22 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
|
||||
}
|
||||
}
|
||||
|
||||
class Contact(var publicKey: String, var name: String, var profilePicture: String?, var profileKey: ByteArray?) {
|
||||
class Contact(var publicKey: String, var name: String, var profilePicture: String?, var profileKey: ByteArray?, var isApproved: Boolean?, var isBlocked: Boolean?, var didApproveMe: Boolean?) {
|
||||
|
||||
internal constructor() : this("", "", null, null)
|
||||
internal constructor() : this("", "", null, null, null, null, null)
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.ConfigurationMessage.Contact): Contact? {
|
||||
if (!proto.hasName() || !proto.hasProfileKey()) return null
|
||||
if (!proto.hasName()) return null
|
||||
val publicKey = proto.publicKey.toByteArray().toHexString()
|
||||
val name = proto.name
|
||||
val profilePicture = if (proto.hasProfilePicture()) proto.profilePicture else null
|
||||
val profileKey = if (proto.hasProfileKey()) proto.profileKey.toByteArray() else null
|
||||
return Contact(publicKey, name, profilePicture, profileKey)
|
||||
val isApproved = if (proto.hasIsApproved()) proto.isApproved else null
|
||||
val isBlocked = if (proto.hasIsBlocked()) proto.isBlocked else null
|
||||
val didApproveMe = if (proto.hasDidApproveMe()) proto.didApproveMe else null
|
||||
return Contact(publicKey, name, profilePicture, profileKey, isApproved, isBlocked, didApproveMe)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +95,18 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
|
||||
if (profileKey != null) {
|
||||
result.profileKey = ByteString.copyFrom(profileKey)
|
||||
}
|
||||
val isApproved = isApproved
|
||||
if (isApproved != null) {
|
||||
result.isApproved = isApproved
|
||||
}
|
||||
val isBlocked = isBlocked
|
||||
if (isBlocked != null) {
|
||||
result.isBlocked = isBlocked
|
||||
}
|
||||
val didApproveMe = didApproveMe
|
||||
if (didApproveMe != null) {
|
||||
result.didApproveMe = didApproveMe
|
||||
}
|
||||
return result.build()
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,33 @@
|
||||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
import org.session.libsignal.protos.SignalServiceProtos
|
||||
import org.session.libsignal.utilities.Log
|
||||
|
||||
class MessageRequestResponse(val isApproved: Boolean) : ControlMessage() {
|
||||
|
||||
override val isSelfSendValid: Boolean = true
|
||||
|
||||
override fun toProto(): SignalServiceProtos.Content? {
|
||||
val messageRequestResponseProto = SignalServiceProtos.MessageRequestResponse.newBuilder()
|
||||
.setIsApproved(isApproved)
|
||||
return try {
|
||||
SignalServiceProtos.Content.newBuilder()
|
||||
.setMessageRequestResponse(messageRequestResponseProto.build())
|
||||
.build()
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't construct message request response proto from: $this")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "MessageRequestResponse"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): MessageRequestResponse? {
|
||||
val messageRequestResponseProto = if (proto.hasMessageRequestResponse()) proto.messageRequestResponse else return null
|
||||
val isApproved = messageRequestResponseProto.isApproved
|
||||
return MessageRequestResponse(isApproved)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -28,6 +28,7 @@ public class IncomingMediaMessage {
|
||||
private final long expiresIn;
|
||||
private final boolean expirationUpdate;
|
||||
private final boolean unidentified;
|
||||
private final boolean messageRequestResponse;
|
||||
|
||||
private final DataExtractionNotificationInfoMessage dataExtractionNotification;
|
||||
private final QuoteModel quote;
|
||||
@@ -42,6 +43,7 @@ public class IncomingMediaMessage {
|
||||
long expiresIn,
|
||||
boolean expirationUpdate,
|
||||
boolean unidentified,
|
||||
boolean messageRequestResponse,
|
||||
Optional<String> body,
|
||||
Optional<SignalServiceGroup> group,
|
||||
Optional<List<SignalServiceAttachment>> attachments,
|
||||
@@ -60,6 +62,7 @@ public class IncomingMediaMessage {
|
||||
this.dataExtractionNotification = dataExtractionNotification.orNull();
|
||||
this.quote = quote.orNull();
|
||||
this.unidentified = unidentified;
|
||||
this.messageRequestResponse = messageRequestResponse;
|
||||
|
||||
if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.INSTANCE.getEncodedId(group.get()));
|
||||
else this.groupId = null;
|
||||
@@ -78,7 +81,7 @@ public class IncomingMediaMessage {
|
||||
Optional<List<LinkPreview>> linkPreviews)
|
||||
{
|
||||
return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false,
|
||||
false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent());
|
||||
false, false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent());
|
||||
}
|
||||
|
||||
public int getSubscriptionId() {
|
||||
@@ -150,4 +153,8 @@ public class IncomingMediaMessage {
|
||||
public boolean isUnidentified() {
|
||||
return unidentified;
|
||||
}
|
||||
|
||||
public boolean isMessageRequestResponse() {
|
||||
return messageRequestResponse;
|
||||
}
|
||||
}
|
||||
|
@@ -95,6 +95,7 @@ object MessageReceiver {
|
||||
ExpirationTimerUpdate.fromProto(proto) ?:
|
||||
ConfigurationMessage.fromProto(proto) ?:
|
||||
UnsendRequest.fromProto(proto) ?:
|
||||
MessageRequestResponse.fromProto(proto) ?:
|
||||
VisibleMessage.fromProto(proto) ?: throw Error.UnknownMessage
|
||||
// Ignore self send if needed
|
||||
if (!message.isSelfSendValid && sender == userPublicKey) throw Error.SelfSend
|
||||
|
@@ -5,7 +5,14 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.AttachmentDownloadJob
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.control.*
|
||||
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
|
||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.control.DataExtractionNotification
|
||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||
import org.session.libsession.messaging.messages.control.ReadReceipt
|
||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
||||
import org.session.libsession.messaging.messages.visible.Attachment
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment
|
||||
@@ -15,7 +22,12 @@ import org.session.libsession.messaging.sending_receiving.notifications.PushNoti
|
||||
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
|
||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.*
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.GroupRecord
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.ProfileKeyUtil
|
||||
import org.session.libsession.utilities.SSKEnvironment
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
||||
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
||||
@@ -28,8 +40,7 @@ import org.session.libsignal.utilities.guava.Optional
|
||||
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import java.util.LinkedList
|
||||
|
||||
internal fun MessageReceiver.isBlocked(publicKey: String): Boolean {
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
@@ -46,6 +57,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
|
||||
is DataExtractionNotification -> handleDataExtractionNotification(message)
|
||||
is ConfigurationMessage -> handleConfigurationMessage(message)
|
||||
is UnsendRequest -> handleUnsendRequest(message)
|
||||
is MessageRequestResponse -> handleMessageRequestResponse(message)
|
||||
is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID)
|
||||
}
|
||||
}
|
||||
@@ -160,6 +172,10 @@ fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) {
|
||||
SSKEnvironment.shared.notificationManager.updateNotification(context)
|
||||
}
|
||||
}
|
||||
|
||||
fun handleMessageRequestResponse(message: MessageRequestResponse) {
|
||||
MessagingModuleConfiguration.shared.storage.insertMessageRequestResponse(message)
|
||||
}
|
||||
//endregion
|
||||
|
||||
// region Visible Messages
|
||||
@@ -177,10 +193,10 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS
|
||||
throw MessageReceiver.Error.NoThread
|
||||
}
|
||||
// Update profile if needed
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false)
|
||||
val profile = message.profile
|
||||
if (profile != null && userPublicKey != message.sender) {
|
||||
val profileManager = SSKEnvironment.shared.profileManager
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(message.sender!!), false)
|
||||
val name = profile.displayName!!
|
||||
if (name.isNotEmpty()) {
|
||||
profileManager.setName(context, recipient, name)
|
||||
@@ -269,6 +285,8 @@ private fun MessageReceiver.handleClosedGroupControlMessage(message: ClosedGroup
|
||||
|
||||
private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMessage) {
|
||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.New ?: return
|
||||
val recipient = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(message.sender!!), false)
|
||||
if (!recipient.isApproved) return
|
||||
val groupPublicKey = kind.publicKey.toByteArray().toHexString()
|
||||
val members = kind.members.map { it.toByteArray().toHexString() }
|
||||
val admins = kind.admins.map { it.toByteArray().toHexString() }
|
||||
|
@@ -151,6 +151,8 @@ interface TextSecurePreferences {
|
||||
fun setLastOpenDate()
|
||||
fun hasSeenLinkPreviewSuggestionDialog(): Boolean
|
||||
fun setHasSeenLinkPreviewSuggestionDialog()
|
||||
fun hasHiddenMessageRequests(): Boolean
|
||||
fun setHasHiddenMessageRequests()
|
||||
fun clearAll()
|
||||
|
||||
companion object {
|
||||
@@ -227,6 +229,7 @@ interface TextSecurePreferences {
|
||||
const val CONFIGURATION_SYNCED = "pref_configuration_synced"
|
||||
const val LAST_PROFILE_UPDATE_TIME = "pref_last_profile_update_time"
|
||||
const val LAST_OPEN_DATE = "pref_last_open_date"
|
||||
const val HAS_HIDDEN_MESSAGE_REQUESTS = "pref_message_requests_hidden"
|
||||
|
||||
@JvmStatic
|
||||
fun getLastConfigurationSyncTime(context: Context): Long {
|
||||
@@ -870,6 +873,16 @@ interface TextSecurePreferences {
|
||||
setBooleanPreference(context, "has_seen_link_preview_suggestion_dialog", true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun hasHiddenMessageRequests(context: Context): Boolean {
|
||||
return getBooleanPreference(context, HAS_HIDDEN_MESSAGE_REQUESTS, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun removeHasHiddenMessageRequests(context: Context) {
|
||||
removePreference(context, HAS_HIDDEN_MESSAGE_REQUESTS)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun clearAll(context: Context) {
|
||||
getDefaultSharedPreferences(context).edit().clear().commit()
|
||||
@@ -1426,6 +1439,14 @@ class AppTextSecurePreferences @Inject constructor(
|
||||
setBooleanPreference("has_seen_link_preview_suggestion_dialog", true)
|
||||
}
|
||||
|
||||
override fun hasHiddenMessageRequests(): Boolean {
|
||||
return getBooleanPreference(TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS, false)
|
||||
}
|
||||
|
||||
override fun setHasHiddenMessageRequests() {
|
||||
setBooleanPreference(TextSecurePreferences.HAS_HIDDEN_MESSAGE_REQUESTS, true)
|
||||
}
|
||||
|
||||
override fun clearAll() {
|
||||
getDefaultSharedPreferences(context).edit().clear().commit()
|
||||
}
|
||||
|
@@ -81,6 +81,8 @@ public class Recipient implements RecipientModifiedListener {
|
||||
public long mutedUntil = 0;
|
||||
public int notifyType = 0;
|
||||
private boolean blocked = false;
|
||||
private boolean approved = false;
|
||||
private boolean approvedMe = false;
|
||||
private VibrateState messageVibrate = VibrateState.DEFAULT;
|
||||
private VibrateState callVibrate = VibrateState.DEFAULT;
|
||||
private int expireMessages = 0;
|
||||
@@ -141,6 +143,8 @@ public class Recipient implements RecipientModifiedListener {
|
||||
this.callRingtone = stale.callRingtone;
|
||||
this.mutedUntil = stale.mutedUntil;
|
||||
this.blocked = stale.blocked;
|
||||
this.approved = stale.approved;
|
||||
this.approvedMe = stale.approvedMe;
|
||||
this.messageVibrate = stale.messageVibrate;
|
||||
this.callVibrate = stale.callVibrate;
|
||||
this.expireMessages = stale.expireMessages;
|
||||
@@ -169,6 +173,8 @@ public class Recipient implements RecipientModifiedListener {
|
||||
this.callRingtone = details.get().callRingtone;
|
||||
this.mutedUntil = details.get().mutedUntil;
|
||||
this.blocked = details.get().blocked;
|
||||
this.approved = details.get().approved;
|
||||
this.approvedMe = details.get().approvedMe;
|
||||
this.messageVibrate = details.get().messageVibrateState;
|
||||
this.callVibrate = details.get().callVibrateState;
|
||||
this.expireMessages = details.get().expireMessages;
|
||||
@@ -570,6 +576,30 @@ public class Recipient implements RecipientModifiedListener {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized boolean isApproved() {
|
||||
return approved;
|
||||
}
|
||||
|
||||
public void setApproved(boolean approved) {
|
||||
synchronized (this) {
|
||||
this.approved = approved;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized boolean hasApprovedMe() {
|
||||
return approvedMe;
|
||||
}
|
||||
|
||||
public void setHasApprovedMe(boolean approvedMe) {
|
||||
synchronized (this) {
|
||||
this.approvedMe = approvedMe;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized VibrateState getMessageVibrate() {
|
||||
return messageVibrate;
|
||||
}
|
||||
@@ -779,6 +809,8 @@ public class Recipient implements RecipientModifiedListener {
|
||||
|
||||
public static class RecipientSettings {
|
||||
private final boolean blocked;
|
||||
private final boolean approved;
|
||||
private final boolean approvedMe;
|
||||
private final long muteUntil;
|
||||
private final int notifyType;
|
||||
private final VibrateState messageVibrateState;
|
||||
@@ -801,7 +833,7 @@ public class Recipient implements RecipientModifiedListener {
|
||||
private final UnidentifiedAccessMode unidentifiedAccessMode;
|
||||
private final boolean forceSmsSelection;
|
||||
|
||||
public RecipientSettings(boolean blocked, long muteUntil,
|
||||
public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil,
|
||||
int notifyType,
|
||||
@NonNull VibrateState messageVibrateState,
|
||||
@NonNull VibrateState callVibrateState,
|
||||
@@ -824,6 +856,8 @@ public class Recipient implements RecipientModifiedListener {
|
||||
boolean forceSmsSelection)
|
||||
{
|
||||
this.blocked = blocked;
|
||||
this.approved = approved;
|
||||
this.approvedMe = approvedMe;
|
||||
this.muteUntil = muteUntil;
|
||||
this.notifyType = notifyType;
|
||||
this.messageVibrateState = messageVibrateState;
|
||||
@@ -855,6 +889,14 @@ public class Recipient implements RecipientModifiedListener {
|
||||
return blocked;
|
||||
}
|
||||
|
||||
public boolean isApproved() {
|
||||
return approved;
|
||||
}
|
||||
|
||||
public boolean hasApprovedMe() {
|
||||
return approvedMe;
|
||||
}
|
||||
|
||||
public long getMuteUntil() {
|
||||
return muteUntil;
|
||||
}
|
||||
|
@@ -171,6 +171,8 @@ class RecipientProvider {
|
||||
@Nullable final VibrateState messageVibrateState;
|
||||
@Nullable final VibrateState callVibrateState;
|
||||
final boolean blocked;
|
||||
final boolean approved;
|
||||
final boolean approvedMe;
|
||||
final int expireMessages;
|
||||
@NonNull final List<Recipient> participants;
|
||||
@Nullable final String profileName;
|
||||
@@ -201,6 +203,8 @@ class RecipientProvider {
|
||||
this.messageVibrateState = settings != null ? settings.getMessageVibrateState() : null;
|
||||
this.callVibrateState = settings != null ? settings.getCallVibrateState() : null;
|
||||
this.blocked = settings != null && settings.isBlocked();
|
||||
this.approved = settings != null && settings.isApproved();
|
||||
this.approvedMe = settings != null && settings.hasApprovedMe();
|
||||
this.expireMessages = settings != null ? settings.getExpireMessages() : 0;
|
||||
this.participants = participants == null ? new LinkedList<>() : participants;
|
||||
this.profileName = settings != null ? settings.getProfileName() : null;
|
||||
|
Reference in New Issue
Block a user