mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 20:45:17 +00:00
Tidy up
This commit is contained in:
parent
0657ab2305
commit
88df9ff65a
@ -196,12 +196,14 @@ class ConfigToDatabaseSync @Inject constructor(
|
||||
private data class UpdateGroupInfo(
|
||||
val id: AccountId,
|
||||
val name: String?,
|
||||
val destroyed: Boolean,
|
||||
val deleteBefore: Long?,
|
||||
val deleteAttachmentsBefore: Long?
|
||||
) {
|
||||
constructor(groupInfoConfig: ReadableGroupInfoConfig) : this(
|
||||
id = groupInfoConfig.id(),
|
||||
name = groupInfoConfig.getName(),
|
||||
destroyed = groupInfoConfig.isDestroyed(),
|
||||
deleteBefore = groupInfoConfig.getDeleteBefore(),
|
||||
deleteAttachmentsBefore = groupInfoConfig.getDeleteAttachmentsBefore()
|
||||
)
|
||||
@ -212,11 +214,16 @@ class ConfigToDatabaseSync @Inject constructor(
|
||||
val recipient = storage.getRecipientForThread(threadId) ?: return
|
||||
recipientDatabase.setProfileName(recipient, groupInfoConfig.name)
|
||||
profileManager.setName(context, recipient, groupInfoConfig.name ?: "")
|
||||
groupInfoConfig.deleteBefore?.let { removeBefore ->
|
||||
storage.trimThreadBefore(threadId, removeBefore)
|
||||
}
|
||||
groupInfoConfig.deleteAttachmentsBefore?.let { removeAttachmentsBefore ->
|
||||
mmsDatabase.deleteMessagesInThreadBeforeDate(threadId, removeAttachmentsBefore, onlyMedia = true)
|
||||
|
||||
if (groupInfoConfig.destroyed) {
|
||||
storage.clearMessages(threadId)
|
||||
} else {
|
||||
groupInfoConfig.deleteBefore?.let { removeBefore ->
|
||||
storage.trimThreadBefore(threadId, removeBefore)
|
||||
}
|
||||
groupInfoConfig.deleteAttachmentsBefore?.let { removeAttachmentsBefore ->
|
||||
mmsDatabase.deleteMessagesInThreadBeforeDate(threadId, removeAttachmentsBefore, onlyMedia = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1231,17 +1231,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
if (item.itemId == android.R.id.home) {
|
||||
return false
|
||||
}
|
||||
return viewModel.recipient?.let { recipient ->
|
||||
ConversationMenuHelper.onOptionItemSelected(
|
||||
context = this,
|
||||
item = item,
|
||||
thread = recipient,
|
||||
threadID = threadId,
|
||||
factory = configFactory,
|
||||
storage = storage,
|
||||
groupManager = groupManagerV2,
|
||||
)
|
||||
} ?: false
|
||||
|
||||
return viewModel.onOptionItemSelected(this, item)
|
||||
}
|
||||
|
||||
override fun block(deleteThread: Boolean) {
|
||||
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.StringRes
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModel
|
||||
@ -13,11 +14,8 @@ import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -25,6 +23,7 @@ import network.loki.messenger.R
|
||||
import network.loki.messenger.libsession_util.util.GroupMember
|
||||
import org.session.libsession.database.MessageDataProvider
|
||||
import org.session.libsession.database.StorageProtocol
|
||||
import org.session.libsession.messaging.groups.GroupManagerV2
|
||||
import org.session.libsession.messaging.messages.ExpirationConfiguration
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
@ -40,12 +39,14 @@ import org.session.libsignal.utilities.IdPrefix
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.AccountId
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer
|
||||
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||
import org.thoughtcrime.securesms.database.LokiMessageDatabase
|
||||
import org.thoughtcrime.securesms.database.Storage
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide
|
||||
import org.thoughtcrime.securesms.repository.ConversationRepository
|
||||
@ -61,7 +62,9 @@ class ConversationViewModel(
|
||||
private val groupDb: GroupDatabase,
|
||||
private val threadDb: ThreadDatabase,
|
||||
private val lokiMessageDb: LokiMessageDatabase,
|
||||
private val textSecurePreferences: TextSecurePreferences
|
||||
private val textSecurePreferences: TextSecurePreferences,
|
||||
private val configFactory: ConfigFactory,
|
||||
private val groupManagerV2: GroupManagerV2,
|
||||
) : ViewModel() {
|
||||
|
||||
val showSendAfterApprovalText: Boolean
|
||||
@ -225,7 +228,7 @@ class ConversationViewModel(
|
||||
*/
|
||||
private fun shouldShowInput(recipient: Recipient?): Boolean {
|
||||
return when {
|
||||
recipient?.isClosedGroupV2Recipient == true -> !repository.isKicked(recipient)
|
||||
recipient?.isClosedGroupV2Recipient == true -> !repository.isGroupReadOnly(recipient)
|
||||
recipient?.isLegacyClosedGroupRecipient == true -> {
|
||||
groupDb.getGroup(recipient.address.toGroupString()).orNull()?.isActive == true
|
||||
}
|
||||
@ -872,6 +875,37 @@ class ConversationViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun onOptionItemSelected(
|
||||
// This must be the context of the activity as requirement from ConversationMenuHelper
|
||||
context: Context,
|
||||
item: MenuItem
|
||||
): Boolean {
|
||||
val recipient = recipient ?: return false
|
||||
|
||||
val inProgress = ConversationMenuHelper.onOptionItemSelected(
|
||||
context = context,
|
||||
item = item,
|
||||
thread = recipient,
|
||||
threadID = threadId,
|
||||
factory = configFactory,
|
||||
storage = storage,
|
||||
groupManager = groupManagerV2,
|
||||
)
|
||||
|
||||
if (inProgress != null) {
|
||||
viewModelScope.launch {
|
||||
_uiState.update { it.copy(showLoader = true) }
|
||||
try {
|
||||
inProgress.receive()
|
||||
} finally {
|
||||
_uiState.update { it.copy(showLoader = false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@dagger.assisted.AssistedFactory
|
||||
interface AssistedFactory {
|
||||
fun create(threadId: Long, edKeyPair: KeyPair?): Factory
|
||||
@ -890,7 +924,9 @@ class ConversationViewModel(
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val lokiMessageDb: LokiMessageDatabase,
|
||||
private val textSecurePreferences: TextSecurePreferences
|
||||
private val textSecurePreferences: TextSecurePreferences,
|
||||
private val configFactory: ConfigFactory,
|
||||
private val groupManagerV2: GroupManagerV2,
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
@ -904,7 +940,9 @@ class ConversationViewModel(
|
||||
groupDb = groupDb,
|
||||
threadDb = threadDb,
|
||||
lokiMessageDb = lokiMessageDb,
|
||||
textSecurePreferences = textSecurePreferences
|
||||
textSecurePreferences = textSecurePreferences,
|
||||
configFactory = configFactory,
|
||||
groupManagerV2 = groupManagerV2,
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import androidx.core.graphics.drawable.IconCompat
|
||||
import com.squareup.phrase.Phrase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
@ -156,6 +158,12 @@ object ConversationMenuHelper {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the selected option
|
||||
*
|
||||
* @return An asynchronous channel that can be used to wait for the action to complete. Null if
|
||||
* the action does not require waiting.
|
||||
*/
|
||||
fun onOptionItemSelected(
|
||||
context: Context,
|
||||
item: MenuItem,
|
||||
@ -164,7 +172,7 @@ object ConversationMenuHelper {
|
||||
factory: ConfigFactory,
|
||||
storage: StorageProtocol,
|
||||
groupManager: GroupManagerV2,
|
||||
): Boolean {
|
||||
): ReceiveChannel<Unit>? {
|
||||
when (item.itemId) {
|
||||
R.id.menu_view_all_media -> { showAllMedia(context, thread) }
|
||||
R.id.menu_search -> { search(context) }
|
||||
@ -176,14 +184,15 @@ object ConversationMenuHelper {
|
||||
R.id.menu_copy_account_id -> { copyAccountID(context, thread) }
|
||||
R.id.menu_copy_open_group_url -> { copyOpenGroupUrl(context, thread) }
|
||||
R.id.menu_edit_group -> { editClosedGroup(context, thread) }
|
||||
R.id.menu_leave_group -> { leaveClosedGroup(context, thread, threadID, factory, storage, groupManager) }
|
||||
R.id.menu_leave_group -> { return leaveClosedGroup(context, thread, threadID, factory, storage, groupManager) }
|
||||
R.id.menu_invite_to_open_group -> { inviteContacts(context, thread) }
|
||||
R.id.menu_unmute_notifications -> { unmute(context, thread) }
|
||||
R.id.menu_mute_notifications -> { mute(context, thread) }
|
||||
R.id.menu_notification_settings -> { setNotifyType(context, thread) }
|
||||
R.id.menu_call -> { call(context, thread) }
|
||||
}
|
||||
return true
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun showAllMedia(context: Context, thread: Recipient) {
|
||||
@ -330,7 +339,7 @@ object ConversationMenuHelper {
|
||||
configFactory: ConfigFactory,
|
||||
storage: StorageProtocol,
|
||||
groupManager: GroupManagerV2,
|
||||
) {
|
||||
): ReceiveChannel<Unit>? {
|
||||
when {
|
||||
thread.isLegacyClosedGroupRecipient -> {
|
||||
val group = DatabaseComponent.get(context).groupDatabase().getGroup(thread.address.toGroupString()).orNull()
|
||||
@ -357,11 +366,13 @@ object ConversationMenuHelper {
|
||||
|
||||
thread.isClosedGroupV2Recipient -> {
|
||||
val accountId = AccountId(thread.address.serialize())
|
||||
val group = configFactory.withUserConfigs { it.userGroups.getClosedGroup(accountId.hexString) } ?: return
|
||||
val group = configFactory.withUserConfigs { it.userGroups.getClosedGroup(accountId.hexString) } ?: return null
|
||||
val name = configFactory.withGroupConfigs(accountId) {
|
||||
it.groupInfo.getName()
|
||||
} ?: group.name
|
||||
|
||||
val channel = Channel<Unit>()
|
||||
|
||||
confirmAndLeaveClosedGroup(
|
||||
context = context,
|
||||
groupName = name,
|
||||
@ -369,11 +380,19 @@ object ConversationMenuHelper {
|
||||
threadID = threadID,
|
||||
storage = storage,
|
||||
doLeave = {
|
||||
groupManager.leaveGroup(accountId, true)
|
||||
try {
|
||||
groupManager.leaveGroup(accountId, true)
|
||||
} finally {
|
||||
channel.send(Unit)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return channel
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun confirmAndLeaveClosedGroup(
|
||||
|
@ -365,7 +365,9 @@ class ConfigFactory @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
Unit to configs.dumpIfNeeded(clock)
|
||||
configs.dumpIfNeeded(clock)
|
||||
|
||||
Unit to true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,22 +388,20 @@ class GroupManagerV2Impl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun leaveGroup(group: AccountId, deleteOnLeave: Boolean) {
|
||||
val canSendGroupMessage = configFactory.getClosedGroup(group)?.kicked == false
|
||||
override suspend fun leaveGroup(groupId: AccountId, deleteOnLeave: Boolean) {
|
||||
val group = configFactory.getClosedGroup(groupId)
|
||||
|
||||
if (canSendGroupMessage) {
|
||||
val destination = Destination.ClosedGroup(group.hexString)
|
||||
// Only send the left/left notification group message when we are not kicked and we are not the only admin (only admin has a special treatment)
|
||||
val weAreTheOnlyAdmin = configFactory.withGroupConfigs(groupId) { config ->
|
||||
val allMembers = config.groupMembers.all()
|
||||
allMembers.count { it.admin } == 1 &&
|
||||
allMembers.first { it.admin }.sessionId == storage.getUserPublicKey()
|
||||
}
|
||||
|
||||
MessageSender.send(
|
||||
GroupUpdated(
|
||||
GroupUpdateMessage.newBuilder()
|
||||
.setMemberLeftMessage(DataMessage.GroupUpdateMemberLeftMessage.getDefaultInstance())
|
||||
.build()
|
||||
),
|
||||
destination,
|
||||
isSyncMessage = false
|
||||
).await()
|
||||
if (group?.kicked == false) {
|
||||
val destination = Destination.ClosedGroup(groupId.hexString)
|
||||
|
||||
// Always send a "XXX left" message to the group if we can
|
||||
MessageSender.send(
|
||||
GroupUpdated(
|
||||
GroupUpdateMessage.newBuilder()
|
||||
@ -412,14 +410,40 @@ class GroupManagerV2Impl @Inject constructor(
|
||||
),
|
||||
destination,
|
||||
isSyncMessage = false
|
||||
).await()
|
||||
)
|
||||
|
||||
// If we are not the only admin, send a left message for other admin to handle the member removal
|
||||
if (!weAreTheOnlyAdmin) {
|
||||
MessageSender.send(
|
||||
GroupUpdated(
|
||||
GroupUpdateMessage.newBuilder()
|
||||
.setMemberLeftMessage(DataMessage.GroupUpdateMemberLeftMessage.getDefaultInstance())
|
||||
.build()
|
||||
),
|
||||
destination,
|
||||
isSyncMessage = false
|
||||
).await()
|
||||
}
|
||||
}
|
||||
|
||||
pollerFactory.pollerFor(group)?.stop()
|
||||
// If we are the only admin, leaving this group will destroy the group
|
||||
if (weAreTheOnlyAdmin) {
|
||||
configFactory.withMutableGroupConfigs(groupId) { configs ->
|
||||
configs.groupInfo.destroyGroup()
|
||||
}
|
||||
|
||||
// Must wait until the config is pushed, otherwise if we go through the rest
|
||||
// of the code it will destroy the conversation, destroying the necessary configs
|
||||
// along the way, we won't be able to push the "destroyed" state anymore.
|
||||
configFactory.waitUntilGroupConfigsPushed(groupId)
|
||||
}
|
||||
|
||||
pollerFactory.pollerFor(groupId)?.stop()
|
||||
|
||||
if (deleteOnLeave) {
|
||||
storage.getThreadId(Address.fromSerialized(group.hexString))
|
||||
storage.getThreadId(Address.fromSerialized(groupId.hexString))
|
||||
?.let(storage::deleteConversation)
|
||||
configFactory.removeGroup(group)
|
||||
configFactory.removeGroup(groupId)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import org.session.libsession.snode.utilities.await
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.getClosedGroup
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.AccountId
|
||||
import org.thoughtcrime.securesms.database.DatabaseContentProviders
|
||||
@ -59,7 +60,7 @@ interface ConversationRepository {
|
||||
fun deleteMessages(messages: Set<MessageRecord>, threadId: Long)
|
||||
fun deleteAllLocalMessagesInThreadFromSenderOfMessage(messageRecord: MessageRecord)
|
||||
fun setApproved(recipient: Recipient, isApproved: Boolean)
|
||||
fun isKicked(recipient: Recipient): Boolean
|
||||
fun isGroupReadOnly(recipient: Recipient): Boolean
|
||||
|
||||
suspend fun deleteCommunityMessagesRemotely(threadId: Long, messages: Set<MessageRecord>)
|
||||
suspend fun delete1on1MessagesRemotely(
|
||||
@ -170,15 +171,16 @@ class DefaultConversationRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun isKicked(recipient: Recipient): Boolean {
|
||||
// For now, we only know care we are kicked for a groups v2 recipient
|
||||
override fun isGroupReadOnly(recipient: Recipient): Boolean {
|
||||
// We only care about group v2 recipient
|
||||
if (!recipient.isClosedGroupV2Recipient) {
|
||||
return false
|
||||
}
|
||||
|
||||
val groupId = recipient.address.serialize()
|
||||
return configFactory.withUserConfigs {
|
||||
it.userGroups.getClosedGroup(recipient.address.serialize())?.kicked == true
|
||||
}
|
||||
it.userGroups.getClosedGroup(groupId)?.kicked == true
|
||||
} || configFactory.withGroupConfigs(AccountId(groupId)) { it.groupInfo.isDestroyed() }
|
||||
}
|
||||
|
||||
// This assumes that recipient.isContactRecipient is true
|
||||
|
@ -44,7 +44,7 @@ interface GroupManagerV2 {
|
||||
|
||||
suspend fun handleMemberLeft(message: GroupUpdated, group: AccountId)
|
||||
|
||||
suspend fun leaveGroup(group: AccountId, deleteOnLeave: Boolean)
|
||||
suspend fun leaveGroup(groupId: AccountId, deleteOnLeave: Boolean)
|
||||
|
||||
suspend fun promoteMember(group: AccountId, members: List<AccountId>)
|
||||
|
||||
|
@ -120,7 +120,6 @@ class JobQueue : JobDelegate {
|
||||
is NotifyPNServerJob,
|
||||
is AttachmentUploadJob,
|
||||
is GroupLeavingJob,
|
||||
is LibSessionGroupLeavingJob,
|
||||
is MessageSendJob -> {
|
||||
txQueue.send(job)
|
||||
}
|
||||
@ -226,7 +225,6 @@ class JobQueue : JobDelegate {
|
||||
RetrieveProfileAvatarJob.KEY,
|
||||
GroupLeavingJob.KEY,
|
||||
InviteContactsJob.KEY,
|
||||
LibSessionGroupLeavingJob.KEY
|
||||
)
|
||||
allJobTypes.forEach { type ->
|
||||
resumePendingJobs(type)
|
||||
|
@ -1,67 +0,0 @@
|
||||
package org.session.libsession.messaging.jobs
|
||||
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
import org.session.libsession.messaging.utilities.UpdateMessageData
|
||||
import org.session.libsignal.utilities.AccountId
|
||||
|
||||
class LibSessionGroupLeavingJob(val accountId: AccountId, val deleteOnLeave: Boolean): Job {
|
||||
|
||||
|
||||
override var delegate: JobDelegate? = null
|
||||
override var id: String? = null
|
||||
override var failureCount: Int = 0
|
||||
override val maxFailureCount: Int = 4
|
||||
|
||||
override suspend fun execute(dispatcherName: String) {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
// start leaving
|
||||
// create message ID with leaving state
|
||||
val messageId = storage.insertGroupInfoLeaving(accountId) ?: run {
|
||||
delegate?.handleJobFailedPermanently(
|
||||
this,
|
||||
dispatcherName,
|
||||
Exception("Couldn't insert GroupInfoLeaving message in leaving group job")
|
||||
)
|
||||
return
|
||||
}
|
||||
// do actual group leave request
|
||||
|
||||
// on success
|
||||
val leaveGroup = kotlin.runCatching {
|
||||
MessagingModuleConfiguration.shared.groupManagerV2.leaveGroup(accountId, deleteOnLeave)
|
||||
}
|
||||
|
||||
if (leaveGroup.isSuccess) {
|
||||
// message is already deleted, succeed
|
||||
delegate?.handleJobSucceeded(this, dispatcherName)
|
||||
} else {
|
||||
// Error leaving group, update the info message
|
||||
storage.updateGroupInfoChange(messageId, UpdateMessageData.Kind.GroupErrorQuit)
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(): Data =
|
||||
Data.Builder()
|
||||
.putString(SESSION_ID_KEY, accountId.hexString)
|
||||
.putBoolean(DELETE_ON_LEAVE_KEY, deleteOnLeave)
|
||||
.build()
|
||||
|
||||
class Factory : Job.Factory<LibSessionGroupLeavingJob> {
|
||||
override fun create(data: Data): LibSessionGroupLeavingJob {
|
||||
return LibSessionGroupLeavingJob(
|
||||
AccountId(data.getString(SESSION_ID_KEY)),
|
||||
data.getBoolean(DELETE_ON_LEAVE_KEY)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
companion object {
|
||||
const val KEY = "LibSessionGroupLeavingJob"
|
||||
private const val SESSION_ID_KEY = "SessionId"
|
||||
private const val DELETE_ON_LEAVE_KEY = "DeleteOnLeave"
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user