Merge pull request #13 from SimpleMobileTools/master

upd
This commit is contained in:
solokot 2018-04-30 11:11:35 +03:00 committed by GitHub
commit 749741c25c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 143 additions and 100 deletions

View File

@ -45,7 +45,7 @@ ext {
}
dependencies {
implementation 'com.simplemobiletools:commons:3.19.21'
implementation 'com.simplemobiletools:commons:3.20.6'
implementation 'joda-time:joda-time:2.9.9'
implementation 'com.facebook.stetho:stetho:1.5.0'

View File

@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<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
android:name="android.permission.USE_FINGERPRINT"
@ -26,8 +28,6 @@
android:name=".activities.SplashActivity"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
@ -190,5 +190,30 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</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>
</manifest>

View File

@ -87,6 +87,7 @@ class EditContactActivity : ContactActivity() {
if (wasActivityInitialized) {
menu.findItem(R.id.delete).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
}
@ -94,8 +95,9 @@ class EditContactActivity : ContactActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save -> saveContact()
R.id.delete -> deleteContact()
R.id.share -> shareContact()
R.id.open_with -> openWith()
R.id.delete -> deleteContact()
else -> return super.onOptionsItemSelected(item)
}
return true
@ -224,6 +226,14 @@ class EditContactActivity : ContactActivity() {
invalidateOptionsMenu()
}
private fun openWith() {
Intent().apply {
action = Intent.ACTION_EDIT
data = getContactPublicUri(contact!!)
startActivity(this)
}
}
private fun startCropPhotoIntent(uri: Uri?) {
if (uri == null) {
toast(R.string.unknown_error_occurred)

View File

@ -234,16 +234,6 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
private fun storeLocalAccountData() {
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 {
var localAccountType = ""
var localAccountName = ""

View File

@ -17,6 +17,7 @@ import com.simplemobiletools.contacts.adapters.SelectContactsAdapter
import com.simplemobiletools.contacts.dialogs.ChangeSortingDialog
import com.simplemobiletools.contacts.dialogs.FilterContactSourcesDialog
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.getContactPublicUri
import com.simplemobiletools.contacts.helpers.ContactsHelper
import com.simplemobiletools.contacts.helpers.SMT_PRIVATE
import com.simplemobiletools.contacts.models.Contact
@ -98,9 +99,7 @@ class SelectContactActivity : SimpleActivity() {
} as ArrayList<Contact>
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.startWithSurname = config.startNameWithSurname
@ -135,8 +134,7 @@ class SelectContactActivity : SimpleActivity() {
Uri.withAppendedPath(ContactsContract.Data.CONTENT_URI, contactId)
}
else -> {
val lookupKey = ContactsHelper(this).getContactLookupKey(contact.id.toString())
Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)
getContactPublicUri(contact)
}
}
}

View File

@ -1,5 +1,13 @@
package com.simplemobiletools.contacts.activities
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)
}

View File

