mirror of
https://github.com/oxen-io/session-android.git
synced 2023-12-14 02:53:01 +01:00
273 lines
9.5 KiB
Kotlin
273 lines
9.5 KiB
Kotlin
package org.thoughtcrime.securesms.conversation.v2
|
|
|
|
import android.content.Context
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.ViewModelProvider
|
|
import androidx.lifecycle.viewModelScope
|
|
import com.goterl.lazysodium.utils.KeyPair
|
|
import dagger.assisted.Assisted
|
|
import dagger.assisted.AssistedInject
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.GlobalScope
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.flow.update
|
|
import kotlinx.coroutines.launch
|
|
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
|
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.recipients.Recipient
|
|
import org.session.libsignal.utilities.IdPrefix
|
|
import org.session.libsignal.utilities.Log
|
|
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(
|
|
val threadId: Long,
|
|
val edKeyPair: KeyPair?,
|
|
private val repository: ConversationRepository,
|
|
private val storage: Storage
|
|
) : ViewModel() {
|
|
|
|
val showSendAfterApprovalText: Boolean
|
|
get() = recipient?.run { isContactRecipient && !isLocalNumber && !hasApprovedMe() } ?: false
|
|
|
|
private val _uiState = MutableStateFlow(ConversationUiState(conversationExists = true))
|
|
val uiState: StateFlow<ConversationUiState> = _uiState
|
|
|
|
private var _recipient: RetrieveOnce<Recipient> = RetrieveOnce {
|
|
repository.maybeGetRecipientForThreadId(threadId)
|
|
}
|
|
val expirationConfiguration: ExpirationConfiguration?
|
|
get() = storage.getExpirationConfiguration(threadId)
|
|
|
|
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)
|
|
}
|
|
val openGroup: OpenGroup?
|
|
get() = _openGroup.value
|
|
|
|
val serverCapabilities: List<String>
|
|
get() = openGroup?.let { storage.getServerCapabilities(it.server) } ?: listOf()
|
|
|
|
val blindedPublicKey: String?
|
|
get() = if (openGroup == null || edKeyPair == null || !serverCapabilities.contains(OpenGroupApi.Capability.BLIND.name.lowercase())) null else {
|
|
SodiumUtilities.blindedKeyPair(openGroup!!.publicKey, edKeyPair)?.publicKey?.asBytes
|
|
?.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) {
|
|
repository.recipientUpdateFlow(threadId)
|
|
.collect { recipient ->
|
|
if (recipient == null && _uiState.value.conversationExists) {
|
|
_uiState.update { it.copy(conversationExists = false) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun saveDraft(text: String) {
|
|
GlobalScope.launch(Dispatchers.IO) {
|
|
repository.saveDraft(threadId, text)
|
|
}
|
|
}
|
|
|
|
fun getDraft(): String? {
|
|
val draft: String? = repository.getDraft(threadId)
|
|
|
|
viewModelScope.launch(Dispatchers.IO) {
|
|
repository.clearDrafts(threadId)
|
|
}
|
|
|
|
return draft
|
|
}
|
|
|
|
fun inviteContacts(contacts: List<Recipient>) {
|
|
repository.inviteContacts(threadId, contacts)
|
|
}
|
|
|
|
fun block() {
|
|
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for block action")
|
|
if (recipient.isContactRecipient) {
|
|
repository.setBlocked(recipient, true)
|
|
}
|
|
}
|
|
|
|
fun unblock() {
|
|
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for unblock action")
|
|
if (recipient.isContactRecipient) {
|
|
repository.setBlocked(recipient, false)
|
|
}
|
|
}
|
|
|
|
fun deleteThread() = viewModelScope.launch {
|
|
repository.deleteThread(threadId)
|
|
}
|
|
|
|
fun deleteLocally(message: MessageRecord) {
|
|
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for delete locally action")
|
|
repository.deleteLocally(recipient, message)
|
|
}
|
|
|
|
fun setRecipientApproved() {
|
|
val recipient = recipient ?: return Log.w("Loki", "Recipient was null for set approved action")
|
|
repository.setApproved(recipient, true)
|
|
}
|
|
|
|
fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch {
|
|
val recipient = recipient ?: return@launch
|
|
repository.deleteForEveryone(threadId, recipient, message)
|
|
.onFailure {
|
|
showMessage("Couldn't delete message due to error: $it")
|
|
}
|
|
}
|
|
|
|
fun deleteMessagesWithoutUnsendRequest(messages: Set<MessageRecord>) = viewModelScope.launch {
|
|
repository.deleteMessageWithoutUnsendRequest(threadId, messages)
|
|
.onFailure {
|
|
showMessage("Couldn't delete message due to error: $it")
|
|
}
|
|
}
|
|
|
|
fun banUser(recipient: Recipient) = viewModelScope.launch {
|
|
repository.banUser(threadId, recipient)
|
|
.onSuccess {
|
|
showMessage("Successfully banned user")
|
|
}
|
|
.onFailure {
|
|
showMessage("Couldn't ban user due to error: $it")
|
|
}
|
|
}
|
|
|
|
fun banAndDeleteAll(recipient: Recipient) = viewModelScope.launch {
|
|
repository.banAndDeleteAll(threadId, recipient)
|
|
.onSuccess {
|
|
showMessage("Successfully banned user and deleted all their messages")
|
|
}
|
|
.onFailure {
|
|
showMessage("Couldn't execute request due to error: $it")
|
|
}
|
|
}
|
|
|
|
fun acceptMessageRequest() = viewModelScope.launch {
|
|
val recipient = recipient ?: return@launch Log.w("Loki", "Recipient was null for accept message request action")
|
|
repository.acceptMessageRequest(threadId, recipient)
|
|
.onSuccess {
|
|
_uiState.update {
|
|
it.copy(isMessageRequestAccepted = true)
|
|
}
|
|
}
|
|
.onFailure {
|
|
showMessage("Couldn't accept message request due to error: $it")
|
|
}
|
|
}
|
|
|
|
fun declineMessageRequest() {
|
|
repository.declineMessageRequest(threadId)
|
|
}
|
|
|
|
private fun showMessage(message: String) {
|
|
_uiState.update { currentUiState ->
|
|
val messages = currentUiState.uiMessages + UiMessage(
|
|
id = UUID.randomUUID().mostSignificantBits,
|
|
message = message
|
|
)
|
|
currentUiState.copy(uiMessages = messages)
|
|
}
|
|
}
|
|
|
|
fun messageShown(messageId: Long) {
|
|
_uiState.update { currentUiState ->
|
|
val messages = currentUiState.uiMessages.filterNot { it.id == messageId }
|
|
currentUiState.copy(uiMessages = messages)
|
|
}
|
|
}
|
|
|
|
fun hasReceived(): Boolean {
|
|
return repository.hasReceived(threadId)
|
|
}
|
|
|
|
fun updateRecipient() {
|
|
_recipient.updateTo(repository.maybeGetRecipientForThreadId(threadId))
|
|
}
|
|
|
|
fun hidesInputBar(): Boolean = openGroup?.canWrite != true &&
|
|
blindedRecipient?.blocksCommunityMessageRequests == true
|
|
|
|
fun legacyBannerRecipient(context: Context): Recipient? = recipient?.let { recipient ->
|
|
val legacyAddress = storage.getLastLegacyRecipient(recipient.address.serialize()) ?: return@let null
|
|
return Recipient.from(context, Address.fromSerialized(legacyAddress), false)
|
|
}
|
|
|
|
|
|
@dagger.assisted.AssistedFactory
|
|
interface AssistedFactory {
|
|
fun create(threadId: Long, edKeyPair: KeyPair?): Factory
|
|
}
|
|
|
|
@Suppress("UNCHECKED_CAST")
|
|
class Factory @AssistedInject constructor(
|
|
@Assisted private val threadId: Long,
|
|
@Assisted private val edKeyPair: KeyPair?,
|
|
private val repository: ConversationRepository,
|
|
private val storage: Storage
|
|
) : ViewModelProvider.Factory {
|
|
|
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
return ConversationViewModel(threadId, edKeyPair, repository, storage) as T
|
|
}
|
|
}
|
|
}
|
|
|
|
data class UiMessage(val id: Long, val message: String)
|
|
|
|
data class ConversationUiState(
|
|
val uiMessages: List<UiMessage> = emptyList(),
|
|
val isMessageRequestAccepted: Boolean? = null,
|
|
val conversationExists: Boolean
|
|
)
|
|
|
|
data class RetrieveOnce<T>(val retrieval: () -> T?) {
|
|
private var triedToRetrieve: Boolean = false
|
|
private var _value: T? = null
|
|
|
|
val value: T?
|
|
get() {
|
|
if (triedToRetrieve) { return _value }
|
|
|
|
triedToRetrieve = true
|
|
_value = retrieval()
|
|
return _value
|
|
}
|
|
|
|
fun updateTo(value: T?) { _value = value }
|
|
}
|