mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 11:05:25 +00:00
Fixed crashes leaving un-polled groups
This commit is contained in:
parent
71009c373b
commit
962df473b6
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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,31 +47,44 @@ 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>()
|
||||||
|
.filter { it.groupId == groupId }
|
||||||
|
.onStart { emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId)) },
|
||||||
|
memberPendingState
|
||||||
|
) { _, pending ->
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
val currentUserId = checkNotNull(storage.getUserPublicKey()) {
|
val currentUserId = checkNotNull(storage.getUserPublicKey()) {
|
||||||
"User public key is null"
|
"User public key is null"
|
||||||
}
|
}
|
||||||
|
|
||||||
val displayInfo = storage.getClosedGroupDisplayInfo(groupSessionId)
|
val displayInfo = storage.getClosedGroupDisplayInfo(groupId.hexString)
|
||||||
?: return@withContext null
|
?: return@withContext null
|
||||||
|
|
||||||
val members = storage.getMembers(groupSessionId)
|
val members = storage.getMembers(groupId.hexString)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { !it.removed }
|
.filter { !it.removed }
|
||||||
.mapTo(mutableListOf()) { member ->
|
.mapTo(arrayListOf()) { member ->
|
||||||
createGroupMember(
|
createGroupMember(
|
||||||
member = member,
|
member = member,
|
||||||
myAccountId = currentUserId,
|
myAccountId = currentUserId,
|
||||||
amIAdmin = displayInfo.isUserAdmin,
|
amIAdmin = displayInfo.isUserAdmin,
|
||||||
|
pendingState = pending[AccountId(member.sessionId)]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 {
|
||||||
|
try {
|
||||||
|
// Mark the contacts as pending
|
||||||
|
memberPendingState.update { states ->
|
||||||
|
states + contacts.associate { AccountId(it.accountID) to MemberPendingState.Inviting }
|
||||||
|
}
|
||||||
|
|
||||||
groupManager.inviteMembers(
|
groupManager.inviteMembers(
|
||||||
AccountId(hexString = groupSessionId),
|
groupId,
|
||||||
contacts.map { AccountId(it.accountID) },
|
contacts.map { AccountId(it.accountID) },
|
||||||
shareHistory = true
|
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,
|
||||||
|
@ -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(
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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?
|
||||||
|
@ -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()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user