@ -49,13 +49,18 @@ class ViewContactActivity : ContactActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_view_contact, menu)
menu.apply {
findItem(R.id.open_with).isVisible = contact?.source != SMT_PRIVATE
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.edit -> editContact(contact!!)
R.id.share -> shareContact()
R.id.open_with -> openWith()
R.id.delete -> deleteContact()
else -> return super.onOptionsItemSelected(item)
}
@ -151,6 +156,14 @@ class ViewContactActivity : ContactActivity() {
setupContactSource()
}
private fun openWith() {
Intent().apply {
action = ContactsContract.QuickContact.ACTION_QUICK_CONTACT
data = getContactPublicUri(contact!!)
startActivity(this)
}
}
private fun setupFavorite() {
contact_toggle_favorite.apply {
beVisible()

View File

@ -21,7 +21,7 @@ class FilterContactSourcesAdapter(val activity: SimpleActivity, private val cont
init {
contactSources.forEachIndexed { index, contactSource ->
if (activity.config.showAllContacts() || displayContactSources.contains(contactSource.name)) {
if (displayContactSources.contains(contactSource.name)) {
selectedPositions.add(index)
}

View File

@ -19,9 +19,7 @@ class SelectContactsDialog(val activity: SimpleActivity, initialContacts: ArrayL
var allContacts = initialContacts
if (selectContacts == null) {
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>
} else {

View File

@ -2,6 +2,7 @@ package com.simplemobiletools.contacts.extensions
import android.content.Intent
import android.net.Uri
import android.provider.ContactsContract
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.sharePathIntent
@ -175,3 +176,8 @@ fun BaseSimpleActivity.removeContactsFromGroup(contacts: ArrayList<Contact>, gro
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)
}

View File

@ -99,15 +99,11 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
contacts.sort()
allContacts = contacts
val filtered = if (this is GroupsFragment) {
contacts
} else if (this is FavoritesFragment) {
contacts.filter { it.starred == 1 } as ArrayList<Contact>
} else {
val contactSources = config.displayContactSources
if (config.showAllContacts()) {
contacts
} else {
val filtered = when {
this is GroupsFragment -> contacts
this is FavoritesFragment -> contacts.filter { it.starred == 1 } as ArrayList<Contact>
else -> {
val contactSources = config.displayContactSources
contacts.filter { contactSources.contains(it.source) } as ArrayList<Contact>
}
}

View File

@ -12,8 +12,6 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getStringSet(DISPLAY_CONTACT_SOURCES, hashSetOf("-1"))
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
get() = prefs.getBoolean(SHOW_CONTACT_THUMBNAILS, true)
set(showContactThumbnails) = prefs.edit().putBoolean(SHOW_CONTACT_THUMBNAILS, showContactThumbnails).apply()

View File

@ -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_ORGANIZATION_TYPE = CommonDataKinds.Organization.TYPE_WORK
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"
)

View File

@ -1,9 +1,7 @@
package com.simplemobiletools.contacts.helpers
import android.content.ContentProviderOperation
import android.content.ContentProviderResult
import android.content.ContentUris
import android.content.ContentValues
import android.accounts.AccountManager
import android.content.*
import android.database.Cursor
import android.graphics.Bitmap
import android.net.Uri
@ -37,7 +35,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val contactsSize = contacts.size()
var resultContacts = ArrayList<Contact>(contactsSize)
(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
val groups = getContactGroups(getStoredGroups())
@ -90,6 +90,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val websites = ArrayList<String>()
val contact = Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events,
accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites)
contacts.put(id, contact)
} while (cursor.moveToNext())
}
@ -621,64 +622,34 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
fun getContactSources(callback: (ArrayList<ContactSource>) -> Unit) {
Thread {
val sources = LinkedHashSet<ContactSource>()
getDeviceContactSources(sources)
val sources = getDeviceContactSources()
sources.add(ContactSource(activity.getString(R.string.phone_storage_hidden), SMT_PRIVATE))
callback(ArrayList(sources))
}.start()
}
private fun getDeviceContactSources(sources: LinkedHashSet<ContactSource>) {
private fun getDeviceContactSources(): LinkedHashSet<ContactSource> {
val sources = LinkedHashSet<ContactSource>()
if (!activity.hasContactPermissions()) {
return
return sources
}
val uri = ContactsContract.RawContacts.CONTENT_URI
val projection = arrayOf(ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE)
var cursor: Cursor? = null
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)
} while (cursor.moveToNext())
val accountManager = AccountManager.get(activity)
accountManager.accounts.filter { it.name.contains("@") || localAccountTypes.contains(it.type) }.forEach {
if (ContentResolver.getIsSyncable(it, ContactsContract.AUTHORITY) == 1) {
val contactSource = ContactSource(it.name, it.type)
sources.add(contactSource)
}
} catch (e: Exception) {
activity.showErrorToast(e)
} finally {
cursor?.close()
}
if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) {
sources.add(ContactSource("", ""))
}
return sources
}
private fun getContactSourceType(accountName: String): String {
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 getContactSourceType(accountName: String) = getDeviceContactSources().firstOrNull { it.name == accountName }?.type ?: ""
private fun getContactProjection() = arrayOf(
ContactsContract.Data.CONTACT_ID,
@ -1060,14 +1031,16 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
}
// organization
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Organization.COMPANY, contact.organization.company)
withValue(CommonDataKinds.Organization.TYPE, DEFAULT_ORGANIZATION_TYPE)
withValue(CommonDataKinds.Organization.TITLE, contact.organization.jobPosition)
withValue(CommonDataKinds.Organization.TYPE, DEFAULT_ORGANIZATION_TYPE)
operations.add(build())
if (!contact.organization.isEmpty()) {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Organization.COMPANY, contact.organization.company)
withValue(CommonDataKinds.Organization.TYPE, DEFAULT_ORGANIZATION_TYPE)
withValue(CommonDataKinds.Organization.TITLE, contact.organization.jobPosition)
withValue(CommonDataKinds.Organization.TYPE, DEFAULT_ORGANIZATION_TYPE)
operations.add(build())
}
}
// websites
@ -1246,16 +1219,14 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
activity.dbHelper.deleteContacts(localContacts)
try {
val contactIDs = HashSet<String>()
val operations = ArrayList<ContentProviderOperation>()
val selection = "${ContactsContract.Data.CONTACT_ID} = ?"
contacts.filter { it.source != SMT_PRIVATE }.forEach {
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
val selectionArgs = arrayOf(it.contactId.toString())
withSelection(selection, selectionArgs)
operations.add(this.build())
operations.add(build())
}
contactIDs.add(it.id.toString())
}
activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)

View File

@ -119,6 +119,10 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
fun deleteContact(id: Int) = deleteContacts(arrayOf(id.toString()))
fun deleteContacts(ids: Array<String>) {
if (ids.isEmpty()) {
return
}
val args = TextUtils.join(", ", ids)
val selection = "$CONTACTS_TABLE_NAME.$COL_ID IN ($args)"
mDb.delete(CONTACTS_TABLE_NAME, selection, null)

View File

@ -12,6 +12,7 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m
companion object {
var sorting = 0
var startWithSurname = false
val pattern = "\\D+".toRegex()
}
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
}
}
fun getHashToCompare(): Int {
val newPhoneNumbers = ArrayList<PhoneNumber>()
phoneNumbers.mapTo(newPhoneNumbers, { PhoneNumber(it.value.replace(pattern, ""), 0) })
return copy(id = 0, phoneNumbers = newPhoneNumbers).hashCode()
}
}

