package org.thoughtcrime.securesms.conversation.disappearingmessages import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.BuildConfig import network.loki.messenger.libsession_util.util.ExpiryMode import org.session.libsession.messaging.messages.ExpirationConfiguration import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.SSKEnvironment.MessageExpirationManagerProtocol import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.ExpiryCallbacks import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.UiState import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.toUiState import org.thoughtcrime.securesms.database.GroupDatabase import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities class DisappearingMessagesViewModel( private val threadId: Long, private val application: Application, private val textSecurePreferences: TextSecurePreferences, private val messageExpirationManager: MessageExpirationManagerProtocol, private val threadDb: ThreadDatabase, private val groupDb: GroupDatabase, private val storage: Storage, isNewConfigEnabled: Boolean, showDebugOptions: Boolean ) : AndroidViewModel(application), ExpiryCallbacks { private val _event = Channel() val event = _event.receiveAsFlow() private val _state = MutableStateFlow( State( isNewConfigEnabled = isNewConfigEnabled, showDebugOptions = showDebugOptions ) ) val state = _state.asStateFlow() val uiState = _state .map(State::toUiState) .stateIn(viewModelScope, SharingStarted.Eagerly, UiState()) init { viewModelScope.launch { val expiryMode = storage.getExpirationConfiguration(threadId)?.expiryMode?.maybeConvertToLegacy(isNewConfigEnabled) ?: ExpiryMode.NONE val recipient = threadDb.getRecipientForThreadId(threadId) val groupRecord = recipient?.takeIf { it.isClosedGroupRecipient } ?.run { groupDb.getGroup(address.toGroupString()).orNull() } _state.update { state -> state.copy( address = recipient?.address, isGroup = groupRecord != null, isNoteToSelf = recipient?.address?.serialize() == textSecurePreferences.getLocalNumber(), isSelfAdmin = groupRecord == null || groupRecord.admins.any{ it.serialize() == textSecurePreferences.getLocalNumber() }, expiryMode = expiryMode, persistedMode = expiryMode ) } } } override fun setValue(value: ExpiryMode) = _state.update { it.copy(expiryMode = value) } override fun onSetClick() = viewModelScope.launch { val state = _state.value val mode = state.expiryMode?.coerceLegacyToAfterSend() val address = state.address if (address == null || mode == null) { _event.send(Event.FAIL) return@launch } val expiryChangeTimestampMs = SnodeAPI.nowWithOffset storage.setExpirationConfiguration(ExpirationConfiguration(threadId, mode, expiryChangeTimestampMs)) val message = ExpirationTimerUpdate(mode).apply { sender = textSecurePreferences.getLocalNumber() isSenderSelf = true recipient = address.serialize() sentTimestamp = expiryChangeTimestampMs } messageExpirationManager.setExpirationTimer(message, mode) MessageSender.send(message, address) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(application) _event.send(Event.SUCCESS) } private fun ExpiryMode.coerceLegacyToAfterSend() = takeUnless { it is ExpiryMode.Legacy } ?: ExpiryMode.AfterSend(expirySeconds) @dagger.assisted.AssistedFactory interface AssistedFactory { fun create(threadId: Long): Factory } @Suppress("UNCHECKED_CAST") class Factory @AssistedInject constructor( @Assisted private val threadId: Long, private val application: Application, private val textSecurePreferences: TextSecurePreferences, private val messageExpirationManager: MessageExpirationManagerProtocol, private val threadDb: ThreadDatabase, private val groupDb: GroupDatabase, private val storage: Storage ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T = DisappearingMessagesViewModel( threadId, application, textSecurePreferences, messageExpirationManager, threadDb, groupDb, storage, ExpirationConfiguration.isNewConfigEnabled, BuildConfig.DEBUG ) as T } } private fun ExpiryMode.maybeConvertToLegacy(isNewConfigEnabled: Boolean): ExpiryMode = takeIf { isNewConfigEnabled } ?: ExpiryMode.Legacy(expirySeconds)