mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-28 20:45:17 +00:00
feat: add basic contact logic for setting local contact state. Need to implement handling properly
This commit is contained in:
parent
164810e533
commit
8a000fe5a9
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()) }
|
||||
|
Loading…
Reference in New Issue
Block a user