View File

@ -341,7 +341,7 @@
android:layout_marginTop="@dimen/normal_margin"
android:layout_toRightOf="@+id/contact_notes_image"
android:hint="@string/notes"
android:inputType="textCapWords|textMultiLine"
android:inputType="textCapSentences|textMultiLine"
android:textCursorDrawable="@null"
android:textSize="@dimen/bigger_text_size"/>

View File

@ -11,6 +11,10 @@
android:icon="@drawable/ic_share"
android:title="@string/share"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/open_with"
android:title="@string/open_with"
app:showAsAction="never"/>
<item
android:id="@+id/delete"
android:icon="@drawable/ic_delete"

View File

@ -11,6 +11,10 @@
android:icon="@drawable/ic_share"
android:title="@string/share"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/open_with"
android:title="@string/open_with"
app:showAsAction="never"/>
<item
android:id="@+id/delete"
android:icon="@drawable/ic_delete"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -9,10 +9,10 @@
<string name="company">Organização</string>
<string name="job_position">Cargo</string>
<string name="website">Site</string>
<string name="send_sms_to_contacts">Send SMS to contacts</string>
<string name="send_email_to_contacts">Send email to contacts</string>
<string name="send_sms_to_group">Send SMS to group</string>
<string name="send_email_to_group">Send email to group</string>
<string name="send_sms_to_contacts">Enviar SMS aos contactos</string>
<string name="send_email_to_contacts">Enviar e-mail aos contactos</string>
<string name="send_sms_to_group">Enviar SMS para o grupo</string>
<string name="send_email_to_group">Enviar e-mail para o grupo</string>
<string name="new_contact">Novo contacto</string>
<string name="edit_contact">Editar contacto</string>
@ -73,7 +73,7 @@
<string name="add_favorites">Adicionar favoritos</string>
<string name="add_to_favorites">Adicionar aos 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 -->
<string name="search_contacts">Pesquisar contactos</string>

View File

@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.2.40'
ext.kotlin_version = '1.2.41'
repositories {
google()