diff --git a/data/src/main/java/com/moez/QKSMS/listener/ContactAddedListenerImpl.kt b/data/src/main/java/com/moez/QKSMS/listener/ContactAddedListenerImpl.kt new file mode 100644 index 00000000..10bf7d63 --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/listener/ContactAddedListenerImpl.kt @@ -0,0 +1,54 @@ +package com.moez.QKSMS.listener + +import android.content.Context +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.provider.ContactsContract +import com.moez.QKSMS.repository.SyncRepository +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.subjects.PublishSubject +import javax.inject.Inject + +/** + * Listens for a contact being added, and then syncs it to Realm + * + * TODO: Stop listening automatically. Currently, this will only happen if the contact is added + */ +class ContactAddedListenerImpl @Inject constructor( + private val context: Context, + private val syncRepo: SyncRepository +) : ContactAddedListener { + + companion object { + private val URI = ContactsContract.CommonDataKinds.Phone.CONTENT_URI + } + + override fun listen(address: String): Single<*> { + val observable = ContactContentObserver(context).observable + .filter { syncRepo.syncContact(address) } + + return Single.fromObservable(observable) + } + + private class ContactContentObserver(context: Context) : ContentObserver(Handler()) { + + private val subject = PublishSubject.create() + + val observable: Observable = subject + .doOnSubscribe { context.contentResolver.registerContentObserver(URI, true, this) } + .doOnDispose { context.contentResolver.unregisterContentObserver(this) } + .share() + + override fun onChange(selfChange: Boolean) { + this.onChange(selfChange, null) + } + + override fun onChange(selfChange: Boolean, uri: Uri?) { + subject.onNext(Unit) + } + + } + +} \ No newline at end of file diff --git a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt index e042eb52..61936b7a 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/SyncRepositoryImpl.kt @@ -26,6 +26,7 @@ import android.telephony.PhoneNumberUtils import com.f2prateek.rx.preferences2.RxSharedPreferences import com.moez.QKSMS.extensions.insertOrUpdate import com.moez.QKSMS.extensions.map +import com.moez.QKSMS.filter.PhoneNumberFilter import com.moez.QKSMS.manager.KeyManager import com.moez.QKSMS.mapper.CursorToContact import com.moez.QKSMS.mapper.CursorToConversation @@ -54,6 +55,7 @@ class SyncRepositoryImpl @Inject constructor( private val cursorToRecipient: CursorToRecipient, private val cursorToContact: CursorToContact, private val keys: KeyManager, + private val phoneNumberFilter: PhoneNumberFilter, private val rxPrefs: RxSharedPreferences ) : SyncRepository { @@ -239,6 +241,30 @@ class SyncRepositoryImpl @Inject constructor( } } + override fun syncContact(address: String): Boolean { + // See if there's a contact that matches this phone number + var contact = getContacts().firstOrNull { + it.numbers.any { number -> PhoneNumberUtils.compare(number.address, address) } + } ?: return false + + Realm.getDefaultInstance().use { realm -> + val recipients = realm.where(Recipient::class.java).findAll() + + realm.executeTransaction { + contact = realm.copyToRealm(contact) + + // Update all the matching recipients with the new contact + val updatedRecipients = recipients + .filter { recipient -> contact.numbers.any { number -> PhoneNumberUtils.compare(recipient.address, number.address) } } + .map { recipient -> recipient.apply { this.contact = contact } } + + realm.insertOrUpdate(updatedRecipients) + } + } + + return true + } + private fun getContacts(): List { return cursorToContact.getContactsCursor() ?.map { cursor -> cursorToContact.map(cursor) } diff --git a/domain/src/main/java/com/moez/QKSMS/listener/ContactAddedListener.kt b/domain/src/main/java/com/moez/QKSMS/listener/ContactAddedListener.kt new file mode 100644 index 00000000..15675d94 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/listener/ContactAddedListener.kt @@ -0,0 +1,9 @@ +package com.moez.QKSMS.listener + +import io.reactivex.Single + +interface ContactAddedListener { + + fun listen(address: String): Single<*> + +} \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/repository/SyncRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/SyncRepository.kt index c563366a..e2c48f8a 100644 --- a/domain/src/main/java/com/moez/QKSMS/repository/SyncRepository.kt +++ b/domain/src/main/java/com/moez/QKSMS/repository/SyncRepository.kt @@ -37,4 +37,11 @@ interface SyncRepository { fun syncContacts() + /** + * Syncs a single contact to the Realm + * + * Return false if the contact couldn't be found + */ + fun syncContact(address: String): Boolean + } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt index 89b34b12..7fd4b936 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/widget/AvatarView.kt @@ -31,15 +31,19 @@ import com.moez.QKSMS.common.util.Colors import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.injection.appComponent +import com.moez.QKSMS.listener.ContactAddedListener import com.moez.QKSMS.model.Contact import com.moez.QKSMS.model.Recipient import com.moez.QKSMS.util.GlideApp +import com.uber.autodispose.android.ViewScopeProvider +import com.uber.autodispose.kotlin.autoDisposable import kotlinx.android.synthetic.main.avatar_view.view.* import javax.inject.Inject class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { @Inject lateinit var colors: Colors + @Inject lateinit var contactAddedListener: ContactAddedListener @Inject lateinit var navigator: Navigator /** @@ -68,7 +72,15 @@ class AvatarView @JvmOverloads constructor(context: Context, attrs: AttributeSet setOnClickListener { if (lookupKey.isNullOrEmpty()) { - address?.let { address -> navigator.addContact(address) } + address?.let { address -> + // Allow the user to add the contact + navigator.addContact(address) + + // Listen for contact changes + contactAddedListener.listen(address) + .autoDisposable(ViewScopeProvider.from(this)) + .subscribe() + } } else { val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey) ContactsContract.QuickContact.showQuickContact(context, this@AvatarView, uri, diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index 1b595b7c..27591c61 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -28,6 +28,8 @@ import com.moez.QKSMS.common.ViewModelFactory import com.moez.QKSMS.common.util.NotificationManagerImpl import com.moez.QKSMS.feature.conversationinfo.injection.ConversationInfoComponent import com.moez.QKSMS.feature.themepicker.injection.ThemePickerComponent +import com.moez.QKSMS.listener.ContactAddedListener +import com.moez.QKSMS.listener.ContactAddedListenerImpl import com.moez.QKSMS.manager.AlarmManager import com.moez.QKSMS.manager.AlarmManagerImpl import com.moez.QKSMS.manager.AnalyticsManager @@ -100,6 +102,11 @@ class AppModule(private var application: Application) { @Provides fun provideViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory = factory + // Listener + + @Provides + fun provideContactAddedListener(listener: ContactAddedListenerImpl): ContactAddedListener = listener + // Manager @Provides