| @@ -45,7 +45,7 @@ ext { | |||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation 'com.simplemobiletools:commons:3.19.21' |     implementation 'com.simplemobiletools:commons:3.20.6' | ||||||
|     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' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ | |||||||
|     <uses-permission android:name="android.permission.READ_CONTACTS"/> |     <uses-permission android:name="android.permission.READ_CONTACTS"/> | ||||||
|     <uses-permission android:name="android.permission.WRITE_CONTACTS"/> |     <uses-permission android:name="android.permission.WRITE_CONTACTS"/> | ||||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> |     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||||||
|  |     <uses-permission android:name="android.permission.GET_ACCOUNTS"/> | ||||||
|  |     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/> | ||||||
|  |  | ||||||
|     <uses-permission |     <uses-permission | ||||||
|         android:name="android.permission.USE_FINGERPRINT" |         android:name="android.permission.USE_FINGERPRINT" | ||||||
| @@ -26,8 +28,6 @@ | |||||||
|             android:name=".activities.SplashActivity" |             android:name=".activities.SplashActivity" | ||||||
|             android:theme="@style/SplashTheme"> |             android:theme="@style/SplashTheme"> | ||||||
|             <intent-filter> |             <intent-filter> | ||||||
|                 <action android:name="android.intent.action.MAIN"/> |  | ||||||
|  |  | ||||||
|                 <category android:name="android.intent.category.LAUNCHER"/> |                 <category android:name="android.intent.category.LAUNCHER"/> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </activity> |         </activity> | ||||||
| @@ -190,5 +190,30 @@ | |||||||
|                 android:name="android.support.FILE_PROVIDER_PATHS" |                 android:name="android.support.FILE_PROVIDER_PATHS" | ||||||
|                 android:resource="@xml/provider_paths"/> |                 android:resource="@xml/provider_paths"/> | ||||||
|         </provider> |         </provider> | ||||||
|  |  | ||||||
|  |         <!-- Do not append ".Orange" to the default alias "name", it would remove the old homescreen launcher of users at upgrade --> | ||||||
|  |         <activity-alias | ||||||
|  |             android:name=".activities.SplashActivity" | ||||||
|  |             android:enabled="true" | ||||||
|  |             android:icon="@mipmap/ic_launcher" | ||||||
|  |             android:roundIcon="@mipmap/ic_launcher" | ||||||
|  |             android:targetActivity=".activities.SplashActivity"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.MAIN"/> | ||||||
|  |                 <category android:name="android.intent.category.LAUNCHER"/> | ||||||
|  |             </intent-filter> | ||||||
|  |         </activity-alias> | ||||||
|  |  | ||||||
|  |         <activity-alias | ||||||
|  |             android:name=".activities.SplashActivity.Red" | ||||||
|  |             android:enabled="false" | ||||||
|  |             android:icon="@mipmap/ic_launcher_red" | ||||||
|  |             android:roundIcon="@mipmap/ic_launcher_red" | ||||||
|  |             android:targetActivity=".activities.SplashActivity"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.MAIN"/> | ||||||
|  |                 <category android:name="android.intent.category.LAUNCHER"/> | ||||||
|  |             </intent-filter> | ||||||
|  |         </activity-alias> | ||||||
|     </application> |     </application> | ||||||
| </manifest> | </manifest> | ||||||
|   | |||||||
| @@ -87,6 +87,7 @@ class EditContactActivity : ContactActivity() { | |||||||
|         if (wasActivityInitialized) { |         if (wasActivityInitialized) { | ||||||
|             menu.findItem(R.id.delete).isVisible = contact?.id != 0 |             menu.findItem(R.id.delete).isVisible = contact?.id != 0 | ||||||
|             menu.findItem(R.id.share).isVisible = contact?.id != 0 |             menu.findItem(R.id.share).isVisible = contact?.id != 0 | ||||||
|  |             menu.findItem(R.id.open_with).isVisible = contact?.id != 0 && contact?.source != SMT_PRIVATE | ||||||
|         } |         } | ||||||
|         return true |         return true | ||||||
|     } |     } | ||||||
| @@ -94,8 +95,9 @@ class EditContactActivity : ContactActivity() { | |||||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { |     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||||
|         when (item.itemId) { |         when (item.itemId) { | ||||||
|             R.id.save -> saveContact() |             R.id.save -> saveContact() | ||||||
|             R.id.delete -> deleteContact() |  | ||||||
|             R.id.share -> shareContact() |             R.id.share -> shareContact() | ||||||
|  |             R.id.open_with -> openWith() | ||||||
|  |             R.id.delete -> deleteContact() | ||||||
|             else -> return super.onOptionsItemSelected(item) |             else -> return super.onOptionsItemSelected(item) | ||||||
|         } |         } | ||||||
|         return true |         return true | ||||||
| @@ -224,6 +226,14 @@ class EditContactActivity : ContactActivity() { | |||||||
|         invalidateOptionsMenu() |         invalidateOptionsMenu() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun openWith() { | ||||||
|  |         Intent().apply { | ||||||
|  |             action = Intent.ACTION_EDIT | ||||||
|  |             data = getContactPublicUri(contact!!) | ||||||
|  |             startActivity(this) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun startCropPhotoIntent(uri: Uri?) { |     private fun startCropPhotoIntent(uri: Uri?) { | ||||||
|         if (uri == null) { |         if (uri == null) { | ||||||
|             toast(R.string.unknown_error_occurred) |             toast(R.string.unknown_error_occurred) | ||||||
|   | |||||||
| @@ -234,16 +234,6 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { | |||||||
|  |  | ||||||
|     private fun storeLocalAccountData() { |     private fun storeLocalAccountData() { | ||||||
|         if (config.localAccountType == "-1") { |         if (config.localAccountType == "-1") { | ||||||
|             // some manufacturer contact account types from https://stackoverflow.com/a/44802016/1967672 |  | ||||||
|             val localAccountTypes = arrayListOf("vnd.sec.contact.phone", |  | ||||||
|                     "com.htc.android.pcsc", |  | ||||||
|                     "com.sonyericsson.localcontacts", |  | ||||||
|                     "com.lge.sync", |  | ||||||
|                     "com.lge.phone", |  | ||||||
|                     "vnd.tmobileus.contact.phone", |  | ||||||
|                     "com.android.huawei.phone", |  | ||||||
|                     "Local Phone Account") |  | ||||||
|  |  | ||||||
|             ContactsHelper(this).getContactSources { |             ContactsHelper(this).getContactSources { | ||||||
|                 var localAccountType = "" |                 var localAccountType = "" | ||||||
|                 var localAccountName = "" |                 var localAccountName = "" | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ import com.simplemobiletools.contacts.adapters.SelectContactsAdapter | |||||||
| import com.simplemobiletools.contacts.dialogs.ChangeSortingDialog | import com.simplemobiletools.contacts.dialogs.ChangeSortingDialog | ||||||
| import com.simplemobiletools.contacts.dialogs.FilterContactSourcesDialog | import com.simplemobiletools.contacts.dialogs.FilterContactSourcesDialog | ||||||
| import com.simplemobiletools.contacts.extensions.config | import com.simplemobiletools.contacts.extensions.config | ||||||
|  | import com.simplemobiletools.contacts.extensions.getContactPublicUri | ||||||
| import com.simplemobiletools.contacts.helpers.ContactsHelper | import com.simplemobiletools.contacts.helpers.ContactsHelper | ||||||
| import com.simplemobiletools.contacts.helpers.SMT_PRIVATE | import com.simplemobiletools.contacts.helpers.SMT_PRIVATE | ||||||
| import com.simplemobiletools.contacts.models.Contact | import com.simplemobiletools.contacts.models.Contact | ||||||
| @@ -98,9 +99,7 @@ class SelectContactActivity : SimpleActivity() { | |||||||
|             } as ArrayList<Contact> |             } as ArrayList<Contact> | ||||||
|  |  | ||||||
|             val contactSources = config.displayContactSources |             val contactSources = config.displayContactSources | ||||||
|             if (!config.showAllContacts()) { |  | ||||||
|             contacts = contacts.filter { contactSources.contains(it.source) } as ArrayList<Contact> |             contacts = contacts.filter { contactSources.contains(it.source) } as ArrayList<Contact> | ||||||
|             } |  | ||||||
|  |  | ||||||
|             Contact.sorting = config.sorting |             Contact.sorting = config.sorting | ||||||
|             Contact.startWithSurname = config.startNameWithSurname |             Contact.startWithSurname = config.startNameWithSurname | ||||||
| @@ -135,8 +134,7 @@ class SelectContactActivity : SimpleActivity() { | |||||||
|                 Uri.withAppendedPath(ContactsContract.Data.CONTENT_URI, contactId) |                 Uri.withAppendedPath(ContactsContract.Data.CONTENT_URI, contactId) | ||||||
|             } |             } | ||||||
|             else -> { |             else -> { | ||||||
|                 val lookupKey = ContactsHelper(this).getContactLookupKey(contact.id.toString()) |                 getContactPublicUri(contact) | ||||||
|                 Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey) |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,5 +1,13 @@ | |||||||
| package com.simplemobiletools.contacts.activities | package com.simplemobiletools.contacts.activities | ||||||
|  |  | ||||||
| import com.simplemobiletools.commons.activities.BaseSimpleActivity | import com.simplemobiletools.commons.activities.BaseSimpleActivity | ||||||
|  | import com.simplemobiletools.contacts.R | ||||||
|  |  | ||||||
| open class SimpleActivity : BaseSimpleActivity() | open class SimpleActivity : BaseSimpleActivity() { | ||||||
|  |     override fun getAppIconIDs() = arrayListOf( | ||||||
|  |             R.mipmap.ic_launcher_red, | ||||||
|  |             R.mipmap.ic_launcher | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     override fun getAppLauncherName() = getString(R.string.app_launcher_name) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -49,13 +49,18 @@ class ViewContactActivity : ContactActivity() { | |||||||
|  |  | ||||||
|     override fun onCreateOptionsMenu(menu: Menu): Boolean { |     override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||||||
|         menuInflater.inflate(R.menu.menu_view_contact, menu) |         menuInflater.inflate(R.menu.menu_view_contact, menu) | ||||||
|  |         menu.apply { | ||||||
|  |             findItem(R.id.open_with).isVisible = contact?.source != SMT_PRIVATE | ||||||
|  |         } | ||||||
|         return true |         return true | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { |     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||||
|         when (item.itemId) { |         when (item.itemId) { | ||||||
|             R.id.edit -> editContact(contact!!) |             R.id.edit -> editContact(contact!!) | ||||||
|             R.id.share -> shareContact() |             R.id.share -> shareContact() | ||||||
|  |             R.id.open_with -> openWith() | ||||||
|             R.id.delete -> deleteContact() |             R.id.delete -> deleteContact() | ||||||
|             else -> return super.onOptionsItemSelected(item) |             else -> return super.onOptionsItemSelected(item) | ||||||
|         } |         } | ||||||
| @@ -151,6 +156,14 @@ class ViewContactActivity : ContactActivity() { | |||||||
|         setupContactSource() |         setupContactSource() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun openWith() { | ||||||
|  |         Intent().apply { | ||||||
|  |             action = ContactsContract.QuickContact.ACTION_QUICK_CONTACT | ||||||
|  |             data = getContactPublicUri(contact!!) | ||||||
|  |             startActivity(this) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun setupFavorite() { |     private fun setupFavorite() { | ||||||
|         contact_toggle_favorite.apply { |         contact_toggle_favorite.apply { | ||||||
|             beVisible() |             beVisible() | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ class FilterContactSourcesAdapter(val activity: SimpleActivity, private val cont | |||||||
|  |  | ||||||
|     init { |     init { | ||||||
|         contactSources.forEachIndexed { index, contactSource -> |         contactSources.forEachIndexed { index, contactSource -> | ||||||
|             if (activity.config.showAllContacts() || displayContactSources.contains(contactSource.name)) { |             if (displayContactSources.contains(contactSource.name)) { | ||||||
|                 selectedPositions.add(index) |                 selectedPositions.add(index) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -19,9 +19,7 @@ class SelectContactsDialog(val activity: SimpleActivity, initialContacts: ArrayL | |||||||
|         var allContacts = initialContacts |         var allContacts = initialContacts | ||||||
|         if (selectContacts == null) { |         if (selectContacts == null) { | ||||||
|             val contactSources = activity.config.displayContactSources |             val contactSources = activity.config.displayContactSources | ||||||
|             if (!activity.config.showAllContacts()) { |  | ||||||
|             allContacts = allContacts.filter { contactSources.contains(it.source) } as ArrayList<Contact> |             allContacts = allContacts.filter { contactSources.contains(it.source) } as ArrayList<Contact> | ||||||
|             } |  | ||||||
|  |  | ||||||
|             initiallySelectedContacts = allContacts.filter { it.starred == 1 } as ArrayList<Contact> |             initiallySelectedContacts = allContacts.filter { it.starred == 1 } as ArrayList<Contact> | ||||||
|         } else { |         } else { | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package com.simplemobiletools.contacts.extensions | |||||||
|  |  | ||||||
| import android.content.Intent | import android.content.Intent | ||||||
| import android.net.Uri | import android.net.Uri | ||||||
|  | import android.provider.ContactsContract | ||||||
| import com.simplemobiletools.commons.activities.BaseSimpleActivity | import com.simplemobiletools.commons.activities.BaseSimpleActivity | ||||||
| import com.simplemobiletools.commons.dialogs.RadioGroupDialog | import com.simplemobiletools.commons.dialogs.RadioGroupDialog | ||||||
| import com.simplemobiletools.commons.extensions.sharePathIntent | import com.simplemobiletools.commons.extensions.sharePathIntent | ||||||
| @@ -175,3 +176,8 @@ fun BaseSimpleActivity.removeContactsFromGroup(contacts: ArrayList<Contact>, gro | |||||||
|         dbHelper.removeContactsFromGroup(contacts, groupId) |         dbHelper.removeContactsFromGroup(contacts, groupId) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fun BaseSimpleActivity.getContactPublicUri(contact: Contact): Uri { | ||||||
|  |     val lookupKey = ContactsHelper(this).getContactLookupKey(contact.id.toString()) | ||||||
|  |     return Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -99,15 +99,11 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) | |||||||
|         contacts.sort() |         contacts.sort() | ||||||
|         allContacts = contacts |         allContacts = contacts | ||||||
|  |  | ||||||
|         val filtered = if (this is GroupsFragment) { |         val filtered = when { | ||||||
|             contacts |             this is GroupsFragment -> contacts | ||||||
|         } else if (this is FavoritesFragment) { |             this is FavoritesFragment -> contacts.filter { it.starred == 1 } as ArrayList<Contact> | ||||||
|             contacts.filter { it.starred == 1 } as ArrayList<Contact> |             else -> { | ||||||
|         } else { |  | ||||||
|                 val contactSources = config.displayContactSources |                 val contactSources = config.displayContactSources | ||||||
|             if (config.showAllContacts()) { |  | ||||||
|                 contacts |  | ||||||
|             } else { |  | ||||||
|                 contacts.filter { contactSources.contains(it.source) } as ArrayList<Contact> |                 contacts.filter { contactSources.contains(it.source) } as ArrayList<Contact> | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -12,8 +12,6 @@ class Config(context: Context) : BaseConfig(context) { | |||||||
|         get() = prefs.getStringSet(DISPLAY_CONTACT_SOURCES, hashSetOf("-1")) |         get() = prefs.getStringSet(DISPLAY_CONTACT_SOURCES, hashSetOf("-1")) | ||||||
|         set(displayContactSources) = prefs.edit().remove(DISPLAY_CONTACT_SOURCES).putStringSet(DISPLAY_CONTACT_SOURCES, displayContactSources).apply() |         set(displayContactSources) = prefs.edit().remove(DISPLAY_CONTACT_SOURCES).putStringSet(DISPLAY_CONTACT_SOURCES, displayContactSources).apply() | ||||||
|  |  | ||||||
|     fun showAllContacts() = displayContactSources.size == 1 && displayContactSources.first() == "-1" |  | ||||||
|  |  | ||||||
|     var showContactThumbnails: Boolean |     var showContactThumbnails: Boolean | ||||||
|         get() = prefs.getBoolean(SHOW_CONTACT_THUMBNAILS, true) |         get() = prefs.getBoolean(SHOW_CONTACT_THUMBNAILS, true) | ||||||
|         set(showContactThumbnails) = prefs.edit().putBoolean(SHOW_CONTACT_THUMBNAILS, showContactThumbnails).apply() |         set(showContactThumbnails) = prefs.edit().putBoolean(SHOW_CONTACT_THUMBNAILS, showContactThumbnails).apply() | ||||||
|   | |||||||
| @@ -93,3 +93,14 @@ const val DEFAULT_ADDRESS_TYPE = CommonDataKinds.StructuredPostal.TYPE_HOME | |||||||
| const val DEFAULT_EVENT_TYPE = CommonDataKinds.Event.TYPE_BIRTHDAY | const val DEFAULT_EVENT_TYPE = CommonDataKinds.Event.TYPE_BIRTHDAY | ||||||
| const val DEFAULT_ORGANIZATION_TYPE = CommonDataKinds.Organization.TYPE_WORK | const val DEFAULT_ORGANIZATION_TYPE = CommonDataKinds.Organization.TYPE_WORK | ||||||
| const val DEFAULT_WEBSITE_TYPE = CommonDataKinds.Website.TYPE_HOMEPAGE | const val DEFAULT_WEBSITE_TYPE = CommonDataKinds.Website.TYPE_HOMEPAGE | ||||||
|  |  | ||||||
|  | // some manufacturer contact account types from https://stackoverflow.com/a/44802016/1967672 | ||||||
|  | val localAccountTypes = arrayListOf("vnd.sec.contact.phone", | ||||||
|  |         "com.htc.android.pcsc", | ||||||
|  |         "com.sonyericsson.localcontacts", | ||||||
|  |         "com.lge.sync", | ||||||
|  |         "com.lge.phone", | ||||||
|  |         "vnd.tmobileus.contact.phone", | ||||||
|  |         "com.android.huawei.phone", | ||||||
|  |         "Local Phone Account" | ||||||
|  | ) | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| package com.simplemobiletools.contacts.helpers | package com.simplemobiletools.contacts.helpers | ||||||
|  |  | ||||||
| import android.content.ContentProviderOperation | import android.accounts.AccountManager | ||||||
| import android.content.ContentProviderResult | import android.content.* | ||||||
| import android.content.ContentUris |  | ||||||
| import android.content.ContentValues |  | ||||||
| import android.database.Cursor | import android.database.Cursor | ||||||
| import android.graphics.Bitmap | import android.graphics.Bitmap | ||||||
| import android.net.Uri | import android.net.Uri | ||||||
| @@ -37,7 +35,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) { | |||||||
|             val contactsSize = contacts.size() |             val contactsSize = contacts.size() | ||||||
|             var resultContacts = ArrayList<Contact>(contactsSize) |             var resultContacts = ArrayList<Contact>(contactsSize) | ||||||
|             (0 until contactsSize).mapTo(resultContacts) { contacts.valueAt(it) } |             (0 until contactsSize).mapTo(resultContacts) { contacts.valueAt(it) } | ||||||
|             resultContacts = resultContacts.distinctBy { it.contactId } as ArrayList<Contact> |             resultContacts = resultContacts.distinctBy { | ||||||
|  |                 it.getHashToCompare() | ||||||
|  |             } as ArrayList<Contact> | ||||||
|  |  | ||||||
|             // groups are obtained with contactID, not rawID, so assign them to proper contacts like this |             // groups are obtained with contactID, not rawID, so assign them to proper contacts like this | ||||||
|             val groups = getContactGroups(getStoredGroups()) |             val groups = getContactGroups(getStoredGroups()) | ||||||
| @@ -90,6 +90,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) { | |||||||
|                     val websites = ArrayList<String>() |                     val websites = ArrayList<String>() | ||||||
|                     val contact = Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events, |                     val contact = Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events, | ||||||
|                             accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites) |                             accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites) | ||||||
|  |  | ||||||
|                     contacts.put(id, contact) |                     contacts.put(id, contact) | ||||||
|                 } while (cursor.moveToNext()) |                 } while (cursor.moveToNext()) | ||||||
|             } |             } | ||||||
| @@ -621,64 +622,34 @@ class ContactsHelper(val activity: BaseSimpleActivity) { | |||||||
|  |  | ||||||
|     fun getContactSources(callback: (ArrayList<ContactSource>) -> Unit) { |     fun getContactSources(callback: (ArrayList<ContactSource>) -> Unit) { | ||||||
|         Thread { |         Thread { | ||||||
|             val sources = LinkedHashSet<ContactSource>() |             val sources = getDeviceContactSources() | ||||||
|             getDeviceContactSources(sources) |  | ||||||
|             sources.add(ContactSource(activity.getString(R.string.phone_storage_hidden), SMT_PRIVATE)) |             sources.add(ContactSource(activity.getString(R.string.phone_storage_hidden), SMT_PRIVATE)) | ||||||
|             callback(ArrayList(sources)) |             callback(ArrayList(sources)) | ||||||
|         }.start() |         }.start() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun getDeviceContactSources(sources: LinkedHashSet<ContactSource>) { |     private fun getDeviceContactSources(): LinkedHashSet<ContactSource> { | ||||||
|  |         val sources = LinkedHashSet<ContactSource>() | ||||||
|         if (!activity.hasContactPermissions()) { |         if (!activity.hasContactPermissions()) { | ||||||
|             return |             return sources | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         val uri = ContactsContract.RawContacts.CONTENT_URI |         val accountManager = AccountManager.get(activity) | ||||||
|         val projection = arrayOf(ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE) |         accountManager.accounts.filter { it.name.contains("@") || localAccountTypes.contains(it.type) }.forEach { | ||||||
|  |             if (ContentResolver.getIsSyncable(it, ContactsContract.AUTHORITY) == 1) { | ||||||
|         var cursor: Cursor? = null |                 val contactSource = ContactSource(it.name, it.type) | ||||||
|         try { |  | ||||||
|             cursor = activity.contentResolver.query(uri, projection, null, null, null) |  | ||||||
|             if (cursor?.moveToFirst() == true) { |  | ||||||
|                 do { |  | ||||||
|                     val name = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: continue |  | ||||||
|                     val type = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_TYPE) ?: continue |  | ||||||
|                     val contactSource = ContactSource(name, type) |  | ||||||
|                 sources.add(contactSource) |                 sources.add(contactSource) | ||||||
|                 } while (cursor.moveToNext()) |  | ||||||
|             } |             } | ||||||
|         } catch (e: Exception) { |  | ||||||
|             activity.showErrorToast(e) |  | ||||||
|         } finally { |  | ||||||
|             cursor?.close() |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) { |         if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) { | ||||||
|             sources.add(ContactSource("", "")) |             sources.add(ContactSource("", "")) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         return sources | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun getContactSourceType(accountName: String): String { |     private fun getContactSourceType(accountName: String) = getDeviceContactSources().firstOrNull { it.name == accountName }?.type ?: "" | ||||||
|         if (accountName.isEmpty()) { |  | ||||||
|             return "" |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         val uri = ContactsContract.RawContacts.CONTENT_URI |  | ||||||
|         val projection = arrayOf(ContactsContract.RawContacts.ACCOUNT_TYPE) |  | ||||||
|         val selection = "${ContactsContract.RawContacts.ACCOUNT_NAME} = ?" |  | ||||||
|         val selectionArgs = arrayOf(accountName) |  | ||||||
|  |  | ||||||
|         var cursor: Cursor? = null |  | ||||||
|         try { |  | ||||||
|             cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null) |  | ||||||
|             if (cursor?.moveToFirst() == true) { |  | ||||||
|                 return cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_TYPE) |  | ||||||
|             } |  | ||||||
|         } finally { |  | ||||||
|             cursor?.close() |  | ||||||
|         } |  | ||||||
|         return "" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun getContactProjection() = arrayOf( |     private fun getContactProjection() = arrayOf( | ||||||
|             ContactsContract.Data.CONTACT_ID, |             ContactsContract.Data.CONTACT_ID, | ||||||
| @@ -1060,6 +1031,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             // organization |             // organization | ||||||
|  |             if (!contact.organization.isEmpty()) { | ||||||
|                 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { |                 ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { | ||||||
|                     withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) |                     withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) | ||||||
|                     withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE) |                     withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE) | ||||||
| @@ -1069,6 +1041,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) { | |||||||
|                     withValue(CommonDataKinds.Organization.TYPE, DEFAULT_ORGANIZATION_TYPE) |                     withValue(CommonDataKinds.Organization.TYPE, DEFAULT_ORGANIZATION_TYPE) | ||||||
|                     operations.add(build()) |                     operations.add(build()) | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // websites |             // websites | ||||||
|             contact.websites.forEach { |             contact.websites.forEach { | ||||||
| @@ -1246,16 +1219,14 @@ class ContactsHelper(val activity: BaseSimpleActivity) { | |||||||
|             activity.dbHelper.deleteContacts(localContacts) |             activity.dbHelper.deleteContacts(localContacts) | ||||||
|  |  | ||||||
|             try { |             try { | ||||||
|                 val contactIDs = HashSet<String>() |  | ||||||
|                 val operations = ArrayList<ContentProviderOperation>() |                 val operations = ArrayList<ContentProviderOperation>() | ||||||
|                 val selection = "${ContactsContract.Data.CONTACT_ID} = ?" |                 val selection = "${ContactsContract.Data.CONTACT_ID} = ?" | ||||||
|                 contacts.filter { it.source != SMT_PRIVATE }.forEach { |                 contacts.filter { it.source != SMT_PRIVATE }.forEach { | ||||||
|                     ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { |                     ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { | ||||||
|                         val selectionArgs = arrayOf(it.contactId.toString()) |                         val selectionArgs = arrayOf(it.contactId.toString()) | ||||||
|                         withSelection(selection, selectionArgs) |                         withSelection(selection, selectionArgs) | ||||||
|                         operations.add(this.build()) |                         operations.add(build()) | ||||||
|                     } |                     } | ||||||
|                     contactIDs.add(it.id.toString()) |  | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) |                 activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) | ||||||
|   | |||||||
| @@ -119,6 +119,10 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont | |||||||
|     fun deleteContact(id: Int) = deleteContacts(arrayOf(id.toString())) |     fun deleteContact(id: Int) = deleteContacts(arrayOf(id.toString())) | ||||||
|  |  | ||||||
|     fun deleteContacts(ids: Array<String>) { |     fun deleteContacts(ids: Array<String>) { | ||||||
|  |         if (ids.isEmpty()) { | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|         val args = TextUtils.join(", ", ids) |         val args = TextUtils.join(", ", ids) | ||||||
|         val selection = "$CONTACTS_TABLE_NAME.$COL_ID IN ($args)" |         val selection = "$CONTACTS_TABLE_NAME.$COL_ID IN ($args)" | ||||||
|         mDb.delete(CONTACTS_TABLE_NAME, selection, null) |         mDb.delete(CONTACTS_TABLE_NAME, selection, null) | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m | |||||||
|     companion object { |     companion object { | ||||||
|         var sorting = 0 |         var sorting = 0 | ||||||
|         var startWithSurname = false |         var startWithSurname = false | ||||||
|  |         val pattern = "\\D+".toRegex() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun compareTo(other: Contact): Int { |     override fun compareTo(other: Contact): Int { | ||||||
| @@ -81,4 +82,10 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m | |||||||
|             fullName |             fullName | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getHashToCompare(): Int { | ||||||
|  |         val newPhoneNumbers = ArrayList<PhoneNumber>() | ||||||
|  |         phoneNumbers.mapTo(newPhoneNumbers, { PhoneNumber(it.value.replace(pattern, ""), 0) }) | ||||||
|  |         return copy(id = 0, phoneNumbers = newPhoneNumbers).hashCode() | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -341,7 +341,7 @@ | |||||||
|             android:layout_marginTop="@dimen/normal_margin" |             android:layout_marginTop="@dimen/normal_margin" | ||||||
|             android:layout_toRightOf="@+id/contact_notes_image" |             android:layout_toRightOf="@+id/contact_notes_image" | ||||||
|             android:hint="@string/notes" |             android:hint="@string/notes" | ||||||
|             android:inputType="textCapWords|textMultiLine" |             android:inputType="textCapSentences|textMultiLine" | ||||||
|             android:textCursorDrawable="@null" |             android:textCursorDrawable="@null" | ||||||
|             android:textSize="@dimen/bigger_text_size"/> |             android:textSize="@dimen/bigger_text_size"/> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,10 @@ | |||||||
|         android:icon="@drawable/ic_share" |         android:icon="@drawable/ic_share" | ||||||
|         android:title="@string/share" |         android:title="@string/share" | ||||||
|         app:showAsAction="ifRoom"/> |         app:showAsAction="ifRoom"/> | ||||||
|  |     <item | ||||||
|  |         android:id="@+id/open_with" | ||||||
|  |         android:title="@string/open_with" | ||||||
|  |         app:showAsAction="never"/> | ||||||
|     <item |     <item | ||||||
|         android:id="@+id/delete" |         android:id="@+id/delete" | ||||||
|         android:icon="@drawable/ic_delete" |         android:icon="@drawable/ic_delete" | ||||||
|   | |||||||
| @@ -11,6 +11,10 @@ | |||||||
|         android:icon="@drawable/ic_share" |         android:icon="@drawable/ic_share" | ||||||
|         android:title="@string/share" |         android:title="@string/share" | ||||||
|         app:showAsAction="ifRoom"/> |         app:showAsAction="ifRoom"/> | ||||||
|  |     <item | ||||||
|  |         android:id="@+id/open_with" | ||||||
|  |         android:title="@string/open_with" | ||||||
|  |         app:showAsAction="never"/> | ||||||
|     <item |     <item | ||||||
|         android:id="@+id/delete" |         android:id="@+id/delete" | ||||||
|         android:icon="@drawable/ic_delete" |         android:icon="@drawable/ic_delete" | ||||||
|   | |||||||
| Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-hdpi/ic_launcher_red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.8 KiB | 
| Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xhdpi/ic_launcher_red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.5 KiB | 
| Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 6.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxhdpi/ic_launcher_red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.3 KiB | 
| Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 6.8 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.1 KiB | 
| @@ -9,10 +9,10 @@ | |||||||
|     <string name="company">Organização</string> |     <string name="company">Organização</string> | ||||||
|     <string name="job_position">Cargo</string> |     <string name="job_position">Cargo</string> | ||||||
|     <string name="website">Site</string> |     <string name="website">Site</string> | ||||||
|     <string name="send_sms_to_contacts">Send SMS to contacts</string> |     <string name="send_sms_to_contacts">Enviar SMS aos contactos</string> | ||||||
|     <string name="send_email_to_contacts">Send email to contacts</string> |     <string name="send_email_to_contacts">Enviar e-mail aos contactos</string> | ||||||
|     <string name="send_sms_to_group">Send SMS to group</string> |     <string name="send_sms_to_group">Enviar SMS para o grupo</string> | ||||||
|     <string name="send_email_to_group">Send email to group</string> |     <string name="send_email_to_group">Enviar e-mail para o grupo</string> | ||||||
|  |  | ||||||
|     <string name="new_contact">Novo contacto</string> |     <string name="new_contact">Novo contacto</string> | ||||||
|     <string name="edit_contact">Editar contacto</string> |     <string name="edit_contact">Editar contacto</string> | ||||||
| @@ -73,7 +73,7 @@ | |||||||
|     <string name="add_favorites">Adicionar favoritos</string> |     <string name="add_favorites">Adicionar favoritos</string> | ||||||
|     <string name="add_to_favorites">Adicionar aos favoritos</string> |     <string name="add_to_favorites">Adicionar aos favoritos</string> | ||||||
|     <string name="remove_from_favorites">Remover dos favoritos</string> |     <string name="remove_from_favorites">Remover dos favoritos</string> | ||||||
|     <string name="must_be_at_edit">You must be at the Edit screen to modify a contact</string> |     <string name="must_be_at_edit">Tem que estar no ecrã de edição para alterar um contacto</string> | ||||||
|  |  | ||||||
|     <!-- Search --> |     <!-- Search --> | ||||||
|     <string name="search_contacts">Pesquisar contactos</string> |     <string name="search_contacts">Pesquisar contactos</string> | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| // Top-level build file where you can add configuration options common to all sub-projects/modules. | // Top-level build file where you can add configuration options common to all sub-projects/modules. | ||||||
|  |  | ||||||
| buildscript { | buildscript { | ||||||
|     ext.kotlin_version = '1.2.40' |     ext.kotlin_version = '1.2.41' | ||||||
|  |  | ||||||
|     repositories { |     repositories { | ||||||
|         google() |         google() | ||||||
|   | |||||||