feat: add in group invited tracking and group message requests in a basic way

This commit is contained in:
0x330a 2023-12-07 17:09:24 +11:00
parent 60f4b17b57
commit 4caa7681f8
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
12 changed files with 67 additions and 49 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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<LibSessionGroupMember> =
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()

View File

@ -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";

View File

@ -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()

View File

@ -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() {

View File

@ -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

View File

@ -306,14 +306,18 @@ class DefaultConversationRepository @Inject constructor(
override suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf<Unit> = 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) {

View File

@ -934,6 +934,7 @@
<string name="global_search_messages">Messages</string>
<string name="activity_message_requests_title">Message Requests</string>
<string name="message_requests_send_notice">Sending a message to this user will automatically accept their message request and reveal your Session ID.</string>
<string name="message_requests_send_group_notice">Sending a message to this group will automatically accept the group invite.</string>
<string name="accept">Accept</string>
<string name="decline">Decline</string>
<string name="message_requests_clear_all">Clear All</string>

View File

@ -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, "<init>", "(Lorg/session/libsignal/utilities/SessionId;[B[BJ)V");
jmethodID constructor = env->GetMethodID(group_info_class, "<init>",
"(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;
}

View File

@ -11,6 +11,7 @@ sealed class GroupInfo {
val adminKey: ByteArray,
val authData: ByteArray,
val priority: Long,
val invited: Boolean,
): GroupInfo() {
companion object {

View File

@ -163,6 +163,7 @@ interface StorageProtocol {
// Closed Groups
fun createNewGroup(groupName: String, groupDescription: String, members: Set<Contact>): Optional<Recipient>
fun getMembers(groupPublicKey: String): List<LibSessionGroupMember>
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?