Fixed crashes leaving un-polled groups

This commit is contained in:
SessionHero01 2024-10-08 10:21:59 +11:00
parent 71009c373b
commit 962df473b6
No known key found for this signature in database
11 changed files with 123 additions and 70 deletions

View File

@ -360,7 +360,7 @@ object ConversationMenuHelper {
val group = configFactory.withUserConfigs { it.userGroups.getClosedGroup(accountId.hexString) } ?: return val group = configFactory.withUserConfigs { it.userGroups.getClosedGroup(accountId.hexString) } ?: return
val name = configFactory.withGroupConfigs(accountId) { val name = configFactory.withGroupConfigs(accountId) {
it.groupInfo.getName() it.groupInfo.getName()
} } ?: group.name
confirmAndLeaveClosedGroup( confirmAndLeaveClosedGroup(
context = context, context = context,
@ -412,6 +412,7 @@ object ConversationMenuHelper {
doLeave() doLeave()
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Conversation", "Error leaving group", e)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onLeaveFailed() onLeaveFailed()
} }

View File

@ -62,6 +62,7 @@ import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.ProfileKeyUtil import org.session.libsession.utilities.ProfileKeyUtil
import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.SSKEnvironment
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.getClosedGroup
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.recipients.Recipient.DisappearingState import org.session.libsession.utilities.recipients.Recipient.DisappearingState
import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.DjbECPublicKey
@ -1004,13 +1005,8 @@ open class Storage @Inject constructor(
it.groupMembers.all() it.groupMembers.all()
} }
override fun getLibSessionClosedGroup(groupAccountId: String): GroupInfo.ClosedGroupInfo? {
return configFactory.withUserConfigs { it.userGroups.getClosedGroup(groupAccountId) }
}
override fun getClosedGroupDisplayInfo(groupAccountId: String): GroupDisplayInfo? { override fun getClosedGroupDisplayInfo(groupAccountId: String): GroupDisplayInfo? {
val groupIsAdmin = getLibSessionClosedGroup(groupAccountId)?.hasAdminKey() ?: return null val groupIsAdmin = configFactory.getClosedGroup(AccountId(groupAccountId))?.hasAdminKey() ?: return null
return configFactory.withGroupConfigs(AccountId(groupAccountId)) { configs -> return configFactory.withGroupConfigs(AccountId(groupAccountId)) { configs ->
val info = configs.groupInfo val info = configs.groupInfo
@ -1031,8 +1027,9 @@ open class Storage @Inject constructor(
val sentTimestamp = message.sentTimestamp ?: clock.currentTimeMills() val sentTimestamp = message.sentTimestamp ?: clock.currentTimeMills()
val senderPublicKey = message.sender val senderPublicKey = message.sender
val groupName = configFactory.withGroupConfigs(closedGroup) { it.groupInfo.getName() } val groupName = configFactory.withGroupConfigs(closedGroup) { it.groupInfo.getName() }
?: configFactory.getClosedGroup(closedGroup)?.name
val updateData = UpdateMessageData.buildGroupUpdate(message, groupName) ?: return null val updateData = UpdateMessageData.buildGroupUpdate(message, groupName.orEmpty()) ?: return null
return insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup) return insertUpdateControlMessage(updateData, sentTimestamp, senderPublicKey, closedGroup)
} }

View File

@ -5,11 +5,10 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.utilities.AccountId
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.groups.compose.EditGroupScreen import org.thoughtcrime.securesms.groups.compose.EditGroupScreen
import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme import org.thoughtcrime.securesms.ui.theme.SessionMaterialTheme
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class EditGroupActivity: PassphraseRequiredActionBarActivity() { class EditGroupActivity: PassphraseRequiredActionBarActivity() {
@ -28,7 +27,7 @@ class EditGroupActivity: PassphraseRequiredActionBarActivity() {
setContent { setContent {
SessionMaterialTheme { SessionMaterialTheme {
EditGroupScreen( EditGroupScreen(
groupSessionId = intent.getStringExtra(EXTRA_GROUP_ID)!!, groupId = AccountId(intent.getStringExtra(EXTRA_GROUP_ID)!!),
onFinish = this::finish onFinish = this::finish
) )
} }

View File

@ -1,30 +1,36 @@
package org.thoughtcrime.securesms.groups package org.thoughtcrime.securesms.groups
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R
import network.loki.messenger.libsession_util.util.GroupDisplayInfo import network.loki.messenger.libsession_util.util.GroupDisplayInfo
import network.loki.messenger.libsession_util.util.GroupMember import network.loki.messenger.libsession_util.util.GroupMember
import org.session.libsession.database.StorageProtocol import org.session.libsession.database.StorageProtocol
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.messaging.groups.GroupManagerV2
import org.session.libsession.messaging.jobs.InviteContactsJob import org.session.libsession.utilities.ConfigUpdateNotification
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
@ -32,7 +38,8 @@ const val MAX_GROUP_NAME_LENGTH = 100
@HiltViewModel(assistedFactory = EditGroupViewModel.Factory::class) @HiltViewModel(assistedFactory = EditGroupViewModel.Factory::class)
class EditGroupViewModel @AssistedInject constructor( class EditGroupViewModel @AssistedInject constructor(
@Assisted private val groupSessionId: String, @Assisted private val groupId: AccountId,
@ApplicationContext private val context: Context,
private val storage: StorageProtocol, private val storage: StorageProtocol,
configFactory: ConfigFactory, configFactory: ConfigFactory,
private val groupManager: GroupManagerV2, private val groupManager: GroupManagerV2,
@ -40,39 +47,52 @@ class EditGroupViewModel @AssistedInject constructor(
// Input/Output state // Input/Output state
private val mutableEditingName = MutableStateFlow<String?>(null) private val mutableEditingName = MutableStateFlow<String?>(null)
// Input: invite/promote member's intermediate states. This is needed because we don't have
// a state that we can map into in the config system. The config system only provides "sent", "failed", etc.
// The intermediate states are needed to show the user that the operation is in progress, and the
// states are limited to the view model (i.e. lost if the user navigates away). This is a trade-off
// between the complexity of the config system and the user experience.
private val memberPendingState = MutableStateFlow<Map<AccountId, MemberPendingState>>(emptyMap())
// Output: The name of the group being edited. Null if it's not in edit mode, not to be confused // Output: The name of the group being edited. Null if it's not in edit mode, not to be confused
// with empty string, where it's a valid editing state. // with empty string, where it's a valid editing state.
val editingName: StateFlow<String?> get() = mutableEditingName val editingName: StateFlow<String?> get() = mutableEditingName
// Output: the source-of-truth group information. Other states are derived from this. // Output: the source-of-truth group information. Other states are derived from this.
@OptIn(ExperimentalCoroutinesApi::class)
private val groupInfo: StateFlow<Pair<GroupDisplayInfo, List<GroupMemberState>>?> = private val groupInfo: StateFlow<Pair<GroupDisplayInfo, List<GroupMemberState>>?> =
(configFactory.configUpdateNotifications as Flow<Any>) combine(
.onStart { emit(Unit) } configFactory.configUpdateNotifications
.map { .filterIsInstance<ConfigUpdateNotification.GroupConfigsUpdated>()
withContext(Dispatchers.Default) { .filter { it.groupId == groupId }
val currentUserId = checkNotNull(storage.getUserPublicKey()) { .onStart { emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId)) },
"User public key is null" memberPendingState
) { _, pending ->
withContext(Dispatchers.Default) {
val currentUserId = checkNotNull(storage.getUserPublicKey()) {
"User public key is null"
}
val displayInfo = storage.getClosedGroupDisplayInfo(groupId.hexString)
?: return@withContext null
val members = storage.getMembers(groupId.hexString)
.asSequence()
.filter { !it.removed }
.mapTo(arrayListOf()) { member ->
createGroupMember(
member = member,
myAccountId = currentUserId,
amIAdmin = displayInfo.isUserAdmin,
pendingState = pending[AccountId(member.sessionId)]
)
} }
val displayInfo = storage.getClosedGroupDisplayInfo(groupSessionId) sortMembers(members, currentUserId)
?: return@withContext null
val members = storage.getMembers(groupSessionId) displayInfo to members
.asSequence() }
.filter { !it.removed } }.stateIn(viewModelScope, SharingStarted.Eagerly, null)
.mapTo(mutableListOf()) { member ->
createGroupMember(
member = member,
myAccountId = currentUserId,
amIAdmin = displayInfo.isUserAdmin,
)
}
sortMembers(members, currentUserId)
displayInfo to members
}
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
// Output: whether the group name can be edited. This is true if the group is loaded successfully. // Output: whether the group name can be edited. This is true if the group is loaded successfully.
val canEditGroupName: StateFlow<Boolean> = groupInfo val canEditGroupName: StateFlow<Boolean> = groupInfo
@ -110,6 +130,7 @@ class EditGroupViewModel @AssistedInject constructor(
member: GroupMember, member: GroupMember,
myAccountId: String, myAccountId: String,
amIAdmin: Boolean, amIAdmin: Boolean,
pendingState: MemberPendingState?
): GroupMemberState { ): GroupMemberState {
var status = "" var status = ""
var highlightStatus = false var highlightStatus = false
@ -117,24 +138,32 @@ class EditGroupViewModel @AssistedInject constructor(
when { when {
member.sessionId == myAccountId -> { member.sessionId == myAccountId -> {
name = "You" name = context.getString(R.string.you)
}
pendingState == MemberPendingState.Inviting -> {
status = context.getString(R.string.groupInviteSending)
}
pendingState == MemberPendingState.Promoting -> {
status = context.getString(R.string.groupInviteSending)
} }
member.promotionPending -> { member.promotionPending -> {
status = "Promotion sent" status = context.getString(R.string.adminPromotionSent)
} }
member.invitePending -> { member.invitePending -> {
status = "Invite Sent" status = context.getString(R.string.groupInviteSent)
} }
member.inviteFailed -> { member.inviteFailed -> {
status = "Invite Failed" status = context.getString(R.string.groupInviteFailed)
highlightStatus = true highlightStatus = true
} }
member.promotionFailed -> { member.promotionFailed -> {
status = "Promotion Failed" status = context.getString(R.string.adminPromotionFailed)
highlightStatus = true highlightStatus = true
} }
} }
@ -145,7 +174,8 @@ class EditGroupViewModel @AssistedInject constructor(
canRemove = amIAdmin && member.sessionId != myAccountId && !member.isAdminOrBeingPromoted, canRemove = amIAdmin && member.sessionId != myAccountId && !member.isAdminOrBeingPromoted,
canPromote = amIAdmin && member.sessionId != myAccountId && !member.isAdminOrBeingPromoted, canPromote = amIAdmin && member.sessionId != myAccountId && !member.isAdminOrBeingPromoted,
canResendPromotion = amIAdmin && member.sessionId != myAccountId && member.promotionFailed, canResendPromotion = amIAdmin && member.sessionId != myAccountId && member.promotionFailed,
canResendInvite = amIAdmin && member.sessionId != myAccountId && member.inviteFailed, canResendInvite = amIAdmin && member.sessionId != myAccountId &&
(member.inviteFailed || member.invitePending),
status = status, status = status,
highlightStatus = highlightStatus highlightStatus = highlightStatus
) )
@ -167,30 +197,46 @@ class EditGroupViewModel @AssistedInject constructor(
fun onContactSelected(contacts: Set<Contact>) { fun onContactSelected(contacts: Set<Contact>) {
performGroupOperation { performGroupOperation {
groupManager.inviteMembers( try {
AccountId(hexString = groupSessionId), // Mark the contacts as pending
contacts.map { AccountId(it.accountID) }, memberPendingState.update { states ->
shareHistory = true states + contacts.associate { AccountId(it.accountID) to MemberPendingState.Inviting }
) }
groupManager.inviteMembers(
groupId,
contacts.map { AccountId(it.accountID) },
shareHistory = false
)
} finally {
// Remove pending state (so the real state will be revealed)
memberPendingState.update { states -> states - contacts.mapTo(hashSetOf()) { AccountId(it.accountID) } }
}
} }
} }
fun onResendInviteClicked(contactSessionId: String) { fun onResendInviteClicked(contactSessionId: String) {
viewModelScope.launch(Dispatchers.Default) { onContactSelected(setOf(Contact(contactSessionId)))
JobQueue.shared.add(InviteContactsJob(groupSessionId, arrayOf(contactSessionId)))
}
} }
fun onPromoteContact(memberSessionId: String) { fun onPromoteContact(memberSessionId: String) {
performGroupOperation { performGroupOperation {
groupManager.promoteMember(AccountId(groupSessionId), listOf(AccountId(memberSessionId))) try {
memberPendingState.update { states ->
states + (AccountId(memberSessionId) to MemberPendingState.Promoting)
}
groupManager.promoteMember(groupId, listOf(AccountId(memberSessionId)))
} finally {
memberPendingState.update { states -> states - AccountId(memberSessionId) }
}
} }
} }
fun onRemoveContact(contactSessionId: String, removeMessages: Boolean) { fun onRemoveContact(contactSessionId: String, removeMessages: Boolean) {
performGroupOperation { performGroupOperation {
groupManager.removeMembers( groupManager.removeMembers(
groupAccountId = AccountId(groupSessionId), groupAccountId = groupId,
removedMembers = listOf(AccountId(contactSessionId)), removedMembers = listOf(AccountId(contactSessionId)),
removeMessages = removeMessages removeMessages = removeMessages
) )
@ -223,7 +269,7 @@ class EditGroupViewModel @AssistedInject constructor(
performGroupOperation { performGroupOperation {
if (!newName.isNullOrBlank()) { if (!newName.isNullOrBlank()) {
groupManager.setName(AccountId(groupSessionId), newName) groupManager.setName(groupId, newName)
mutableEditingName.value = null mutableEditingName.value = null
} }
} }
@ -238,12 +284,15 @@ class EditGroupViewModel @AssistedInject constructor(
* *
* This is a helper function that encapsulates the common error handling and progress tracking. * This is a helper function that encapsulates the common error handling and progress tracking.
*/ */
private fun performGroupOperation(operation: suspend () -> Unit) { private fun performGroupOperation(
genericErrorMessage: (() -> String?)? = null,
operation: suspend () -> Unit) {
viewModelScope.launch { viewModelScope.launch {
mutableInProgress.value = true mutableInProgress.value = true
// We need to use GlobalScope here because we don't want // We need to use GlobalScope here because we don't want
// any group operation to be cancelled when the view model is cleared. // any group operation to be cancelled when the view model is cleared.
@Suppress("OPT_IN_USAGE")
val task = GlobalScope.async { val task = GlobalScope.async {
operation() operation()
} }
@ -251,7 +300,8 @@ class EditGroupViewModel @AssistedInject constructor(
try { try {
task.await() task.await()
} catch (e: Exception) { } catch (e: Exception) {
mutableError.value = e.localizedMessage.orEmpty() mutableError.value = genericErrorMessage?.invoke()
?: context.getString(R.string.errorUnknown)
} finally { } finally {
mutableInProgress.value = false mutableInProgress.value = false
} }
@ -260,10 +310,15 @@ class EditGroupViewModel @AssistedInject constructor(
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(groupSessionId: String): EditGroupViewModel fun create(groupId: AccountId): EditGroupViewModel
} }
} }
private enum class MemberPendingState {
Inviting,
Promoting,
}
data class GroupMemberState( data class GroupMemberState(
val accountId: String, val accountId: String,
val name: String, val name: String,

View File

@ -741,7 +741,7 @@ class GroupManagerV2Impl @Inject constructor(
// read the group name anymore. // read the group name anymore.
val groupName = configFactory.withGroupConfigs(groupId) { configs -> val groupName = configFactory.withGroupConfigs(groupId) { configs ->
configs.groupInfo.getName() configs.groupInfo.getName()
} } ?: group.name
configFactory.withMutableUserConfigs { configFactory.withMutableUserConfigs {
it.userGroups.set( it.userGroups.set(

View File

@ -44,6 +44,7 @@ import kotlinx.serialization.Serializable
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
import org.session.libsignal.utilities.AccountId
import org.thoughtcrime.securesms.groups.EditGroupViewModel import org.thoughtcrime.securesms.groups.EditGroupViewModel
import org.thoughtcrime.securesms.groups.GroupMemberState import org.thoughtcrime.securesms.groups.GroupMemberState
import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.ui.AlertDialog
@ -60,12 +61,12 @@ import org.thoughtcrime.securesms.ui.theme.bold
@Composable @Composable
fun EditGroupScreen( fun EditGroupScreen(
groupSessionId: String, groupId: AccountId,
onFinish: () -> Unit, onFinish: () -> Unit,
) { ) {
val navController = rememberNavController() val navController = rememberNavController()
val viewModel = hiltViewModel<EditGroupViewModel, EditGroupViewModel.Factory> { factory -> val viewModel = hiltViewModel<EditGroupViewModel, EditGroupViewModel.Factory> { factory ->
factory.create(groupSessionId) factory.create(groupId)
} }
NavHost(navController = navController, startDestination = RouteEditGroup) { NavHost(navController = navController, startDestination = RouteEditGroup) {

View File

@ -328,7 +328,7 @@ interface ReadableGroupInfoConfig: ReadableConfig {
fun getDeleteAttachmentsBefore(): Long? fun getDeleteAttachmentsBefore(): Long?
fun getDeleteBefore(): Long? fun getDeleteBefore(): Long?
fun getExpiryTimer(): Long fun getExpiryTimer(): Long
fun getName(): String fun getName(): String?
fun getCreated(): Long? fun getCreated(): Long?
fun getProfilePic(): UserPic fun getProfilePic(): UserPic
fun isDestroyed(): Boolean fun isDestroyed(): Boolean
@ -367,7 +367,7 @@ class GroupInfoConfig private constructor(pointer: Long): ConfigBase(pointer), M
external override fun getDeleteAttachmentsBefore(): Long? external override fun getDeleteAttachmentsBefore(): Long?
external override fun getDeleteBefore(): Long? external override fun getDeleteBefore(): Long?
external override fun getExpiryTimer(): Long external override fun getExpiryTimer(): Long
external override fun getName(): String external override fun getName(): String?
external override fun getProfilePic(): UserPic external override fun getProfilePic(): UserPic
external override fun isDestroyed(): Boolean external override fun isDestroyed(): Boolean
external override fun setCreated(createdAt: Long) external override fun setCreated(createdAt: Long)

View File

@ -170,7 +170,6 @@ interface StorageProtocol {
// Closed Groups // Closed Groups
fun getMembers(groupPublicKey: String): List<LibSessionGroupMember> fun getMembers(groupPublicKey: String): List<LibSessionGroupMember>
fun getLibSessionClosedGroup(groupAccountId: String): GroupInfo.ClosedGroupInfo?
fun getClosedGroupDisplayInfo(groupAccountId: String): GroupDisplayInfo? fun getClosedGroupDisplayInfo(groupAccountId: String): GroupDisplayInfo?
fun insertGroupInfoChange(message: GroupUpdated, closedGroup: AccountId): Long? fun insertGroupInfoChange(message: GroupUpdated, closedGroup: AccountId): Long?
fun insertGroupInfoLeaving(closedGroup: AccountId): Long? fun insertGroupInfoLeaving(closedGroup: AccountId): Long?

View File

@ -20,6 +20,7 @@ import org.session.libsession.snode.utilities.await
import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY
import org.session.libsession.utilities.getClosedGroup
import org.session.libsession.utilities.truncateIdForDisplay import org.session.libsession.utilities.truncateIdForDisplay
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteMessage import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteMessage
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage
@ -100,6 +101,7 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<
} }
val groupName = configs.withGroupConfigs(sessionId) { it.groupInfo.getName() } val groupName = configs.withGroupConfigs(sessionId) { it.groupInfo.getName() }
?: configs.getClosedGroup(sessionId)?.name
val failures = results.filter { it.second.isFailure } val failures = results.filter { it.second.isFailure }
// if there are failed invites, display a message // if there are failed invites, display a message
@ -117,7 +119,7 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<
toaster.toast(R.string.groupInviteFailedUser, Toast.LENGTH_LONG, toaster.toast(R.string.groupInviteFailedUser, Toast.LENGTH_LONG,
mapOf( mapOf(
NAME_KEY to firstString, NAME_KEY to firstString,
GROUP_NAME_KEY to groupName GROUP_NAME_KEY to groupName.orEmpty()
) )
) )
} }
@ -134,7 +136,7 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<
mapOf( mapOf(
NAME_KEY to firstString, NAME_KEY to firstString,
OTHER_NAME_KEY to secondString, OTHER_NAME_KEY to secondString,
GROUP_NAME_KEY to groupName GROUP_NAME_KEY to groupName.orEmpty()
) )
) )
} }
@ -149,7 +151,7 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<
mapOf( mapOf(
NAME_KEY to firstString, NAME_KEY to firstString,
OTHER_NAME_KEY to remaining.toString(), OTHER_NAME_KEY to remaining.toString(),
GROUP_NAME_KEY to groupName GROUP_NAME_KEY to groupName.orEmpty()
) )
) )
} }

View File

@ -50,7 +50,6 @@ import org.session.libsignal.utilities.defaultRequiresAuth
import org.session.libsignal.utilities.hasNamespaces import org.session.libsignal.utilities.hasNamespaces
import org.session.libsignal.utilities.hexEncodedPublicKey import org.session.libsignal.utilities.hexEncodedPublicKey
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote

View File

@ -645,7 +645,7 @@ object SnodeAPI {
for ((req, resp) in batch.zip(responses.results)) { for ((req, resp) in batch.zip(responses.results)) {
val result = runCatching { val result = runCatching {
check(resp.code == 200) { check(resp.code == 200) {
"Error with code = ${resp.code}, msg = ${resp.body}" "Error calling \"${req.request.method}\" with code = ${resp.code}, msg = ${resp.body}"
} }
JsonUtil.fromJson(resp.body, req.responseType) JsonUtil.fromJson(resp.body, req.responseType)