properly handle displaying local private photos

This commit is contained in:
tibbi 2018-02-11 23:24:16 +01:00
parent 192658c0d3
commit 0b9b1b7f1a
11 changed files with 106 additions and 46 deletions

View File

@ -45,7 +45,7 @@ ext {
} }
dependencies { dependencies {
implementation 'com.simplemobiletools:commons:3.11.3' implementation 'com.simplemobiletools:commons:3.11.5'
implementation 'joda-time:joda-time:2.9.9' implementation 'joda-time:joda-time:2.9.9'
implementation 'com.facebook.stetho:stetho:1.5.0' implementation 'com.facebook.stetho:stetho:1.5.0'
implementation 'com.google.code.gson:gson:2.8.2' implementation 'com.google.code.gson:gson:2.8.2'

View File

@ -1,5 +1,6 @@
package com.simplemobiletools.contacts.activities package com.simplemobiletools.contacts.activities
import android.graphics.Bitmap
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.provider.ContactsContract import android.provider.ContactsContract
import android.widget.ImageView import android.widget.ImageView
@ -41,16 +42,17 @@ abstract class ContactActivity : SimpleActivity() {
photoView.setPadding(padding, padding, padding, padding) photoView.setPadding(padding, padding, padding, padding)
photoView.setImageBitmap(placeholder) photoView.setImageBitmap(placeholder)
currentContactPhotoPath = "" currentContactPhotoPath = ""
contact!!.photo = null
} }
fun updateContactPhoto(path: String, photoView: ImageView) { fun updateContactPhoto(path: String, photoView: ImageView, bitmap: Bitmap? = null) {
currentContactPhotoPath = path currentContactPhotoPath = path
val options = RequestOptions() val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop() .centerCrop()
Glide.with(this) Glide.with(this)
.load(path) .load(bitmap ?: path)
.transition(DrawableTransitionOptions.withCrossFade()) .transition(DrawableTransitionOptions.withCrossFade())
.apply(options) .apply(options)
.listener(object : RequestListener<Drawable> { .listener(object : RequestListener<Drawable> {

View File

@ -146,10 +146,10 @@ class EditContactActivity : ContactActivity() {
contact_photo.background = ColorDrawable(config.primaryColor) contact_photo.background = ColorDrawable(config.primaryColor)
if (contact!!.photoUri.isEmpty()) { if (contact!!.photoUri.isEmpty() && contact!!.photo == null) {
showPhotoPlaceholder(contact_photo) showPhotoPlaceholder(contact_photo)
} else { } else {
updateContactPhoto(contact!!.photoUri, contact_photo) updateContactPhoto(contact!!.photoUri, contact_photo, contact!!.photo)
} }
val textColor = config.textColor val textColor = config.textColor
@ -283,7 +283,7 @@ class EditContactActivity : ContactActivity() {
private fun setupNewContact() { private fun setupNewContact() {
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
supportActionBar?.title = resources.getString(R.string.new_contact) supportActionBar?.title = resources.getString(R.string.new_contact)
contact = Contact(0, "", "", "", "", ArrayList(), ArrayList(), ArrayList(), config.lastUsedContactSource, 0, 0, "") contact = Contact(0, "", "", "", "", ArrayList(), ArrayList(), ArrayList(), config.lastUsedContactSource, 0, 0, "", null)
contact_source.text = getPublicContactSource(contact!!.source) contact_source.text = getPublicContactSource(contact!!.source)
contact_source.setOnClickListener { contact_source.setOnClickListener {
showContactSourcePicker(contact!!.source) { showContactSourcePicker(contact!!.source) {
@ -577,7 +577,7 @@ class EditContactActivity : ContactActivity() {
RadioItem(CHOOSE_PHOTO, getString(R.string.choose_photo)) RadioItem(CHOOSE_PHOTO, getString(R.string.choose_photo))
) )
if (currentContactPhotoPath.isNotEmpty()) { if (currentContactPhotoPath.isNotEmpty() || contact!!.photo != null) {
items.add(RadioItem(REMOVE_PHOTO, getString(R.string.remove_photo))) items.add(RadioItem(REMOVE_PHOTO, getString(R.string.remove_photo)))
} }

View File

@ -98,10 +98,10 @@ class ViewContactActivity : ContactActivity() {
contact_photo.background = ColorDrawable(config.primaryColor) contact_photo.background = ColorDrawable(config.primaryColor)
if (contact!!.photoUri.isEmpty()) { if (contact!!.photoUri.isEmpty() && contact!!.photo == null) {
showPhotoPlaceholder(contact_photo) showPhotoPlaceholder(contact_photo)
} else { } else {
updateContactPhoto(contact!!.photoUri, contact_photo) updateContactPhoto(contact!!.photoUri, contact_photo, contact!!.photo)
} }
val textColor = config.textColor val textColor = config.textColor

View File

@ -199,16 +199,26 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList<Co
contact_tmb.beVisibleIf(showContactThumbnails) contact_tmb.beVisibleIf(showContactThumbnails)
if (showContactThumbnails) { if (showContactThumbnails) {
if (contact.photoUri.isNotEmpty()) { when {
val options = RequestOptions() contact.photoUri.isNotEmpty() -> {
.signature(ObjectKey(contact.photoUri)) val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) .signature(ObjectKey(contact.photoUri))
.error(contactDrawable) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop() .error(contactDrawable)
.centerCrop()
Glide.with(activity).load(contact.photoUri).transition(DrawableTransitionOptions.withCrossFade()).apply(options).into(contact_tmb) Glide.with(activity).load(contact.photoUri).transition(DrawableTransitionOptions.withCrossFade()).apply(options).into(contact_tmb)
} else { }
contact_tmb.setImageDrawable(contactDrawable) contact.photo != null -> {
val options = RequestOptions()
.signature(ObjectKey(contact.photo!!))
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.error(contactDrawable)
.centerCrop()
Glide.with(activity).load(contact.photo).transition(DrawableTransitionOptions.withCrossFade()).apply(options).into(contact_tmb)
}
else -> contact_tmb.setImageDrawable(contactDrawable)
} }
} }
} }

View File

@ -0,0 +1,15 @@
package com.simplemobiletools.contacts.extensions
import android.graphics.Bitmap
import java.io.ByteArrayOutputStream
fun Bitmap.getByteArray(): ByteArray {
var baos: ByteArrayOutputStream? = null
try {
baos = ByteArrayOutputStream()
compress(Bitmap.CompressFormat.JPEG, 80, baos)
return baos.toByteArray()
} finally {
baos?.close()
}
}

View File

@ -128,3 +128,18 @@ fun Context.getCachePhoto(): File {
} }
fun Context.getCachePhotoUri(file: File = getCachePhoto()) = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", file) fun Context.getCachePhotoUri(file: File = getCachePhoto()) = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", file)
fun Context.getPhotoThumbnailSize(): Int {
val uri = ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
val projection = arrayOf(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM)
var cursor: Cursor? = null
try {
cursor = contentResolver.query(uri, projection, null, null, null)
if (cursor?.moveToFirst() == true) {
return cursor.getIntValue(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM)
}
} finally {
cursor?.close()
}
return 0
}

View File

@ -23,6 +23,8 @@ import com.simplemobiletools.commons.helpers.SORT_DESCENDING
import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.dbHelper import com.simplemobiletools.contacts.extensions.dbHelper
import com.simplemobiletools.contacts.extensions.getByteArray
import com.simplemobiletools.contacts.extensions.getPhotoThumbnailSize
import com.simplemobiletools.contacts.models.* import com.simplemobiletools.contacts.models.*
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.util.* import java.util.*
@ -54,7 +56,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val starred = cursor.getIntValue(CommonDataKinds.StructuredName.STARRED) val starred = cursor.getIntValue(CommonDataKinds.StructuredName.STARRED)
val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID)
val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: "" val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
val contact = Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId, thumbnailUri) val contact = Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId, thumbnailUri, null)
contacts.put(id, contact) contacts.put(id, contact)
} while (cursor.moveToNext()) } while (cursor.moveToNext())
} }
@ -224,7 +226,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val starred = cursor.getIntValue(CommonDataKinds.StructuredName.STARRED) val starred = cursor.getIntValue(CommonDataKinds.StructuredName.STARRED)
val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID)
val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: "" val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
return Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId, thumbnailUri) return Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId, thumbnailUri, null)
} }
} finally { } finally {
cursor?.close() cursor?.close()
@ -429,12 +431,12 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val photoUri = Uri.parse(contact.photoUri) val photoUri = Uri.parse(contact.photoUri)
val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri) val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri)
val thumbnailSize = getThumbnailSize() val thumbnailSize = activity.getPhotoThumbnailSize()
val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false) val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false)
val scaledSizePhotoData = bitmapToByteArray(scaledPhoto) val scaledSizePhotoData = scaledPhoto.getByteArray()
scaledPhoto.recycle() scaledPhoto.recycle()
val fullSizePhotoData = bitmapToByteArray(bitmap) val fullSizePhotoData = bitmap.getByteArray()
bitmap.recycle() bitmap.recycle()
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
@ -522,11 +524,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val photoUri = Uri.parse(contact.photoUri) val photoUri = Uri.parse(contact.photoUri)
val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri) val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri)
val thumbnailSize = getThumbnailSize() val thumbnailSize = activity.getPhotoThumbnailSize()
val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false) val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false)
scaledSizePhotoData = bitmapToByteArray(scaledPhoto) scaledSizePhotoData = scaledPhoto.getByteArray()
fullSizePhotoData = bitmapToByteArray(bitmap) fullSizePhotoData = bitmap.getByteArray()
scaledPhoto.recycle() scaledPhoto.recycle()
bitmap.recycle() bitmap.recycle()
@ -677,19 +679,4 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
activity.showErrorToast(e) activity.showErrorToast(e)
} }
} }
private fun getThumbnailSize(): Int {
val uri = ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI
val projection = arrayOf(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM)
var cursor: Cursor? = null
try {
cursor = activity.contentResolver.query(uri, projection, null, null, null)
if (cursor?.moveToFirst() == true) {
return cursor.getIntValue(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM)
}
} finally {
cursor?.close()
}
return 0
}
} }

