mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-02 06:33:38 +01:00
Improve contact sync for individual contacts.
This commit is contained in:
committed by
Cody Henthorne
parent
e2292dfa34
commit
5478285362
@@ -0,0 +1,31 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class ContactListActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_contact_list)
|
||||
|
||||
val list: RecyclerView = findViewById(R.id.list)
|
||||
val adapter = ContactsAdapter { uri ->
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
data = uri
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
list.layoutManager = LinearLayoutManager(this)
|
||||
list.adapter = adapter
|
||||
|
||||
val viewModel: ContactListViewModel by viewModels()
|
||||
viewModel.contacts.observe(this) { adapter.submitList(it) }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.signal.contactstest
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Application
|
||||
import android.telephony.PhoneNumberUtils
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
@@ -11,10 +12,10 @@ import org.signal.contacts.SystemContactsRepository.ContactIterator
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
class ContactsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
class ContactListViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ContactsViewModel::class.java)
|
||||
private val TAG = Log.tag(ContactListViewModel::class.java)
|
||||
}
|
||||
|
||||
private val _contacts: MutableLiveData<List<ContactDetails>> = MutableLiveData()
|
||||
@@ -30,16 +31,20 @@ class ContactsViewModel(application: Application) : AndroidViewModel(application
|
||||
accountDisplayName = "Test"
|
||||
)
|
||||
|
||||
val startTime: Long = System.currentTimeMillis()
|
||||
|
||||
if (account != null) {
|
||||
val contactList: List<ContactDetails> = SystemContactsRepository.getAllSystemContacts(
|
||||
context = application,
|
||||
e164Formatter = { number -> number }
|
||||
e164Formatter = { number -> PhoneNumberUtils.formatNumberToE164(number, "US") ?: number }
|
||||
).use { it.toList() }
|
||||
|
||||
_contacts.postValue(contactList)
|
||||
} else {
|
||||
Log.w(TAG, "Failed to create an account!")
|
||||
}
|
||||
|
||||
Log.d(TAG, "Took ${System.currentTimeMillis() - startTime} ms to fetch contacts.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class ContactLookupActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_contact_lookup)
|
||||
|
||||
val list: RecyclerView = findViewById(R.id.list)
|
||||
val adapter = ContactsAdapter { uri ->
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
data = uri
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
list.layoutManager = LinearLayoutManager(this)
|
||||
list.adapter = adapter
|
||||
|
||||
val viewModel: ContactLookupViewModel by viewModels()
|
||||
viewModel.contacts.observe(this) { adapter.submitList(it) }
|
||||
|
||||
val lookupText: TextView = findViewById(R.id.lookup_text)
|
||||
val lookupButton: Button = findViewById(R.id.lookup_button)
|
||||
|
||||
lookupButton.setOnClickListener {
|
||||
viewModel.onLookup(lookupText.text.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.accounts.Account
|
||||
import android.app.Application
|
||||
import android.telephony.PhoneNumberUtils
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.signal.contacts.SystemContactsRepository
|
||||
import org.signal.contacts.SystemContactsRepository.ContactDetails
|
||||
import org.signal.contacts.SystemContactsRepository.ContactIterator
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
|
||||
class ContactLookupViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ContactLookupViewModel::class.java)
|
||||
}
|
||||
|
||||
private val _contacts: MutableLiveData<List<ContactDetails>> = MutableLiveData()
|
||||
|
||||
val contacts: LiveData<List<ContactDetails>>
|
||||
get() = _contacts
|
||||
|
||||
fun onLookup(lookup: String) {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val account: Account? = SystemContactsRepository.getOrCreateSystemAccount(
|
||||
context = getApplication(),
|
||||
applicationId = BuildConfig.APPLICATION_ID,
|
||||
accountDisplayName = "Test"
|
||||
)
|
||||
|
||||
val startTime: Long = System.currentTimeMillis()
|
||||
|
||||
if (account != null) {
|
||||
val contactList: List<ContactDetails> = SystemContactsRepository.getContactDetailsByQueries(
|
||||
context = getApplication(),
|
||||
queries = listOf(lookup),
|
||||
e164Formatter = { number -> PhoneNumberUtils.formatNumberToE164(number, "US") ?: number }
|
||||
).use { it.toList() }
|
||||
|
||||
_contacts.postValue(contactList)
|
||||
} else {
|
||||
Log.w(TAG, "Failed to create an account!")
|
||||
}
|
||||
|
||||
Log.d(TAG, "Took ${System.currentTimeMillis() - startTime} ms to fetch contacts.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ContactIterator.toList(): List<ContactDetails> {
|
||||
val list: MutableList<ContactDetails> = mutableListOf()
|
||||
forEach { list += it }
|
||||
return list
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.ContactsContract
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.signal.contacts.SystemContactsRepository.ContactDetails
|
||||
import org.signal.contacts.SystemContactsRepository.ContactPhoneDetails
|
||||
|
||||
class ContactsActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_contacts)
|
||||
|
||||
val list: RecyclerView = findViewById(R.id.list)
|
||||
val adapter = ContactsAdapter()
|
||||
|
||||
list.layoutManager = LinearLayoutManager(this)
|
||||
list.adapter = adapter
|
||||
|
||||
val viewModel: ContactsViewModel by viewModels()
|
||||
viewModel.contacts.observe(this) { adapter.submitList(it) }
|
||||
}
|
||||
|
||||
private inner class ContactsAdapter : ListAdapter<ContactDetails, ContactViewHolder>(object : DiffUtil.ItemCallback<ContactDetails>() {
|
||||
override fun areItemsTheSame(oldItem: ContactDetails, newItem: ContactDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ContactDetails, newItem: ContactDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder {
|
||||
return ContactViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.parent_item, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ContactViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ContactViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val givenName: TextView = itemView.findViewById(R.id.given_name)
|
||||
val familyName: TextView = itemView.findViewById(R.id.family_name)
|
||||
val phoneAdapter: PhoneAdapter = PhoneAdapter()
|
||||
val phoneList: RecyclerView = itemView.findViewById<RecyclerView?>(R.id.phone_list).apply {
|
||||
layoutManager = LinearLayoutManager(itemView.context)
|
||||
adapter = phoneAdapter
|
||||
}
|
||||
|
||||
fun bind(contact: ContactDetails) {
|
||||
givenName.text = "Given Name: ${contact.givenName}"
|
||||
familyName.text = "Family Name: ${contact.familyName}"
|
||||
phoneAdapter.submitList(contact.numbers)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PhoneAdapter : ListAdapter<ContactPhoneDetails, PhoneViewHolder>(object : DiffUtil.ItemCallback<ContactPhoneDetails>() {
|
||||
override fun areItemsTheSame(oldItem: ContactPhoneDetails, newItem: ContactPhoneDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ContactPhoneDetails, newItem: ContactPhoneDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhoneViewHolder {
|
||||
return PhoneViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.child_item, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PhoneViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PhoneViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val photo: ImageView = itemView.findViewById(R.id.contact_photo)
|
||||
val displayName: TextView = itemView.findViewById(R.id.display_name)
|
||||
val number: TextView = itemView.findViewById(R.id.number)
|
||||
val type: TextView = itemView.findViewById(R.id.type)
|
||||
val goButton: View = itemView.findViewById(R.id.go_button)
|
||||
|
||||
fun bind(details: ContactPhoneDetails) {
|
||||
if (details.photoUri != null) {
|
||||
photo.setImageBitmap(BitmapFactory.decodeStream(itemView.context.contentResolver.openInputStream(Uri.parse(details.photoUri))))
|
||||
} else {
|
||||
photo.setImageBitmap(null)
|
||||
}
|
||||
displayName.text = details.displayName
|
||||
number.text = details.number
|
||||
type.text = ContactsContract.CommonDataKinds.Phone.getTypeLabel(itemView.resources, details.type, details.label)
|
||||
goButton.setOnClickListener {
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
data = details.contactUri
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.signal.contacts.SystemContactsRepository
|
||||
|
||||
class ContactsAdapter(private val onContactClickedListener: (Uri) -> Unit) : ListAdapter<SystemContactsRepository.ContactDetails, ContactsAdapter.ContactViewHolder>(object : DiffUtil.ItemCallback<SystemContactsRepository.ContactDetails>() {
|
||||
override fun areItemsTheSame(oldItem: SystemContactsRepository.ContactDetails, newItem: SystemContactsRepository.ContactDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: SystemContactsRepository.ContactDetails, newItem: SystemContactsRepository.ContactDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder {
|
||||
return ContactViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.parent_item, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ContactViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class ContactViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val givenName: TextView = itemView.findViewById(R.id.given_name)
|
||||
private val familyName: TextView = itemView.findViewById(R.id.family_name)
|
||||
private val phoneAdapter: PhoneAdapter = PhoneAdapter(onContactClickedListener)
|
||||
|
||||
init {
|
||||
itemView.findViewById<RecyclerView?>(R.id.phone_list).apply {
|
||||
layoutManager = LinearLayoutManager(itemView.context)
|
||||
adapter = phoneAdapter
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(contact: SystemContactsRepository.ContactDetails) {
|
||||
givenName.text = "Given Name: ${contact.givenName}"
|
||||
familyName.text = "Family Name: ${contact.familyName}"
|
||||
phoneAdapter.submitList(contact.numbers)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,15 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
findViewById<Button>(R.id.contact_list_button).setOnClickListener { v ->
|
||||
if (hasPermission(Manifest.permission.READ_CONTACTS) && hasPermission(Manifest.permission.WRITE_CONTACTS)) {
|
||||
startActivity(Intent(this, ContactsActivity::class.java))
|
||||
finish()
|
||||
startActivity(Intent(this, ContactListActivity::class.java))
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS), PERMISSION_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
findViewById<Button>(R.id.contact_lookup_button).setOnClickListener { v ->
|
||||
if (hasPermission(Manifest.permission.READ_CONTACTS) && hasPermission(Manifest.permission.WRITE_CONTACTS)) {
|
||||
startActivity(Intent(this, ContactLookupActivity::class.java))
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS), PERMISSION_CODE)
|
||||
}
|
||||
@@ -92,12 +99,13 @@ class MainActivity : AppCompatActivity() {
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
if (requestCode == PERMISSION_CODE) {
|
||||
if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
|
||||
startActivity(Intent(this, ContactsActivity::class.java))
|
||||
finish()
|
||||
startActivity(Intent(this, ContactListActivity::class.java))
|
||||
} else {
|
||||
Toast.makeText(this, "You must provide permissions to continue.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
private fun hasPermission(permission: String): Boolean {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.signal.contactstest
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.signal.contacts.SystemContactsRepository
|
||||
|
||||
class PhoneAdapter(private val onContactClickedListener: (Uri) -> Unit) : ListAdapter<SystemContactsRepository.ContactPhoneDetails, PhoneAdapter.PhoneViewHolder>(object : DiffUtil.ItemCallback<SystemContactsRepository.ContactPhoneDetails>() {
|
||||
override fun areItemsTheSame(oldItem: SystemContactsRepository.ContactPhoneDetails, newItem: SystemContactsRepository.ContactPhoneDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: SystemContactsRepository.ContactPhoneDetails, newItem: SystemContactsRepository.ContactPhoneDetails): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhoneViewHolder {
|
||||
return PhoneViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.child_item, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: PhoneViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class PhoneViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
private val photo: ImageView = itemView.findViewById(R.id.contact_photo)
|
||||
private val displayName: TextView = itemView.findViewById(R.id.display_name)
|
||||
private val number: TextView = itemView.findViewById(R.id.number)
|
||||
private val type: TextView = itemView.findViewById(R.id.type)
|
||||
private val goButton: View = itemView.findViewById(R.id.go_button)
|
||||
|
||||
fun bind(details: SystemContactsRepository.ContactPhoneDetails) {
|
||||
if (details.photoUri != null) {
|
||||
photo.setImageBitmap(BitmapFactory.decodeStream(itemView.context.contentResolver.openInputStream(Uri.parse(details.photoUri))))
|
||||
} else {
|
||||
photo.setImageBitmap(null)
|
||||
}
|
||||
displayName.text = details.displayName
|
||||
number.text = details.number
|
||||
type.text = ContactsContract.CommonDataKinds.Phone.getTypeLabel(itemView.resources, details.type, details.label)
|
||||
goButton.setOnClickListener { onContactClickedListener(details.contactUri) }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user