Add email value type
This commit is contained in:
parent
7a9c373f92
commit
5e09e067b0
11 changed files with 84 additions and 51 deletions
|
@ -1,11 +1,13 @@
|
|||
package com.fsck.k9.helper
|
||||
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
|
||||
interface ContactNameProvider {
|
||||
fun getNameForAddress(address: String): String?
|
||||
}
|
||||
|
||||
class RealContactNameProvider(private val contacts: Contacts) : ContactNameProvider {
|
||||
override fun getNameForAddress(address: String): String? {
|
||||
return contacts.getNameForAddress(address)
|
||||
return contacts.getNameFor(EmailAddress(address))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.provider.ContactsContract
|
|||
import android.provider.ContactsContract.CommonDataKinds
|
||||
import androidx.core.content.ContextCompat
|
||||
import app.k9mail.core.android.common.database.EmptyCursor
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import com.fsck.k9.mail.Address
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -28,9 +29,9 @@ open class Contacts(
|
|||
* @return <tt>true</tt>, if the email address belongs to a contact.
|
||||
* <tt>false</tt>, otherwise.
|
||||
*/
|
||||
fun isInContacts(emailAddress: String): Boolean {
|
||||
fun isInContacts(emailAddress: EmailAddress): Boolean {
|
||||
var result = false
|
||||
val cursor = getContactByAddress(emailAddress)
|
||||
val cursor = getContactFor(emailAddress)
|
||||
if (cursor != null) {
|
||||
if (cursor.count > 0) {
|
||||
result = true
|
||||
|
@ -41,26 +42,17 @@ open class Contacts(
|
|||
}
|
||||
|
||||
/**
|
||||
* Check whether one of the provided addresses belongs to one of the contacts.
|
||||
* Check whether one of the provided email addresses belongs to one of the contacts.
|
||||
*
|
||||
* @param addresses The addresses to search in contacts
|
||||
* @return <tt>true</tt>, if one address belongs to a contact.
|
||||
* @param emailAddresses The email addresses to search in contacts
|
||||
* @return <tt>true</tt>, if one of the email addresses belongs to a contact.
|
||||
* <tt>false</tt>, otherwise.
|
||||
*/
|
||||
fun isAnyInContacts(addresses: Array<Address>?): Boolean {
|
||||
if (addresses == null) {
|
||||
return false
|
||||
}
|
||||
for (addr in addresses) {
|
||||
if (isInContacts(addr.address)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
fun isAnyInContacts(emailAddresses: List<EmailAddress>): Boolean =
|
||||
emailAddresses.any { emailAddress -> isInContacts(emailAddress) }
|
||||
|
||||
fun getContactUri(emailAddress: String): Uri? {
|
||||
val cursor = getContactByAddress(emailAddress) ?: return null
|
||||
fun getContactUri(emailAddress: EmailAddress): Uri? {
|
||||
val cursor = getContactFor(emailAddress) ?: return null
|
||||
cursor.use {
|
||||
if (!cursor.moveToFirst()) {
|
||||
return null
|
||||
|
@ -74,17 +66,15 @@ open class Contacts(
|
|||
/**
|
||||
* Get the name of the contact an email address belongs to.
|
||||
*
|
||||
* @param address The email address to search for.
|
||||
* @param emailAddress The email address to search for.
|
||||
* @return The name of the contact the email address belongs to. Or
|
||||
* <tt>null</tt> if there's no matching contact.
|
||||
*/
|
||||
open fun getNameForAddress(address: String?): String? {
|
||||
if (address == null) {
|
||||
return null
|
||||
} else if (nameCache.containsKey(address)) {
|
||||
return nameCache[address]
|
||||
open fun getNameFor(emailAddress: EmailAddress): String? {
|
||||
if (nameCache.containsKey(emailAddress)) {
|
||||
return nameCache[emailAddress]
|
||||
}
|
||||
val cursor = getContactByAddress(address)
|
||||
val cursor = getContactFor(emailAddress)
|
||||
var name: String? = null
|
||||
if (cursor != null) {
|
||||
if (cursor.count > 0) {
|
||||
|
@ -93,7 +83,7 @@ open class Contacts(
|
|||
}
|
||||
cursor.close()
|
||||
}
|
||||
nameCache[address] = name
|
||||
nameCache[emailAddress] = name
|
||||
return name
|
||||
}
|
||||
|
||||
|
@ -108,16 +98,14 @@ open class Contacts(
|
|||
/**
|
||||
* Get URI to the picture of the contact with the supplied email address.
|
||||
*
|
||||
* @param address
|
||||
* An email address. The contact database is searched for a contact with this email
|
||||
* address.
|
||||
* @param emailAddress An email address, the contact database is searched for.
|
||||
*
|
||||
* @return URI to the picture of the contact with the supplied email address. `null` if
|
||||
* no such contact could be found or the contact doesn't have a picture.
|
||||
*/
|
||||
fun getPhotoUri(address: String): Uri? {
|
||||
fun getPhotoUri(emailAddress: EmailAddress): Uri? {
|
||||
return try {
|
||||
val cursor = getContactByAddress(address) ?: return null
|
||||
val cursor = getContactFor(emailAddress) ?: return null
|
||||
try {
|
||||
if (!cursor.moveToFirst()) {
|
||||
return null
|
||||
|
@ -131,7 +119,7 @@ open class Contacts(
|
|||
cursor.close()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Couldn't fetch photo for contact with email $address")
|
||||
Timber.e(e, "Couldn't fetch photo for contact with email ${emailAddress.address}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -147,12 +135,12 @@ open class Contacts(
|
|||
* Return a [Cursor] instance that can be used to fetch information
|
||||
* about the contact with the given email address.
|
||||
*
|
||||
* @param address The email address to search for.
|
||||
* @param emailAddress The email address to search for.
|
||||
* @return A [Cursor] instance that can be used to fetch information
|
||||
* about the contact with the given email address
|
||||
*/
|
||||
private fun getContactByAddress(address: String): Cursor? {
|
||||
val uri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_LOOKUP_URI, Uri.encode(address))
|
||||
private fun getContactFor(emailAddress: EmailAddress): Cursor? {
|
||||
val uri = Uri.withAppendedPath(CommonDataKinds.Email.CONTENT_LOOKUP_URI, Uri.encode(emailAddress.address))
|
||||
return if (hasContactPermission()) {
|
||||
contentResolver.query(
|
||||
uri,
|
||||
|
@ -169,7 +157,7 @@ open class Contacts(
|
|||
companion object {
|
||||
/**
|
||||
* The order in which the search results are returned by
|
||||
* [.getContactByAddress].
|
||||
* [.getContactBy].
|
||||
*/
|
||||
private const val SORT_ORDER = CommonDataKinds.Email.TIMES_CONTACTED + " DESC, " +
|
||||
ContactsContract.Contacts.DISPLAY_NAME + ", " +
|
||||
|
@ -199,7 +187,7 @@ open class Contacts(
|
|||
private const val CONTACT_ID_INDEX = 2
|
||||
private const val LOOKUP_KEY_INDEX = 4
|
||||
|
||||
private val nameCache = HashMap<String, String?>()
|
||||
private val nameCache = HashMap<EmailAddress, String?>()
|
||||
|
||||
/**
|
||||
* Clears the cache for names and photo uris
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.text.SpannableString
|
|||
import android.text.SpannableStringBuilder
|
||||
import android.text.TextUtils
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import com.fsck.k9.CoreResourceProvider
|
||||
import com.fsck.k9.K9.contactNameColor
|
||||
import com.fsck.k9.K9.isChangeContactNameColor
|
||||
|
@ -99,7 +100,7 @@ class MessageHelper(
|
|||
if (!showCorrespondentNames) {
|
||||
return address.address
|
||||
} else if (contacts != null) {
|
||||
val name = contacts.getNameForAddress(address.address)
|
||||
val name = contacts.getNameFor(EmailAddress(address.address))
|
||||
if (name != null) {
|
||||
return if (changeContactNameColor) {
|
||||
val coloredName = SpannableString(name)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.fsck.k9.helper
|
|||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.text.SpannableString
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isInstanceOf
|
||||
|
@ -24,8 +25,8 @@ class MessageHelperTest : RobolectricTest() {
|
|||
val context: Context = RuntimeEnvironment.getApplication()
|
||||
contacts = Contacts(context)
|
||||
contactsWithFakeContact = object : Contacts(context) {
|
||||
override fun getNameForAddress(address: String?): String? {
|
||||
return if ("test@testor.com" == address) {
|
||||
override fun getNameFor(emailAddress: EmailAddress): String? {
|
||||
return if ("test@testor.com" == emailAddress.address) {
|
||||
"Tim Testor"
|
||||
} else {
|
||||
null
|
||||
|
@ -33,8 +34,8 @@ class MessageHelperTest : RobolectricTest() {
|
|||
}
|
||||
}
|
||||
contactsWithFakeSpoofContact = object : Contacts(context) {
|
||||
override fun getNameForAddress(address: String?): String? {
|
||||
return if ("test@testor.com" == address) {
|
||||
override fun getNameFor(emailAddress: EmailAddress): String? {
|
||||
return if ("test@testor.com" == emailAddress.address) {
|
||||
"Tim@Testor"
|
||||
} else {
|
||||
null
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.fsck.k9.notification
|
||||
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.helper.Contacts
|
||||
|
@ -84,7 +85,9 @@ class K9NotificationStrategy(private val contacts: Contacts) : NotificationStrat
|
|||
return false
|
||||
}
|
||||
|
||||
if (account.isNotifyContactsMailOnly && !contacts.isAnyInContacts(message.from)) {
|
||||
if (account.isNotifyContactsMailOnly &&
|
||||
!contacts.isAnyInContacts(message.from.map { EmailAddress(it.address) })
|
||||
) {
|
||||
Timber.v("No notification: Message is not from a known contact")
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@ package com.fsck.k9.contacts
|
|||
import android.content.ContentResolver
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import com.fsck.k9.helper.Contacts
|
||||
import timber.log.Timber
|
||||
|
||||
internal class ContactPhotoLoader(private val contentResolver: ContentResolver, private val contacts: Contacts) {
|
||||
fun loadContactPhoto(emailAddress: String): Bitmap? {
|
||||
val photoUri = contacts.getPhotoUri(emailAddress) ?: return null
|
||||
val photoUri = contacts.getPhotoUri(EmailAddress(emailAddress)) ?: return null
|
||||
return try {
|
||||
contentResolver.openInputStream(photoUri).use { inputStream ->
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.PendingIntent
|
|||
import android.content.res.Resources
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.controller.MessageReference
|
||||
import com.fsck.k9.helper.ClipboardManager
|
||||
|
@ -103,7 +104,7 @@ internal class MessageDetailsViewModel(
|
|||
Participant(
|
||||
displayName = displayName,
|
||||
emailAddress = emailAddress,
|
||||
contactLookupUri = contacts.getContactUri(emailAddress),
|
||||
contactLookupUri = contacts.getContactUri(EmailAddress(emailAddress)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import android.widget.ImageView
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Account.ShowPictures
|
||||
import com.fsck.k9.helper.Contacts
|
||||
|
@ -261,7 +262,7 @@ class MessageTopView(
|
|||
return false
|
||||
}
|
||||
val senderEmailAddress = getSenderEmailAddress(message) ?: return false
|
||||
return contacts.isInContacts(senderEmailAddress)
|
||||
return contacts.isInContacts(EmailAddress(senderEmailAddress))
|
||||
}
|
||||
|
||||
private fun getSenderEmailAddress(message: Message): String? {
|
||||
|
|
|
@ -528,10 +528,8 @@
|
|||
<ID>ReturnCount:AutocryptDraftStateHeaderParser.kt$AutocryptDraftStateHeaderParser$fun parseAutocryptDraftStateHeader(headerValue: String): AutocryptDraftStateHeader?</ID>
|
||||
<ID>ReturnCount:ChooseFolderActivity.kt$ChooseFolderActivity$private fun decodeArguments(savedInstanceState: Bundle?): Boolean</ID>
|
||||
<ID>ReturnCount:CommandSetFlag.kt$CommandSetFlag$fun setFlag(folderServerId: String, messageServerIds: List<String>, flag: Flag, newState: Boolean)</ID>
|
||||
<ID>ReturnCount:Contacts.kt$Contacts$fun getContactUri(emailAddress: String): Uri?</ID>
|
||||
<ID>ReturnCount:Contacts.kt$Contacts$fun getPhotoUri(address: String): Uri?</ID>
|
||||
<ID>ReturnCount:Contacts.kt$Contacts$fun isAnyInContacts(addresses: Array<Address>?): Boolean</ID>
|
||||
<ID>ReturnCount:Contacts.kt$Contacts$open fun getNameForAddress(address: String?): String?</ID>
|
||||
<ID>ReturnCount:Contacts.kt$Contacts$fun getContactUri(emailAddress: EmailAddress): Uri?</ID>
|
||||
<ID>ReturnCount:Contacts.kt$Contacts$fun getPhotoUri(emailAddress: EmailAddress): Uri?</ID>
|
||||
<ID>ReturnCount:DecoderUtil.kt$DecoderUtil$@JvmStatic fun decodeEncodedWords(body: String, message: Message?): String</ID>
|
||||
<ID>ReturnCount:DecoderUtil.kt$DecoderUtil$private fun extractEncodedWord(body: String, begin: Int, end: Int, message: Message?): EncodedWord?</ID>
|
||||
<ID>ReturnCount:EditIdentity.kt$EditIdentity$override fun onOptionsItemSelected(item: MenuItem): Boolean</ID>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package app.k9mail.core.common.mail
|
||||
|
||||
@JvmInline
|
||||
value class EmailAddress(val address: String) {
|
||||
init {
|
||||
require(address.isNotBlank()) { "Email address must not be blank" }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package app.k9mail.core.common.mail
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFails
|
||||
|
||||
internal class EmailAddressTest {
|
||||
|
||||
@Test
|
||||
fun `should reject blank email address`() {
|
||||
assertFails("Email address must not be blank") {
|
||||
EmailAddress("")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return email address`() {
|
||||
val emailAddress = EmailAddress(EMAIL_ADDRESS)
|
||||
|
||||
val address = emailAddress.address
|
||||
|
||||
assertThat(address).isEqualTo(EMAIL_ADDRESS)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val EMAIL_ADDRESS = "email@example.com"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue