mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-12 16:47:43 +00:00
[SES-1002] Synced blind requests (#1303)
* feat: update config to use blinded-msg-requests pr * feat: add block community message requests bool to protos * feat: add everything needed for recipientDB to have blocked community requests potentially * feat: add db migrations * feat: add sending community block flags and preference options * feat: add parsing block request flag * fix: open group message requests were broken anyway * fix: delete all encoded open group inbox ID bs, fix privacy settings using user config as privacy store * feat: initial creation sets flag, rename to match libsession implementation value * fix: recipient blinded checks from open group message for blocking community requests on blinded ID version of recipient, use correct (inverted) values from before for checking polling and empty states etc * fix: pr comments for view model factory context ref, simplified user config object check for category in PrivacySettingsPreferenceFragment * fix: pr comments * fix: migrate some dependencies and functionality out of VM into repository to remove content resolver and context dependecy so tests pass again * refactor: better naming for hidesInputBar and add more tests for expected recipient view states * fix: use contact information as opposed to active conversations * fix: PR comments
This commit is contained in:
@@ -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
|
||||
@@ -78,7 +79,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
|
||||
@@ -105,13 +105,12 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.attachments.ScreenshotObserver
|
||||
import org.thoughtcrime.securesms.audio.AudioRecorder
|
||||
import org.thoughtcrime.securesms.contacts.SelectContactsActivity.Companion.selectedContactsKey
|
||||
import org.thoughtcrime.securesms.util.SimpleTextWatcher
|
||||
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
|
||||
@@ -174,6 +173,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
|
||||
@@ -240,11 +240,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
|
||||
}
|
||||
@@ -253,7 +249,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
|
||||
@@ -312,8 +308,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 {
|
||||
@@ -596,26 +592,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
|
||||
@@ -764,7 +761,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,
|
||||
@@ -850,11 +847,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 &&
|
||||
@@ -1069,11 +1061,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
|
||||
@@ -21,7 +19,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
|
||||
@@ -30,7 +27,6 @@ import java.util.UUID
|
||||
class ConversationViewModel(
|
||||
val threadId: Long,
|
||||
val edKeyPair: KeyPair?,
|
||||
private val contentResolver: ContentResolver,
|
||||
private val repository: ConversationRepository,
|
||||
private val storage: Storage
|
||||
) : ViewModel() {
|
||||
@@ -47,6 +43,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)
|
||||
}
|
||||
@@ -62,12 +67,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) }
|
||||
}
|
||||
}
|
||||
@@ -199,22 +214,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: Storage
|
||||
) : 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,13 +63,14 @@ 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[] 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
|
||||
FORCE_SMS_SELECTION, NOTIFY_TYPE, WRAPPER_HASH, BLOCKS_COMMUNITY_MESSAGE_REQUESTS
|
||||
};
|
||||
|
||||
static final List<String> TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION)
|
||||
@@ -142,6 +143,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;
|
||||
@@ -197,6 +203,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;
|
||||
@@ -228,7 +235,7 @@ public class RecipientDatabase extends Database {
|
||||
systemPhoneLabel, systemContactUri,
|
||||
signalProfileName, signalProfileAvatar, profileSharing,
|
||||
notificationChannel, Recipient.UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
|
||||
forceSmsSelection, wrapperHash));
|
||||
forceSmsSelection, wrapperHash, blocksCommunityMessageRequests));
|
||||
}
|
||||
|
||||
public void setColor(@NonNull Recipient recipient, @NonNull MaterialColor color) {
|
||||
@@ -395,6 +402,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();
|
||||
|
||||
|
@@ -190,6 +190,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)
|
||||
@@ -430,6 +435,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)
|
||||
@@ -1405,7 +1414,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) {
|
||||
@@ -1524,16 +1533,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 {
|
||||
|
@@ -88,9 +88,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
private static final int lokiV40 = 61;
|
||||
private static final int lokiV41 = 62;
|
||||
private static final int lokiV42 = 63;
|
||||
private static final int lokiV43 = 64;
|
||||
|
||||
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||
private static final int DATABASE_VERSION = lokiV42;
|
||||
private static final int DATABASE_VERSION = lokiV43;
|
||||
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";
|
||||
@@ -356,6 +357,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS);
|
||||
db.execSQL(RecipientDatabase.getAddWrapperHash());
|
||||
db.execSQL(RecipientDatabase.getAddBlocksCommunityMessageRequests());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -598,6 +600,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(RecipientDatabase.getAddWrapperHash());
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV43) {
|
||||
db.execSQL(RecipientDatabase.getAddBlocksCommunityMessageRequests());
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@@ -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()
|
||||
|
@@ -627,6 +627,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 -->
|
||||
<!-- **************************************** -->
|
||||
@@ -1033,6 +1036,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))
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user