fix: migrate some dependencies and functionality out of VM into repository to remove content resolver and context dependecy so tests pass again

This commit is contained in:
0x330a 2023-08-22 11:54:37 +10:00
parent 162debdcf1
commit 637dc80d48
5 changed files with 68 additions and 34 deletions

View File

@ -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.Reaction
import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.messages.visible.VisibleMessage
import org.session.libsession.messaging.open_groups.OpenGroupApi 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.MessageSender
import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
@ -250,7 +249,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
} ?: finish() } ?: finish()
} }
viewModelFactory.create(applicationContext, threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair(), contentResolver) viewModelFactory.create(threadId, MessagingModuleConfiguration.shared.getUserED25519KeyPair())
} }
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private var unreadCount = 0 private var unreadCount = 0
@ -309,8 +308,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
handleSwipeToReply(message) handleSwipeToReply(message)
}, },
onItemLongPress = { message, position, view -> onItemLongPress = { message, position, view ->
if (!isMessageRequestThread() && if (!viewModel.isMessageRequestThread &&
(viewModel.openGroup == null || Capability.REACTIONS.name.lowercase() in viewModel.serverCapabilities) viewModel.canReactToMessages
) { ) {
showEmojiPicker(message, view) showEmojiPicker(message, view)
} else { } else {
@ -762,7 +761,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun onPrepareOptionsMenu(menu: Menu): Boolean { override fun onPrepareOptionsMenu(menu: Menu): Boolean {
val recipient = viewModel.recipient ?: return false val recipient = viewModel.recipient ?: return false
if (!isMessageRequestThread()) { if (!viewModel.isMessageRequestThread) {
ConversationMenuHelper.onPrepareOptionsMenu( ConversationMenuHelper.onPrepareOptionsMenu(
menu, menu,
menuInflater, 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 { private fun isOutgoingMessageRequestThread(): Boolean {
val recipient = viewModel.recipient ?: return false val recipient = viewModel.recipient ?: return false
return !recipient.isGroupRecipient && return !recipient.isGroupRecipient &&

View File

@ -1,11 +1,8 @@
package org.thoughtcrime.securesms.conversation.v2 package org.thoughtcrime.securesms.conversation.v2
import android.content.ContentResolver
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.cash.copper.flow.observeQuery
import com.goterl.lazysodium.utils.KeyPair import com.goterl.lazysodium.utils.KeyPair
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject 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.open_groups.OpenGroupApi
import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities 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.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.repository.ConversationRepository import org.thoughtcrime.securesms.repository.ConversationRepository
import java.util.UUID import java.util.UUID
class ConversationViewModel( class ConversationViewModel(
private val context: Context,
val threadId: Long, val threadId: Long,
val edKeyPair: KeyPair?, val edKeyPair: KeyPair?,
private val contentResolver: ContentResolver,
private val repository: ConversationRepository, private val repository: ConversationRepository,
private val storage: Storage private val storage: Storage
) : ViewModel() { ) : ViewModel() {
@ -55,11 +47,7 @@ class ConversationViewModel(
get() = _recipient.value?.let { recipient -> get() = _recipient.value?.let { recipient ->
when { when {
recipient.isOpenGroupOutboxRecipient -> recipient recipient.isOpenGroupOutboxRecipient -> recipient
recipient.isOpenGroupInboxRecipient -> Recipient.from( recipient.isOpenGroupInboxRecipient -> repository.maybeGetBlindedRecipient(recipient)
context,
Address.fromSerialized(GroupUtil.getDecodedOpenGroupInboxSessionId(recipient.address.serialize())),
false
)
else -> null else -> null
} }
} }
@ -79,12 +67,22 @@ class ConversationViewModel(
?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString ?.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 { init {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
contentResolver.observeQuery(DatabaseContentProviders.Conversation.getUriForThread(threadId)) repository.recipientUpdateFlow(threadId)
.collect { .collect { recipient ->
val recipientExists = storage.getRecipientForThread(threadId) != null if (recipient == null && _uiState.value.conversationExists) {
if (!recipientExists && _uiState.value.conversationExists) {
_uiState.update { it.copy(conversationExists = false) } _uiState.update { it.copy(conversationExists = false) }
} }
} }
@ -222,21 +220,19 @@ class ConversationViewModel(
@dagger.assisted.AssistedFactory @dagger.assisted.AssistedFactory
interface AssistedFactory { interface AssistedFactory {
fun create(context: Context, threadId: Long, edKeyPair: KeyPair?, contentResolver: ContentResolver): Factory fun create(threadId: Long, edKeyPair: KeyPair?): Factory
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class Factory @AssistedInject constructor( class Factory @AssistedInject constructor(
@Assisted private val threadId: Long, @Assisted private val threadId: Long,
@Assisted private val edKeyPair: KeyPair?, @Assisted private val edKeyPair: KeyPair?,
@Assisted private val contentResolver: ContentResolver,
@Assisted private val context: Context,
private val repository: ConversationRepository, private val repository: ConversationRepository,
private val storage: Storage private val storage: Storage
) : ViewModelProvider.Factory { ) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ConversationViewModel(context, threadId, edKeyPair, contentResolver, repository, storage) as T return ConversationViewModel(threadId, edKeyPair, repository, storage) as T
} }
} }
} }

View File

@ -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
}

View File

@ -1,5 +1,11 @@
package org.thoughtcrime.securesms.repository 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.database.MessageDataProvider
import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.MessageRequestResponse 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.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.database.DatabaseContentProviders
import org.thoughtcrime.securesms.database.DraftDatabase import org.thoughtcrime.securesms.database.DraftDatabase
import org.thoughtcrime.securesms.database.LokiMessageDatabase import org.thoughtcrime.securesms.database.LokiMessageDatabase
import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase
@ -35,6 +42,8 @@ import kotlin.coroutines.suspendCoroutine
interface ConversationRepository { interface ConversationRepository {
fun maybeGetRecipientForThreadId(threadId: Long): Recipient? fun maybeGetRecipientForThreadId(threadId: Long): Recipient?
fun maybeGetBlindedRecipient(recipient: Recipient): Recipient?
fun recipientUpdateFlow(threadId: Long): Flow<Recipient?>
fun saveDraft(threadId: Long, text: String) fun saveDraft(threadId: Long, text: String)
fun getDraft(threadId: Long): String? fun getDraft(threadId: Long): String?
fun clearDrafts(threadId: Long) fun clearDrafts(threadId: Long)
@ -75,6 +84,7 @@ interface ConversationRepository {
} }
class DefaultConversationRepository @Inject constructor( class DefaultConversationRepository @Inject constructor(
@ApplicationContext private val context: Context,
private val textSecurePreferences: TextSecurePreferences, private val textSecurePreferences: TextSecurePreferences,
private val messageDataProvider: MessageDataProvider, private val messageDataProvider: MessageDataProvider,
private val threadDb: ThreadDatabase, private val threadDb: ThreadDatabase,
@ -87,13 +97,29 @@ class DefaultConversationRepository @Inject constructor(
private val storage: Storage, private val storage: Storage,
private val lokiMessageDb: LokiMessageDatabase, private val lokiMessageDb: LokiMessageDatabase,
private val sessionJobDb: SessionJobDatabase, private val sessionJobDb: SessionJobDatabase,
private val configFactory: ConfigFactory private val configFactory: ConfigFactory,
private val contentResolver: ContentResolver,
) : ConversationRepository { ) : ConversationRepository {
override fun maybeGetRecipientForThreadId(threadId: Long): Recipient? { override fun maybeGetRecipientForThreadId(threadId: Long): Recipient? {
return threadDb.getRecipientForThreadId(threadId) 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) { override fun saveDraft(threadId: Long, text: String) {
if (text.isEmpty()) return if (text.isEmpty()) return
val drafts = DraftDatabase.Drafts() val drafts = DraftDatabase.Drafts()

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.conversation.v2 package org.thoughtcrime.securesms.conversation.v2
import com.goterl.lazysodium.utils.KeyPair import com.goterl.lazysodium.utils.KeyPair
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import org.hamcrest.CoreMatchers.endsWith import org.hamcrest.CoreMatchers.endsWith
import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.equalTo
@ -30,13 +31,14 @@ class ConversationViewModelTest: BaseViewModelTest() {
private lateinit var recipient: Recipient private lateinit var recipient: Recipient
private val viewModel: ConversationViewModel by lazy { private val viewModel: ConversationViewModel by lazy {
ConversationViewModel(threadId, edKeyPair, mock(), repository, storage) ConversationViewModel(threadId, edKeyPair, repository, storage)
} }
@Before @Before
fun setUp() { fun setUp() {
recipient = mock(Recipient::class.java) recipient = mock(Recipient::class.java)
whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient) whenever(repository.maybeGetRecipientForThreadId(anyLong())).thenReturn(recipient)
whenever(repository.recipientUpdateFlow(anyLong())).thenReturn(emptyFlow())
} }
@Test @Test