Merge remote-tracking branch 'upstream/dev' into closed_groups
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt # app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java # app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java # libsession-util/libsession-util # libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt # libsession/src/main/java/org/session/libsession/utilities/GroupUtil.kt # libsession/src/main/java/org/session/libsession/utilities/recipients/Recipient.java # libsignal/protobuf/SignalService.proto # libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java
This commit is contained in:
commit
b6ff1deb64
|
@ -10,7 +10,6 @@ import androidx.annotation.DimenRes
|
|||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewProfilePictureBinding
|
||||
import network.loki.messenger.databinding.ViewUserBinding
|
||||
import org.session.libsession.avatars.ContactColors
|
||||
import org.session.libsession.avatars.PlaceholderAvatarPhoto
|
||||
import org.session.libsession.avatars.ProfileContactPhoto
|
||||
|
@ -74,7 +73,7 @@ class ProfilePictureView @JvmOverloads constructor(
|
|||
additionalDisplayName = getUserDisplayName(apk)
|
||||
}
|
||||
} else if(recipient.isOpenGroupInboxRecipient) {
|
||||
val publicKey = GroupUtil.getDecodedOpenGroupInbox(recipient.address.serialize())
|
||||
val publicKey = GroupUtil.getDecodedOpenGroupInboxSessionId(recipient.address.serialize())
|
||||
this.publicKey = publicKey
|
||||
displayName = getUserDisplayName(publicKey)
|
||||
additionalPublicKey = null
|
||||
|
|
|
@ -40,6 +40,7 @@ import androidx.annotation.DimenRes
|
|||
import androidx.core.text.set
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.drawToBitmap
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -79,7 +80,6 @@ import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
|
|||
import org.session.libsession.messaging.messages.visible.Reaction
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi.Capability
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
|
@ -112,9 +112,9 @@ import org.thoughtcrime.securesms.conversation.settings.ConversationSettingsActi
|
|||
import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnActionSelectedListener
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationReactionOverlay.OnReactionSelectedListener
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.MESSAGE_TIMESTAMP
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_REPLY
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_RESEND
|
||||
import org.thoughtcrime.securesms.conversation.v2.MessageDetailActivity.Companion.ON_DELETE
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.BlockedDialog
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.LinkPreviewDialog
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.SendSeedDialog
|
||||
|
@ -177,6 +177,7 @@ import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
|||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask
|
||||
import org.thoughtcrime.securesms.util.SimpleTextWatcher
|
||||
import org.thoughtcrime.securesms.util.isScrolledToBottom
|
||||
import org.thoughtcrime.securesms.util.push
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
|
@ -250,11 +251,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
val address = if (sessionId.prefix == IdPrefix.BLINDED && openGroup != null) {
|
||||
storage.getOrCreateBlindedIdMapping(sessionId.hexString, openGroup.server, openGroup.publicKey).sessionId?.let {
|
||||
fromSerialized(it)
|
||||
} ?: run {
|
||||
val openGroupInboxId =
|
||||
"${openGroup.server}!${openGroup.publicKey}!${sessionId.hexString}".toByteArray()
|
||||
fromSerialized(GroupUtil.getEncodedOpenGroupInboxID(openGroupInboxId))
|
||||
}
|
||||
} ?: GroupUtil.getEncodedOpenGroupInboxID(openGroup, sessionId)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
|
@ -263,7 +260,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
}
|
||||
} ?: finish()
|
||||
}
|
||||
viewModelFactory.create(threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair(), contentResolver)
|
||||
viewModelFactory.create(threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair())
|
||||
}
|
||||
private var actionMode: ActionMode? = null
|
||||
private var unreadCount = 0
|
||||
|
@ -322,8 +319,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
handleSwipeToReply(message)
|
||||
},
|
||||
onItemLongPress = { message, position, view ->
|
||||
if (!isMessageRequestThread() &&
|
||||
(viewModel.openGroup == null || Capability.REACTIONS.name.lowercase() in viewModel.serverCapabilities)
|
||||
if (!viewModel.isMessageRequestThread &&
|
||||
viewModel.canReactToMessages
|
||||
) {
|
||||
showEmojiPicker(message, view)
|
||||
} else {
|
||||
|
@ -606,26 +603,27 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
|
||||
// called from onCreate
|
||||
private fun setUpInputBar() {
|
||||
binding!!.inputBar.isVisible = viewModel.openGroup == null || viewModel.openGroup?.canWrite == true
|
||||
binding!!.inputBar.delegate = this
|
||||
binding!!.inputBarRecordingView.delegate = this
|
||||
val binding = binding ?: return
|
||||
binding.inputBar.isGone = viewModel.hidesInputBar()
|
||||
binding.inputBar.delegate = this
|
||||
binding.inputBarRecordingView.delegate = this
|
||||
// GIF button
|
||||
binding!!.gifButtonContainer.addView(gifButton)
|
||||
binding.gifButtonContainer.addView(gifButton)
|
||||
gifButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
||||
gifButton.onUp = { showGIFPicker() }
|
||||
gifButton.snIsEnabled = false
|
||||
// Document button
|
||||
binding!!.documentButtonContainer.addView(documentButton)
|
||||
binding.documentButtonContainer.addView(documentButton)
|
||||
documentButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
||||
documentButton.onUp = { showDocumentPicker() }
|
||||
documentButton.snIsEnabled = false
|
||||
// Library button
|
||||
binding!!.libraryButtonContainer.addView(libraryButton)
|
||||
binding.libraryButtonContainer.addView(libraryButton)
|
||||
libraryButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
||||
libraryButton.onUp = { pickFromLibrary() }
|
||||
libraryButton.snIsEnabled = false
|
||||
// Camera button
|
||||
binding!!.cameraButtonContainer.addView(cameraButton)
|
||||
binding.cameraButtonContainer.addView(cameraButton)
|
||||
cameraButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
|
||||
cameraButton.onUp = { showCamera() }
|
||||
cameraButton.snIsEnabled = false
|
||||
|
@ -774,7 +772,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
val recipient = viewModel.recipient ?: return false
|
||||
if (!isMessageRequestThread()) {
|
||||
if (!viewModel.isMessageRequestThread) {
|
||||
ConversationMenuHelper.onPrepareOptionsMenu(
|
||||
menu,
|
||||
menuInflater,
|
||||
|
@ -860,11 +858,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
}
|
||||
}
|
||||
|
||||
private fun isMessageRequestThread(): Boolean {
|
||||
val recipient = viewModel.recipient ?: return false
|
||||
return !recipient.isGroupRecipient && !recipient.isApproved
|
||||
}
|
||||
|
||||
private fun isOutgoingMessageRequestThread(): Boolean {
|
||||
val recipient = viewModel.recipient ?: return false
|
||||
return !recipient.isGroupRecipient &&
|
||||
|
@ -1079,11 +1072,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
private fun updatePlaceholder() {
|
||||
val recipient = viewModel.recipient
|
||||
?: return Log.w("Loki", "recipient was null in placeholder update")
|
||||
val blindedRecipient = viewModel.blindedRecipient
|
||||
val binding = binding ?: return
|
||||
val openGroup = viewModel.openGroup
|
||||
val (textResource, insertParam) = when {
|
||||
recipient.isLocalNumber -> R.string.activity_conversation_empty_state_note_to_self to null
|
||||
openGroup != null && !openGroup.canWrite -> R.string.activity_conversation_empty_state_read_only to recipient.toShortString()
|
||||
blindedRecipient?.blocksCommunityMessageRequests == true -> R.string.activity_conversation_empty_state_blocks_community_requests to recipient.toShortString()
|
||||
else -> R.string.activity_conversation_empty_state_default to recipient.toShortString()
|
||||
}
|
||||
val showPlaceholder = adapter.itemCount == 0
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.content.ContentResolver
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.cash.copper.flow.observeQuery
|
||||
import com.goterl.lazysodium.utils.KeyPair
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
@ -22,7 +20,6 @@ import org.session.libsession.messaging.utilities.SodiumUtilities
|
|||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
||||
import org.thoughtcrime.securesms.database.Storage
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.repository.ConversationRepository
|
||||
|
@ -31,7 +28,6 @@ import java.util.UUID
|
|||
class ConversationViewModel(
|
||||
val threadId: Long,
|
||||
val edKeyPair: KeyPair?,
|
||||
private val contentResolver: ContentResolver,
|
||||
private val repository: ConversationRepository,
|
||||
private val storage: StorageProtocol
|
||||
) : ViewModel() {
|
||||
|
@ -48,6 +44,15 @@ class ConversationViewModel(
|
|||
val recipient: Recipient?
|
||||
get() = _recipient.value
|
||||
|
||||
val blindedRecipient: Recipient?
|
||||
get() = _recipient.value?.let { recipient ->
|
||||
when {
|
||||
recipient.isOpenGroupOutboxRecipient -> recipient
|
||||
recipient.isOpenGroupInboxRecipient -> repository.maybeGetBlindedRecipient(recipient)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private var _openGroup: RetrieveOnce<OpenGroup> = RetrieveOnce {
|
||||
storage.getOpenGroup(threadId)
|
||||
}
|
||||
|
@ -63,12 +68,22 @@ class ConversationViewModel(
|
|||
?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
|
||||
}
|
||||
|
||||
val isMessageRequestThread : Boolean
|
||||
get() {
|
||||
val recipient = recipient ?: return false
|
||||
return !recipient.isLocalNumber && !recipient.isGroupRecipient && !recipient.isApproved
|
||||
}
|
||||
|
||||
val canReactToMessages: Boolean
|
||||
// allow reactions if the open group is null (normal conversations) or the open group's capabilities include reactions
|
||||
get() = (openGroup == null || OpenGroupApi.Capability.REACTIONS.name.lowercase() in serverCapabilities)
|
||||
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
contentResolver.observeQuery(DatabaseContentProviders.Conversation.getUriForThread(threadId))
|
||||
.collect {
|
||||
val recipientExists = storage.getRecipientForThread(threadId) != null
|
||||
if (!recipientExists && _uiState.value.conversationExists) {
|
||||
repository.recipientUpdateFlow(threadId)
|
||||
.collect { recipient ->
|
||||
if (recipient == null && _uiState.value.conversationExists) {
|
||||
_uiState.update { it.copy(conversationExists = false) }
|
||||
}
|
||||
}
|
||||
|
@ -200,22 +215,25 @@ class ConversationViewModel(
|
|||
_recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId))
|
||||
}
|
||||
|
||||
fun hidesInputBar(): Boolean = openGroup?.canWrite != true &&
|
||||
blindedRecipient?.blocksCommunityMessageRequests == true
|
||||
|
||||
|
||||
@dagger.assisted.AssistedFactory
|
||||
interface AssistedFactory {
|
||||
fun create(threadId: Long, edKeyPair: KeyPair?, contentResolver: ContentResolver): Factory
|
||||
fun create(threadId: Long, edKeyPair: KeyPair?): Factory
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class Factory @AssistedInject constructor(
|
||||
@Assisted private val threadId: Long,
|
||||
@Assisted private val edKeyPair: KeyPair?,
|
||||
@Assisted private val contentResolver: ContentResolver,
|
||||
private val repository: ConversationRepository,
|
||||
private val storage: StorageProtocol
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return ConversationViewModel(threadId, edKeyPair, contentResolver, repository, storage) as T
|
||||
return ConversationViewModel(threadId, edKeyPair, repository, storage) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,14 +63,15 @@ public class RecipientDatabase extends Database {
|
|||
private static final String FORCE_SMS_SELECTION = "force_sms_selection";
|
||||
private static final String NOTIFY_TYPE = "notify_type"; // all, mentions only, none
|
||||
private static final String WRAPPER_HASH = "wrapper_hash";
|
||||
private static final String BLOCKS_COMMUNITY_MESSAGE_REQUESTS = "blocks_community_message_requests";
|
||||
private static final String AUTO_DOWNLOAD = "auto_download"; // 1 / 0 / -1 flag for whether to auto-download in a conversation, or if the user hasn't selected a preference
|
||||
|
||||
private static final String[] RECIPIENT_PROJECTION = new String[] {
|
||||
BLOCK, APPROVED, APPROVED_ME, NOTIFICATION, CALL_RINGTONE, VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, EXPIRE_MESSAGES, REGISTERED,
|
||||
PROFILE_KEY, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_CONTACT_URI,
|
||||
SIGNAL_PROFILE_NAME, SIGNAL_PROFILE_AVATAR, PROFILE_SHARING, NOTIFICATION_CHANNEL,
|
||||
UNIDENTIFIED_ACCESS_MODE,
|
||||
FORCE_SMS_SELECTION, NOTIFY_TYPE, WRAPPER_HASH, AUTO_DOWNLOAD,
|
||||
UNIDENTIFIED_ACCESS_MODE, FORCE_SMS_SELECTION, NOTIFY_TYPE, WRAPPER_HASH,
|
||||
BLOCKS_COMMUNITY_MESSAGE_REQUESTS, AUTO_DOWNLOAD,
|
||||
};
|
||||
|
||||
static final List<String> TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
|
||||
|
@ -154,6 +155,11 @@ public class RecipientDatabase extends Database {
|
|||
"ADD COLUMN "+WRAPPER_HASH+" TEXT DEFAULT NULL;";
|
||||
}
|
||||
|
||||
public static String getAddBlocksCommunityMessageRequests() {
|
||||
return "ALTER TABLE "+TABLE_NAME+" "+
|
||||
"ADD COLUMN "+BLOCKS_COMMUNITY_MESSAGE_REQUESTS+" INT DEFAULT 0;";
|
||||
}
|
||||
|
||||
public static final int NOTIFY_TYPE_ALL = 0;
|
||||
public static final int NOTIFY_TYPE_MENTIONS = 1;
|
||||
public static final int NOTIFY_TYPE_NONE = 2;
|
||||
|
@ -210,6 +216,7 @@ public class RecipientDatabase extends Database {
|
|||
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE));
|
||||
boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1;
|
||||
String wrapperHash = cursor.getString(cursor.getColumnIndexOrThrow(WRAPPER_HASH));
|
||||
boolean blocksCommunityMessageRequests = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKS_COMMUNITY_MESSAGE_REQUESTS)) == 1;
|
||||
|
||||
MaterialColor color;
|
||||
byte[] profileKey = null;
|
||||
|
@ -241,7 +248,7 @@ public class RecipientDatabase extends Database {
|
|||
systemPhoneLabel, systemContactUri,
|
||||
signalProfileName, signalProfileAvatar, profileSharing,
|
||||
notificationChannel, Recipient.UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
|
||||
forceSmsSelection, wrapperHash));
|
||||
forceSmsSelection, wrapperHash, blocksCommunityMessageRequests));
|
||||
}
|
||||
|
||||
public boolean isAutoDownloadFlagSet(Recipient recipient) {
|
||||
|
@ -439,6 +446,14 @@ public class RecipientDatabase extends Database {
|
|||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
public void setBlocksCommunityMessageRequests(@NonNull Recipient recipient, boolean isBlocked) {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(BLOCKS_COMMUNITY_MESSAGE_REQUESTS, isBlocked ? 1 : 0);
|
||||
updateOrInsert(recipient.getAddress(), contentValues);
|
||||
recipient.resolve().setBlocksCommunityMessageRequests(isBlocked);
|
||||
notifyRecipientListeners();
|
||||
}
|
||||
|
||||
private void updateOrInsert(Address address, ContentValues contentValues) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
|
|
|
@ -197,6 +197,11 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||
db.setProfileKey(recipient, newProfileKey)
|
||||
}
|
||||
|
||||
override fun setBlocksCommunityMessageRequests(recipient: Recipient, blocksMessageRequests: Boolean) {
|
||||
val db = DatabaseComponent.get(context).recipientDatabase()
|
||||
db.setBlocksCommunityMessageRequests(recipient, blocksMessageRequests)
|
||||
}
|
||||
|
||||
override fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?) {
|
||||
val ourRecipient = fromSerialized(getUserPublicKey()!!).let {
|
||||
Recipient.from(context, it, false)
|
||||
|
@ -438,6 +443,10 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||
return configFactory.canPerformChange(variant, publicKey, changeTimestampMs)
|
||||
}
|
||||
|
||||
override fun isCheckingCommunityRequests(): Boolean {
|
||||
return configFactory.user?.getCommunityMessageRequests() == true
|
||||
}
|
||||
|
||||
fun notifyUpdates(forConfigObject: ConfigBase) {
|
||||
when (forConfigObject) {
|
||||
is UserProfile -> updateUser(forConfigObject)
|
||||
|
@ -1459,7 +1468,7 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||
val blindedId = when {
|
||||
recipient.isGroupRecipient -> null
|
||||
recipient.isOpenGroupInboxRecipient -> {
|
||||
GroupUtil.getDecodedOpenGroupInbox(address)
|
||||
GroupUtil.getDecodedOpenGroupInboxSessionId(address)
|
||||
}
|
||||
else -> {
|
||||
if (SessionId(address).prefix == IdPrefix.BLINDED) {
|
||||
|
@ -1578,16 +1587,12 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
|
|||
if (mapping.sessionId != null) {
|
||||
return mapping
|
||||
}
|
||||
val threadDb = DatabaseComponent.get(context).threadDatabase()
|
||||
threadDb.readerFor(threadDb.conversationList).use { reader ->
|
||||
while (reader.next != null) {
|
||||
val recipient = reader.current.recipient
|
||||
val sessionId = recipient.address.serialize()
|
||||
if (!recipient.isGroupRecipient && SodiumUtilities.sessionId(sessionId, blindedId, serverPublicKey)) {
|
||||
val contactMapping = mapping.copy(sessionId = sessionId)
|
||||
db.addBlindedIdMapping(contactMapping)
|
||||
return contactMapping
|
||||
}
|
||||
getAllContacts().forEach { contact ->
|
||||
val sessionId = SessionId(contact.sessionID)
|
||||
if (sessionId.prefix == IdPrefix.STANDARD && SodiumUtilities.sessionId(sessionId.hexString, blindedId, serverPublicKey)) {
|
||||
val contactMapping = mapping.copy(sessionId = sessionId.hexString)
|
||||
db.addBlindedIdMapping(contactMapping)
|
||||
return contactMapping
|
||||
}
|
||||
}
|
||||
db.getBlindedIdMappingsExceptFor(server).forEach {
|
||||
|
|
|
@ -89,9 +89,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||
private static final int lokiV41 = 62;
|
||||
private static final int lokiV42 = 63;
|
||||
private static final int lokiV43 = 64;
|
||||
private static final int lokiV44 = 65;
|
||||
|
||||
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||
private static final int DATABASE_VERSION = lokiV43;
|
||||
private static final int DATABASE_VERSION = lokiV44;
|
||||
private static final int MIN_DATABASE_VERSION = lokiV7;
|
||||
private static final String CIPHER3_DATABASE_NAME = "signal.db";
|
||||
public static final String DATABASE_NAME = "signal_v4.db";
|
||||
|
@ -357,6 +358,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||
|
||||
executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS);
|
||||
db.execSQL(RecipientDatabase.getAddWrapperHash());
|
||||
db.execSQL(RecipientDatabase.getAddBlocksCommunityMessageRequests());
|
||||
|
||||
db.execSQL(RecipientDatabase.getCreateAutoDownloadCommand());
|
||||
db.execSQL(RecipientDatabase.getUpdateAutoDownloadValuesCommand());
|
||||
|
@ -603,6 +605,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||
}
|
||||
|
||||
if (oldVersion < lokiV43) {
|
||||
db.execSQL(RecipientDatabase.getAddBlocksCommunityMessageRequests());
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV44) {
|
||||
db.execSQL(RecipientDatabase.getCreateAutoDownloadCommand());
|
||||
db.execSQL(RecipientDatabase.getUpdateAutoDownloadValuesCommand());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package org.thoughtcrime.securesms.dependencies
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object ContentModule {
|
||||
|
||||
@Provides
|
||||
fun providesContentResolver(@ApplicationContext context: Context) =context.contentResolver
|
||||
|
||||
}
|
|
@ -72,8 +72,8 @@ import org.thoughtcrime.securesms.onboarding.SeedActivity
|
|||
import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.preferences.SettingsActivity
|
||||
import org.thoughtcrime.securesms.showSessionDialog
|
||||
import org.thoughtcrime.securesms.showMuteDialog
|
||||
import org.thoughtcrime.securesms.showSessionDialog
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.IP2Country
|
||||
|
@ -299,12 +299,17 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||
}
|
||||
EventBus.getDefault().register(this@HomeActivity)
|
||||
if (intent.hasExtra(FROM_ONBOARDING)
|
||||
&& intent.getBooleanExtra(FROM_ONBOARDING, false)
|
||||
&& !(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled()
|
||||
) {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.POST_NOTIFICATIONS)
|
||||
.execute()
|
||||
&& intent.getBooleanExtra(FROM_ONBOARDING, false)) {
|
||||
if ((getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.POST_NOTIFICATIONS)
|
||||
.execute()
|
||||
}
|
||||
configFactory.user?.let { user ->
|
||||
if (!user.isBlockCommunityMessageRequestsSet()) {
|
||||
user.setCommunityMessageRequests(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package org.thoughtcrime.securesms.preferences
|
||||
|
||||
import android.os.Bundle
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PrivacySettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||
|
|
|
@ -8,6 +8,9 @@ import android.os.Build
|
|||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.BuildConfig
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
|
@ -15,13 +18,19 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.isPasswo
|
|||
import org.session.libsession.utilities.TextSecurePreferences.Companion.setScreenLockEnabled
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService
|
||||
import org.thoughtcrime.securesms.showSessionDialog
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.areNotificationsEnabled
|
||||
import org.thoughtcrime.securesms.util.IntentUtils
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() {
|
||||
|
||||
@Inject lateinit var configFactory: ConfigFactory
|
||||
|
||||
override fun onCreate(paramBundle: Bundle?) {
|
||||
super.onCreate(paramBundle)
|
||||
findPreference<Preference>(TextSecurePreferences.SCREEN_LOCK)!!
|
||||
|
@ -30,6 +39,33 @@ class PrivacySettingsPreferenceFragment : ListSummaryPreferenceFragment() {
|
|||
.onPreferenceChangeListener = TypingIndicatorsToggleListener()
|
||||
findPreference<Preference>(TextSecurePreferences.CALL_NOTIFICATIONS_ENABLED)!!
|
||||
.onPreferenceChangeListener = CallToggleListener(this) { setCall(it) }
|
||||
findPreference<PreferenceCategory>(getString(R.string.preferences__message_requests_category))?.let { category ->
|
||||
when (val user = configFactory.user) {
|
||||
null -> category.isVisible = false
|
||||
else -> SwitchPreferenceCompat(requireContext()).apply {
|
||||
key = TextSecurePreferences.ALLOW_MESSAGE_REQUESTS
|
||||
preferenceDataStore = object : PreferenceDataStore() {
|
||||
|
||||
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
|
||||
if (key == TextSecurePreferences.ALLOW_MESSAGE_REQUESTS) {
|
||||
return user.getCommunityMessageRequests()
|
||||
}
|
||||
return super.getBoolean(key, defValue)
|
||||
}
|
||||
|
||||
override fun putBoolean(key: String?, value: Boolean) {
|
||||
if (key == TextSecurePreferences.ALLOW_MESSAGE_REQUESTS) {
|
||||
user.setCommunityMessageRequests(value)
|
||||
return
|
||||
}
|
||||
super.putBoolean(key, value)
|
||||
}
|
||||
}
|
||||
title = getString(R.string.preferences__message_requests_title)
|
||||
summary = getString(R.string.preferences__message_requests_summary)
|
||||
}.let(category::addPreference)
|
||||
}
|
||||
}
|
||||
initializeVisibility()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
package org.thoughtcrime.securesms.repository
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import app.cash.copper.flow.observeQuery
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.session.libsession.database.MessageDataProvider
|
||||
import org.session.libsession.messaging.messages.Destination
|
||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||
|
@ -15,6 +21,7 @@ import org.session.libsession.utilities.GroupUtil
|
|||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase
|
||||
import org.thoughtcrime.securesms.database.LokiMessageDatabase
|
||||
import org.thoughtcrime.securesms.database.LokiThreadDatabase
|
||||
|
@ -35,6 +42,8 @@ import kotlin.coroutines.suspendCoroutine
|
|||
|
||||
interface ConversationRepository {
|
||||
fun maybeGetRecipientForThreadId(threadId: Long): Recipient?
|
||||
fun maybeGetBlindedRecipient(recipient: Recipient): Recipient?
|
||||
fun recipientUpdateFlow(threadId: Long): Flow<Recipient?>
|
||||
fun saveDraft(threadId: Long, text: String)
|
||||
fun getDraft(threadId: Long): String?
|
||||
fun clearDrafts(threadId: Long)
|
||||
|
@ -75,6 +84,7 @@ interface ConversationRepository {
|
|||
}
|
||||
|
||||
class DefaultConversationRepository @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val textSecurePreferences: TextSecurePreferences,
|
||||
private val messageDataProvider: MessageDataProvider,
|
||||
private val threadDb: ThreadDatabase,
|
||||
|
@ -87,13 +97,29 @@ class DefaultConversationRepository @Inject constructor(
|
|||
private val storage: Storage,
|
||||
private val lokiMessageDb: LokiMessageDatabase,
|
||||
private val sessionJobDb: SessionJobDatabase,
|
||||
private val configFactory: ConfigFactory
|
||||
private val configFactory: ConfigFactory,
|
||||
private val contentResolver: ContentResolver,
|
||||
) : ConversationRepository {
|
||||
|
||||
override fun maybeGetRecipientForThreadId(threadId: Long): Recipient? {
|
||||
return threadDb.getRecipientForThreadId(threadId)
|
||||
}
|
||||
|
||||
override fun maybeGetBlindedRecipient(recipient: Recipient): Recipient? {
|
||||
if (!recipient.isOpenGroupInboxRecipient) return null
|
||||
return Recipient.from(
|
||||
context,
|
||||
Address.fromSerialized(GroupUtil.getDecodedOpenGroupInboxSessionId(recipient.address.serialize())),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
override fun recipientUpdateFlow(threadId: Long): Flow<Recipient?> {
|
||||
return contentResolver.observeQuery(DatabaseContentProviders.Conversation.getUriForThread(threadId)).map {
|
||||
maybeGetRecipientForThreadId(threadId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveDraft(threadId: Long, text: String) {
|
||||
if (text.isEmpty()) return
|
||||
val drafts = DraftDatabase.Drafts()
|
||||
|
|
|
@ -628,6 +628,9 @@
|
|||
<string name="preferences_notifications__priority">Priority</string>
|
||||
<string name="preferences_app_protection__screenshot_notifications">Screenshot Notifications</string>
|
||||
<string name="preferences_app_protected__screenshot_notifications_summary">Receive a notification when a contact takes a screenshot of a one-to-one chat.</string>
|
||||
<string name="preferences__message_requests_category">Message Requests</string>
|
||||
<string name="preferences__message_requests_title">Community Message Requests</string>
|
||||
<string name="preferences__message_requests_summary">Allow message requests from Community conversations</string>
|
||||
<!-- **************************************** -->
|
||||
<!-- menus -->
|
||||
<!-- **************************************** -->
|
||||
|
@ -1036,6 +1039,7 @@
|
|||
<string name="activity_home_outdated_client_config">Some of your devices are using outdated versions. Syncing may be unreliable until they are updated.</string>
|
||||
|
||||
<string name="activity_conversation_empty_state_read_only">There are no messages in <b>%s</b>.</string>
|
||||
<string name="activity_conversation_empty_state_blocks_community_requests"><b>%s</b> has message requests from Community conversations turned off, so you cannot send them a message.</string>
|
||||
<string name="activity_conversation_empty_state_note_to_self">You have no messages in Note to Self.</string>
|
||||
<string name="activity_conversation_empty_state_default">You have no messages from <b>%s</b>.\nSend a message to start the conversation!</string>
|
||||
|
||||
|
|
|
@ -20,6 +20,12 @@
|
|||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/preferences__message_requests_category"
|
||||
android:key="@string/preferences__message_requests_category"
|
||||
android:persistent="false">
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/preferences__read_receipts">
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
|
|
|
@ -1,42 +1,46 @@
|
|||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import com.goterl.lazysodium.utils.KeyPair
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.hamcrest.CoreMatchers.endsWith
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.CoreMatchers.notNullValue
|
||||
import org.hamcrest.CoreMatchers.nullValue
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.anyLong
|
||||
import org.mockito.Mockito.anySet
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.BaseViewModelTest
|
||||
import org.thoughtcrime.securesms.database.Storage
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.repository.ConversationRepository
|
||||
import org.thoughtcrime.securesms.repository.ResultOf
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
class ConversationViewModelTest: BaseViewModelTest() {
|
||||
|
||||
private val repository = mock(ConversationRepository::class.java)
|
||||
private val storage = mock(Storage::class.java)
|
||||
private val repository = mock<ConversationRepository>()
|
||||
private val storage = mock<Storage>()
|
||||
|
||||
private val threadId = 123L
|
||||
private val edKeyPair = mock(KeyPair::class.java)
|
||||
private val edKeyPair = mock<KeyPair>()
|
||||
private lateinit var recipient: Recipient
|
||||
|
||||
private val viewModel: ConversationViewModel by lazy {
|
||||
ConversationViewModel(threadId, edKeyPair, mock(), repository, storage)
|
||||
ConversationViewModel(threadId, edKeyPair, repository, storage)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
recipient = mock(Recipient::class.java)
|
||||
recipient = mock()
|
||||
whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient)
|
||||
whenever(repository.recipientUpdateFlow(anyLong())).thenReturn(emptyFlow())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -79,7 +83,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `should delete locally`() {
|
||||
val message = mock(MessageRecord::class.java)
|
||||
val message = mock<MessageRecord>()
|
||||
|
||||
viewModel.deleteLocally(message)
|
||||
|
||||
|
@ -88,7 +92,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `should emit error message on failure to delete a message for everyone`() = runBlockingTest {
|
||||
val message = mock(MessageRecord::class.java)
|
||||
val message = mock<MessageRecord>()
|
||||
val error = Throwable()
|
||||
whenever(repository.deleteForEveryone(anyLong(), any(), any()))
|
||||
.thenReturn(ResultOf.Failure(error))
|
||||
|
@ -101,7 +105,7 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||
@Test
|
||||
fun `should emit error message on failure to delete messages without unsend request`() =
|
||||
runBlockingTest {
|
||||
val message = mock(MessageRecord::class.java)
|
||||
val message = mock<MessageRecord>()
|
||||
val error = Throwable()
|
||||
whenever(repository.deleteMessageWithoutUnsendRequest(anyLong(), anySet()))
|
||||
.thenReturn(ResultOf.Failure(error))
|
||||
|
@ -181,4 +185,30 @@ class ConversationViewModelTest: BaseViewModelTest() {
|
|||
assertThat(viewModel.uiState.value.uiMessages.size, equalTo(0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `open group recipient should have no blinded recipient`() {
|
||||
whenever(recipient.isOpenGroupRecipient).thenReturn(true)
|
||||
whenever(recipient.isOpenGroupOutboxRecipient).thenReturn(false)
|
||||
whenever(recipient.isOpenGroupInboxRecipient).thenReturn(false)
|
||||
assertThat(viewModel.blindedRecipient, nullValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `local recipient should have input and no blinded recipient`() {
|
||||
whenever(recipient.isLocalNumber).thenReturn(true)
|
||||
assertThat(viewModel.hidesInputBar(), equalTo(false))
|
||||
assertThat(viewModel.blindedRecipient, nullValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contact recipient should hide input bar if not accepting requests`() {
|
||||
whenever(recipient.isOpenGroupInboxRecipient).thenReturn(true)
|
||||
val blinded = mock<Recipient> {
|
||||
whenever(it.blocksCommunityMessageRequests).thenReturn(true)
|
||||
}
|
||||
whenever(repository.maybeGetBlindedRecipient(recipient)).thenReturn(blinded)
|
||||
assertThat(viewModel.blindedRecipient, notNullValue())
|
||||
assertThat(viewModel.hidesInputBar(), equalTo(true))
|
||||
}
|
||||
|
||||
}
|
|
@ -95,4 +95,33 @@ Java_network_loki_messenger_libsession_1util_UserProfile_getNtsPriority(JNIEnv *
|
|||
std::lock_guard lock{util::util_mutex_};
|
||||
auto profile = ptrToProfile(env, thiz);
|
||||
return profile->get_nts_priority();
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_network_loki_messenger_libsession_1util_UserProfile_getCommunityMessageRequests(
|
||||
JNIEnv *env, jobject thiz) {
|
||||
std::lock_guard lock{util::util_mutex_};
|
||||
auto profile = ptrToProfile(env, thiz);
|
||||
auto blinded_msg_requests = profile->get_blinded_msgreqs();
|
||||
if (blinded_msg_requests.has_value()) {
|
||||
return *blinded_msg_requests;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_network_loki_messenger_libsession_1util_UserProfile_setCommunityMessageRequests(
|
||||
JNIEnv *env, jobject thiz, jboolean blocks) {
|
||||
std::lock_guard lock{util::util_mutex_};
|
||||
auto profile = ptrToProfile(env, thiz);
|
||||
profile->set_blinded_msgreqs(std::optional{(bool)blocks});
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_network_loki_messenger_libsession_1util_UserProfile_isBlockCommunityMessageRequestsSet(
|
||||
JNIEnv *env, jobject thiz) {
|
||||
std::lock_guard lock{util::util_mutex_};
|
||||
auto profile = ptrToProfile(env, thiz);
|
||||
return profile->get_blinded_msgreqs().has_value();
|
||||
}
|
|
@ -127,6 +127,9 @@ class UserProfile(pointer: Long) : ConfigBase(pointer) {
|
|||
external fun setPic(userPic: UserPic)
|
||||
external fun setNtsPriority(priority: Int)
|
||||
external fun getNtsPriority(): Int
|
||||
external fun getCommunityMessageRequests(): Boolean
|
||||
external fun setCommunityMessageRequests(blocks: Boolean)
|
||||
external fun isBlockCommunityMessageRequestsSet(): Boolean
|
||||
}
|
||||
|
||||
class ConversationVolatileConfig(pointer: Long): ConfigBase(pointer) {
|
||||
|
|
|
@ -42,6 +42,7 @@ interface StorageProtocol {
|
|||
fun getUserProfile(): Profile
|
||||
fun setProfileAvatar(recipient: Recipient, profileAvatar: String?)
|
||||
fun setProfilePicture(recipient: Recipient, newProfilePicture: String?, newProfileKey: ByteArray?)
|
||||
fun setBlocksCommunityMessageRequests(recipient: Recipient, blocksMessageRequests: Boolean)
|
||||
fun setUserProfilePicture(newProfilePicture: String?, newProfileKey: ByteArray?)
|
||||
fun clearUserPic()
|
||||
// Signal
|
||||
|
@ -233,4 +234,5 @@ interface StorageProtocol {
|
|||
fun notifyConfigUpdates(forConfigObject: ConfigBase)
|
||||
fun conversationInConfig(publicKey: String?, groupPublicKey: String?, openGroupId: String?, visibleOnly: Boolean): Boolean
|
||||
fun canPerformConfigChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean
|
||||
fun isCheckingCommunityRequests(): Boolean
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ class VisibleMessage(
|
|||
var profile: Profile? = null,
|
||||
var openGroupInvitation: OpenGroupInvitation? = null,
|
||||
var reaction: Reaction? = null,
|
||||
var hasMention: Boolean = false
|
||||
var hasMention: Boolean = false,
|
||||
var blocksMessageRequests: Boolean = false
|
||||
) : Message() {
|
||||
|
||||
override val isSelfSendValid: Boolean = true
|
||||
|
@ -74,6 +75,9 @@ class VisibleMessage(
|
|||
val reaction = Reaction.fromProto(reactionProto)
|
||||
result.reaction = reaction
|
||||
}
|
||||
|
||||
result.blocksMessageRequests = with (dataMessage) { hasBlocksCommunityMessageRequests() && blocksCommunityMessageRequests }
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +136,8 @@ class VisibleMessage(
|
|||
Recipient.from(context, Address.fromSerialized(recipient!!), false).expireMessages
|
||||
}
|
||||
dataMessage.expireTimer = expiration
|
||||
// Community blocked message requests flag
|
||||
dataMessage.blocksCommunityMessageRequests = blocksMessageRequests
|
||||
// Sync target
|
||||
if (syncTarget != null) {
|
||||
dataMessage.syncTarget = syncTarget
|
||||
|
|
|
@ -753,7 +753,8 @@ object OpenGroupApi {
|
|||
)
|
||||
}
|
||||
val serverCapabilities = storage.getServerCapabilities(server)
|
||||
if (serverCapabilities.contains(Capability.BLIND.name.lowercase())) {
|
||||
val isAcceptingCommunityRequests = storage.isCheckingCommunityRequests()
|
||||
if (serverCapabilities.contains(Capability.BLIND.name.lowercase()) && isAcceptingCommunityRequests) {
|
||||
requests.add(
|
||||
if (lastInboxMessageId == null) {
|
||||
BatchRequestInfo(
|
||||
|
|
|
@ -242,9 +242,16 @@ object MessageSender {
|
|||
private fun sendToOpenGroupDestination(destination: Destination, message: Message): Promise<Unit, Exception> {
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val configFactory = MessagingModuleConfiguration.shared.configFactory
|
||||
if (message.sentTimestamp == null) {
|
||||
message.sentTimestamp = SnodeAPI.nowWithOffset
|
||||
}
|
||||
// Attach the blocks message requests info
|
||||
configFactory.user?.let { user ->
|
||||
if (message is VisibleMessage) {
|
||||
message.blocksMessageRequests = !user.getCommunityMessageRequests()
|
||||
}
|
||||
}
|
||||
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!
|
||||
var serverCapabilities = listOf<String>()
|
||||
var blindedPublicKey: ByteArray? = null
|
||||
|
|
|
@ -305,6 +305,10 @@ fun MessageReceiver.handleVisibleMessage(
|
|||
profileManager.setProfilePicture(context, recipient, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
if (userPublicKey != messageSender && !isUserBlindedSender) {
|
||||
storage.setBlocksCommunityMessageRequests(recipient, message.blocksMessageRequests)
|
||||
}
|
||||
}
|
||||
// Parse quote if needed
|
||||
var quoteModel: QuoteModel? = null
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.session.libsession.utilities
|
||||
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.utilities.SessionId
|
||||
import org.session.libsignal.messages.SignalServiceGroup
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import java.io.IOException
|
||||
|
@ -15,8 +17,15 @@ object GroupUtil {
|
|||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEncodedOpenGroupInboxID(groupInboxID: ByteArray): String {
|
||||
return OPEN_GROUP_INBOX_PREFIX + Hex.toStringCondensed(groupInboxID)
|
||||
fun getEncodedOpenGroupInboxID(openGroup: OpenGroup, sessionId: SessionId): Address {
|
||||
val openGroupInboxId =
|
||||
"${openGroup.server}!${openGroup.publicKey}!${sessionId.hexString}".toByteArray()
|
||||
return getEncodedOpenGroupInboxID(openGroupInboxId)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEncodedOpenGroupInboxID(groupInboxID: ByteArray): Address {
|
||||
return Address.fromSerialized(OPEN_GROUP_INBOX_PREFIX + Hex.toStringCondensed(groupInboxID))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
@ -51,7 +60,7 @@ object GroupUtil {
|
|||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getDecodedOpenGroupInbox(groupID: String): String {
|
||||
fun getDecodedOpenGroupInboxSessionId(groupID: String): String {
|
||||
val decodedGroupId = getDecodedGroupID(groupID)
|
||||
if (decodedGroupId.split("!").count() > 2) {
|
||||
return decodedGroupId.split("!", limit = 3)[2]
|
||||
|
|
|
@ -287,6 +287,8 @@ interface TextSecurePreferences {
|
|||
const val OCEAN_DARK = "ocean.dark"
|
||||
const val OCEAN_LIGHT = "ocean.light"
|
||||
|
||||
const val ALLOW_MESSAGE_REQUESTS = "libsession.ALLOW_MESSAGE_REQUESTS"
|
||||
|
||||
@JvmStatic
|
||||
fun getLastConfigurationSyncTime(context: Context): Long {
|
||||
return getLongPreference(context, LAST_CONFIGURATION_SYNC_TIME, 0)
|
||||
|
|
|
@ -101,6 +101,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||
private String notificationChannel;
|
||||
private boolean forceSmsSelection;
|
||||
private String wrapperHash;
|
||||
private boolean blocksCommunityMessageRequests;
|
||||
|
||||
private @NonNull UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.ENABLED;
|
||||
|
||||
|
@ -195,6 +196,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||
this.forceSmsSelection = details.get().forceSmsSelection;
|
||||
this.notifyType = details.get().notifyType;
|
||||
this.autoDownloadAttachments = details.get().autoDownloadAttachments;
|
||||
this.blocksCommunityMessageRequests = details.get().blocksCommunityMessageRequests;
|
||||
|
||||
this.participants.clear();
|
||||
this.participants.addAll(details.get().participants);
|
||||
|
@ -232,6 +234,8 @@ public class Recipient implements RecipientModifiedListener {
|
|||
Recipient.this.forceSmsSelection = result.forceSmsSelection;
|
||||
Recipient.this.notifyType = result.notifyType;
|
||||
Recipient.this.autoDownloadAttachments = result.autoDownloadAttachments;
|
||||
Recipient.this.blocksCommunityMessageRequests = result.blocksCommunityMessageRequests;
|
||||
|
||||
|
||||
Recipient.this.participants.clear();
|
||||
Recipient.this.participants.addAll(result.participants);
|
||||
|
@ -286,6 +290,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||
this.unidentifiedAccessMode = details.unidentifiedAccessMode;
|
||||
this.forceSmsSelection = details.forceSmsSelection;
|
||||
this.wrapperHash = details.wrapperHash;
|
||||
this.blocksCommunityMessageRequests = details.blocksCommunityMessageRequests;
|
||||
|
||||
this.participants.addAll(details.participants);
|
||||
this.resolving = false;
|
||||
|
@ -326,7 +331,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||
return this.name;
|
||||
}
|
||||
} else if (isOpenGroupInboxRecipient()){
|
||||
String inboxID = GroupUtil.getDecodedOpenGroupInbox(sessionID);
|
||||
String inboxID = GroupUtil.getDecodedOpenGroupInboxSessionId(sessionID);
|
||||
Contact contact = storage.getContactWithSessionID(inboxID);
|
||||
if (contact == null) { return sessionID; }
|
||||
return contact.displayName(Contact.ContactContext.REGULAR);
|
||||
|
@ -350,6 +355,18 @@ public class Recipient implements RecipientModifiedListener {
|
|||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
public boolean getBlocksCommunityMessageRequests() {
|
||||
return blocksCommunityMessageRequests;
|
||||
}
|
||||
|
||||
public void setBlocksCommunityMessageRequests(boolean blocksCommunityMessageRequests) {
|
||||
synchronized (this) {
|
||||
this.blocksCommunityMessageRequests = blocksCommunityMessageRequests;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
public synchronized @NonNull MaterialColor getColor() {
|
||||
if (isGroupRecipient()) return MaterialColor.GROUP;
|
||||
else if (color != null) return color;
|
||||
|
@ -777,12 +794,43 @@ public class Recipient implements RecipientModifiedListener {
|
|||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Recipient recipient = (Recipient) o;
|
||||
return resolving == recipient.resolving && mutedUntil == recipient.mutedUntil && notifyType == recipient.notifyType && blocked == recipient.blocked && approved == recipient.approved && approvedMe == recipient.approvedMe && expireMessages == recipient.expireMessages && address.equals(recipient.address) && Objects.equals(name, recipient.name) && Objects.equals(customLabel, recipient.customLabel) && Objects.equals(groupAvatarId, recipient.groupAvatarId) && Arrays.equals(profileKey, recipient.profileKey) && Objects.equals(profileName, recipient.profileName) && Objects.equals(profileAvatar, recipient.profileAvatar) && Objects.equals(wrapperHash, recipient.wrapperHash);
|
||||
return resolving == recipient.resolving
|
||||
&& mutedUntil == recipient.mutedUntil
|
||||
&& notifyType == recipient.notifyType
|
||||
&& blocked == recipient.blocked
|
||||
&& approved == recipient.approved
|
||||
&& approvedMe == recipient.approvedMe
|
||||
&& expireMessages == recipient.expireMessages
|
||||
&& address.equals(recipient.address)
|
||||
&& Objects.equals(name, recipient.name)
|
||||
&& Objects.equals(customLabel, recipient.customLabel)
|
||||
&& Objects.equals(groupAvatarId, recipient.groupAvatarId)
|
||||
&& Arrays.equals(profileKey, recipient.profileKey)
|
||||
&& Objects.equals(profileName, recipient.profileName)
|
||||
&& Objects.equals(profileAvatar, recipient.profileAvatar)
|
||||
&& Objects.equals(wrapperHash, recipient.wrapperHash)
|
||||
&& blocksCommunityMessageRequests == recipient.blocksCommunityMessageRequests;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(address, name, customLabel, resolving, groupAvatarId, mutedUntil, notifyType, blocked, approved, approvedMe, expireMessages, profileName, profileAvatar, wrapperHash);
|
||||
int result = Objects.hash(
|
||||
address,
|
||||
name,
|
||||
customLabel,
|
||||
resolving,
|
||||
groupAvatarId,
|
||||
mutedUntil,
|
||||
notifyType,
|
||||
blocked,
|
||||
approved,
|
||||
approvedMe,
|
||||
expireMessages,
|
||||
profileName,
|
||||
profileAvatar,
|
||||
wrapperHash,
|
||||
blocksCommunityMessageRequests
|
||||
);
|
||||
result = 31 * result + Arrays.hashCode(profileKey);
|
||||
return result;
|
||||
}
|
||||
|
@ -888,10 +936,11 @@ public class Recipient implements RecipientModifiedListener {
|
|||
private final UnidentifiedAccessMode unidentifiedAccessMode;
|
||||
private final boolean forceSmsSelection;
|
||||
private final String wrapperHash;
|
||||
private final boolean blocksCommunityMessageRequests;
|
||||
|
||||
public RecipientSettings(boolean blocked, boolean approved, boolean approvedMe, long muteUntil,
|
||||
int notifyType,
|
||||
boolean autoDownloadAttachments,
|
||||
int notifyType,
|
||||
boolean autoDownloadAttachments,
|
||||
@NonNull VibrateState messageVibrateState,
|
||||
@NonNull VibrateState callVibrateState,
|
||||
@Nullable Uri messageRingtone,
|
||||
|
@ -911,7 +960,9 @@ public class Recipient implements RecipientModifiedListener {
|
|||
@Nullable String notificationChannel,
|
||||
@NonNull UnidentifiedAccessMode unidentifiedAccessMode,
|
||||
boolean forceSmsSelection,
|
||||
String wrapperHash)
|
||||
String wrapperHash,
|
||||
boolean blocksCommunityMessageRequests
|
||||
)
|
||||
{
|
||||
this.blocked = blocked;
|
||||
this.approved = approved;
|
||||
|
@ -938,7 +989,7 @@ public class Recipient implements RecipientModifiedListener {
|
|||
this.notificationChannel = notificationChannel;
|
||||
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
||||
this.forceSmsSelection = forceSmsSelection;
|
||||
this.wrapperHash = wrapperHash;
|
||||
this.wrapperHash = wrapperHash;this.blocksCommunityMessageRequests = blocksCommunityMessageRequests;
|
||||
}
|
||||
|
||||
public @Nullable MaterialColor getColor() {
|
||||
|
@ -1045,6 +1096,10 @@ public class Recipient implements RecipientModifiedListener {
|
|||
return wrapperHash;
|
||||
}
|
||||
|
||||
public boolean getBlocksCommunityMessageRequests() {
|
||||
return blocksCommunityMessageRequests;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -179,6 +179,7 @@ class RecipientProvider {
|
|||
@NonNull final UnidentifiedAccessMode unidentifiedAccessMode;
|
||||
final boolean forceSmsSelection;
|
||||
final String wrapperHash;
|
||||
final boolean blocksCommunityMessageRequests;
|
||||
|
||||
RecipientDetails(@Nullable String name, @Nullable Long groupAvatarId,
|
||||
boolean systemContact, boolean isLocalNumber, @Nullable RecipientSettings settings,
|
||||
|
@ -213,6 +214,7 @@ class RecipientProvider {
|
|||
this.unidentifiedAccessMode = settings != null ? settings.getUnidentifiedAccessMode() : UnidentifiedAccessMode.DISABLED;
|
||||
this.forceSmsSelection = settings != null && settings.isForceSmsSelection();
|
||||
this.wrapperHash = settings != null ? settings.getWrapperHash() : null;
|
||||
this.blocksCommunityMessageRequests = settings != null && settings.getBlocksCommunityMessageRequests();
|
||||
|
||||
if (name == null && settings != null) this.name = settings.getSystemDisplayName();
|
||||
else this.name = name;
|
||||
|
|
|
@ -177,19 +177,20 @@ message DataMessage {
|
|||
required Action action = 4;
|
||||
}
|
||||
|
||||
optional string body = 1;
|
||||
repeated AttachmentPointer attachments = 2;
|
||||
optional uint32 flags = 4;
|
||||
optional uint32 expireTimer = 5;
|
||||
optional bytes profileKey = 6;
|
||||
optional uint64 timestamp = 7;
|
||||
optional Quote quote = 8;
|
||||
repeated Preview preview = 10;
|
||||
optional Reaction reaction = 11;
|
||||
optional LokiProfile profile = 101;
|
||||
optional OpenGroupInvitation openGroupInvitation = 102;
|
||||
optional ClosedGroupControlMessage closedGroupControlMessage = 104;
|
||||
optional string syncTarget = 105;
|
||||
optional string body = 1;
|
||||
repeated AttachmentPointer attachments = 2;
|
||||
optional uint32 flags = 4;
|
||||
optional uint32 expireTimer = 5;
|
||||
optional bytes profileKey = 6;
|
||||
optional uint64 timestamp = 7;
|
||||
optional Quote quote = 8;
|
||||
repeated Preview preview = 10;
|
||||
optional Reaction reaction = 11;
|
||||
optional LokiProfile profile = 101;
|
||||
optional OpenGroupInvitation openGroupInvitation = 102;
|
||||
optional ClosedGroupControlMessage closedGroupControlMessage = 104;
|
||||
optional string syncTarget = 105;
|
||||
optional bool blocksCommunityMessageRequests = 106;
|
||||
optional GroupMessage groupMessage = 120;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue