Redesign contact results

This commit is contained in:
MM20
2024-06-23 22:31:11 +02:00
parent 29f8440b24
commit 84582ebbaf
6 changed files with 596 additions and 329 deletions
@@ -14,7 +14,10 @@ import de.mm20.launcher2.icons.TextLayer
import de.mm20.launcher2.ktx.asBitmap
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.Contact
import de.mm20.launcher2.search.ContactInfo
import de.mm20.launcher2.search.ContactApp
import de.mm20.launcher2.search.EmailAddress
import de.mm20.launcher2.search.PhoneNumber
import de.mm20.launcher2.search.PostalAddress
import de.mm20.launcher2.search.SearchableSerializer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -24,7 +27,10 @@ internal data class AndroidContact(
override val firstName: String,
override val lastName: String,
override val displayName: String,
override val contactInfos: Iterable<ContactInfo>,
override val phoneNumbers: List<PhoneNumber>,
override val emailAddresses: List<EmailAddress>,
override val postalAddresses: List<PostalAddress>,
override val contactApps: List<ContactApp>,
internal val lookupKey: String,
override val labelOverride: String? = null,
) : Contact {
@@ -38,7 +44,7 @@ internal data class AndroidContact(
override val summary: String
get() {
return contactInfos.distinctBy { it.label }.take(5).joinToString(separator = ", ") { it.label }
return (phoneNumbers.map { it.number } + emailAddresses.map { it.address }).joinToString(", ")
}
override fun overrideLabel(label: String): Contact {
@@ -1,112 +0,0 @@
package de.mm20.launcher2.contacts
import android.content.Intent
import android.net.Uri
import android.provider.ContactsContract
import de.mm20.launcher2.search.ContactInfo
import de.mm20.launcher2.search.ContactInfoType
import java.net.URLEncoder
internal data class PhoneContactInfo(
val number: String,
) : ContactInfo {
override val label: String
get() = number
override val type: ContactInfoType = ContactInfoType.Phone
override val intent: Intent
get() = Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("tel:$number"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
internal data class MailContactInfo(
val address: String,
) : ContactInfo {
override val label: String
get() = address
override val type: ContactInfoType = ContactInfoType.Email
override val intent: Intent
get() = Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("mailto:$address"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
internal data class PostalContactInfo(
val address: String,
) : ContactInfo {
override val label: String
get() = address.replace("\n", ", ")
override val type: ContactInfoType = ContactInfoType.Postal
override val intent: Intent
get() = Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("geo:0,0?q=${URLEncoder.encode(address, "utf8")}"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
internal data class TelegramContactInfo(
override val label: String,
val userId: String,
) : ContactInfo {
override val type: ContactInfoType = ContactInfoType.Telegram
override val intent: Intent
get() = Intent(Intent.ACTION_VIEW)
.setData(Uri.parse("tg:openmessage?user_id=$userId"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
internal companion object {
const val ItemType = "vnd.android.cursor.item/vnd.org.telegram.messenger.android.profile"
}
}
internal data class SignalContactInfo(
override val label: String,
val dataId: Long,
) : ContactInfo {
override val type: ContactInfoType = ContactInfoType.Signal
override val intent: Intent
get() = Intent(Intent.ACTION_VIEW)
.setData(
Uri.withAppendedPath(
ContactsContract.Data.CONTENT_URI,
dataId.toString()
)
)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
internal companion object {
const val ItemType = "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact"
}
}
internal data class WhatsAppContactInfo(
override val label: String,
val dataId: Long,
) : ContactInfo {
override val type: ContactInfoType = ContactInfoType.Whatsapp
override val intent: Intent
get() = Intent(Intent.ACTION_VIEW)
.setData(
Uri.withAppendedPath(
ContactsContract.Data.CONTENT_URI,
dataId.toString()
)
)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
internal companion object {
const val ItemType = "vnd.android.cursor.item/vnd.com.whatsapp.profile"
}
}
@@ -1,13 +1,19 @@
package de.mm20.launcher2.contacts
import android.content.ContentUris
import android.content.Context
import android.provider.ContactsContract
import androidx.core.database.getLongOrNull
import androidx.core.database.getStringOrNull
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.search.ContactSearchSettings
import de.mm20.launcher2.search.Contact
import de.mm20.launcher2.search.ContactInfo
import de.mm20.launcher2.search.ContactApp
import de.mm20.launcher2.search.ContactInfoType
import de.mm20.launcher2.search.EmailAddress
import de.mm20.launcher2.search.PhoneNumber
import de.mm20.launcher2.search.PostalAddress
import de.mm20.launcher2.search.SearchableRepository
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@@ -51,25 +57,22 @@ internal class ContactRepository(
private suspend fun getWithRawIds(id: Long, rawIds: Set<Long>): Contact? =
withContext(Dispatchers.IO) {
val s = "(" + rawIds.joinToString(separator = " OR ",
transform = { "${ContactsContract.Data.RAW_CONTACT_ID} = $it" }) + ")" +
" AND (${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}\"" +
" OR ${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}\"" +
" OR ${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE}\"" +
" OR ${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}\"" +
" OR ${ContactsContract.Data.MIMETYPE} = \"${TelegramContactInfo.ItemType}\"" +
" OR ${ContactsContract.Data.MIMETYPE} = \"${WhatsAppContactInfo.ItemType}\"" +
" OR ${ContactsContract.Data.MIMETYPE} = \"${SignalContactInfo.ItemType}\"" +
")"
val s = "${ContactsContract.Data.RAW_CONTACT_ID} IN (${rawIds.joinToString(", ")})"
val dataCursor = context.contentResolver.query(
ContactsContract.Data.CONTENT_URI,
null, s, null, null
) ?: return@withContext null
val contactInfos = mutableSetOf<ContactInfo>()
var firstName = ""
var lastName = ""
var displayName = ""
val phoneNumbers = mutableListOf<PhoneNumber>()
val emailAddresses = mutableListOf<EmailAddress>()
val postalAddresses = mutableListOf<PostalAddress>()
val contactApps = mutableListOf<ContactApp>()
val mimeTypeColumn = dataCursor.getColumnIndex(ContactsContract.Data.MIMETYPE)
val typeColumn =
dataCursor.getColumnIndex(ContactsContract.CommonDataKinds.Contactables.TYPE)
val emailAddressColumn =
dataCursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS)
val numberColumn =
@@ -82,25 +85,49 @@ internal class ContactRepository(
dataCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)
val familyNameColumn =
dataCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)
val data1Column = dataCursor.getColumnIndex(ContactsContract.Data.DATA1)
val accountTypeColumn = dataCursor.getColumnIndex(ContactsContract.Data.ACCOUNT_TYPE_AND_DATA_SET)
val data3Column = dataCursor.getColumnIndex(ContactsContract.Data.DATA3)
val idColumn = dataCursor.getColumnIndex(ContactsContract.Data._ID)
loop@ while (dataCursor.moveToNext()) {
when (dataCursor.getStringOrNull(mimeTypeColumn)) {
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE ->
dataCursor.getStringOrNull(emailAddressColumn)?.let {
contactInfos.add(MailContactInfo(it))
emailAddresses += EmailAddress(
it,
when (dataCursor.getInt(typeColumn)) {
ContactsContract.CommonDataKinds.Email.TYPE_HOME -> ContactInfoType.Home
ContactsContract.CommonDataKinds.Email.TYPE_WORK -> ContactInfoType.Work
ContactsContract.CommonDataKinds.Email.TYPE_MOBILE -> ContactInfoType.Mobile
else -> ContactInfoType.Other
}
)
}
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE ->
dataCursor.getStringOrNull(numberColumn)?.let {
val phone = it.replace(Regex("[^+0-9]"), "")
contactInfos.add(PhoneContactInfo(phone))
phoneNumbers += PhoneNumber(
phone,
when (dataCursor.getInt(typeColumn)) {
ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> ContactInfoType.Home
ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> ContactInfoType.Work
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE -> ContactInfoType.Mobile
else -> ContactInfoType.Other
}
)
}
ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE ->
dataCursor.getStringOrNull(addressColumn)?.let {
contactInfos.add(PostalContactInfo(it))
postalAddresses += PostalAddress(
it,
when (dataCursor.getInt(typeColumn)) {
ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME -> ContactInfoType.Home
ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK -> ContactInfoType.Work
else -> ContactInfoType.Other
}
)
}
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> {
@@ -109,34 +136,18 @@ internal class ContactRepository(
displayName = dataCursor.getStringOrNull(displayNameColumn) ?: ""
}
TelegramContactInfo.ItemType -> {
val data1 = dataCursor.getStringOrNull(data1Column)
?: continue@loop
val data3 = dataCursor.getStringOrNull(data3Column)
?: continue@loop
contactInfos.add(
TelegramContactInfo(data3.substringAfterLast(" "), data1)
else -> {
val mimeType = dataCursor.getStringOrNull(mimeTypeColumn) ?: continue
contactApps += ContactApp(
label = dataCursor.getStringOrNull(data3Column) ?: continue,
packageName = dataCursor.getStringOrNull(accountTypeColumn) ?: continue,
mimeType = mimeType,
uri = ContentUris.withAppendedId(
ContactsContract.Data.CONTENT_URI,
dataCursor.getLongOrNull(idColumn) ?: continue
),
)
}
WhatsAppContactInfo.ItemType -> {
val data1 = dataCursor.getStringOrNull(data1Column)
?: continue@loop
val dataId = dataCursor.getLong(idColumn)
contactInfos.add(
WhatsAppContactInfo(
"+${data1.substringBefore('@')}",
dataId
)
)
}
SignalContactInfo.ItemType -> {
val data1 = dataCursor.getStringOrNull(data1Column)
?: continue@loop
val dataId = dataCursor.getLong(idColumn)
contactInfos.add(SignalContactInfo(data1, dataId))
}
}
}
dataCursor.close()
@@ -159,7 +170,10 @@ internal class ContactRepository(
firstName = firstName,
lastName = lastName,
displayName = displayName,
contactInfos = contactInfos,
phoneNumbers = phoneNumbers.distinct(),
emailAddresses = emailAddresses.distinct(),
postalAddresses = postalAddresses.distinct(),
contactApps = contactApps.distinct(),
lookupKey = lookUpKey
)
}