Implement contact plugins
This commit is contained in:
parent
cab93f87aa
commit
02cec92d72
47
.idea/emulatorDisplays.xml
generated
47
.idea/emulatorDisplays.xml
generated
@ -1,47 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="EmulatorDisplays">
|
|
||||||
<option name="displayStateByAvdFolder">
|
|
||||||
<map>
|
|
||||||
<entry key="$USER_HOME$/.android/avd/Pixel_7_API_34.avd">
|
|
||||||
<value>
|
|
||||||
<MultiDisplayState>
|
|
||||||
<option name="displayDescriptors">
|
|
||||||
<list>
|
|
||||||
<DisplayDescriptor>
|
|
||||||
<option name="height" value="2541" />
|
|
||||||
<option name="width" value="1200" />
|
|
||||||
</DisplayDescriptor>
|
|
||||||
<DisplayDescriptor>
|
|
||||||
<option name="displayId" value="1" />
|
|
||||||
<option name="height" value="1920" />
|
|
||||||
<option name="width" value="1080" />
|
|
||||||
</DisplayDescriptor>
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
<option name="panelState">
|
|
||||||
<PanelState>
|
|
||||||
<option name="splitPanel">
|
|
||||||
<SplitPanelState>
|
|
||||||
<option name="proportion" value="0.5263158082962036" />
|
|
||||||
<option name="firstComponent">
|
|
||||||
<PanelState>
|
|
||||||
<option name="displayId" value="0" />
|
|
||||||
</PanelState>
|
|
||||||
</option>
|
|
||||||
<option name="secondComponent">
|
|
||||||
<PanelState>
|
|
||||||
<option name="displayId" value="1" />
|
|
||||||
</PanelState>
|
|
||||||
</option>
|
|
||||||
</SplitPanelState>
|
|
||||||
</option>
|
|
||||||
</PanelState>
|
|
||||||
</option>
|
|
||||||
</MultiDisplayState>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@ -19,6 +19,9 @@ interface Contact : SavableSearchable {
|
|||||||
val postalAddresses: List<PostalAddress>
|
val postalAddresses: List<PostalAddress>
|
||||||
val customActions: List<CustomContactAction>
|
val customActions: List<CustomContactAction>
|
||||||
|
|
||||||
|
override val label: String
|
||||||
|
get() = name
|
||||||
|
|
||||||
val summary: String
|
val summary: String
|
||||||
get() {
|
get() {
|
||||||
return (phoneNumbers.map { it.number } + emailAddresses.map { it.address })
|
return (phoneNumbers.map { it.number } + emailAddresses.map { it.address })
|
||||||
|
|||||||
@ -12,17 +12,41 @@ abstract class ContactPluginContract {
|
|||||||
*/
|
*/
|
||||||
val Id = column<String>("id")
|
val Id = column<String>("id")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uri to view the contact.
|
||||||
|
*/
|
||||||
|
val Uri = column<String>("uri")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The display name of the contact.
|
* The display name of the contact.
|
||||||
* First name + last name, if applicable.
|
* First name + last name, if applicable.
|
||||||
*/
|
*/
|
||||||
val Name = column<String>("name")
|
val Name = column<String>("name")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of phone numbers associated with the contact.
|
||||||
|
*/
|
||||||
val PhoneNumbers = column<List<PhoneNumber>>("phone_numbers")
|
val PhoneNumbers = column<List<PhoneNumber>>("phone_numbers")
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of email addresses associated with the contact.
|
||||||
|
*/
|
||||||
val EmailAddresses = column<List<EmailAddress>>("email_addresses")
|
val EmailAddresses = column<List<EmailAddress>>("email_addresses")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of postal addresses associated with the contact.
|
||||||
|
*/
|
||||||
val PostalAddresses = column<List<PostalAddress>>("postal_addresses")
|
val PostalAddresses = column<List<PostalAddress>>("postal_addresses")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of custom actions associated with the contact.
|
||||||
|
*/
|
||||||
val CustomActions = column<List<CustomContactAction>>("custom_actions")
|
val CustomActions = column<List<CustomContactAction>>("custom_actions")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uri to the contact's photo.
|
||||||
|
*/
|
||||||
val PhotoUri = column<String>("photo_uri")
|
val PhotoUri = column<String>("photo_uri")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -40,6 +40,7 @@ dependencies {
|
|||||||
|
|
||||||
|
|
||||||
implementation(libs.koin.android)
|
implementation(libs.koin.android)
|
||||||
|
implementation(libs.coil.core)
|
||||||
|
|
||||||
implementation(project(":core:ktx"))
|
implementation(project(":core:ktx"))
|
||||||
implementation(project(":core:base"))
|
implementation(project(":core:base"))
|
||||||
|
|||||||
@ -1,18 +1,46 @@
|
|||||||
package de.mm20.launcher2.contacts
|
package de.mm20.launcher2.contacts
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.net.toUri
|
||||||
import de.mm20.launcher2.contacts.providers.AndroidContact
|
import de.mm20.launcher2.contacts.providers.AndroidContact
|
||||||
import de.mm20.launcher2.contacts.providers.AndroidContactProvider
|
import de.mm20.launcher2.contacts.providers.AndroidContactProvider
|
||||||
|
import de.mm20.launcher2.contacts.providers.PluginContact
|
||||||
|
import de.mm20.launcher2.contacts.providers.PluginContactProvider
|
||||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
|
import de.mm20.launcher2.plugin.PluginRepository
|
||||||
|
import de.mm20.launcher2.plugin.config.StorageStrategy
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.SearchableDeserializer
|
import de.mm20.launcher2.search.SearchableDeserializer
|
||||||
import de.mm20.launcher2.search.SearchableSerializer
|
import de.mm20.launcher2.search.SearchableSerializer
|
||||||
import kotlinx.coroutines.flow.first
|
import de.mm20.launcher2.search.UpdateResult
|
||||||
|
import de.mm20.launcher2.search.asUpdateResult
|
||||||
|
import de.mm20.launcher2.search.contact.CustomContactAction
|
||||||
|
import de.mm20.launcher2.search.contact.EmailAddress
|
||||||
|
import de.mm20.launcher2.search.contact.PhoneNumber
|
||||||
|
import de.mm20.launcher2.search.contact.PostalAddress
|
||||||
|
import de.mm20.launcher2.serialization.Json
|
||||||
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
internal class ContactSerializer : SearchableSerializer {
|
@Serializable
|
||||||
|
internal data class SerializedPluginContact(
|
||||||
|
val id: String? = null,
|
||||||
|
val uri: String? = null,
|
||||||
|
val name: String? = null,
|
||||||
|
val phoneNumbers: List<PhoneNumber>? = null,
|
||||||
|
val emailAddresses: List<EmailAddress>? = null,
|
||||||
|
val postalAddresses: List<PostalAddress>? = null,
|
||||||
|
val customActions: List<CustomContactAction>? = null,
|
||||||
|
val photoUri: String? = null,
|
||||||
|
val authority: String? = null,
|
||||||
|
val strategy: StorageStrategy = StorageStrategy.StoreCopy,
|
||||||
|
val timestamp: Long = 0L,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal class AndroidContactSerializer : SearchableSerializer {
|
||||||
override fun serialize(searchable: SavableSearchable): String {
|
override fun serialize(searchable: SavableSearchable): String {
|
||||||
searchable as AndroidContact
|
searchable as AndroidContact
|
||||||
return jsonObjectOf(
|
return jsonObjectOf(
|
||||||
@ -21,10 +49,45 @@ internal class ContactSerializer : SearchableSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val typePrefix: String
|
override val typePrefix: String
|
||||||
get() = "contact"
|
get() = AndroidContact.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ContactDeserializer(
|
|
||||||
|
internal class PluginContactSerializer : SearchableSerializer {
|
||||||
|
override fun serialize(searchable: SavableSearchable): String {
|
||||||
|
searchable as PluginContact
|
||||||
|
if (searchable.storageStrategy == StorageStrategy.StoreReference) {
|
||||||
|
return Json.Lenient.encodeToString(
|
||||||
|
SerializedPluginContact(
|
||||||
|
id = searchable.id,
|
||||||
|
authority = searchable.authority,
|
||||||
|
strategy = searchable.storageStrategy,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Json.Lenient.encodeToString(
|
||||||
|
SerializedPluginContact(
|
||||||
|
id = searchable.id,
|
||||||
|
uri = searchable.uri.toString(),
|
||||||
|
name = searchable.name,
|
||||||
|
phoneNumbers = searchable.phoneNumbers,
|
||||||
|
emailAddresses = searchable.emailAddresses,
|
||||||
|
postalAddresses = searchable.postalAddresses,
|
||||||
|
customActions = searchable.customActions,
|
||||||
|
photoUri = searchable.photoUri?.toString(),
|
||||||
|
authority = searchable.authority,
|
||||||
|
strategy = searchable.storageStrategy,
|
||||||
|
timestamp = searchable.timestamp,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val typePrefix: String
|
||||||
|
get() = PluginContact.Domain
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class AndroidContactDeserializer(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val permissionsManager: PermissionsManager
|
private val permissionsManager: PermissionsManager
|
||||||
) : SearchableDeserializer {
|
) : SearchableDeserializer {
|
||||||
@ -37,4 +100,47 @@ internal class ContactDeserializer(
|
|||||||
|
|
||||||
return androidContactProvider.get(id)
|
return androidContactProvider.get(id)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class PluginContactDeserializer(
|
||||||
|
private val context: Context,
|
||||||
|
private val pluginRepository: PluginRepository,
|
||||||
|
): SearchableDeserializer {
|
||||||
|
override suspend fun deserialize(serialized: String): SavableSearchable? {
|
||||||
|
val json = Json.Lenient.decodeFromString<SerializedPluginContact>(serialized)
|
||||||
|
val authority = json.authority ?: return null
|
||||||
|
val id = json.id ?: return null
|
||||||
|
val strategy = json.strategy
|
||||||
|
val plugin = pluginRepository.get(authority).firstOrNull() ?: return null
|
||||||
|
if (!plugin.enabled) return null
|
||||||
|
|
||||||
|
return when(strategy) {
|
||||||
|
StorageStrategy.StoreReference -> {
|
||||||
|
PluginContactProvider(context, authority).get(id).getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val timestamp = json.timestamp
|
||||||
|
|
||||||
|
PluginContact(
|
||||||
|
id = id,
|
||||||
|
name = json.name ?: return null,
|
||||||
|
uri = json.uri?.toUri() ?: return null,
|
||||||
|
phoneNumbers = json.phoneNumbers ?: emptyList(),
|
||||||
|
emailAddresses = json.emailAddresses ?: emptyList(),
|
||||||
|
postalAddresses = json.postalAddresses ?: emptyList(),
|
||||||
|
customActions = json.customActions ?: emptyList(),
|
||||||
|
photoUri = json.photoUri?.toUri(),
|
||||||
|
authority = authority,
|
||||||
|
storageStrategy = strategy,
|
||||||
|
timestamp = timestamp,
|
||||||
|
updatedSelf = {
|
||||||
|
if (it !is PluginContact) UpdateResult.TemporarilyUnavailable()
|
||||||
|
else PluginContactProvider(context, authority).refresh(it, timestamp).asUpdateResult()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package de.mm20.launcher2.contacts
|
package de.mm20.launcher2.contacts
|
||||||
|
|
||||||
import de.mm20.launcher2.contacts.providers.AndroidContact
|
import de.mm20.launcher2.contacts.providers.AndroidContact
|
||||||
|
import de.mm20.launcher2.contacts.providers.PluginContact
|
||||||
import de.mm20.launcher2.search.Contact
|
import de.mm20.launcher2.search.Contact
|
||||||
import de.mm20.launcher2.search.SearchableDeserializer
|
import de.mm20.launcher2.search.SearchableDeserializer
|
||||||
import de.mm20.launcher2.search.SearchableRepository
|
import de.mm20.launcher2.search.SearchableRepository
|
||||||
@ -11,5 +12,6 @@ import org.koin.dsl.module
|
|||||||
val contactsModule = module {
|
val contactsModule = module {
|
||||||
factory { ContactRepository(androidContext(), get(), get()) }
|
factory { ContactRepository(androidContext(), get(), get()) }
|
||||||
factory<SearchableRepository<Contact>>(named<Contact>()) { get<ContactRepository>() }
|
factory<SearchableRepository<Contact>>(named<Contact>()) { get<ContactRepository>() }
|
||||||
factory<SearchableDeserializer>(named(AndroidContact.Domain)) { ContactDeserializer(androidContext(), get()) }
|
factory<SearchableDeserializer>(named(AndroidContact.Domain)) { AndroidContactDeserializer(androidContext(), get()) }
|
||||||
|
factory<SearchableDeserializer>(named(PluginContact.Domain)) { PluginContactDeserializer(androidContext(), get()) }
|
||||||
}
|
}
|
||||||
@ -6,7 +6,7 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.ContactsContract
|
import android.provider.ContactsContract
|
||||||
import androidx.core.graphics.drawable.toDrawable
|
import androidx.core.graphics.drawable.toDrawable
|
||||||
import de.mm20.launcher2.contacts.ContactSerializer
|
import de.mm20.launcher2.contacts.AndroidContactSerializer
|
||||||
import de.mm20.launcher2.icons.ColorLayer
|
import de.mm20.launcher2.icons.ColorLayer
|
||||||
import de.mm20.launcher2.icons.LauncherIcon
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
import de.mm20.launcher2.icons.StaticIconLayer
|
import de.mm20.launcher2.icons.StaticIconLayer
|
||||||
@ -36,7 +36,6 @@ internal data class AndroidContact(
|
|||||||
override val domain: String = Domain
|
override val domain: String = Domain
|
||||||
override val key: String
|
override val key: String
|
||||||
get() = "$Domain://$id"
|
get() = "$Domain://$id"
|
||||||
override val label: String = name
|
|
||||||
|
|
||||||
override val summary: String
|
override val summary: String
|
||||||
get() {
|
get() {
|
||||||
@ -76,7 +75,7 @@ internal data class AndroidContact(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getSerializer(): SearchableSerializer {
|
override fun getSerializer(): SearchableSerializer {
|
||||||
return ContactSerializer()
|
return AndroidContactSerializer()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import android.provider.ContactsContract
|
|||||||
import android.telephony.PhoneNumberUtils
|
import android.telephony.PhoneNumberUtils
|
||||||
import androidx.core.database.getLongOrNull
|
import androidx.core.database.getLongOrNull
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
import de.mm20.launcher2.contacts.providers.AndroidContact
|
|
||||||
import de.mm20.launcher2.ktx.distinctByEquality
|
import de.mm20.launcher2.ktx.distinctByEquality
|
||||||
import de.mm20.launcher2.search.Contact
|
import de.mm20.launcher2.search.Contact
|
||||||
import de.mm20.launcher2.search.contact.ContactInfoType
|
import de.mm20.launcher2.search.contact.ContactInfoType
|
||||||
@ -21,7 +20,7 @@ import kotlinx.coroutines.withContext
|
|||||||
/**
|
/**
|
||||||
* A contact provider that uses the Android ContactsContract API to search for contacts.
|
* A contact provider that uses the Android ContactsContract API to search for contacts.
|
||||||
*/
|
*/
|
||||||
class AndroidContactProvider(
|
internal class AndroidContactProvider(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
) : ContactProvider {
|
) : ContactProvider {
|
||||||
override suspend fun search(
|
override suspend fun search(
|
||||||
|
|||||||
@ -2,6 +2,6 @@ package de.mm20.launcher2.contacts.providers
|
|||||||
|
|
||||||
import de.mm20.launcher2.search.Contact
|
import de.mm20.launcher2.search.Contact
|
||||||
|
|
||||||
interface ContactProvider {
|
internal interface ContactProvider {
|
||||||
suspend fun search(query: String, allowNetwork: Boolean): List<Contact>
|
suspend fun search(query: String, allowNetwork: Boolean): List<Contact>
|
||||||
}
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package de.mm20.launcher2.contacts.providers
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import coil.imageLoader
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import de.mm20.launcher2.contacts.PluginContactSerializer
|
||||||
|
import de.mm20.launcher2.icons.ColorLayer
|
||||||
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
|
import de.mm20.launcher2.icons.StaticIconLayer
|
||||||
|
import de.mm20.launcher2.icons.StaticLauncherIcon
|
||||||
|
import de.mm20.launcher2.ktx.tryStartActivity
|
||||||
|
import de.mm20.launcher2.plugin.config.StorageStrategy
|
||||||
|
import de.mm20.launcher2.search.Contact
|
||||||
|
import de.mm20.launcher2.search.File
|
||||||
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
|
import de.mm20.launcher2.search.SearchableSerializer
|
||||||
|
import de.mm20.launcher2.search.UpdatableSearchable
|
||||||
|
import de.mm20.launcher2.search.UpdateResult
|
||||||
|
import de.mm20.launcher2.search.contact.CustomContactAction
|
||||||
|
import de.mm20.launcher2.search.contact.EmailAddress
|
||||||
|
import de.mm20.launcher2.search.contact.PhoneNumber
|
||||||
|
import de.mm20.launcher2.search.contact.PostalAddress
|
||||||
|
|
||||||
|
internal data class PluginContact(
|
||||||
|
val id: String,
|
||||||
|
val uri: Uri,
|
||||||
|
override val name: String,
|
||||||
|
override val phoneNumbers: List<PhoneNumber>,
|
||||||
|
override val emailAddresses: List<EmailAddress>,
|
||||||
|
override val postalAddresses: List<PostalAddress>,
|
||||||
|
override val customActions: List<CustomContactAction>,
|
||||||
|
val photoUri: Uri?,
|
||||||
|
override val labelOverride: String? = null,
|
||||||
|
val authority: String,
|
||||||
|
val storageStrategy: StorageStrategy,
|
||||||
|
override val timestamp: Long,
|
||||||
|
override val updatedSelf: (suspend (SavableSearchable) -> UpdateResult<Contact>)?
|
||||||
|
) : Contact, UpdatableSearchable<Contact> {
|
||||||
|
override val domain: String = Domain
|
||||||
|
override fun getSerializer(): SearchableSerializer {
|
||||||
|
return PluginContactSerializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val key: String = "$domain://$authority:$id"
|
||||||
|
override fun overrideLabel(label: String): SavableSearchable {
|
||||||
|
return copy(labelOverride = label)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun launch(
|
||||||
|
context: Context,
|
||||||
|
options: Bundle?
|
||||||
|
): Boolean {
|
||||||
|
return context.tryStartActivity(
|
||||||
|
Intent(
|
||||||
|
Intent.ACTION_VIEW
|
||||||
|
).apply {
|
||||||
|
data = uri
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}, options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadIcon(context: Context, size: Int, themed: Boolean): LauncherIcon? {
|
||||||
|
if (photoUri != null) {
|
||||||
|
val request = ImageRequest.Builder(context)
|
||||||
|
.data(photoUri)
|
||||||
|
.size(size)
|
||||||
|
.build()
|
||||||
|
val result = context.imageLoader.execute(request)
|
||||||
|
val drawable = result.drawable ?: return null
|
||||||
|
return StaticLauncherIcon(
|
||||||
|
foregroundLayer = StaticIconLayer(icon = drawable),
|
||||||
|
backgroundLayer = ColorLayer(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return super.loadIcon(context, size, themed)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val Domain = "plugin.contact"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
package de.mm20.launcher2.contacts.providers
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import de.mm20.launcher2.plugin.PluginApi
|
||||||
|
import de.mm20.launcher2.plugin.QueryPluginApi
|
||||||
|
import de.mm20.launcher2.plugin.config.QueryPluginConfig
|
||||||
|
import de.mm20.launcher2.plugin.contracts.ContactPluginContract
|
||||||
|
import de.mm20.launcher2.plugin.contracts.ContactPluginContract.ContactColumns
|
||||||
|
import de.mm20.launcher2.plugin.contracts.SearchPluginContract
|
||||||
|
import de.mm20.launcher2.plugin.data.set
|
||||||
|
import de.mm20.launcher2.plugin.data.withColumns
|
||||||
|
import de.mm20.launcher2.search.UpdateResult
|
||||||
|
import de.mm20.launcher2.search.asUpdateResult
|
||||||
|
|
||||||
|
internal class PluginContactProvider(
|
||||||
|
private val context: Context,
|
||||||
|
private val authority: String,
|
||||||
|
): QueryPluginApi<String, PluginContact>(context, authority), ContactProvider {
|
||||||
|
|
||||||
|
private fun getPluginConfig(): QueryPluginConfig? {
|
||||||
|
return PluginApi(authority, context.contentResolver).getSearchPluginConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun Uri.Builder.appendQueryParameters(query: String): Uri.Builder {
|
||||||
|
return appendQueryParameter(SearchPluginContract.Params.Query, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun Cursor.getData(): List<PluginContact>? {
|
||||||
|
val config = getPluginConfig()
|
||||||
|
val cursor = this
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
Log.e("MM20", "Plugin $authority returned null config")
|
||||||
|
cursor.close()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val results = mutableListOf<PluginContact>()
|
||||||
|
val timestamp = System.currentTimeMillis()
|
||||||
|
cursor.withColumns(ContactColumns) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
results.add(
|
||||||
|
PluginContact(
|
||||||
|
id = cursor[ContactColumns.Id] ?: continue,
|
||||||
|
name = cursor[ContactColumns.Name] ?: continue,
|
||||||
|
uri = Uri.parse(cursor[ContactColumns.Uri] ?: continue),
|
||||||
|
phoneNumbers = cursor[ContactColumns.PhoneNumbers] ?: emptyList(),
|
||||||
|
emailAddresses = cursor[ContactColumns.EmailAddresses] ?: emptyList(),
|
||||||
|
postalAddresses = cursor[ContactColumns.PostalAddresses] ?: emptyList(),
|
||||||
|
customActions = cursor[ContactColumns.CustomActions] ?: emptyList(),
|
||||||
|
photoUri = cursor[ContactColumns.PhotoUri]?.let { Uri.parse(it) },
|
||||||
|
authority = authority,
|
||||||
|
storageStrategy = config.storageStrategy,
|
||||||
|
timestamp = timestamp,
|
||||||
|
updatedSelf = {
|
||||||
|
if (it !is PluginContact) UpdateResult.TemporarilyUnavailable()
|
||||||
|
else refresh(it, timestamp).asUpdateResult()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun PluginContact.toBundle(): Bundle {
|
||||||
|
return Bundle().apply {
|
||||||
|
set(ContactColumns.Id, id)
|
||||||
|
set(ContactColumns.Uri, uri.toString())
|
||||||
|
set(ContactColumns.Name, name)
|
||||||
|
set(ContactColumns.PhoneNumbers, phoneNumbers)
|
||||||
|
set(ContactColumns.EmailAddresses, emailAddresses)
|
||||||
|
set(ContactColumns.PostalAddresses, postalAddresses)
|
||||||
|
set(ContactColumns.CustomActions, customActions)
|
||||||
|
set(ContactColumns.PhotoUri, photoUri?.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -70,11 +70,12 @@ data class PluginFile(
|
|||||||
if (thumbnailUri != null) {
|
if (thumbnailUri != null) {
|
||||||
val request = ImageRequest.Builder(context)
|
val request = ImageRequest.Builder(context)
|
||||||
.data(thumbnailUri)
|
.data(thumbnailUri)
|
||||||
|
.size(size)
|
||||||
.build()
|
.build()
|
||||||
val result = context.imageLoader.execute(request)
|
val result = context.imageLoader.execute(request)
|
||||||
val drawable = result.drawable ?: return null
|
val drawable = result.drawable ?: return null
|
||||||
return StaticLauncherIcon(
|
return StaticLauncherIcon(
|
||||||
foregroundLayer = StaticIconLayer(icon = drawable, scale = 1.5f),
|
foregroundLayer = StaticIconLayer(icon = drawable),
|
||||||
backgroundLayer = ColorLayer(),
|
backgroundLayer = ColorLayer(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,11 @@ data class Contact(
|
|||||||
* A unique and stable identifier for this contact.
|
* A unique and stable identifier for this contact.
|
||||||
*/
|
*/
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URI to view this contact.
|
||||||
|
*/
|
||||||
|
val uri: Uri,
|
||||||
/**
|
/**
|
||||||
* The display name for this contact.
|
* The display name for this contact.
|
||||||
* First name + last name, if applicable.
|
* First name + last name, if applicable.
|
||||||
|
|||||||
@ -22,6 +22,7 @@ abstract class ContactProvider(
|
|||||||
override fun List<Contact>.toCursor(): Cursor {
|
override fun List<Contact>.toCursor(): Cursor {
|
||||||
return buildCursor(ContactColumns, this) {
|
return buildCursor(ContactColumns, this) {
|
||||||
put(ContactColumns.Id, it.id)
|
put(ContactColumns.Id, it.id)
|
||||||
|
put(ContactColumns.Uri, it.uri.toString())
|
||||||
put(ContactColumns.Name, it.name)
|
put(ContactColumns.Name, it.name)
|
||||||
put(ContactColumns.PhoneNumbers, it.phoneNumbers)
|
put(ContactColumns.PhoneNumbers, it.phoneNumbers)
|
||||||
put(ContactColumns.EmailAddresses, it.emailAddresses)
|
put(ContactColumns.EmailAddresses, it.emailAddresses)
|
||||||
@ -35,6 +36,7 @@ abstract class ContactProvider(
|
|||||||
return Contact(
|
return Contact(
|
||||||
id = get(ContactColumns.Id) ?: return null,
|
id = get(ContactColumns.Id) ?: return null,
|
||||||
name = get(ContactColumns.Name) ?: return null,
|
name = get(ContactColumns.Name) ?: return null,
|
||||||
|
uri = Uri.parse(get(ContactColumns.Uri) ?: return null),
|
||||||
phoneNumbers = get(ContactColumns.PhoneNumbers) ?: emptyList(),
|
phoneNumbers = get(ContactColumns.PhoneNumbers) ?: emptyList(),
|
||||||
emailAddresses = get(ContactColumns.EmailAddresses) ?: emptyList(),
|
emailAddresses = get(ContactColumns.EmailAddresses) ?: emptyList(),
|
||||||
postalAddresses = get(ContactColumns.PostalAddresses) ?: emptyList(),
|
postalAddresses = get(ContactColumns.PostalAddresses) ?: emptyList(),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user