diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 8d8d93476..e1a5729c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -79,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 @@ -250,7 +249,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } ?: finish() } - viewModelFactory.create(applicationContext, threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair(), contentResolver) + viewModelFactory.create(threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair()) } private var actionMode: ActionMode? = null private var unreadCount = 0 @@ -309,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 { @@ -762,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, @@ -848,12 +847,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } - private fun isMessageRequestThread(): Boolean { - val recipient = viewModel.recipient ?: return false - if (recipient.isLocalNumber) return false - return !recipient.isGroupRecipient && !recipient.isApproved - } - private fun isOutgoingMessageRequestThread(): Boolean { val recipient = viewModel.recipient ?: return false return !recipient.isGroupRecipient && diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 665d3fc45..89654d958 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -1,11 +1,8 @@ package org.thoughtcrime.securesms.conversation.v2 -import android.content.ContentResolver -import android.content.Context 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 @@ -19,22 +16,17 @@ import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupUtil 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 import java.util.UUID class ConversationViewModel( - private val context: Context, val threadId: Long, val edKeyPair: KeyPair?, - private val contentResolver: ContentResolver, private val repository: ConversationRepository, private val storage: Storage ) : ViewModel() { @@ -55,11 +47,7 @@ class ConversationViewModel( get() = _recipient.value?.let { recipient -> when { recipient.isOpenGroupOutboxRecipient -> recipient - recipient.isOpenGroupInboxRecipient -> Recipient.from( - context, - Address.fromSerialized(GroupUtil.getDecodedOpenGroupInboxSessionId(recipient.address.serialize())), - false - ) + recipient.isOpenGroupInboxRecipient -> repository.maybeGetBlindedRecipient(recipient) else -> null } } @@ -79,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) } } } @@ -222,21 +220,19 @@ class ConversationViewModel( @dagger.assisted.AssistedFactory interface AssistedFactory { - fun create(context: Context, 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, - @Assisted private val context: Context, private val repository: ConversationRepository, private val storage: Storage ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return ConversationViewModel(context, threadId, edKeyPair, contentResolver, repository, storage) as T + return ConversationViewModel(threadId, edKeyPair, repository, storage) as T } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ContentModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ContentModule.kt new file mode 100644 index 000000000..89098a0f1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ContentModule.kt @@ -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 + +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index dd013afa7..2c0b39d69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -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 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 { + 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() diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt index 361c5a438..c41e4e5b3 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt @@ -1,6 +1,7 @@ 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 @@ -30,13 +31,14 @@ class ConversationViewModelTest: BaseViewModelTest() { 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) whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient) + whenever(repository.recipientUpdateFlow(anyLong())).thenReturn(emptyFlow()) } @Test