From 4caa7681f8ff1e8d073211171ba1d983fc2ef767 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:09:24 +1100 Subject: [PATCH] feat: add in group invited tracking and group message requests in a basic way --- .../conversation/v2/ConversationActivityV2.kt | 10 ++++-- .../conversation/v2/ConversationViewModel.kt | 2 +- .../securesms/database/Storage.kt | 32 ++++++++++++++++--- .../securesms/database/ThreadDatabase.java | 29 ++--------------- .../securesms/dependencies/PollerFactory.kt | 5 +-- .../securesms/home/HomeActivity.kt | 7 ++-- .../securesms/home/HomeAdapter.kt | 1 + .../repository/ConversationRepository.kt | 20 +++++++----- app/src/main/res/values/strings.xml | 1 + libsession-util/src/main/cpp/user_groups.h | 7 ++-- .../libsession_util/util/GroupInfo.kt | 1 + .../libsession/database/StorageProtocol.kt | 1 + 12 files changed, 67 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index a22a0bcb9..4730f147a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -831,8 +831,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun setUpMessageRequestsBar() { + val recipient = viewModel.recipient ?: return binding?.inputBar?.showMediaControls = !isOutgoingMessageRequestThread() binding?.messageRequestBar?.isVisible = isIncomingMessageRequestThread() + binding?.sendAcceptsTextView?.setText( + if (recipient.isClosedGroupRecipient) R.string.message_requests_send_group_notice + else R.string.message_requests_send_notice + ) binding?.acceptMessageRequestButton?.setOnClickListener { acceptMessageRequest() } @@ -866,11 +871,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private fun isIncomingMessageRequestThread(): Boolean { val recipient = viewModel.recipient ?: return false - return !recipient.isGroupRecipient && + return !recipient.isLegacyClosedGroupRecipient && + !recipient.isOpenGroupRecipient && !recipient.isApproved && !recipient.isLocalNumber && !threadDb.getLastSeenAndHasSent(viewModel.threadId).second() && - threadDb.getMessageCount(viewModel.threadId) > 0 + (threadDb.getMessageCount(viewModel.threadId) > 0 || recipient.isClosedGroupRecipient) } override fun inputBarEditTextContentChanged(newContent: CharSequence) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 5bceb6977..2c01f7365 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -79,7 +79,7 @@ class ConversationViewModel( val isMessageRequestThread : Boolean get() { val recipient = recipient ?: return false - return !recipient.isLocalNumber && !recipient.isGroupRecipient && !recipient.isApproved + return !recipient.isLocalNumber && !recipient.isLegacyClosedGroupRecipient && !recipient.isOpenGroupRecipient && !recipient.isApproved } val canReactToMessages: Boolean diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index f8bcd1cf3..42494bca0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -649,10 +649,12 @@ open class Storage( for (closedGroup in newClosedGroups) { val recipient = Recipient.from(context, Address.fromSerialized(closedGroup.groupSessionId.hexString()), false) setRecipientApprovedMe(recipient, true) - setRecipientApproved(recipient, true) + setRecipientApproved(recipient, !closedGroup.invited) val threadId = getOrCreateThreadIdFor(recipient.address) setPinned(threadId, closedGroup.priority == PRIORITY_PINNED) - pollerFactory.pollerFor(closedGroup.groupSessionId)?.start() + if (!closedGroup.invited) { + pollerFactory.pollerFor(closedGroup.groupSessionId)?.start() + } } for (group in lgc) { @@ -1219,6 +1221,24 @@ open class Storage( override fun getMembers(groupPublicKey: String): List = configFactory.getGroupMemberConfig(SessionId.from(groupPublicKey))?.use { it.all() }?.toList() ?: emptyList() + override fun respondToClosedGroupInvitation(groupRecipient: Recipient, approved: Boolean) { + val groups = configFactory.userGroups ?: return + val groupSessionId = SessionId.from(groupRecipient.address.serialize()) + val closedGroupInfo = groups.getClosedGroup(groupSessionId.hexString())?.copy( + invited = false + ) ?: return + groups.set(closedGroupInfo) + ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context) + pollerFactory.pollerFor(groupSessionId)?.start() + val inviteResponse = GroupUpdateInviteResponseMessage.newBuilder() + .setIsApproved(true) + val responseData = GroupUpdateMessage.newBuilder() + .setInviteResponse(inviteResponse) + val responseMessage = GroupUpdated(responseData.build()) + // this will fail the first couple of times :) + MessageSender.send(responseMessage, fromSerialized(groupSessionId.hexString())) + } + override fun addClosedGroupInvite( groupId: SessionId, name: String, @@ -1228,15 +1248,19 @@ open class Storage( val recipient = Recipient.from(context, fromSerialized(groupId.hexString()), false) val profileManager = SSKEnvironment.shared.profileManager val groups = configFactory.userGroups ?: return + val shouldAutoApprove = false //TESTING// getRecipientApproved(fromSerialized(invitingAdmin.hexString())) val closedGroupInfo = GroupInfo.ClosedGroupInfo( - groupId, byteArrayOf(), authData, PRIORITY_VISIBLE + groupId, + byteArrayOf(), + authData, + PRIORITY_VISIBLE, + !shouldAutoApprove, ) groups.set(closedGroupInfo) configFactory.persist(groups, SnodeAPI.nowWithOffset) profileManager.setName(context, recipient, name) getOrCreateThreadIdFor(recipient.address) setRecipientApprovedMe(recipient, true) - val shouldAutoApprove = false //TESTING// getRecipientApproved(fromSerialized(invitingAdmin.hexString())) setRecipientApproved(recipient, shouldAutoApprove) if (shouldAutoApprove) { pollerFactory.pollerFor(groupId)?.start() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 6bd010ec3..7b5db7d21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -439,32 +439,6 @@ public class ThreadDatabase extends Database { return db.rawQuery(query, null); } - public int getUnapprovedConversationCount() { - SQLiteDatabase db = databaseHelper.getReadableDatabase(); - Cursor cursor = null; - - try { - String query = "SELECT COUNT (*) FROM " + TABLE_NAME + - " LEFT OUTER JOIN " + RecipientDatabase.TABLE_NAME + - " ON " + TABLE_NAME + "." + ADDRESS + " = " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.ADDRESS + - " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + - " ON " + TABLE_NAME + "." + ADDRESS + " = " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + - " WHERE " + MESSAGE_COUNT + " != 0 AND " + ARCHIVED + " = 0 AND " + HAS_SENT + " = 0 AND " + - RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = 0 AND " + - RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = 0 AND " + - GroupDatabase.TABLE_NAME + "." + GROUP_ID + " IS NULL"; - cursor = db.rawQuery(query, null); - - if (cursor != null && cursor.moveToFirst()) - return cursor.getInt(0); - } finally { - if (cursor != null) - cursor.close(); - } - - return 0; - } - public long getLatestUnapprovedConversationTimestamp() { SQLiteDatabase db = databaseHelper.getReadableDatabase(); Cursor cursor = null; @@ -510,7 +484,8 @@ public class ThreadDatabase extends Database { } public Cursor getUnapprovedConversationList() { - String where = MESSAGE_COUNT + " != 0 AND " + ARCHIVED + " = 0 AND " + HAS_SENT + " = 0 AND " + + String where = "("+MESSAGE_COUNT + " != 0 OR "+ThreadDatabase.TABLE_NAME+"."+ThreadDatabase.ADDRESS+" LIKE '"+IdPrefix.GROUP.getValue()+"%')" + + " AND " + ARCHIVED + " = 0 AND " + HAS_SENT + " = 0 AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.APPROVED + " = 0 AND " + RecipientDatabase.TABLE_NAME + "." + RecipientDatabase.BLOCK + " = 0 AND " + GroupDatabase.TABLE_NAME + "." + GROUP_ID + " IS NULL"; diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/PollerFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PollerFactory.kt index 821f33cad..a0059cfc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/PollerFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/PollerFactory.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.plus +import network.loki.messenger.libsession_util.util.GroupInfo import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPoller import org.session.libsignal.utilities.SessionId import java.util.concurrent.ConcurrentHashMap @@ -24,7 +25,7 @@ class PollerFactory(private val scope: CoroutineScope, } fun startAll() { - configFactory.userGroups?.allClosedGroupInfo()?.forEach { + configFactory.userGroups?.allClosedGroupInfo()?.filterNot(GroupInfo.ClosedGroupInfo::invited)?.forEach { pollerFor(it.groupSessionId)?.start() } } @@ -36,7 +37,7 @@ class PollerFactory(private val scope: CoroutineScope, } fun updatePollers() { - val currentGroups = configFactory.userGroups?.allClosedGroupInfo() ?: return + val currentGroups = configFactory.userGroups?.allClosedGroupInfo()?.filterNot(GroupInfo.ClosedGroupInfo::invited) ?: return val toRemove = pollers.filter { (id, _) -> id !in currentGroups.map { it.groupSessionId } } toRemove.forEach { (id, _) -> pollers.remove(id)?.stop() diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 0d0b339c6..68255bffd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -334,9 +334,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), } private fun setupMessageRequestsBanner() { - val messageRequestCount = threadDb.unapprovedConversationCount + val messageRequestCount = threadDb.unapprovedConversationList.use { it.count } // Set up message requests - if (messageRequestCount > 0 && !textSecurePreferences.hasHiddenMessageRequests()) { + if (messageRequestCount > 0 && !textSecurePreferences.hasHiddenMessageRequests() && messageRequestCount != homeAdapter.requestCount) { with(ViewMessageRequestBannerBinding.inflate(layoutInflater)) { unreadCountTextView.text = messageRequestCount.toString() timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString( @@ -352,13 +352,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), if (hadHeader) homeAdapter.notifyItemChanged(0) else homeAdapter.notifyItemInserted(0) } - } else { + } else if (messageRequestCount == 0) { val hadHeader = homeAdapter.hasHeaderView() homeAdapter.header = null if (hadHeader) { homeAdapter.notifyItemRemoved(0) } } + homeAdapter.requestCount = messageRequestCount } private fun updateLegacyConfigView() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt index eaf242aae..b47f77343 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -38,6 +38,7 @@ class HomeAdapter( } fun hasHeaderView(): Boolean = header != null + var requestCount = 0 private val headerCount: Int get() = if (header == null) 0 else 1 diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 2c0b39d69..6d25f2ad4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -306,14 +306,18 @@ class DefaultConversationRepository @Inject constructor( override suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf = suspendCoroutine { continuation -> storage.setRecipientApproved(recipient, true) - val message = MessageRequestResponse(true) - MessageSender.send(message, Destination.from(recipient.address), isSyncMessage = recipient.isLocalNumber) - .success { - threadDb.setHasSent(threadId, true) - continuation.resume(ResultOf.Success(Unit)) - }.fail { error -> - continuation.resumeWithException(error) - } + if (recipient.isClosedGroupRecipient) { + storage.respondToClosedGroupInvitation(recipient, true) + } else { + val message = MessageRequestResponse(true) + MessageSender.send(message, Destination.from(recipient.address), isSyncMessage = recipient.isLocalNumber) + .success { + threadDb.setHasSent(threadId, true) + continuation.resume(ResultOf.Success(Unit)) + }.fail { error -> + continuation.resumeWithException(error) + } + } } override fun declineMessageRequest(threadId: Long) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1139fea25..e1d89b3ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -934,6 +934,7 @@ Messages Message Requests Sending a message to this user will automatically accept their message request and reveal your Session ID. + Sending a message to this group will automatically accept the group invite. Accept Decline Clear All diff --git a/libsession-util/src/main/cpp/user_groups.h b/libsession-util/src/main/cpp/user_groups.h index 22d7643ad..296ef6d67 100644 --- a/libsession-util/src/main/cpp/user_groups.h +++ b/libsession-util/src/main/cpp/user_groups.h @@ -132,9 +132,10 @@ inline jobject serialize_closed_group_info(JNIEnv* env, session::config::group_i jbyteArray auth_bytes = util::bytes_from_ustring(env, info.auth_data); jclass group_info_class = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$ClosedGroupInfo"); - jmethodID constructor = env->GetMethodID(group_info_class, "", "(Lorg/session/libsignal/utilities/SessionId;[B[BJ)V"); + jmethodID constructor = env->GetMethodID(group_info_class, "", + "(Lorg/session/libsignal/utilities/SessionId;[B[BJZ)V"); jobject return_object = env->NewObject(group_info_class,constructor, - session_id, admin_bytes, auth_bytes, (jlong)info.priority); + session_id, admin_bytes, auth_bytes, (jlong)info.priority, info.invited); return return_object; } @@ -144,6 +145,7 @@ inline session::config::group_info deserialize_closed_group_info(JNIEnv* env, jo jfieldID secret_field = env->GetFieldID(closed_group_class, "adminKey", "[B"); jfieldID auth_field = env->GetFieldID(closed_group_class, "authData", "[B"); jfieldID priority_field = env->GetFieldID(closed_group_class, "priority", "J"); + jfieldID invited_field = env->GetFieldID(closed_group_class, "invited", "Z"); jobject id_jobject = env->GetObjectField(info_serialized, id_field); @@ -158,6 +160,7 @@ inline session::config::group_info deserialize_closed_group_info(JNIEnv* env, jo group_info.auth_data = auth_bytes; group_info.secretkey = secret_bytes; group_info.priority = env->GetLongField(info_serialized, priority_field); + group_info.invited = env->GetBooleanField(info_serialized, invited_field); return group_info; } diff --git a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/GroupInfo.kt b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/GroupInfo.kt index e72876290..063fc3b88 100644 --- a/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/GroupInfo.kt +++ b/libsession-util/src/main/java/network/loki/messenger/libsession_util/util/GroupInfo.kt @@ -11,6 +11,7 @@ sealed class GroupInfo { val adminKey: ByteArray, val authData: ByteArray, val priority: Long, + val invited: Boolean, ): GroupInfo() { companion object { diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 23ee948d6..6e7e97a29 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -163,6 +163,7 @@ interface StorageProtocol { // Closed Groups fun createNewGroup(groupName: String, groupDescription: String, members: Set): Optional fun getMembers(groupPublicKey: String): List + fun respondToClosedGroupInvitation(groupRecipient: Recipient, approved: Boolean) fun addClosedGroupInvite(groupId: SessionId, name: String, authData: ByteArray, invitingAdmin: SessionId) fun setGroupInviteCompleteIfNeeded(approved: Boolean, invitee: String, closedGroup: SessionId) fun getLibSessionClosedGroup(groupSessionId: String): GroupInfo.ClosedGroupInfo?