feat: add basic contact logic for setting local contact state. Need to implement handling properly

This commit is contained in:
0x330a 2023-02-23 17:19:03 +11:00
parent 164810e533
commit 8a000fe5a9
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
14 changed files with 100 additions and 33 deletions

View File

@ -390,7 +390,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
}
private void initializeProfileManager() {
this.profileManager = new ProfileManager();
this.profileManager = new ProfileManager(this, configFactory);
}
private void initializeTypingStatusSender() {

View File

@ -420,7 +420,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
// only update the conversation every 3 seconds maximum
// channel is rendezvous and shouldn't block on try send calls as often as we want
val bufferedFlow = bufferedLastSeenChannel.consumeAsFlow()
.debounce(3.seconds)
.debounce(1.seconds)
bufferedFlow.collectLatest {
withContext(Dispatchers.IO) {
storage.markConversationAsRead(viewModel.threadId, SnodeAPI.nowWithOffset)
@ -496,6 +496,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
handleRecyclerViewScrolled()
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
}
})
}
@ -951,13 +955,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val wasTypingIndicatorVisibleBefore = binding.typingIndicatorViewContainer.isVisible
binding.typingIndicatorViewContainer.isVisible = wasTypingIndicatorVisibleBefore && isScrolledToBottom
binding.typingIndicatorViewContainer.isVisible
showOrHidScrollToBottomButton()
showOrHideScrollToBottomButton()
val firstVisiblePosition = layoutManager?.findFirstVisibleItemPosition() ?: -1
unreadCount = min(unreadCount, firstVisiblePosition).coerceAtLeast(0)
updateUnreadCountIndicator()
}
private fun showOrHidScrollToBottomButton(show: Boolean = true) {
private fun showOrHideScrollToBottomButton(show: Boolean = true) {
binding?.scrollToBottomButton?.isVisible = show && !isScrolledToBottom && adapter.itemCount > 0
}
@ -1130,7 +1134,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
}
ViewUtil.hideKeyboard(this, visibleMessageView)
binding?.reactionsShade?.isVisible = true
showOrHidScrollToBottomButton(false)
showOrHideScrollToBottomButton(false)
binding?.conversationRecyclerView?.suppressLayout(true)
reactionDelegate.setOnActionSelectedListener(ReactionsToolbarListener(message))
reactionDelegate.setOnHideListener(object: ConversationReactionOverlay.OnHideListener {
@ -1138,7 +1142,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
binding?.reactionsShade?.let {
ViewUtil.fadeOut(it, resources.getInteger(R.integer.reaction_scrubber_hide_duration), View.GONE)
}
showOrHidScrollToBottomButton(true)
showOrHideScrollToBottomButton(true)
}
override fun onHide() {

View File

@ -8,6 +8,7 @@ import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import network.loki.messenger.R
import network.loki.messenger.databinding.DialogBlockedBinding
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
@ -35,7 +36,7 @@ class BlockedDialog(private val recipient: Recipient) : BaseDialog() {
}
private fun unblock() {
DatabaseComponent.get(requireContext()).recipientDatabase().setBlocked(recipient, false)
MessagingModuleConfiguration.shared.storage.setBlocked(listOf(recipient), false)
dismiss()
}
}

View File

@ -268,14 +268,6 @@ public class RecipientDatabase extends Database {
notifyRecipientListeners();
}
public void setBlocked(@NonNull Recipient recipient, boolean blocked) {
ContentValues values = new ContentValues();
values.put(BLOCK, blocked ? 1 : 0);
updateOrInsert(recipient.getAddress(), values);
recipient.resolve().setBlocked(blocked);
notifyRecipientListeners();
}
public void setBlocked(@NonNull List<Recipient> recipients, boolean blocked) {
SQLiteDatabase db = getWritableDatabase();
db.beginTransaction();

View File

@ -191,9 +191,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
if (!targetRecipient.isGroupRecipient) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
if (isUserSender || isUserBlindedSender) {
recipientDb.setApproved(targetRecipient, true)
setRecipientApproved(targetRecipient, true)
} else {
recipientDb.setApprovedMe(targetRecipient, true)
setRecipientApprovedMe(targetRecipient, true)
}
}
if (message.isMediaMessage() || attachments.isNotEmpty()) {
@ -790,7 +790,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
for (contact in moreContacts) {
val address = fromSerialized(contact.id)
val recipient = Recipient.from(context, address, true)
val (url, key) = contact.profilePicture?.let { it.url to it.key } ?: (null to null)
val (url, key) = contact.profilePicture.let { it.url to it.key }
// set or clear the avatar
recipientDatabase.setProfileAvatar(recipient, url)
recipientDatabase.setProfileKey(recipient, key)
@ -826,11 +826,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
recipientDatabase.setApprovedMe(recipient, true)
}
if (contact.isApproved == true) {
recipientDatabase.setApproved(recipient, true)
setRecipientApproved(recipient, true)
threadDatabase.setHasSent(threadId, true)
}
if (contact.isBlocked == true) {
recipientDatabase.setBlocked(recipient, true)
setBlocked(listOf(recipient), true)
threadDatabase.deleteConversation(threadId)
}
}
@ -993,10 +993,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
override fun setRecipientApproved(recipient: Recipient, approved: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApproved(recipient, approved)
configFactory.contacts?.upsertContact(recipient.address.serialize()) {
this.approved = approved
}
}
override fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) {
DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe)
configFactory.contacts?.upsertContact(recipient.address.serialize()) {
this.approvedMe = approvedMe
}
}
override fun insertCallMessage(senderPublicKey: String, callMessageType: CallMessageType, sentTimestamp: Long) {
@ -1126,9 +1132,19 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
DatabaseComponent.get(context).reactionDatabase().deleteMessageReactions(MessageId(messageId, mms))
}
override fun unblock(toUnblock: List<Recipient>) {
override fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean) {
val recipientDb = DatabaseComponent.get(context).recipientDatabase()
recipientDb.setBlocked(toUnblock, false)
recipientDb.setBlocked(recipients, isBlocked)
recipients.filter { it.isContactRecipient }.forEach { recipient ->
configFactory.contacts?.upsertContact(recipient.address.serialize()) {
this.blocked = true
}
}
val contactsConfig = configFactory.contacts ?: return
if (contactsConfig.needsDump()) {
configFactory.persist(contactsConfig)
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
}
}
override fun blockedContacts(): List<Recipient> {

View File

@ -749,7 +749,7 @@ public class ThreadDatabase extends Database {
} else {
MarkReadReceiver.process(context, messages);
}
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, false, 0);
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, threadId);
setLastSeen(threadId, lastSeenTime);
}