View File

@ -4,17 +4,23 @@ import android.content.ContentValues
import android.content.Context import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.text.TextUtils import android.text.TextUtils
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.extensions.getBlobValue
import com.simplemobiletools.commons.extensions.getIntValue import com.simplemobiletools.commons.extensions.getIntValue
import com.simplemobiletools.commons.extensions.getStringValue import com.simplemobiletools.commons.extensions.getStringValue
import com.simplemobiletools.contacts.extensions.getByteArray
import com.simplemobiletools.contacts.extensions.getPhotoThumbnailSize
import com.simplemobiletools.contacts.models.Contact import com.simplemobiletools.contacts.models.Contact
import com.simplemobiletools.contacts.models.Email import com.simplemobiletools.contacts.models.Email
import com.simplemobiletools.contacts.models.Event import com.simplemobiletools.contacts.models.Event
import com.simplemobiletools.contacts.models.PhoneNumber import com.simplemobiletools.contacts.models.PhoneNumber
class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
private val CONTACTS_TABLE_NAME = "contacts" private val CONTACTS_TABLE_NAME = "contacts"
private val COL_ID = "id" private val COL_ID = "id"
@ -86,9 +92,26 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
put(COL_EMAILS, Gson().toJson(contact.emails)) put(COL_EMAILS, Gson().toJson(contact.emails))
put(COL_EVENTS, Gson().toJson(contact.events)) put(COL_EVENTS, Gson().toJson(contact.events))
put(COL_STARRED, contact.starred) put(COL_STARRED, contact.starred)
if (contact.photoUri.isNotEmpty()) {
put(COL_PHOTO, getPhotoByteArray(contact.photoUri))
} else if (contact.photo == null) {
putNull(COL_PHOTO)
}
} }
} }
private fun getPhotoByteArray(uri: String): ByteArray {
val photoUri = Uri.parse(uri)
val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, photoUri)
val thumbnailSize = context.getPhotoThumbnailSize()
val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false)
val scaledSizePhotoData = scaledPhoto.getByteArray()
scaledPhoto.recycle()
return scaledSizePhotoData
}
fun toggleFavorites(ids: Array<String>, addToFavorites: Boolean) { fun toggleFavorites(ids: Array<String>, addToFavorites: Boolean) {
val contactValues = ContentValues() val contactValues = ContentValues()
contactValues.put(COL_STARRED, if (addToFavorites) 1 else 0) contactValues.put(COL_STARRED, if (addToFavorites) 1 else 0)
@ -100,7 +123,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
fun getContacts(selection: String? = null, selectionArgs: Array<String>? = null): ArrayList<Contact> { fun getContacts(selection: String? = null, selectionArgs: Array<String>? = null): ArrayList<Contact> {
val contacts = ArrayList<Contact>() val contacts = ArrayList<Contact>()
val projection = arrayOf(COL_ID, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_PHONE_NUMBERS, COL_EMAILS, COL_EVENTS, COL_STARRED) val projection = arrayOf(COL_ID, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_PHONE_NUMBERS, COL_EMAILS, COL_EVENTS, COL_STARRED, COL_PHOTO)
val cursor = mDb.query(CONTACTS_TABLE_NAME, projection, selection, selectionArgs, null, null, null) val cursor = mDb.query(CONTACTS_TABLE_NAME, projection, selection, selectionArgs, null, null, null)
cursor.use { cursor.use {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
@ -121,8 +144,15 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
val eventsToken = object : TypeToken<List<Event>>() {}.type val eventsToken = object : TypeToken<List<Event>>() {}.type
val events = Gson().fromJson<ArrayList<Event>>(eventsJson, eventsToken) ?: ArrayList(1) val events = Gson().fromJson<ArrayList<Event>>(eventsJson, eventsToken) ?: ArrayList(1)
val photoByteArray = cursor.getBlobValue(COL_PHOTO) ?: null
val photo = if (photoByteArray?.isNotEmpty() == true) {
BitmapFactory.decodeByteArray(photoByteArray, 0, photoByteArray.size)
} else {
null
}
val starred = cursor.getIntValue(COL_STARRED) val starred = cursor.getIntValue(COL_STARRED)
val contact = Contact(id, firstName, middleName, surname, "", phoneNumbers, emails, events, SMT_PRIVATE, starred, id, "") val contact = Contact(id, firstName, middleName, surname, "", phoneNumbers, emails, events, SMT_PRIVATE, starred, id, "", photo)
contacts.add(contact) contacts.add(contact)
} }
} }

View File

@ -199,7 +199,7 @@ class VcfImporter(val activity: SimpleActivity) {
} }
private fun saveContact(source: String) { private fun saveContact(source: String) {
val contact = Contact(0, curFirstName, curMiddleName, curSurname, curPhotoUri, curPhoneNumbers, curEmails, curEvents, source, 0, 0, "") val contact = Contact(0, curFirstName, curMiddleName, curSurname, curPhotoUri, curPhoneNumbers, curEmails, curEvents, source, 0, 0, "", null)
if (ContactsHelper(activity).insertContact(contact)) { if (ContactsHelper(activity).insertContact(contact)) {
contactsImported++ contactsImported++
} }

View File

@ -1,12 +1,13 @@
package com.simplemobiletools.contacts.models package com.simplemobiletools.contacts.models
import android.graphics.Bitmap
import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME
import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME
import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.commons.helpers.SORT_DESCENDING
data class Contact(val id: Int, var firstName: String, var middleName: String, var surname: String, var photoUri: String, data class Contact(val id: Int, var firstName: String, var middleName: String, var surname: String, var photoUri: String,
var phoneNumbers: ArrayList<PhoneNumber>, var emails: ArrayList<Email>, var events: ArrayList<Event>, var source: String, var phoneNumbers: ArrayList<PhoneNumber>, var emails: ArrayList<Email>, var events: ArrayList<Event>, var source: String,
var starred: Int, val contactId: Int, val thumbnailUri: String) : Comparable<Contact> { var starred: Int, val contactId: Int, val thumbnailUri: String, var photo: Bitmap?) : Comparable<Contact> {
companion object { companion object {
var sorting = 0 var sorting = 0
} }