Store contact’s photoUri

So much less complicated now!
This commit is contained in:
Moez Bhatti
2019-11-24 00:27:39 -05:00
parent a1fcecd79e
commit 573b01ff65
15 changed files with 36 additions and 173 deletions
@@ -41,6 +41,7 @@ class CursorToContactImpl @Inject constructor(
Phone.TYPE,
Phone.LABEL,
Phone.DISPLAY_NAME,
Phone.PHOTO_URI,
Phone.STARRED,
Phone.CONTACT_LAST_UPDATED_TIMESTAMP
)
@@ -52,13 +53,15 @@ class CursorToContactImpl @Inject constructor(
const val COLUMN_TYPE = 4
const val COLUMN_LABEL = 5
const val COLUMN_DISPLAY_NAME = 6
const val COLUMN_STARRED = 7
const val CONTACT_LAST_UPDATED = 8
const val COLUMN_PHOTO_URI = 7
const val COLUMN_STARRED = 8
const val CONTACT_LAST_UPDATED = 9
}
override fun map(from: Cursor) = Contact().apply {
lookupKey = from.getString(COLUMN_LOOKUP_KEY)
name = from.getString(COLUMN_DISPLAY_NAME) ?: ""
photoUri = from.getString(COLUMN_PHOTO_URI)
numbers.add(PhoneNumber(
id = from.getLong(COLUMN_ID),
accountType = from.getString(COLUMN_ACCOUNT_TYPE),
@@ -126,6 +126,7 @@ class QkRealmMigration : RealmMigration {
realm.schema.get("Contact")
?.addField("starred", Boolean::class.java, FieldAttribute.REQUIRED)
?.addField("photoUri", String::class.java)
realm.schema.get("PhoneNumber")
?.addField("id", Long::class.java, FieldAttribute.PRIMARY_KEY, FieldAttribute.REQUIRED)
@@ -1,103 +0,0 @@
/*
* Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
*
* This file is part of QKSMS.
*
* QKSMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QKSMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package com.moez.QKSMS.util
import android.content.Context
import android.provider.ContactsContract
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Key
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.moez.QKSMS.repository.ContactRepository
import com.moez.QKSMS.repository.ContactRepositoryImpl
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.io.InputStream
import java.security.MessageDigest
class ContactImageLoader(
private val context: Context,
private val contactRepo: ContactRepository,
private val phoneNumberUtils: PhoneNumberUtils
) : ModelLoader<String, InputStream> {
override fun handles(model: String): Boolean {
return model.startsWith("tel:")
}
override fun buildLoadData(
model: String,
width: Int,
height: Int,
options: Options
): ModelLoader.LoadData<InputStream>? {
return ModelLoader.LoadData(
ContactImageKey(phoneNumberUtils.normalizeNumber(model)),
ContactImageFetcher(context, contactRepo, model))
}
class Factory(
private val context: Context,
private val prefs: Preferences
) : ModelLoaderFactory<String, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ContactImageLoader {
return ContactImageLoader(context, ContactRepositoryImpl(context, prefs), PhoneNumberUtils(context))
}
override fun teardown() {} // nothing to do here
}
class ContactImageKey(private val address: String) : Key {
override fun updateDiskCacheKey(digest: MessageDigest) = digest.update(address.toByteArray())
}
class ContactImageFetcher(
private val context: Context,
private val contactRepo: ContactRepository,
private val address: String
) : DataFetcher<InputStream> {
private var loadPhotoDisposable: Disposable? = null
override fun cleanup() {}
override fun getDataClass() = InputStream::class.java
override fun getDataSource() = DataSource.LOCAL
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
loadPhotoDisposable = contactRepo.findContactUri(address)
.map { uri ->
ContactsContract.Contacts.openContactPhotoInputStream(context.contentResolver, uri, true)
}
.subscribeOn(Schedulers.io())
.subscribe(
{ inputStream -> callback.onDataReady(inputStream) },
{ error -> callback.onLoadFailed(Exception(error)) })
}
override fun cancel() {
loadPhotoDisposable?.dispose()
}
}
}
@@ -19,15 +19,12 @@
package com.moez.QKSMS.util
import android.content.Context
import android.preference.PreferenceManager
import android.util.Log
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule
import com.f2prateek.rx.preferences2.RxSharedPreferences
import java.io.InputStream
@GlideModule
class GlideAppModule : AppGlideModule() {
@@ -37,10 +34,6 @@ class GlideAppModule : AppGlideModule() {
}
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
// TODO use DI to create the ContactImageLoader.Factory
val rxPrefs = RxSharedPreferences.create(PreferenceManager.getDefaultSharedPreferences(context))
val prefs = Preferences(context, rxPrefs)
registry.append(String::class.java, InputStream::class.java, ContactImageLoader.Factory(context, prefs))
}
}
}
@@ -26,6 +26,7 @@ open class Contact(
@PrimaryKey var lookupKey: String = "",
var numbers: RealmList<PhoneNumber> = RealmList(),
var name: String = "",
var photoUri: String? = null,
var starred: Boolean = false,
var lastUpdate: Long = 0
) : RealmObject() {
@@ -154,11 +154,10 @@ class NotificationManagerImpl @Inject constructor(
.firstOrNull { phoneNumberUtils.compare(it.address, message.address) }
person.setName(recipient?.getDisplayName() ?: message.address)
person.setIcon(GlideApp.with(context)
.asBitmap()
.circleCrop()
.load("tel:${message.address}")
.load(recipient?.contact?.photoUri)
.submit(64.dpToPx(context), 64.dpToPx(context))
.let { futureGet -> tryOrNull(false) { futureGet.get() } }
?.let(IconCompat::createWithBitmap))
@@ -178,12 +177,12 @@ class NotificationManagerImpl @Inject constructor(
// Set the large icon
val avatar = conversation.recipients.takeIf { it.size == 1 }
?.first()?.address
?.let { address ->
?.first()?.contact?.photoUri
?.let { photoUri ->
GlideApp.with(context)
.asBitmap()
.circleCrop()
.load("tel:$address")
.load(photoUri)
.submit(64.dpToPx(context), 64.dpToPx(context))
}
?.let { futureGet -> tryOrNull(false) { futureGet.get() } }
@@ -52,13 +52,13 @@ class QkChooserTargetService : ChooserTargetService() {
}
private fun createShortcutForConversation(conversation: Conversation): ChooserTarget {
val icon = when {
conversation.recipients.size == 1 -> {
val address = conversation.recipients.first()!!.address
val icon = when (conversation.recipients.size) {
1 -> {
val photoUri = conversation.recipients.first()?.contact?.photoUri
val request = GlideApp.with(this)
.asBitmap()
.circleCrop()
.load("tel:$address")
.load(photoUri)
.submit()
val bitmap = tryOrNull(false) { request.get() }
@@ -22,7 +22,6 @@ import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import com.bumptech.glide.signature.ObjectKey
import com.moez.QKSMS.R
import com.moez.QKSMS.common.Navigator
import com.moez.QKSMS.common.util.Colors
@@ -30,7 +29,6 @@ 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.model.Contact
import com.moez.QKSMS.model.Recipient
import com.moez.QKSMS.util.GlideApp
import kotlinx.android.synthetic.main.avatar_view.view.*
import javax.inject.Inject
@@ -54,7 +52,7 @@ class AvatarView @JvmOverloads constructor(
private var lookupKey: String? = null
private var name: String? = null
private var address: String? = null
private var photoUri: String? = null
private var lastUpdated: Long? = null
init {
@@ -68,33 +66,13 @@ class AvatarView @JvmOverloads constructor(
clipToOutline = true
}
/**
* If the [recipient] has a contact: use the contact's avatar, but keep the address.
* Use the recipient address otherwise.
*/
fun setContact(recipient: Recipient?) {
// If the recipient has a contact, just use that and return
recipient?.contact?.let { contact ->
setContact(contact, recipient.address)
return
}
lookupKey = null
name = null
address = recipient?.address
lastUpdated = 0
updateView()
}
/**
* Use the [contact] information to display the avatar.
* A specific [contactAddress] can be specified (useful when the contact has several addresses).
*/
fun setContact(contact: Contact?, contactAddress: String? = null) {
fun setContact(contact: Contact?) {
lookupKey = contact?.lookupKey
name = contact?.name
// If a contactAddress has been given, we use it. Use the contact address otherwise.
address = contactAddress?.takeIf { it.isNotEmpty() } ?: contact?.numbers?.firstOrNull()?.address
photoUri = contact?.photoUri
lastUpdated = contact?.lastUpdate
updateView()
}
@@ -108,7 +86,7 @@ class AvatarView @JvmOverloads constructor(
}
}
fun applyTheme(threadId: Long) {
private fun applyTheme(threadId: Long) {
colors.theme(threadId).run {
setBackgroundTint(theme)
initial.setTextColor(textPrimary)
@@ -130,11 +108,10 @@ class AvatarView @JvmOverloads constructor(
}
photo.setImageDrawable(null)
address?.let { address ->
photoUri?.let { photoUri ->
GlideApp.with(photo)
.load("tel:$address")
.signature(ObjectKey(lastUpdated ?: 0L))
.load(photoUri)
.into(photo)
}
}
}
}
@@ -63,8 +63,8 @@ class GroupAvatarView @JvmOverloads constructor(
}
avatar2.isVisible = contacts.size > 1
avatar1.setContact(contacts.getOrNull(0))
avatar2.setContact(contacts.getOrNull(1))
avatar1.setContact(contacts.getOrNull(0)?.contact)
avatar2.setContact(contacts.getOrNull(1)?.contact)
}
}
@@ -234,7 +234,7 @@ class MessagesAdapter @Inject constructor(
// Bind the avatar
if (!message.isMe()) {
view.avatar.threadId = conversation?.id ?: 0
view.avatar.setContact(contactCache[message.address])
view.avatar.setContact(contactCache[message.address]?.contact)
view.avatar.setVisible(!canGroup(message, next), View.INVISIBLE)
}
@@ -51,7 +51,7 @@ class ChipsAdapter @Inject constructor() : QkAdapter<Chip>() {
val chip = getItem(position)
val view = holder.containerView
view.avatar.setContact(chip.contact, chip.address)
view.avatar.setContact(chip.contact)
view.name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address
}
@@ -54,7 +54,7 @@ class DetailedChipView(context: Context) : RelativeLayout(context) {
}
fun setChip(chip: Chip) {
avatar.setContact(chip.contact, chip.address)
avatar.setContact(chip.contact)
name.text = chip.contact?.name?.takeIf { it.isNotBlank() } ?: chip.address
info.text = chip.address
}
@@ -55,7 +55,7 @@ class ConversationRecipientAdapter @Inject constructor() : QkRealmAdapter<Recipi
val view = holder.containerView
view.avatar.threadId = threadId
view.avatar.setContact(recipient)
view.avatar.setContact(recipient.contact)
view.name.text = recipient.contact?.name ?: recipient.address
@@ -40,6 +40,7 @@ import com.moez.QKSMS.model.PhoneNumber
import com.moez.QKSMS.repository.ConversationRepository
import com.moez.QKSMS.util.GlideApp
import com.moez.QKSMS.util.Preferences
import com.moez.QKSMS.util.tryOrNull
import javax.inject.Inject
class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory {
@@ -130,17 +131,11 @@ class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory {
}
remoteViews.setImageViewBitmap(R.id.photo, null)
contact?.numbers?.firstOrNull()?.address?.let { address ->
val futureGet = GlideApp.with(context)
.asBitmap()
.load("tel:$address")
.submit(48.dpToPx(context), 48.dpToPx(context))
try {
remoteViews.setImageViewBitmap(R.id.photo, futureGet.get())
} catch (e: Exception) {
}
}
val futureGet = GlideApp.with(context)
.asBitmap()
.load(contact?.photoUri)
.submit(48.dpToPx(context), 48.dpToPx(context))
tryOrNull(false) { remoteViews.setImageViewBitmap(R.id.photo, futureGet.get()) }
// Name
remoteViews.setTextColor(R.id.name, textPrimary)
@@ -43,7 +43,6 @@ import com.moez.QKSMS.feature.widget.WidgetAdapter
import com.moez.QKSMS.injection.android.ActivityBuilderModule
import com.moez.QKSMS.injection.android.BroadcastReceiverBuilderModule
import com.moez.QKSMS.injection.android.ServiceBuilderModule
import com.moez.QKSMS.util.ContactImageLoader
import dagger.Component
import dagger.android.support.AndroidSupportInjectionModule
import javax.inject.Singleton
@@ -73,8 +72,6 @@ interface AppComponent {
fun inject(dialog: QkDialog)
fun inject(fetcher: ContactImageLoader.ContactImageFetcher)
fun inject(service: WidgetAdapter)
/**
@@ -91,4 +88,4 @@ interface AppComponent {
fun inject(view: QkSwitch)
fun inject(view: QkTextView)
}
}