View File

@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.MmsSmsDatabase
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.dependencies.ConfigFactory
@ -90,6 +91,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
@Inject lateinit var threadDb: ThreadDatabase
@Inject lateinit var mmsSmsDatabase: MmsSmsDatabase
@Inject lateinit var recipientDatabase: RecipientDatabase
@Inject lateinit var storage: Storage
@Inject lateinit var groupDatabase: GroupDatabase
@Inject lateinit var textSecurePreferences: TextSecurePreferences
@Inject lateinit var configFactory: ConfigFactory
@ -515,7 +517,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_block) { dialog, _ ->
lifecycleScope.launch(Dispatchers.IO) {
recipientDatabase.setBlocked(thread.recipient, true)
storage.setBlocked(listOf(thread.recipient), true)
withContext(Dispatchers.Main) {
binding.recyclerView.adapter!!.notifyDataSetChanged()
dialog.dismiss()
@ -531,7 +533,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_unblock) { dialog, _ ->
lifecycleScope.launch(Dispatchers.IO) {
recipientDatabase.setBlocked(thread.recipient, false)
storage.setBlocked(listOf(thread.recipient), false)
withContext(Dispatchers.Main) {
binding.recyclerView.adapter!!.notifyDataSetChanged()
dialog.dismiss()

View File

@ -55,7 +55,7 @@ class BlockedContactsViewModel @Inject constructor(private val storage: Storage)
}
fun unblock(toUnblock: List<Recipient>) {
storage.unblock(toUnblock)
storage.setBlocked(toUnblock, false)
}
data class BlockedContactsViewState(

View File

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.repository
import org.jetbrains.annotations.Contract
import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.messages.Destination
import org.session.libsession.messaging.messages.control.MessageRequestResponse
@ -23,6 +24,7 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.SessionJobDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ThreadRecord
@ -83,6 +85,7 @@ class DefaultConversationRepository @Inject constructor(
private val mmsDb: MmsDatabase,
private val mmsSmsDb: MmsSmsDatabase,
private val recipientDb: RecipientDatabase,
private val storage: Storage,
private val lokiMessageDb: LokiMessageDatabase,
private val sessionJobDb: SessionJobDatabase,
private val configFactory: ConfigFactory
@ -127,8 +130,10 @@ class DefaultConversationRepository @Inject constructor(
}
}
// This assumes that recipient.isContactRecipient is true
@Contract
override fun setBlocked(recipient: Recipient, blocked: Boolean) {
recipientDb.setBlocked(recipient, blocked)
storage.setBlocked(listOf(recipient), blocked)
}
override fun deleteLocally(recipient: Recipient, message: MessageRecord) {
@ -141,7 +146,7 @@ class DefaultConversationRepository @Inject constructor(
}
override fun setApproved(recipient: Recipient, isApproved: Boolean) {
recipientDb.setApproved(recipient, isApproved)
storage.setRecipientApproved(recipient, isApproved)
}
override suspend fun deleteForEveryone(
@ -272,7 +277,7 @@ class DefaultConversationRepository @Inject constructor(
}
override suspend fun acceptMessageRequest(threadId: Long, recipient: Recipient): ResultOf<Unit> = suspendCoroutine { continuation ->
recipientDb.setApproved(recipient, true)
storage.setRecipientApproved(recipient, true)
val message = MessageRequestResponse(true)
MessageSender.send(message, Destination.from(recipient.address))
.success {

View File

@ -1,14 +1,17 @@
package org.thoughtcrime.securesms.sskenvironment
import android.content.Context
import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.SSKEnvironment
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
class ProfileManager : SSKEnvironment.ProfileManagerProtocol {
class ProfileManager(private val context: Context, private val configFactory: ConfigFactory) : SSKEnvironment.ProfileManagerProtocol {
override fun setNickname(context: Context, recipient: Recipient, nickname: String?) {
val sessionID = recipient.address.serialize()
@ -20,6 +23,7 @@ class ProfileManager : SSKEnvironment.ProfileManagerProtocol {
contact.nickname = nickname
contactDatabase.setContact(contact)
}
contactUpdatedInternal(contact)
}
override fun setName(context: Context, recipient: Recipient, name: String) {
@ -37,6 +41,7 @@ class ProfileManager : SSKEnvironment.ProfileManagerProtocol {
val database = DatabaseComponent.get(context).recipientDatabase()
database.setProfileName(recipient, name)
recipient.notifyListeners()
contactUpdatedInternal(contact)
}
override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) {
@ -52,6 +57,7 @@ class ProfileManager : SSKEnvironment.ProfileManagerProtocol {
contact.profilePictureURL = profilePictureURL
contactDatabase.setContact(contact)
}
contactUpdatedInternal(contact)
}
override fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray?) {
@ -68,10 +74,28 @@ class ProfileManager : SSKEnvironment.ProfileManagerProtocol {
// Old API
val database = DatabaseComponent.get(context).recipientDatabase()
database.setProfileKey(recipient, profileKey)
contactUpdatedInternal(contact)
}
override fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) {
val database = DatabaseComponent.get(context).recipientDatabase()
database.setUnidentifiedAccessMode(recipient, unidentifiedAccessMode)
}
private fun contactUpdatedInternal(contact: Contact) {
val contactConfig = configFactory.contacts ?: return
contactConfig.upsertContact(contact.sessionID) {
this.name = contact.name.orEmpty()
this.nickname = contact.nickname.orEmpty()
val url = contact.profilePictureURL
val key = contact.profilePictureEncryptionKey
if (!url.isNullOrEmpty() && key != null && key.size == 32) {
this.profilePicture = UserPic(url, key)
}
}
if (contactConfig.needsDump()) {
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(context)
}
}
}

View File

@ -62,6 +62,25 @@ class Contacts(pointer: Long) : ConfigBase(pointer) {
external fun all(): List<Contact>
external fun set(contact: Contact)
external fun erase(sessionId: String): Boolean
/**
* Similar to [updateIfExists], but will create the underlying contact if it doesn't exist before passing to [updateFunction]
*/
fun upsertContact(sessionId: String, updateFunction: Contact.()->Unit) {
val contact = getOrConstruct(sessionId)
updateFunction(contact)
set(contact)
}
/**
* Updates the contact by sessionId with a given [updateFunction], and applies to the underlying config.
* the [updateFunction] doesn't run if there is no contact
*/
fun updateIfExists(sessionId: String, updateFunction: Contact.()->Unit) {
val contact = get(sessionId) ?: return
updateFunction(contact)
set(contact)
}
}
class UserProfile(pointer: Long) : ConfigBase(pointer) {

View File

@ -204,7 +204,7 @@ interface StorageProtocol {
fun removeReaction(emoji: String, messageTimestamp: Long, author: String, notifyUnread: Boolean)
fun updateReactionIfNeeded(message: Message, sender: String, openGroupSentTimestamp: Long)
fun deleteReactions(messageId: Long, mms: Boolean)
fun unblock(toUnblock: List<Recipient>)
fun setBlocked(recipients: List<Recipient>, isBlocked: Boolean)
fun blockedContacts(): List<Recipient>
// Shared configs

View File

@ -150,8 +150,10 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
// store the new hash in list of hashes to track against
configFactory.appendHash(config, insertHash)
// dump and write config after successful
if (config.needsDump()) { // usually this will be true?
configFactory.persist(config)
}
}
} catch (e: Exception) {
Log.e(TAG, "Error performing batch request", e)
return@wrap delegate.handleJobFailedPermanently(this, dispatcherName, e)

View File

@ -163,8 +163,10 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
}
}
// process new results
if (forConfigObject.needsDump()) {
configFactory.persist(forConfigObject)
}
}
private fun poll(snode: Snode, deferred: Deferred<Unit, Exception>): Promise<Unit, Exception> {
if (!hasStarted) { return Promise.ofFail(PromiseCanceledException()) }