diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c6a8c2c..ae86dd8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,29 @@ Changelog ========== +Version 4.5.0 *(2018-09-28)* +---------------------------- + + * Added a simple dialpad + * Do not allow creating contacts under Signal or Telegram contact source + * Allow copying phone numbers into clipboard by long pressing them on the View screen + * Properly handle intents adding numbers to existing contacts + * Many other smaller improvements and bugfixes + +Version 4.4.0 *(2018-09-04)* +---------------------------- + + * Added support for custom phone number/email/address types + * Added IM field + * Fixed some exporting/importing issues + * Improved duplicate filtering + +Version 4.3.0 *(2018-08-24)* +---------------------------- + + * Reworked contact exporting/importing from scratch, use ez-vcard for parsing files + * Couple stability and translation improvements + Version 4.2.2 *(2018-08-13)* ---------------------------- diff --git a/app/build.gradle b/app/build.gradle index dd3e3290..63ebf515 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,14 +4,14 @@ apply plugin: 'kotlin-android-extensions' android { compileSdkVersion 28 - buildToolsVersion "28.0.2" + buildToolsVersion "28.0.3" defaultConfig { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 28 - versionCode 29 - versionName "4.2.2" + versionCode 32 + versionName "4.5.0" setProperty("archivesBaseName", "contacts") } @@ -40,19 +40,12 @@ android { } } -ext { - leakCanaryVersion = '1.5.4' -} - dependencies { - implementation 'com.simplemobiletools:commons:4.6.15' + implementation 'com.simplemobiletools:commons:4.8.0' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' - implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' compile 'com.googlecode.ez-vcard:ez-vcard:0.10.4' - - debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" - releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" } Properties props = new Properties() diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4422660d..998d518a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,10 @@ + + @@ -114,15 +118,6 @@ android:name=".activities.EditContactActivity" android:parentActivityName=".activities.MainActivity"> - - - - - - - - - @@ -159,6 +154,20 @@ + + + + + + + + + + + + + + R.string.mobile - ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> R.string.home - ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> R.string.work - ContactsContract.CommonDataKinds.Phone.TYPE_MAIN -> R.string.main_number - ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK -> R.string.work_fax - ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME -> R.string.home_fax - ContactsContract.CommonDataKinds.Phone.TYPE_PAGER -> R.string.pager - else -> R.string.other + fun getPhoneNumberTypeText(type: Int, label: String): String { + return if (type == ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM) { + label + } else { + getString(when (type) { + ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE -> R.string.mobile + ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> R.string.home + ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> R.string.work + ContactsContract.CommonDataKinds.Phone.TYPE_MAIN -> R.string.main_number + ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK -> R.string.work_fax + ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME -> R.string.home_fax + ContactsContract.CommonDataKinds.Phone.TYPE_PAGER -> R.string.pager + else -> R.string.other + }) + } } - fun getEmailTextId(type: Int) = when (type) { - ContactsContract.CommonDataKinds.Email.TYPE_HOME -> R.string.home - ContactsContract.CommonDataKinds.Email.TYPE_WORK -> R.string.work - ContactsContract.CommonDataKinds.Email.TYPE_MOBILE -> R.string.mobile - else -> R.string.other + fun getEmailTypeText(type: Int, label: String): String { + return if (type == ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM) { + label + } else { + getString(when (type) { + ContactsContract.CommonDataKinds.Email.TYPE_HOME -> R.string.home + ContactsContract.CommonDataKinds.Email.TYPE_WORK -> R.string.work + ContactsContract.CommonDataKinds.Email.TYPE_MOBILE -> R.string.mobile + else -> R.string.other + }) + } } - fun getAddressTextId(type: Int) = when (type) { - ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME -> R.string.home - ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK -> R.string.work - else -> R.string.other + fun getAddressTypeText(type: Int, label: String): String { + return if (type == ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM) { + label + } else { + getString(when (type) { + ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME -> R.string.home + ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK -> R.string.work + else -> R.string.other + }) + } + } + + fun getIMTypeText(type: Int, label: String): String { + return if (type == ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM) { + label + } else { + getString(when (type) { + ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM -> R.string.aim + ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN -> R.string.windows_live + ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO -> R.string.yahoo + ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE -> R.string.skype + ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ -> R.string.qq + ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK -> R.string.hangouts + ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ -> R.string.icq + else -> R.string.jabber + }) + } } fun getEventTextId(type: Int) = when (type) { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/DialpadActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/DialpadActivity.kt new file mode 100644 index 00000000..11a45b07 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/DialpadActivity.kt @@ -0,0 +1,169 @@ +package com.simplemobiletools.contacts.activities + +import android.annotation.TargetApi +import android.content.Intent +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import android.text.InputType +import android.view.KeyEvent +import android.view.Menu +import android.view.MenuItem +import android.view.View +import com.simplemobiletools.commons.extensions.* +import com.simplemobiletools.commons.helpers.isLollipopPlus +import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.adapters.ContactsAdapter +import com.simplemobiletools.contacts.dialogs.CallConfirmationDialog +import com.simplemobiletools.contacts.extensions.afterTextChanged +import com.simplemobiletools.contacts.extensions.callContact +import com.simplemobiletools.contacts.extensions.config +import com.simplemobiletools.contacts.extensions.startCallIntent +import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.KEY_PHONE +import com.simplemobiletools.contacts.helpers.LOCATION_DIALPAD +import com.simplemobiletools.contacts.models.Contact +import kotlinx.android.synthetic.main.activity_dialpad.* + +class DialpadActivity : SimpleActivity() { + var contacts = ArrayList() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_dialpad) + + dialpad_0_holder.setOnClickListener { dialpadPressed("0", it) } + dialpad_1.setOnClickListener { dialpadPressed("1", it) } + dialpad_2.setOnClickListener { dialpadPressed("2", it) } + dialpad_3.setOnClickListener { dialpadPressed("3", it) } + dialpad_4.setOnClickListener { dialpadPressed("4", it) } + dialpad_5.setOnClickListener { dialpadPressed("5", it) } + dialpad_6.setOnClickListener { dialpadPressed("6", it) } + dialpad_7.setOnClickListener { dialpadPressed("7", it) } + dialpad_8.setOnClickListener { dialpadPressed("8", it) } + dialpad_9.setOnClickListener { dialpadPressed("9", it) } + dialpad_0_holder.setOnLongClickListener { dialpadPressed("+", null); true } + dialpad_asterisk.setOnClickListener { dialpadPressed("*", it) } + dialpad_hashtag.setOnClickListener { dialpadPressed("#", it) } + dialpad_clear_char.setOnClickListener { clearChar(it) } + dialpad_clear_char.setOnLongClickListener { clearInput(); true } + dialpad_call_button.setOnClickListener { initCall() } + dialpad_input.afterTextChanged { dialpadValueChanged(it) } + ContactsHelper(this).getContacts { gotContacts(it) } + disableKeyboardPopping() + + val callIcon = resources.getColoredDrawableWithColor(R.drawable.ic_phone_big, if (isBlackAndWhiteTheme()) Color.BLACK else Color.WHITE) + dialpad_call_button.setImageDrawable(callIcon) + dialpad_call_button.background.applyColorFilter(getAdjustedPrimaryColor()) + } + + override fun onResume() { + super.onResume() + updateTextColors(dialpad_holder) + dialpad_clear_char.applyColorFilter(config.textColor) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_dialpad, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.add_number_to_contact -> addNumberToContact() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + private fun addNumberToContact() { + Intent().apply { + action = Intent.ACTION_INSERT_OR_EDIT + type = "vnd.android.cursor.item/contact" + putExtra(KEY_PHONE, dialpad_input.value) + if (resolveActivity(packageManager) != null) { + startActivity(this) + } else { + toast(R.string.no_app_found) + } + } + } + + private fun dialpadPressed(char: String, view: View?) { + dialpad_input.dispatchKeyEvent(getKeyEvent(getCharKeyCode(char))) + view?.performHapticFeedback() + } + + private fun clearChar(view: View) { + dialpad_input.dispatchKeyEvent(getKeyEvent(KeyEvent.KEYCODE_DEL)) + view.performHapticFeedback() + } + + private fun clearInput() { + dialpad_input.setText("") + } + + private fun getKeyEvent(keyCode: Int) = KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 0) + + private fun getCharKeyCode(char: String) = when (char) { + "0" -> KeyEvent.KEYCODE_0 + "1" -> KeyEvent.KEYCODE_1 + "2" -> KeyEvent.KEYCODE_2 + "3" -> KeyEvent.KEYCODE_3 + "4" -> KeyEvent.KEYCODE_4 + "5" -> KeyEvent.KEYCODE_5 + "6" -> KeyEvent.KEYCODE_6 + "7" -> KeyEvent.KEYCODE_7 + "8" -> KeyEvent.KEYCODE_8 + "9" -> KeyEvent.KEYCODE_9 + "*" -> KeyEvent.KEYCODE_STAR + "+" -> KeyEvent.KEYCODE_PLUS + else -> KeyEvent.KEYCODE_POUND + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private fun disableKeyboardPopping() { + if (isLollipopPlus()) { + dialpad_input.showSoftInputOnFocus = false + } else { + dialpad_input.inputType = InputType.TYPE_NULL + } + } + + private fun gotContacts(newContacts: ArrayList) { + contacts = newContacts + Contact.sorting = config.sorting + Contact.startWithSurname = config.startNameWithSurname + contacts.sort() + } + + private fun dialpadValueChanged(text: String) { + (dialpad_list.adapter as? ContactsAdapter)?.finishActMode() + val filtered = contacts.filter { it.doesContainPhoneNumber(text) } as ArrayList + + ContactsAdapter(this, filtered, null, LOCATION_DIALPAD, null, dialpad_list, dialpad_fastscroller, text) { + callContact(it as Contact) + }.apply { + addVerticalDividers(true) + dialpad_list.adapter = this + } + + dialpad_fastscroller.setScrollToY(0) + dialpad_fastscroller.setViews(dialpad_list) { + val item = (dialpad_list.adapter as ContactsAdapter).contactItems.getOrNull(it) + dialpad_fastscroller.updateBubbleText(item?.getBubbleText() ?: "") + } + } + + private fun initCall() { + val number = dialpad_input.value + if (number.isNotEmpty()) { + if (config.showCallConfirmation) { + CallConfirmationDialog(this, number) { + startCallIntent(number) + } + } else { + startCallIntent(number) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt index 5acf55af..385f155a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt @@ -1,5 +1,6 @@ package com.simplemobiletools.contacts.activities +import android.app.Activity import android.app.DatePickerDialog import android.content.ClipData import android.content.ContentValues @@ -13,6 +14,7 @@ import android.view.Menu import android.view.MenuItem import android.view.ViewGroup import android.view.WindowManager +import android.widget.EditText import android.widget.ImageView import android.widget.TextView import com.simplemobiletools.commons.dialogs.RadioGroupDialog @@ -21,6 +23,7 @@ import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CONTACTS import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.dialogs.CustomLabelDialog import com.simplemobiletools.contacts.dialogs.SelectGroupsDialog import com.simplemobiletools.contacts.extensions.* import com.simplemobiletools.contacts.helpers.* @@ -29,12 +32,12 @@ import kotlinx.android.synthetic.main.activity_edit_contact.* import kotlinx.android.synthetic.main.item_edit_address.view.* import kotlinx.android.synthetic.main.item_edit_email.view.* import kotlinx.android.synthetic.main.item_edit_group.view.* +import kotlinx.android.synthetic.main.item_edit_im.view.* import kotlinx.android.synthetic.main.item_edit_phone_number.view.* import kotlinx.android.synthetic.main.item_edit_website.view.* import kotlinx.android.synthetic.main.item_event.view.* import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat -import java.util.* class EditContactActivity : ContactActivity() { private val INTENT_TAKE_PHOTO = 1 @@ -49,6 +52,8 @@ class EditContactActivity : ContactActivity() { private var lastPhotoIntentUri: Uri? = null private var isSaving = false private var isThirdPartyIntent = false + private var highlightLastPhoneNumber = false + private var numberViewToColor: EditText? = null private var originalContactSource = "" override fun onCreate(savedInstanceState: Bundle?) { @@ -57,7 +62,7 @@ class EditContactActivity : ContactActivity() { supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_cross) val action = intent.action - isThirdPartyIntent = action == Intent.ACTION_EDIT || action == Intent.ACTION_INSERT_OR_EDIT || action == Intent.ACTION_INSERT + isThirdPartyIntent = action == Intent.ACTION_EDIT || action == Intent.ACTION_INSERT || action == ADD_NEW_CONTACT_NUMBER val isFromSimpleContacts = intent.getBooleanExtra(IS_FROM_SIMPLE_CONTACTS, false) if (isThirdPartyIntent && !isFromSimpleContacts) { handlePermission(PERMISSION_READ_CONTACTS) { @@ -118,7 +123,7 @@ class EditContactActivity : ContactActivity() { private fun initContact() { var contactId = intent.getIntExtra(CONTACT_ID, 0) val action = intent.action - if (contactId == 0 && action == Intent.ACTION_EDIT) { + if (contactId == 0 && (action == Intent.ACTION_EDIT || action == ADD_NEW_CONTACT_NUMBER)) { val data = intent.data if (data != null) { val rawId = if (data.path.contains("lookup")) { @@ -148,11 +153,20 @@ class EditContactActivity : ContactActivity() { setupEditContact() } - if (contact!!.id == 0 && intent.extras?.containsKey(KEY_PHONE) == true && (action == Intent.ACTION_INSERT_OR_EDIT || action == Intent.ACTION_INSERT)) { - val phoneNumber = intent.extras.get(KEY_PHONE)?.toString() ?: "" - contact!!.phoneNumbers.add(PhoneNumber(phoneNumber, DEFAULT_PHONE_NUMBER_TYPE)) + if ((contact!!.id == 0 && intent.extras != null && intent.extras.containsKey(KEY_PHONE) && action == Intent.ACTION_INSERT) || action == ADD_NEW_CONTACT_NUMBER) { + val phone = intent.extras.get(KEY_PHONE) + if (phone != null) { + val phoneNumber = phone.toString() + contact!!.phoneNumbers.add(PhoneNumber(phoneNumber, DEFAULT_PHONE_NUMBER_TYPE, "")) + if (phoneNumber.isNotEmpty() && action == ADD_NEW_CONTACT_NUMBER) { + highlightLastPhoneNumber = true + } + } - contact!!.firstName = intent.extras.get(KEY_NAME)?.toString() ?: "" + val firstName = intent.extras.get(KEY_NAME) + if (firstName != null) { + contact!!.firstName = firstName.toString() + } val data = intent.extras.getParcelableArrayList("data") if (data != null) { @@ -182,6 +196,7 @@ class EditContactActivity : ContactActivity() { contact_numbers_image.applyColorFilter(textColor) contact_emails_image.applyColorFilter(textColor) contact_addresses_image.applyColorFilter(textColor) + contact_ims_image.applyColorFilter(textColor) contact_events_image.applyColorFilter(textColor) contact_notes_image.applyColorFilter(textColor) contact_organization_image.applyColorFilter(textColor) @@ -196,6 +211,8 @@ class EditContactActivity : ContactActivity() { contact_emails_add_new.background.applyColorFilter(textColor) contact_addresses_add_new.applyColorFilter(adjustedPrimaryColor) contact_addresses_add_new.background.applyColorFilter(textColor) + contact_ims_add_new.applyColorFilter(adjustedPrimaryColor) + contact_ims_add_new.background.applyColorFilter(textColor) contact_events_add_new.applyColorFilter(adjustedPrimaryColor) contact_events_add_new.background.applyColorFilter(textColor) contact_websites_add_new.applyColorFilter(adjustedPrimaryColor) @@ -211,6 +228,7 @@ class EditContactActivity : ContactActivity() { contact_numbers_add_new.setOnClickListener { addNewPhoneNumberField() } contact_emails_add_new.setOnClickListener { addNewEmailField() } contact_addresses_add_new.setOnClickListener { addNewAddressField() } + contact_ims_add_new.setOnClickListener { addNewIMField() } contact_events_add_new.setOnClickListener { addNewEventField() } contact_websites_add_new.setOnClickListener { addNewWebsiteField() } contact_groups_add_new.setOnClickListener { showSelectGroupsDialog() } @@ -225,6 +243,7 @@ class EditContactActivity : ContactActivity() { } updateTextColors(contact_scrollview) + numberViewToColor?.setTextColor(getAdjustedPrimaryColor()) wasActivityInitialized = true invalidateOptionsMenu() } @@ -299,6 +318,11 @@ class EditContactActivity : ContactActivity() { contact_addresses_holder.beVisibleIf(areAddressesVisible) contact_addresses_add_new.beVisibleIf(areAddressesVisible) + val areIMsVisible = showFields and SHOW_IMS_FIELD != 0 + contact_ims_image.beVisibleIf(areIMsVisible) + contact_ims_holder.beVisibleIf(areIMsVisible) + contact_ims_add_new.beVisibleIf(areIMsVisible) + val isOrganizationVisible = showFields and SHOW_ORGANIZATION_FIELD != 0 contact_organization_company.beVisibleIf(isOrganizationVisible) contact_organization_job_position.beVisibleIf(isOrganizationVisible) @@ -332,6 +356,7 @@ class EditContactActivity : ContactActivity() { setupPhoneNumbers() setupEmails() setupAddresses() + setupIMs() setupNotes() setupOrganization() setupWebsites() @@ -361,7 +386,10 @@ class EditContactActivity : ContactActivity() { numberHolder!!.apply { contact_number.setText(number.value) - setupPhoneNumberTypePicker(contact_number_type, number.type) + setupPhoneNumberTypePicker(contact_number_type, number.type, number.label) + if (highlightLastPhoneNumber && index == contact!!.phoneNumbers.size - 1) { + numberViewToColor = contact_number + } } } } @@ -376,7 +404,7 @@ class EditContactActivity : ContactActivity() { emailHolder!!.apply { contact_email.setText(email.value) - setupEmailTypePicker(contact_email_type, email.type) + setupEmailTypePicker(contact_email_type, email.type, email.label) } } } @@ -391,7 +419,22 @@ class EditContactActivity : ContactActivity() { addressHolder!!.apply { contact_address.setText(address.value) - setupAddressTypePicker(contact_address_type, address.type) + setupAddressTypePicker(contact_address_type, address.type, address.label) + } + } + } + + private fun setupIMs() { + contact!!.IMs.forEachIndexed { index, IM -> + var imHolder = contact_ims_holder.getChildAt(index) + if (imHolder == null) { + imHolder = layoutInflater.inflate(R.layout.item_edit_im, contact_ims_holder, false) + contact_ims_holder.addView(imHolder) + } + + imHolder!!.apply { + contact_im.setText(IM.value) + setupIMTypePicker(contact_im_type, IM.type, IM.label) } } } @@ -506,7 +549,7 @@ class EditContactActivity : ContactActivity() { originalContactSource = if (hasContactPermissions()) config.lastUsedContactSource else SMT_PRIVATE val organization = Organization("", "") contact = Contact(0, "", "", "", "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), originalContactSource, 0, 0, "", - null, "", ArrayList(), organization, ArrayList()) + null, "", ArrayList(), organization, ArrayList(), ArrayList(), ArrayList()) contact_source.text = getPublicContactSource(contact!!.source) } @@ -514,21 +557,28 @@ class EditContactActivity : ContactActivity() { if (contact!!.phoneNumbers.isEmpty()) { val numberHolder = contact_numbers_holder.getChildAt(0) (numberHolder as? ViewGroup)?.contact_number_type?.apply { - setupPhoneNumberTypePicker(this) + setupPhoneNumberTypePicker(this, DEFAULT_PHONE_NUMBER_TYPE, "") } } if (contact!!.emails.isEmpty()) { val emailHolder = contact_emails_holder.getChildAt(0) (emailHolder as? ViewGroup)?.contact_email_type?.apply { - setupEmailTypePicker(this) + setupEmailTypePicker(this, DEFAULT_EMAIL_TYPE, "") } } if (contact!!.addresses.isEmpty()) { val addressHolder = contact_addresses_holder.getChildAt(0) (addressHolder as? ViewGroup)?.contact_address_type?.apply { - setupAddressTypePicker(this) + setupAddressTypePicker(this, DEFAULT_ADDRESS_TYPE, "") + } + } + + if (contact!!.IMs.isEmpty()) { + val IMHolder = contact_ims_holder.getChildAt(0) + (IMHolder as? ViewGroup)?.contact_im_type?.apply { + setupIMTypePicker(this, DEFAULT_IM_TYPE, "") } } @@ -547,33 +597,42 @@ class EditContactActivity : ContactActivity() { } } - private fun setupPhoneNumberTypePicker(numberTypeField: TextView, type: Int = DEFAULT_PHONE_NUMBER_TYPE) { + private fun setupPhoneNumberTypePicker(numberTypeField: TextView, type: Int, label: String) { numberTypeField.apply { - setText(getPhoneNumberTextId(type)) + text = getPhoneNumberTypeText(type, label) setOnClickListener { showNumberTypePicker(it as TextView) } } } - private fun setupEmailTypePicker(emailTypeField: TextView, type: Int = DEFAULT_EMAIL_TYPE) { + private fun setupEmailTypePicker(emailTypeField: TextView, type: Int, label: String) { emailTypeField.apply { - setText(getEmailTextId(type)) + text = getEmailTypeText(type, label) setOnClickListener { showEmailTypePicker(it as TextView) } } } - private fun setupAddressTypePicker(addressTypeField: TextView, type: Int = DEFAULT_ADDRESS_TYPE) { + private fun setupAddressTypePicker(addressTypeField: TextView, type: Int, label: String) { addressTypeField.apply { - setText(getAddressTextId(type)) + text = getAddressTypeText(type, label) setOnClickListener { showAddressTypePicker(it as TextView) } } } + private fun setupIMTypePicker(imTypeField: TextView, type: Int, label: String) { + imTypeField.apply { + text = getIMTypeText(type, label) + setOnClickListener { + showIMTypePicker(it as TextView) + } + } + } + private fun setupEventTypePicker(eventHolder: ViewGroup, type: Int = DEFAULT_EVENT_TYPE) { eventHolder.contact_event_type.apply { setText(getEventTextId(type)) @@ -641,12 +700,19 @@ class EditContactActivity : ContactActivity() { RadioItem(CommonDataKinds.Phone.TYPE_FAX_WORK, getString(R.string.work_fax)), RadioItem(CommonDataKinds.Phone.TYPE_FAX_HOME, getString(R.string.home_fax)), RadioItem(CommonDataKinds.Phone.TYPE_PAGER, getString(R.string.pager)), - RadioItem(CommonDataKinds.Phone.TYPE_OTHER, getString(R.string.other)) + RadioItem(CommonDataKinds.Phone.TYPE_OTHER, getString(R.string.other)), + RadioItem(CommonDataKinds.Phone.TYPE_CUSTOM, getString(R.string.custom)) ) val currentNumberTypeId = getPhoneNumberTypeId(numberTypeField.value) RadioGroupDialog(this, items, currentNumberTypeId) { - numberTypeField.setText(getPhoneNumberTextId(it as Int)) + if (it as Int == CommonDataKinds.Phone.TYPE_CUSTOM) { + CustomLabelDialog(this) { + numberTypeField.text = it + } + } else { + numberTypeField.text = getPhoneNumberTypeText(it, "") + } } } @@ -655,12 +721,19 @@ class EditContactActivity : ContactActivity() { RadioItem(CommonDataKinds.Email.TYPE_HOME, getString(R.string.home)), RadioItem(CommonDataKinds.Email.TYPE_WORK, getString(R.string.work)), RadioItem(CommonDataKinds.Email.TYPE_MOBILE, getString(R.string.mobile)), - RadioItem(CommonDataKinds.Email.TYPE_OTHER, getString(R.string.other)) + RadioItem(CommonDataKinds.Email.TYPE_OTHER, getString(R.string.other)), + RadioItem(CommonDataKinds.Email.TYPE_CUSTOM, getString(R.string.custom)) ) val currentEmailTypeId = getEmailTypeId(emailTypeField.value) RadioGroupDialog(this, items, currentEmailTypeId) { - emailTypeField.setText(getEmailTextId(it as Int)) + if (it as Int == CommonDataKinds.Email.TYPE_CUSTOM) { + CustomLabelDialog(this) { + emailTypeField.text = it + } + } else { + emailTypeField.text = getEmailTypeText(it, "") + } } } @@ -668,12 +741,44 @@ class EditContactActivity : ContactActivity() { val items = arrayListOf( RadioItem(CommonDataKinds.StructuredPostal.TYPE_HOME, getString(R.string.home)), RadioItem(CommonDataKinds.StructuredPostal.TYPE_WORK, getString(R.string.work)), - RadioItem(CommonDataKinds.StructuredPostal.TYPE_OTHER, getString(R.string.other)) + RadioItem(CommonDataKinds.StructuredPostal.TYPE_OTHER, getString(R.string.other)), + RadioItem(CommonDataKinds.StructuredPostal.TYPE_CUSTOM, getString(R.string.custom)) ) val currentAddressTypeId = getAddressTypeId(addressTypeField.value) RadioGroupDialog(this, items, currentAddressTypeId) { - addressTypeField.setText(getAddressTextId(it as Int)) + if (it as Int == CommonDataKinds.StructuredPostal.TYPE_CUSTOM) { + CustomLabelDialog(this) { + addressTypeField.text = it + } + } else { + addressTypeField.text = getAddressTypeText(it, "") + } + } + } + + private fun showIMTypePicker(imTypeField: TextView) { + val items = arrayListOf( + RadioItem(CommonDataKinds.Im.PROTOCOL_AIM, getString(R.string.aim)), + RadioItem(CommonDataKinds.Im.PROTOCOL_MSN, getString(R.string.windows_live)), + RadioItem(CommonDataKinds.Im.PROTOCOL_YAHOO, getString(R.string.yahoo)), + RadioItem(CommonDataKinds.Im.PROTOCOL_SKYPE, getString(R.string.skype)), + RadioItem(CommonDataKinds.Im.PROTOCOL_QQ, getString(R.string.qq)), + RadioItem(CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK, getString(R.string.hangouts)), + RadioItem(CommonDataKinds.Im.PROTOCOL_ICQ, getString(R.string.icq)), + RadioItem(CommonDataKinds.Im.PROTOCOL_JABBER, getString(R.string.jabber)), + RadioItem(CommonDataKinds.Im.PROTOCOL_CUSTOM, getString(R.string.custom)) + ) + + val currentIMTypeId = getIMTypeId(imTypeField.value) + RadioGroupDialog(this, items, currentIMTypeId) { + if (it as Int == CommonDataKinds.Im.PROTOCOL_CUSTOM) { + CustomLabelDialog(this) { + imTypeField.text = it + } + } else { + imTypeField.text = getIMTypeText(it, "") + } } } @@ -722,6 +827,7 @@ class EditContactActivity : ContactActivity() { phoneNumbers = getFilledPhoneNumbers() emails = getFilledEmails() addresses = getFilledAddresses() + IMs = getFilledIMs() events = getFilledEvents() starred = if (isContactStarred()) 1 else 0 notes = contact_notes.value @@ -752,9 +858,10 @@ class EditContactActivity : ContactActivity() { val numberHolder = contact_numbers_holder.getChildAt(i) val number = numberHolder.contact_number.value val numberType = getPhoneNumberTypeId(numberHolder.contact_number_type.value) + val numberLabel = if (numberType == CommonDataKinds.Phone.TYPE_CUSTOM) numberHolder.contact_number_type.value else "" if (number.isNotEmpty()) { - phoneNumbers.add(PhoneNumber(number, numberType)) + phoneNumbers.add(PhoneNumber(number, numberType, numberLabel)) } } return phoneNumbers @@ -767,9 +874,10 @@ class EditContactActivity : ContactActivity() { val emailHolder = contact_emails_holder.getChildAt(i) val email = emailHolder.contact_email.value val emailType = getEmailTypeId(emailHolder.contact_email_type.value) + val emailLabel = if (emailType == CommonDataKinds.Email.TYPE_CUSTOM) emailHolder.contact_email_type.value else "" if (email.isNotEmpty()) { - emails.add(Email(email, emailType)) + emails.add(Email(email, emailType, emailLabel)) } } return emails @@ -782,14 +890,31 @@ class EditContactActivity : ContactActivity() { val addressHolder = contact_addresses_holder.getChildAt(i) val address = addressHolder.contact_address.value val addressType = getAddressTypeId(addressHolder.contact_address_type.value) + val addressLabel = if (addressType == CommonDataKinds.StructuredPostal.TYPE_CUSTOM) addressHolder.contact_address_type.value else "" if (address.isNotEmpty()) { - addresses.add(Address(address, addressType)) + addresses.add(Address(address, addressType, addressLabel)) } } return addresses } + private fun getFilledIMs(): ArrayList { + val IMs = ArrayList() + val IMsCount = contact_ims_holder.childCount + for (i in 0 until IMsCount) { + val IMsHolder = contact_ims_holder.getChildAt(i) + val IM = IMsHolder.contact_im.value + val IMType = getIMTypeId(IMsHolder.contact_im_type.value) + val IMLabel = if (IMType == CommonDataKinds.Im.PROTOCOL_CUSTOM) IMsHolder.contact_im_type.value else "" + + if (IM.isNotEmpty()) { + IMs.add(IM(IM, IMType, IMLabel)) + } + } + return IMs + } + private fun getFilledEvents(): ArrayList { val unknown = getString(R.string.unknown) val events = ArrayList() @@ -830,6 +955,7 @@ class EditContactActivity : ContactActivity() { contact!!.source = originalContactSource ContactsHelper(this).deleteContact(contact!!) } + setResult(Activity.RESULT_OK) finish() } else { toast(R.string.unknown_error_occurred) @@ -839,6 +965,7 @@ class EditContactActivity : ContactActivity() { private fun updateContact(photoUpdateStatus: Int) { isSaving = true if (ContactsHelper(this@EditContactActivity).updateContact(contact!!, photoUpdateStatus)) { + setResult(Activity.RESULT_OK) finish() } else { toast(R.string.unknown_error_occurred) @@ -860,7 +987,7 @@ class EditContactActivity : ContactActivity() { private fun addNewPhoneNumberField() { val numberHolder = layoutInflater.inflate(R.layout.item_edit_phone_number, contact_numbers_holder, false) as ViewGroup updateTextColors(numberHolder) - setupPhoneNumberTypePicker(numberHolder.contact_number_type) + setupPhoneNumberTypePicker(numberHolder.contact_number_type, DEFAULT_PHONE_NUMBER_TYPE, "") contact_numbers_holder.addView(numberHolder) contact_numbers_holder.onGlobalLayout { numberHolder.contact_number.requestFocus() @@ -871,7 +998,7 @@ class EditContactActivity : ContactActivity() { private fun addNewEmailField() { val emailHolder = layoutInflater.inflate(R.layout.item_edit_email, contact_emails_holder, false) as ViewGroup updateTextColors(emailHolder) - setupEmailTypePicker(emailHolder.contact_email_type) + setupEmailTypePicker(emailHolder.contact_email_type, DEFAULT_EMAIL_TYPE, "") contact_emails_holder.addView(emailHolder) contact_emails_holder.onGlobalLayout { emailHolder.contact_email.requestFocus() @@ -882,7 +1009,7 @@ class EditContactActivity : ContactActivity() { private fun addNewAddressField() { val addressHolder = layoutInflater.inflate(R.layout.item_edit_address, contact_addresses_holder, false) as ViewGroup updateTextColors(addressHolder) - setupAddressTypePicker(addressHolder.contact_address_type) + setupAddressTypePicker(addressHolder.contact_address_type, DEFAULT_ADDRESS_TYPE, "") contact_addresses_holder.addView(addressHolder) contact_addresses_holder.onGlobalLayout { addressHolder.contact_address.requestFocus() @@ -890,6 +1017,17 @@ class EditContactActivity : ContactActivity() { } } + private fun addNewIMField() { + val IMHolder = layoutInflater.inflate(R.layout.item_edit_im, contact_ims_holder, false) as ViewGroup + updateTextColors(IMHolder) + setupIMTypePicker(IMHolder.contact_im_type, DEFAULT_IM_TYPE, "") + contact_ims_holder.addView(IMHolder) + contact_ims_holder.onGlobalLayout { + IMHolder.contact_im.requestFocus() + showKeyboard(IMHolder.contact_im) + } + } + private fun addNewEventField() { val eventHolder = layoutInflater.inflate(R.layout.item_event, contact_events_holder, false) as ViewGroup updateTextColors(eventHolder) @@ -955,7 +1093,7 @@ class EditContactActivity : ContactActivity() { private fun parseEmail(contentValues: ContentValues) { val type = contentValues.getAsInteger(CommonDataKinds.Email.DATA2) ?: DEFAULT_EMAIL_TYPE val emailValue = contentValues.getAsString(CommonDataKinds.Email.DATA1) ?: return - val email = Email(emailValue, type) + val email = Email(emailValue, type, "") contact!!.emails.add(email) } @@ -963,7 +1101,7 @@ class EditContactActivity : ContactActivity() { val type = contentValues.getAsInteger(CommonDataKinds.StructuredPostal.DATA2) ?: DEFAULT_ADDRESS_TYPE val addressValue = contentValues.getAsString(CommonDataKinds.StructuredPostal.DATA4) ?: contentValues.getAsString(CommonDataKinds.StructuredPostal.DATA1) ?: return - val address = Address(addressValue, type) + val address = Address(addressValue, type, "") contact!!.addresses.add(address) } @@ -1027,14 +1165,16 @@ class EditContactActivity : ContactActivity() { getString(R.string.work_fax) -> CommonDataKinds.Phone.TYPE_FAX_WORK getString(R.string.home_fax) -> CommonDataKinds.Phone.TYPE_FAX_HOME getString(R.string.pager) -> CommonDataKinds.Phone.TYPE_PAGER - else -> CommonDataKinds.Phone.TYPE_OTHER + getString(R.string.other) -> CommonDataKinds.Phone.TYPE_OTHER + else -> CommonDataKinds.Phone.TYPE_CUSTOM } private fun getEmailTypeId(value: String) = when (value) { getString(R.string.home) -> CommonDataKinds.Email.TYPE_HOME getString(R.string.work) -> CommonDataKinds.Email.TYPE_WORK getString(R.string.mobile) -> CommonDataKinds.Email.TYPE_MOBILE - else -> CommonDataKinds.Email.TYPE_OTHER + getString(R.string.other) -> CommonDataKinds.Email.TYPE_OTHER + else -> CommonDataKinds.Email.TYPE_CUSTOM } private fun getEventTypeId(value: String) = when (value) { @@ -1046,6 +1186,19 @@ class EditContactActivity : ContactActivity() { private fun getAddressTypeId(value: String) = when (value) { getString(R.string.home) -> CommonDataKinds.StructuredPostal.TYPE_HOME getString(R.string.work) -> CommonDataKinds.StructuredPostal.TYPE_WORK - else -> CommonDataKinds.StructuredPostal.TYPE_OTHER + getString(R.string.other) -> CommonDataKinds.StructuredPostal.TYPE_OTHER + else -> CommonDataKinds.StructuredPostal.TYPE_CUSTOM + } + + private fun getIMTypeId(value: String) = when (value) { + getString(R.string.aim) -> CommonDataKinds.Im.PROTOCOL_AIM + getString(R.string.windows_live) -> CommonDataKinds.Im.PROTOCOL_MSN + getString(R.string.yahoo) -> CommonDataKinds.Im.PROTOCOL_YAHOO + getString(R.string.skype) -> CommonDataKinds.Im.PROTOCOL_SKYPE + getString(R.string.qq) -> CommonDataKinds.Im.PROTOCOL_QQ + getString(R.string.hangouts) -> CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK + getString(R.string.icq) -> CommonDataKinds.Im.PROTOCOL_ICQ + getString(R.string.jabber) -> CommonDataKinds.Im.PROTOCOL_JABBER + else -> CommonDataKinds.Im.PROTOCOL_CUSTOM } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/InsertOrEditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/InsertOrEditContactActivity.kt new file mode 100644 index 00000000..16430225 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/InsertOrEditContactActivity.kt @@ -0,0 +1,90 @@ +package com.simplemobiletools.contacts.activities + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.provider.ContactsContract +import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor +import com.simplemobiletools.commons.extensions.getColoredDrawableWithColor +import com.simplemobiletools.commons.extensions.toast +import com.simplemobiletools.commons.extensions.updateTextColors +import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS +import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.adapters.ContactsAdapter +import com.simplemobiletools.contacts.extensions.config +import com.simplemobiletools.contacts.extensions.getContactPublicUri +import com.simplemobiletools.contacts.helpers.ADD_NEW_CONTACT_NUMBER +import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.KEY_PHONE +import com.simplemobiletools.contacts.helpers.LOCATION_INSERT_OR_EDIT +import com.simplemobiletools.contacts.models.Contact +import kotlinx.android.synthetic.main.activity_insert_edit_contact.* + +class InsertOrEditContactActivity : SimpleActivity() { + private val START_INSERT_ACTIVITY = 1 + private val START_EDIT_ACTIVITY = 2 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_insert_edit_contact) + title = getString(R.string.select_contact) + setupViews() + + handlePermission(PERMISSION_READ_CONTACTS) { + // we do not really care about the permission request result. Even if it was denied, load private contacts + ContactsHelper(this).getContacts { + gotContacts(it) + } + } + } + + private fun setupViews() { + updateTextColors(insert_edit_contact_holder) + new_contact_tmb.setImageDrawable(resources.getColoredDrawableWithColor(R.drawable.ic_new_contact, config.textColor)) + new_contact_holder.setOnClickListener { + Intent().apply { + action = Intent.ACTION_INSERT + data = ContactsContract.Contacts.CONTENT_URI + putExtra(KEY_PHONE, intent.getStringExtra(KEY_PHONE)) + if (resolveActivity(packageManager) != null) { + startActivityForResult(this, START_INSERT_ACTIVITY) + } else { + toast(R.string.no_app_found) + } + } + } + + existing_contact_label.setTextColor(getAdjustedPrimaryColor()) + } + + private fun gotContacts(contacts: ArrayList) { + Contact.sorting = config.sorting + Contact.startWithSurname = config.startNameWithSurname + contacts.sort() + + ContactsAdapter(this, contacts, null, LOCATION_INSERT_OR_EDIT, null, existing_contact_list, existing_contact_fastscroller) { + Intent(applicationContext, EditContactActivity::class.java).apply { + data = getContactPublicUri(it as Contact) + action = ADD_NEW_CONTACT_NUMBER + putExtra(KEY_PHONE, intent.getStringExtra(KEY_PHONE)) + startActivityForResult(this, START_EDIT_ACTIVITY) + } + }.apply { + addVerticalDividers(true) + existing_contact_list.adapter = this + } + + existing_contact_fastscroller.setScrollToY(0) + existing_contact_fastscroller.setViews(existing_contact_list) { + val item = (existing_contact_list.adapter as ContactsAdapter).contactItems.getOrNull(it) + existing_contact_fastscroller.updateBubbleText(item?.getBubbleText() ?: "") + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { + super.onActivityResult(requestCode, resultCode, resultData) + if (resultCode == Activity.RESULT_OK) { + finish() + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt index 19c92a75..0cb2281b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -3,6 +3,7 @@ package com.simplemobiletools.contacts.activities import android.app.SearchManager import android.content.Context import android.content.Intent +import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.net.Uri @@ -163,6 +164,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } } isFirstResume = false + + val dialpadIcon = resources.getColoredDrawableWithColor(R.drawable.ic_dialpad, if (isBlackAndWhiteTheme()) Color.BLACK else Color.WHITE) + main_dialpad_button.setImageDrawable(dialpadIcon) + main_dialpad_button.background.applyColorFilter(getAdjustedPrimaryColor()) } override fun onPause() { @@ -369,6 +374,11 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } main_tabs_holder.beVisibleIf(skippedTabs < 3) + + main_dialpad_button.setOnClickListener { + val intent = Intent(applicationContext, DialpadActivity::class.java) + startActivity(intent) + } } private fun getTabIcon(position: Int): Drawable { @@ -539,6 +549,8 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { add(Release(16, R.string.release_16)) add(Release(27, R.string.release_27)) add(Release(29, R.string.release_29)) + add(Release(31, R.string.release_31)) + add(Release(32, R.string.release_32)) checkWhatsNew(this, BuildConfig.VERSION_CODE) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt index 3b41f777..a7995535 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt @@ -11,6 +11,7 @@ import android.widget.RelativeLayout import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.dialogs.CallConfirmationDialog import com.simplemobiletools.contacts.extensions.* import com.simplemobiletools.contacts.helpers.* import kotlinx.android.synthetic.main.activity_view_contact.* @@ -18,6 +19,7 @@ import kotlinx.android.synthetic.main.item_event.view.* import kotlinx.android.synthetic.main.item_view_address.view.* import kotlinx.android.synthetic.main.item_view_email.view.* import kotlinx.android.synthetic.main.item_view_group.view.* +import kotlinx.android.synthetic.main.item_view_im.view.* import kotlinx.android.synthetic.main.item_view_phone_number.view.* import kotlinx.android.synthetic.main.item_website.view.* @@ -56,7 +58,6 @@ class ViewContactActivity : ContactActivity() { return true } - override fun onOptionsItemSelected(item: MenuItem): Boolean { if (contact == null) { return true @@ -153,6 +154,7 @@ class ViewContactActivity : ContactActivity() { setupPhoneNumbers() setupEmails() setupAddresses() + setupIMs() setupEvents() setupNotes() setupOrganization() @@ -222,10 +224,22 @@ class ViewContactActivity : ContactActivity() { val phoneNumber = it contact_numbers_holder.addView(this) contact_number.text = phoneNumber.value - contact_number_type.setText(getPhoneNumberTextId(phoneNumber.type)) + contact_number_type.text = getPhoneNumberTypeText(phoneNumber.type, phoneNumber.label) setOnClickListener { - startCallIntent(phoneNumber.value) + if (config.showCallConfirmation) { + CallConfirmationDialog(this@ViewContactActivity, phoneNumber.value) { + startCallIntent(phoneNumber.value) + } + } else { + startCallIntent(phoneNumber.value) + } + } + + setOnLongClickListener { + copyToClipboard(phoneNumber.value) + toast(R.string.value_copied_to_clipboard) + true } } } @@ -246,7 +260,7 @@ class ViewContactActivity : ContactActivity() { val email = it contact_emails_holder.addView(this) contact_email.text = email.value - contact_email_type.setText(getEmailTextId(email.type)) + contact_email_type.text = getEmailTypeText(email.type, email.label) setOnClickListener { sendEmailIntent(email.value) @@ -270,7 +284,7 @@ class ViewContactActivity : ContactActivity() { val address = it contact_addresses_holder.addView(this) contact_address.text = address.value - contact_address_type.setText(getAddressTextId(address.type)) + contact_address_type.text = getAddressTypeText(address.type, address.label) setOnClickListener { sendAddressIntent(address.value) @@ -285,6 +299,26 @@ class ViewContactActivity : ContactActivity() { } } + private fun setupIMs() { + contact_ims_holder.removeAllViews() + val IMs = contact!!.IMs + if (IMs.isNotEmpty() && showFields and SHOW_IMS_FIELD != 0) { + IMs.forEach { + layoutInflater.inflate(R.layout.item_view_im, contact_ims_holder, false).apply { + val IM = it + contact_ims_holder.addView(this) + contact_im.text = IM.value + contact_im_type.text = getIMTypeText(IM.type, IM.label) + } + } + contact_ims_image.beVisible() + contact_ims_holder.beVisible() + } else { + contact_ims_image.beGone() + contact_ims_holder.beGone() + } + } + private fun setupEvents() { contact_events_holder.removeAllViews() val events = contact!!.events diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt index 39bd3220..4372437e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt @@ -29,12 +29,12 @@ import java.util.* class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList, private val refreshListener: RefreshContactsListener?, private val location: Int, private val removeListener: RemoveFromGroupListener?, recyclerView: MyRecyclerView, - fastScroller: FastScroller, itemClick: (Any) -> Unit) : + fastScroller: FastScroller, highlightText: String = "", itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { private lateinit var contactDrawable: Drawable private var config = activity.config - private var textToHighlight = "" + private var textToHighlight = highlightText var adjustedPrimaryColor = activity.getAdjustedPrimaryColor() var startNameWithSurname: Boolean @@ -63,6 +63,8 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList + val allowLongClick = location != LOCATION_INSERT_OR_EDIT + val view = holder.bindView(contact, true, allowLongClick) { itemView, layoutPosition -> setupView(itemView, contact) } bindViewHolder(holder, position, view) @@ -260,7 +263,13 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList Unit) { +class CallConfirmationDialog(val activity: BaseSimpleActivity, val callee: String, private val callback: () -> Unit) { private var view = activity.layoutInflater.inflate(R.layout.dialog_call_confirmation, null) init { @@ -18,7 +17,7 @@ class CallConfirmationDialog(val activity: BaseSimpleActivity, val contact: Cont AlertDialog.Builder(activity) .setNegativeButton(R.string.cancel, null) .create().apply { - val title = String.format(activity.getString(R.string.call_person), contact.getFullName()) + val title = String.format(activity.getString(R.string.call_person), callee) activity.setupDialogStuff(view, this, titleText = title) { view.call_confirm_phone.apply { startAnimation(AnimationUtils.loadAnimation(activity, R.anim.pulsing_animation)) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CustomLabelDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CustomLabelDialog.kt new file mode 100644 index 00000000..047f5fe5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CustomLabelDialog.kt @@ -0,0 +1,36 @@ +package com.simplemobiletools.contacts.dialogs + +import android.support.v7.app.AlertDialog +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.setupDialogStuff +import com.simplemobiletools.commons.extensions.showKeyboard +import com.simplemobiletools.commons.extensions.toast +import com.simplemobiletools.commons.extensions.value +import com.simplemobiletools.contacts.R +import kotlinx.android.synthetic.main.dialog_custom_label.view.* + +class CustomLabelDialog(val activity: BaseSimpleActivity, val callback: (label: String) -> Unit) { + init { + + val view = activity.layoutInflater.inflate(R.layout.dialog_custom_label, null) + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok, null) + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this, R.string.label) { + showKeyboard(view.custom_label_edittext) + getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val label = view.custom_label_edittext.value + if (label.isEmpty()) { + activity.toast(R.string.empty_name) + return@setOnClickListener + } + + callback(label) + dismiss() + } + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt index 9560f4b2..9b9afcf6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt @@ -23,6 +23,7 @@ class ManageVisibleFieldsDialog(val activity: BaseSimpleActivity) { put(SHOW_PHONE_NUMBERS_FIELD, R.id.manage_visible_fields_phone_numbers) put(SHOW_EMAILS_FIELD, R.id.manage_visible_fields_emails) put(SHOW_ADDRESSES_FIELD, R.id.manage_visible_fields_addresses) + put(SHOW_IMS_FIELD, R.id.manage_visible_fields_ims) put(SHOW_EVENTS_FIELD, R.id.manage_visible_fields_events) put(SHOW_NOTES_FIELD, R.id.manage_visible_fields_notes) put(SHOW_ORGANIZATION_FIELD, R.id.manage_visible_fields_organization) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt index a63eb96d..a61b26f2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt @@ -36,7 +36,7 @@ fun SimpleActivity.startCallIntent(recipient: String) { fun SimpleActivity.tryStartCall(contact: Contact) { if (config.showCallConfirmation) { - CallConfirmationDialog(this, contact) { + CallConfirmationDialog(this, contact.getFullName()) { startCall(contact) } } else { @@ -62,8 +62,13 @@ fun SimpleActivity.startCall(contact: Contact) { fun SimpleActivity.showContactSourcePicker(currentSource: String, callback: (newSource: String) -> Unit) { ContactsHelper(this).getContactSources { + val ignoredTypes = arrayListOf( + "org.thoughtcrime.securesms", // Signal + "org.telegram.messenger" // Telegram + ) + val items = ArrayList() - val sources = it.map { it.name } + val sources = it.filter { !ignoredTypes.contains(it.type) }.map { it.name } var currentSourceIndex = -1 sources.forEachIndexed { index, account -> var publicAccount = account @@ -203,14 +208,16 @@ fun Activity.getVisibleContactSources(): ArrayList { fun SimpleActivity.contactClicked(contact: Contact) { when (config.onContactClick) { - ON_CLICK_CALL_CONTACT -> { - if (contact.phoneNumbers.isNotEmpty()) { - tryStartCall(contact) - } else { - toast(R.string.no_phone_number_found) - } - } + ON_CLICK_CALL_CONTACT -> callContact(contact) ON_CLICK_VIEW_CONTACT -> viewContact(contact) ON_CLICK_EDIT_CONTACT -> editContact(contact) } } + +fun SimpleActivity.callContact(contact: Contact) { + if (contact.phoneNumbers.isNotEmpty()) { + tryStartCall(contact) + } else { + toast(R.string.no_phone_number_found) + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/EditText.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/EditText.kt new file mode 100644 index 00000000..65f826c2 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/EditText.kt @@ -0,0 +1,13 @@ +package com.simplemobiletools.contacts.extensions + +import android.text.Editable +import android.text.TextWatcher +import android.widget.EditText + +fun EditText.afterTextChanged(callback: (String) -> Unit) = addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(text: Editable) = callback(text.toString()) +}) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt index 6134b1ad..f6d41a2b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt @@ -2,6 +2,7 @@ package com.simplemobiletools.contacts.extensions import android.widget.TextView import com.simplemobiletools.commons.helpers.getDateFormats +import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat import java.text.DateFormat @@ -32,3 +33,5 @@ fun String.getDateTimeFromDateString(viewToUpdate: TextView? = null): DateTime { } return date } + +fun String.applyRegexFiltering() = replace(PHONE_NUMBER_PATTERN.toRegex(), "") diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt index 7afecd04..208c7715 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -241,9 +241,10 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) val filtered = contactsIgnoringSearch.filter { getProperText(it.getFullName(), shouldNormalize).contains(text, true) || getProperText(it.nickname, shouldNormalize).contains(text, true) || - it.phoneNumbers.any { it.value.contains(text, true) } || + it.doesContainPhoneNumber(text) || it.emails.any { it.value.contains(text, true) } || it.addresses.any { getProperText(it.value, shouldNormalize).contains(text, true) } || + it.IMs.any { it.value.contains(text, true) } || getProperText(it.notes, shouldNormalize).contains(text, true) || getProperText(it.organization.company, shouldNormalize).contains(text, true) || getProperText(it.organization.jobPosition, shouldNormalize).contains(text, true) || diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt index c1319997..d4b30ab9 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt @@ -10,10 +10,10 @@ import com.simplemobiletools.commons.helpers.PERMISSION_READ_CALL_LOG import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALL_LOG import com.simplemobiletools.contacts.activities.EditContactActivity import com.simplemobiletools.contacts.adapters.RecentCallsAdapter +import com.simplemobiletools.contacts.extensions.applyRegexFiltering import com.simplemobiletools.contacts.extensions.contactClicked import com.simplemobiletools.contacts.helpers.IS_FROM_SIMPLE_CONTACTS import com.simplemobiletools.contacts.helpers.KEY_PHONE -import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN import com.simplemobiletools.contacts.helpers.RECENTS_TAB_MASK import com.simplemobiletools.contacts.models.Contact import com.simplemobiletools.contacts.models.RecentCall @@ -44,10 +44,10 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage val currAdapter = fragment_list.adapter if (currAdapter == null) { RecentCallsAdapter(activity!!, recentCalls, activity, fragment_list, fragment_fastscroller) { - val recentCall = (it as RecentCall).number.replace(PHONE_NUMBER_PATTERN.toRegex(), "") + val recentCall = (it as RecentCall).number.applyRegexFiltering() var selectedContact: Contact? = null for (contact in allContacts) { - if (contact.phoneNumbers.any { it.value.replace(PHONE_NUMBER_PATTERN.toRegex(), "") == recentCall }) { + if (contact.phoneNumbers.any { it.value.applyRegexFiltering() == recentCall }) { selectedContact = contact break } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt index 0f56d22b..58309511 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -21,8 +21,9 @@ const val SMT_PRIVATE = "smt_private" // used at the contact source of local c const val IS_PRIVATE = "is_private" const val GROUP = "group" const val FIRST_GROUP_ID = 10000 -const val PHONE_NUMBER_PATTERN = "\\D+" +const val PHONE_NUMBER_PATTERN = "[^0-9#*+]" const val IS_FROM_SIMPLE_CONTACTS = "is_from_simple_contacts" +const val ADD_NEW_CONTACT_NUMBER = "add_new_contact_number" // extras used at third party intents const val KEY_PHONE = "phone" @@ -33,6 +34,8 @@ const val LOCATION_FAVORITES_TAB = 1 const val LOCATION_RECENTS_TAB = 2 const val LOCATION_GROUPS_TAB = 3 const val LOCATION_GROUP_CONTACTS = 4 +const val LOCATION_DIALPAD = 5 +const val LOCATION_INSERT_OR_EDIT = 6 const val CONTACTS_TAB_MASK = 1 const val FAVORITES_TAB_MASK = 2 @@ -56,6 +59,7 @@ const val PHOTO_UNCHANGED = 4 const val CELL = "CELL" const val WORK = "WORK" const val HOME = "HOME" +const val OTHER = "OTHER" const val PREF = "PREF" const val MAIN = "MAIN" const val FAX = "FAX" @@ -64,6 +68,11 @@ const val HOME_FAX = "HOME;FAX" const val PAGER = "PAGER" const val MOBILE = "MOBILE" +// IMs not supported by Ez-vcard +const val HANGOUTS = "Hangouts" +const val QQ = "QQ" +const val JABBER = "Jabber" + const val ON_CLICK_CALL_CONTACT = 1 const val ON_CLICK_VIEW_CONTACT = 2 const val ON_CLICK_EDIT_CONTACT = 3 @@ -84,6 +93,7 @@ const val SHOW_GROUPS_FIELD = 2048 const val SHOW_CONTACT_SOURCE_FIELD = 4096 const val SHOW_WEBSITES_FIELD = 8192 const val SHOW_NICKNAME_FIELD = 16384 +const val SHOW_IMS_FIELD = 32768 const val DEFAULT_EMAIL_TYPE = CommonDataKinds.Email.TYPE_HOME const val DEFAULT_PHONE_NUMBER_TYPE = CommonDataKinds.Phone.TYPE_MOBILE @@ -91,6 +101,7 @@ 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 +const val DEFAULT_IM_TYPE = CommonDataKinds.Im.PROTOCOL_SKYPE // some manufacturer contact account types from https://stackoverflow.com/a/44802016/1967672 val localAccountTypes = arrayListOf("vnd.sec.contact.phone", diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt index 17d26c16..88e3b7d7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -24,6 +24,7 @@ import com.simplemobiletools.contacts.models.* import com.simplemobiletools.contacts.overloads.times import java.text.SimpleDateFormat import java.util.* +import kotlin.collections.ArrayList class ContactsHelper(val activity: Activity) { private val BATCH_SIZE = 100 @@ -42,12 +43,24 @@ class ContactsHelper(val activity: Activity) { } val contactsSize = contacts.size() - var resultContacts = ArrayList(contactsSize) - (0 until contactsSize).mapTo(resultContacts) { contacts.valueAt(it) } + var tempContacts = ArrayList(contactsSize) + val resultContacts = ArrayList(contactsSize) + (0 until contactsSize).mapTo(tempContacts) { contacts.valueAt(it) } if (activity.config.filterDuplicates) { - resultContacts = resultContacts.distinctBy { + tempContacts = tempContacts.distinctBy { it.getHashToCompare() } as ArrayList + + tempContacts.groupBy { "${it.getFullName().toLowerCase()}${it.emails}" }.values.forEach { + if (it.size == 1) { + resultContacts.add(it.first()) + } else { + val sorted = it.sortedByDescending { it.getStringToCompare().length } + resultContacts.add(sorted.first()) + } + } + } else { + resultContacts.addAll(tempContacts) } // groups are obtained with contactID, not rawID, so assign them to proper contacts like this @@ -115,7 +128,7 @@ class ContactsHelper(val activity: Activity) { val suffix = cursor.getStringValue(CommonDataKinds.StructuredName.SUFFIX) ?: "" val nickname = "" val photoUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_URI) ?: "" - val number = ArrayList() // proper value is obtained below + val numbers = ArrayList() // proper value is obtained below val emails = ArrayList() val addresses = ArrayList
() val events = ArrayList() @@ -127,8 +140,10 @@ class ContactsHelper(val activity: Activity) { val groups = ArrayList() val organization = Organization("", "") val websites = ArrayList() - val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, - events, accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites) + val cleanNumbers = ArrayList() + val ims = ArrayList() + val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, numbers, emails, addresses, + events, accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, cleanNumbers, ims) contacts.put(id, contact) } while (cursor.moveToNext()) @@ -139,11 +154,22 @@ class ContactsHelper(val activity: Activity) { cursor?.close() } + val filterDuplicates = activity.config.filterDuplicates val phoneNumbers = getPhoneNumbers(null) var size = phoneNumbers.size() for (i in 0 until size) { val key = phoneNumbers.keyAt(i) - contacts[key]?.phoneNumbers = phoneNumbers.valueAt(i) + if (contacts[key] != null) { + val numbers = phoneNumbers.valueAt(i) + contacts[key].phoneNumbers = numbers + + if (filterDuplicates) { + // remove all spaces, dashes etc from numbers for easier comparing, used only at list views + numbers.forEach { + numbers.mapTo(contacts[key].cleanPhoneNumbers) { PhoneNumber(it.value.applyRegexFiltering(), 0, "") } + } + } + } } val nicknames = getNicknames() @@ -167,6 +193,13 @@ class ContactsHelper(val activity: Activity) { contacts[key]?.addresses = addresses.valueAt(i) } + val IMs = getIMs() + size = IMs.size() + for (i in 0 until size) { + val key = IMs.keyAt(i) + contacts[key]?.IMs = IMs.valueAt(i) + } + val events = getEvents() size = events.size() for (i in 0 until size) { @@ -202,7 +235,8 @@ class ContactsHelper(val activity: Activity) { val projection = arrayOf( ContactsContract.Data.RAW_CONTACT_ID, CommonDataKinds.Phone.NUMBER, - CommonDataKinds.Phone.TYPE + CommonDataKinds.Phone.TYPE, + CommonDataKinds.Phone.LABEL ) val selection = if (contactId == null) getSourcesSelection() else "${ContactsContract.Data.RAW_CONTACT_ID} = ?" @@ -216,12 +250,13 @@ class ContactsHelper(val activity: Activity) { val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) val number = cursor.getStringValue(CommonDataKinds.Phone.NUMBER) ?: continue val type = cursor.getIntValue(CommonDataKinds.Phone.TYPE) + val label = cursor.getStringValue(CommonDataKinds.Phone.LABEL) ?: "" if (phoneNumbers[id] == null) { phoneNumbers.put(id, ArrayList()) } - val phoneNumber = PhoneNumber(number, type) + val phoneNumber = PhoneNumber(number, type, label) phoneNumbers[id].add(phoneNumber) } while (cursor.moveToNext()) } @@ -270,7 +305,8 @@ class ContactsHelper(val activity: Activity) { val projection = arrayOf( ContactsContract.Data.RAW_CONTACT_ID, CommonDataKinds.Email.DATA, - CommonDataKinds.Email.TYPE + CommonDataKinds.Email.TYPE, + CommonDataKinds.Email.LABEL ) val selection = if (contactId == null) getSourcesSelection() else "${ContactsContract.Data.RAW_CONTACT_ID} = ?" @@ -284,12 +320,13 @@ class ContactsHelper(val activity: Activity) { val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) val email = cursor.getStringValue(CommonDataKinds.Email.DATA) ?: continue val type = cursor.getIntValue(CommonDataKinds.Email.TYPE) + val label = cursor.getStringValue(CommonDataKinds.Email.LABEL) ?: "" if (emails[id] == null) { emails.put(id, ArrayList()) } - emails[id]!!.add(Email(email, type)) + emails[id]!!.add(Email(email, type, label)) } while (cursor.moveToNext()) } } catch (e: Exception) { @@ -307,7 +344,8 @@ class ContactsHelper(val activity: Activity) { val projection = arrayOf( ContactsContract.Data.RAW_CONTACT_ID, CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, - CommonDataKinds.StructuredPostal.TYPE + CommonDataKinds.StructuredPostal.TYPE, + CommonDataKinds.StructuredPostal.LABEL ) val selection = if (contactId == null) getSourcesSelection() else "${ContactsContract.Data.RAW_CONTACT_ID} = ?" @@ -321,12 +359,13 @@ class ContactsHelper(val activity: Activity) { val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) val address = cursor.getStringValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) ?: continue val type = cursor.getIntValue(CommonDataKinds.StructuredPostal.TYPE) + val label = cursor.getStringValue(CommonDataKinds.StructuredPostal.LABEL) ?: "" if (addresses[id] == null) { addresses.put(id, ArrayList()) } - addresses[id]!!.add(Address(address, type)) + addresses[id]!!.add(Address(address, type, label)) } while (cursor.moveToNext()) } } catch (e: Exception) { @@ -338,6 +377,45 @@ class ContactsHelper(val activity: Activity) { return addresses } + private fun getIMs(contactId: Int? = null): SparseArray> { + val IMs = SparseArray>() + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf( + ContactsContract.Data.RAW_CONTACT_ID, + CommonDataKinds.Im.DATA, + CommonDataKinds.Im.PROTOCOL, + CommonDataKinds.Im.CUSTOM_PROTOCOL + ) + + val selection = getSourcesSelection(true, contactId != null) + val selectionArgs = getSourcesSelectionArgs(CommonDataKinds.Im.CONTENT_ITEM_TYPE, contactId) + + var cursor: Cursor? = null + try { + cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null) + if (cursor?.moveToFirst() == true) { + do { + val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) + val IM = cursor.getStringValue(CommonDataKinds.Im.DATA) ?: continue + val type = cursor.getIntValue(CommonDataKinds.Im.PROTOCOL) + val label = cursor.getStringValue(CommonDataKinds.Im.CUSTOM_PROTOCOL) ?: "" + + if (IMs[id] == null) { + IMs.put(id, ArrayList()) + } + + IMs[id]!!.add(IM(IM, type, label)) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + activity.showErrorToast(e) + } finally { + cursor?.close() + } + + return IMs + } + private fun getEvents(contactId: Int? = null): SparseArray> { val events = SparseArray>() val uri = ContactsContract.Data.CONTENT_URI @@ -707,8 +785,10 @@ class ContactsHelper(val activity: Activity) { val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: "" val organization = getOrganizations(id)[id] ?: Organization("", "") val websites = getWebsites(id)[id] ?: ArrayList() + val cleanNumbers = ArrayList() + val ims = getIMs(id)[id] ?: ArrayList() return Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, events, - accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites) + accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, cleanNumbers, ims) } } finally { cursor?.close() @@ -856,6 +936,7 @@ class ContactsHelper(val activity: Activity) { withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE) withValue(CommonDataKinds.Phone.NUMBER, it.value) withValue(CommonDataKinds.Phone.TYPE, it.type) + withValue(CommonDataKinds.Phone.LABEL, it.label) operations.add(build()) } } @@ -875,6 +956,7 @@ class ContactsHelper(val activity: Activity) { withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE) withValue(CommonDataKinds.Email.DATA, it.value) withValue(CommonDataKinds.Email.TYPE, it.type) + withValue(CommonDataKinds.Email.LABEL, it.label) operations.add(build()) } } @@ -894,6 +976,27 @@ class ContactsHelper(val activity: Activity) { withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) withValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, it.value) withValue(CommonDataKinds.StructuredPostal.TYPE, it.type) + withValue(CommonDataKinds.StructuredPostal.LABEL, it.label) + operations.add(build()) + } + } + + // delete IMs + ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { + val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? " + val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.Im.CONTENT_ITEM_TYPE) + withSelection(selection, selectionArgs) + operations.add(build()) + } + + // add IMs + contact.IMs.forEach { + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id) + withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Im.CONTENT_ITEM_TYPE) + withValue(CommonDataKinds.Im.DATA, it.value) + withValue(CommonDataKinds.Im.PROTOCOL, it.type) + withValue(CommonDataKinds.Im.CUSTOM_PROTOCOL, it.label) operations.add(build()) } } @@ -1134,6 +1237,7 @@ class ContactsHelper(val activity: Activity) { withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE) withValue(CommonDataKinds.Phone.NUMBER, it.value) withValue(CommonDataKinds.Phone.TYPE, it.type) + withValue(CommonDataKinds.Phone.LABEL, it.label) operations.add(build()) } } @@ -1145,6 +1249,7 @@ class ContactsHelper(val activity: Activity) { withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE) withValue(CommonDataKinds.Email.DATA, it.value) withValue(CommonDataKinds.Email.TYPE, it.type) + withValue(CommonDataKinds.Email.LABEL, it.label) operations.add(build()) } } @@ -1156,6 +1261,19 @@ class ContactsHelper(val activity: Activity) { withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) withValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, it.value) withValue(CommonDataKinds.StructuredPostal.TYPE, it.type) + withValue(CommonDataKinds.StructuredPostal.LABEL, it.label) + operations.add(build()) + } + } + + // IMs + contact.IMs.forEach { + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Im.CONTENT_ITEM_TYPE) + withValue(CommonDataKinds.Im.DATA, it.value) + withValue(CommonDataKinds.Im.PROTOCOL, it.type) + withValue(CommonDataKinds.Im.CUSTOM_PROTOCOL, it.label) operations.add(build()) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt index 860b3698..b73283c6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt @@ -16,6 +16,8 @@ import com.simplemobiletools.commons.extensions.getBlobValue import com.simplemobiletools.commons.extensions.getIntValue import com.simplemobiletools.commons.extensions.getLongValue import com.simplemobiletools.commons.extensions.getStringValue +import com.simplemobiletools.contacts.extensions.applyRegexFiltering +import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.extensions.getByteArray import com.simplemobiletools.contacts.extensions.getPhotoThumbnailSize import com.simplemobiletools.contacts.models.* @@ -35,6 +37,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont private val COL_EVENTS = "events" private val COL_STARRED = "starred" private val COL_ADDRESSES = "addresses" + private val COL_IMS = "ims" private val COL_NOTES = "notes" private val COL_COMPANY = "company" private val COL_JOB_POSITION = "job_position" @@ -50,7 +53,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont companion object { const val DB_NAME = "contacts.db" - private const val DB_VERSION = 6 + private const val DB_VERSION = 7 private var dbInstance: DBHelper? = null var gson = Gson() @@ -66,7 +69,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont db.execSQL("CREATE TABLE $CONTACTS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_FIRST_NAME TEXT, $COL_MIDDLE_NAME TEXT, " + "$COL_SURNAME TEXT, $COL_PHOTO BLOB, $COL_PHONE_NUMBERS TEXT, $COL_EMAILS TEXT, $COL_EVENTS TEXT, $COL_STARRED INTEGER, " + "$COL_ADDRESSES TEXT, $COL_NOTES TEXT, $COL_GROUPS TEXT, $COL_PREFIX TEXT, $COL_SUFFIX TEXT, $COL_COMPANY TEXT, $COL_JOB_POSITION TEXT," + - "$COL_WEBSITES TEXT, $COL_NICKNAME TEXT)") + "$COL_WEBSITES TEXT, $COL_NICKNAME TEXT, $COL_IMS TEXT)") // start autoincrement ID from FIRST_CONTACT_ID to avoid conflicts db.execSQL("REPLACE INTO sqlite_sequence (name, seq) VALUES ('$CONTACTS_TABLE_NAME', $FIRST_CONTACT_ID)") @@ -99,6 +102,10 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont if (oldVersion < 6) { db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_NICKNAME TEXT DEFAULT ''") } + + if (oldVersion < 7) { + db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_IMS TEXT DEFAULT ''") + } } private fun createGroupsTable(db: SQLiteDatabase) { @@ -144,6 +151,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont put(COL_PHONE_NUMBERS, gson.toJson(contact.phoneNumbers)) put(COL_EMAILS, gson.toJson(contact.emails)) put(COL_ADDRESSES, gson.toJson(contact.addresses)) + put(COL_IMS, gson.toJson(contact.IMs)) put(COL_EVENTS, gson.toJson(contact.events)) put(COL_STARRED, contact.starred) put(COL_NOTES, contact.notes) @@ -257,13 +265,15 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont fun getContacts(activity: Activity, selection: String? = null, selectionArgs: Array? = null): ArrayList { val storedGroups = ContactsHelper(activity).getStoredGroups() + val filterDuplicates = activity.config.filterDuplicates val contacts = ArrayList() val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_NICKNAME, COL_PHONE_NUMBERS, - COL_EMAILS, COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION, COL_WEBSITES) + COL_EMAILS, COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_IMS, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION, COL_WEBSITES) val phoneNumbersToken = object : TypeToken>() {}.type val emailsToken = object : TypeToken>() {}.type val addressesToken = object : TypeToken>() {}.type + val IMsToken = object : TypeToken>() {}.type val eventsToken = object : TypeToken>() {}.type val groupIdsToken = object : TypeToken>() {}.type val websitesToken = object : TypeToken>() {}.type @@ -283,14 +293,30 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont val phoneNumbers = if (phoneNumbersJson == "[]") ArrayList() else gson.fromJson>(phoneNumbersJson, phoneNumbersToken) ?: ArrayList(1) + // labels can be null at upgrading from older app versions, when the label wasn't available at all yet + phoneNumbers.filter { it.label == null }.forEach { + it.label = "" + } + val emailsJson = cursor.getStringValue(COL_EMAILS) val emails = if (emailsJson == "[]") ArrayList() else gson.fromJson>(emailsJson, emailsToken) ?: ArrayList(1) + emails.filter { it.label == null }.forEach { + it.label = "" + } + val addressesJson = cursor.getStringValue(COL_ADDRESSES) val addresses = if (addressesJson == "[]") ArrayList() else gson.fromJson>(addressesJson, addressesToken) ?: ArrayList(1) + addresses.filter { it.label == null }.forEach { + it.label = "" + } + + val IMsJson = cursor.getStringValue(COL_IMS) + val IMs = if (IMsJson == "[]") ArrayList() else gson.fromJson>(IMsJson, IMsToken) ?: ArrayList(1) + val eventsJson = cursor.getStringValue(COL_EVENTS) val events = if (eventsJson == "[]") ArrayList() else gson.fromJson>(eventsJson, eventsToken) ?: ArrayList(1) @@ -322,8 +348,13 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont val websites = if (websitesJson == "[]") ArrayList() else gson.fromJson>(websitesJson, websitesToken) ?: ArrayList(1) + val cleanPhoneNumbers = ArrayList() + if (filterDuplicates) { + phoneNumbers.mapTo(cleanPhoneNumbers) { PhoneNumber(it.value.applyRegexFiltering(), 0, "") } + } + val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, "", phoneNumbers, emails, addresses, - events, SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites) + events, SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites, cleanPhoneNumbers, IMs) contacts.add(contact) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt index 4832c2ac..3a3dfb92 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt @@ -15,10 +15,7 @@ import com.simplemobiletools.contacts.helpers.VcfExporter.ExportResult.EXPORT_FA import com.simplemobiletools.contacts.models.Contact import ezvcard.Ezvcard import ezvcard.VCard -import ezvcard.parameter.AddressType -import ezvcard.parameter.EmailType import ezvcard.parameter.ImageType -import ezvcard.parameter.TelephoneType import ezvcard.property.* import ezvcard.util.PartialDate import java.io.File @@ -62,13 +59,13 @@ class VcfExporter { contact.phoneNumbers.forEach { val phoneNumber = Telephone(it.value) - phoneNumber.types.add(TelephoneType.find(getPhoneNumberLabel(it.type))) + phoneNumber.parameters.addType(getPhoneNumberTypeLabel(it.type, it.label)) card.addTelephoneNumber(phoneNumber) } contact.emails.forEach { val email = Email(it.value) - email.types.add(EmailType.find(getEmailTypeLabel(it.type))) + email.parameters.addType(getEmailTypeLabel(it.type, it.label)) card.addEmail(email) } @@ -76,7 +73,7 @@ class VcfExporter { if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY || it.type == CommonDataKinds.Event.TYPE_ANNIVERSARY) { val dateTime = it.value.getDateTimeFromDateString() if (it.value.startsWith("--")) { - val partialDate = PartialDate.builder().year(null).month(dateTime.monthOfYear - 1).date(dateTime.dayOfMonth).build() + val partialDate = PartialDate.builder().year(null).month(dateTime.monthOfYear).date(dateTime.dayOfMonth).build() if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) { card.birthdays.add(Birthday(partialDate)) } else { @@ -101,10 +98,26 @@ class VcfExporter { contact.addresses.forEach { val address = Address() address.streetAddress = it.value - address.types.add(AddressType.find(getAddressTypeLabel(it.type))) + address.parameters.addType(getAddressTypeLabel(it.type, it.label)) card.addAddress(address) } + contact.IMs.forEach { + val impp = when (it.type) { + CommonDataKinds.Im.PROTOCOL_AIM -> Impp.aim(it.value) + CommonDataKinds.Im.PROTOCOL_YAHOO -> Impp.yahoo(it.value) + CommonDataKinds.Im.PROTOCOL_MSN -> Impp.msn(it.value) + CommonDataKinds.Im.PROTOCOL_ICQ -> Impp.icq(it.value) + CommonDataKinds.Im.PROTOCOL_SKYPE -> Impp.skype(it.value) + CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK -> Impp(HANGOUTS, it.value) + CommonDataKinds.Im.PROTOCOL_QQ -> Impp(QQ, it.value) + CommonDataKinds.Im.PROTOCOL_JABBER -> Impp(JABBER, it.value) + else -> Impp(it.label, it.value) + } + + card.addImpp(impp) + } + if (contact.notes.isNotEmpty()) { card.addNote(contact.notes) } @@ -126,6 +139,15 @@ class VcfExporter { card.addPhoto(photo) } + if (contact.groups.isNotEmpty()) { + val groupList = Categories() + contact.groups.forEach { + groupList.values.add(it.title) + } + + card.categories = groupList + } + cards.add(card) contactsExported++ } @@ -143,24 +165,30 @@ class VcfExporter { } } - private fun getPhoneNumberLabel(type: Int) = when (type) { + private fun getPhoneNumberTypeLabel(type: Int, label: String) = when (type) { CommonDataKinds.Phone.TYPE_MOBILE -> CELL + CommonDataKinds.Phone.TYPE_HOME -> HOME CommonDataKinds.Phone.TYPE_WORK -> WORK CommonDataKinds.Phone.TYPE_MAIN -> PREF CommonDataKinds.Phone.TYPE_FAX_WORK -> WORK_FAX CommonDataKinds.Phone.TYPE_FAX_HOME -> HOME_FAX CommonDataKinds.Phone.TYPE_PAGER -> PAGER - else -> HOME + CommonDataKinds.Phone.TYPE_OTHER -> OTHER + else -> label } - private fun getEmailTypeLabel(type: Int) = when (type) { + private fun getEmailTypeLabel(type: Int, label: String) = when (type) { + CommonDataKinds.Email.TYPE_HOME -> HOME CommonDataKinds.Email.TYPE_WORK -> WORK CommonDataKinds.Email.TYPE_MOBILE -> MOBILE - else -> HOME + CommonDataKinds.Email.TYPE_OTHER -> OTHER + else -> label } - private fun getAddressTypeLabel(type: Int) = when (type) { + private fun getAddressTypeLabel(type: Int, label: String) = when (type) { + CommonDataKinds.StructuredPostal.TYPE_HOME -> HOME CommonDataKinds.StructuredPostal.TYPE_WORK -> WORK - else -> HOME + CommonDataKinds.StructuredPostal.TYPE_OTHER -> OTHER + else -> label } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt index d247b0ba..9cc27af8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -6,15 +6,18 @@ import android.provider.ContactsContract.CommonDataKinds import android.widget.Toast import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.contacts.activities.SimpleActivity +import com.simplemobiletools.contacts.extensions.dbHelper import com.simplemobiletools.contacts.extensions.getCachePhoto import com.simplemobiletools.contacts.extensions.getCachePhotoUri import com.simplemobiletools.contacts.helpers.VcfImporter.ImportResult.* import com.simplemobiletools.contacts.models.* import ezvcard.Ezvcard +import ezvcard.VCard import org.joda.time.DateTime import org.joda.time.format.DateTimeFormat import java.io.File import java.io.FileOutputStream +import java.net.URLDecoder import java.util.* class VcfImporter(val activity: SimpleActivity) { @@ -48,24 +51,42 @@ class VcfImporter(val activity: SimpleActivity) { val phoneNumbers = ArrayList() ezContact.telephoneNumbers.forEach { - val type = getPhoneNumberTypeId(it.types.firstOrNull()?.value ?: MOBILE) val number = it.text - phoneNumbers.add(PhoneNumber(number, type)) + val type = getPhoneNumberTypeId(it.types.firstOrNull()?.value ?: MOBILE, it.types.getOrNull(1)?.value) + val label = if (type == CommonDataKinds.Phone.TYPE_CUSTOM) { + it.types.firstOrNull()?.value ?: "" + } else { + "" + } + + phoneNumbers.add(PhoneNumber(number, type, label)) } val emails = ArrayList() ezContact.emails.forEach { - val type = getEmailTypeId(it.types.firstOrNull()?.value ?: HOME) val email = it.value - emails.add(Email(email, type)) + val type = getEmailTypeId(it.types.firstOrNull()?.value ?: HOME) + val label = if (type == CommonDataKinds.Email.TYPE_CUSTOM) { + it.types.firstOrNull()?.value ?: "" + } else { + "" + } + + emails.add(Email(email, type, label)) } val addresses = ArrayList
() ezContact.addresses.forEach { - val type = getAddressTypeId(it.types.firstOrNull()?.value ?: HOME) val address = it.streetAddress + val type = getAddressTypeId(it.types.firstOrNull()?.value ?: HOME) + val label = if (type == CommonDataKinds.StructuredPostal.TYPE_CUSTOM) { + it.types.firstOrNull()?.value ?: "" + } else { + "" + } + if (address?.isNotEmpty() == true) { - addresses.add(Address(address, type)) + addresses.add(Address(address, type, label)) } } @@ -83,18 +104,44 @@ class VcfImporter(val activity: SimpleActivity) { val starred = 0 val contactId = 0 val notes = ezContact.notes.firstOrNull()?.value ?: "" - val groups = ArrayList() + val groups = getContactGroups(ezContact) val company = ezContact.organization?.values?.firstOrNull() ?: "" val jobPosition = ezContact.titles?.firstOrNull()?.value ?: "" val organization = Organization(company, jobPosition) val websites = ezContact.urls.map { it.value } as ArrayList - val photoData = ezContact.photos.firstOrNull()?.data val photo = null val thumbnailUri = savePhoto(photoData) + val cleanPhoneNumbers = ArrayList() + + val IMs = ArrayList() + ezContact.impps.forEach { + val typeString = it.uri.scheme + val value = URLDecoder.decode(it.uri.toString().substring(it.uri.scheme.length + 1), "UTF-8") + val type = when { + it.isAim -> CommonDataKinds.Im.PROTOCOL_AIM + it.isYahoo -> CommonDataKinds.Im.PROTOCOL_YAHOO + it.isMsn -> CommonDataKinds.Im.PROTOCOL_MSN + it.isIcq -> CommonDataKinds.Im.PROTOCOL_ICQ + it.isSkype -> CommonDataKinds.Im.PROTOCOL_SKYPE + typeString == HANGOUTS -> CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK + typeString == QQ -> CommonDataKinds.Im.PROTOCOL_QQ + typeString == JABBER -> CommonDataKinds.Im.PROTOCOL_JABBER + else -> CommonDataKinds.Im.PROTOCOL_CUSTOM + } + + val label = if (type == CommonDataKinds.Im.PROTOCOL_CUSTOM) URLDecoder.decode(typeString, "UTF-8") else "" + val IM = IM(value, type, label) + IMs.add(IM) + } val contact = Contact(0, prefix, firstName, middleName, surname, suffix, nickname, photoUri, phoneNumbers, emails, addresses, events, - targetContactSource, starred, contactId, thumbnailUri, photo, notes, groups, organization, websites) + targetContactSource, starred, contactId, thumbnailUri, photo, notes, groups, organization, websites, cleanPhoneNumbers, IMs) + + // if there is no N and ORG fields at the given contact, only FN, treat it as an organization + if (contact.getFullName().isEmpty() && contact.organization.isEmpty() && ezContact.formattedName.value.isNotEmpty()) { + contact.organization.company = ezContact.formattedName.value + } if (ContactsHelper(activity).insertContact(contact)) { contactsImported++ @@ -117,29 +164,71 @@ class VcfImporter(val activity: SimpleActivity) { return dateTime.toString("yyyy-MM-dd") } - private fun getPhoneNumberTypeId(type: String) = when (type.toUpperCase()) { + private fun getContactGroups(ezContact: VCard): ArrayList { + val groups = ArrayList() + if (ezContact.categories != null) { + val groupNames = ezContact.categories.values + + if (groupNames != null) { + val storedGroups = ContactsHelper(activity).getStoredGroups() + + groupNames.forEach { + val groupName = it + val storedGroup = storedGroups.firstOrNull { it.title == groupName } + + if (storedGroup != null) { + groups.add(storedGroup) + } else { + val newContactGroup = activity.dbHelper.insertGroup(Group(0, groupName)) + + if (newContactGroup != null) { + groups.add(newContactGroup) + } + } + } + } + } + return groups + } + + private fun getPhoneNumberTypeId(type: String, subtype: String?) = when (type.toUpperCase()) { CELL -> CommonDataKinds.Phone.TYPE_MOBILE - HOME -> CommonDataKinds.Phone.TYPE_HOME - WORK -> CommonDataKinds.Phone.TYPE_WORK + HOME -> { + if (subtype?.toUpperCase() == FAX) { + CommonDataKinds.Phone.TYPE_FAX_HOME + } else { + CommonDataKinds.Phone.TYPE_HOME + } + } + WORK -> { + if (subtype?.toUpperCase() == FAX) { + CommonDataKinds.Phone.TYPE_FAX_WORK + } else { + CommonDataKinds.Phone.TYPE_WORK + } + } PREF, MAIN -> CommonDataKinds.Phone.TYPE_MAIN WORK_FAX -> CommonDataKinds.Phone.TYPE_FAX_WORK HOME_FAX -> CommonDataKinds.Phone.TYPE_FAX_HOME FAX -> CommonDataKinds.Phone.TYPE_FAX_WORK PAGER -> CommonDataKinds.Phone.TYPE_PAGER - else -> CommonDataKinds.Phone.TYPE_OTHER + OTHER -> CommonDataKinds.Phone.TYPE_OTHER + else -> CommonDataKinds.Phone.TYPE_CUSTOM } private fun getEmailTypeId(type: String) = when (type.toUpperCase()) { HOME -> CommonDataKinds.Email.TYPE_HOME WORK -> CommonDataKinds.Email.TYPE_WORK MOBILE -> CommonDataKinds.Email.TYPE_MOBILE - else -> CommonDataKinds.Email.TYPE_OTHER + OTHER -> CommonDataKinds.Email.TYPE_OTHER + else -> CommonDataKinds.Email.TYPE_CUSTOM } private fun getAddressTypeId(type: String) = when (type.toUpperCase()) { - HOME -> CommonDataKinds.Email.TYPE_HOME - WORK -> CommonDataKinds.Email.TYPE_WORK - else -> CommonDataKinds.Email.TYPE_OTHER + HOME -> CommonDataKinds.StructuredPostal.TYPE_HOME + WORK -> CommonDataKinds.StructuredPostal.TYPE_WORK + OTHER -> CommonDataKinds.StructuredPostal.TYPE_OTHER + else -> CommonDataKinds.StructuredPostal.TYPE_CUSTOM } private fun savePhoto(byteArray: ByteArray?): String { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Address.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Address.kt index 7571a228..cc07321b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Address.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Address.kt @@ -1,3 +1,3 @@ package com.simplemobiletools.contacts.models -data class Address(var value: String, var type: Int) +data class Address(var value: String, var type: Int, var label: String) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt index 6a18a265..c710d572 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -5,12 +5,14 @@ import com.simplemobiletools.commons.extensions.normalizeString import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME import com.simplemobiletools.commons.helpers.SORT_DESCENDING -import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN +import com.simplemobiletools.contacts.extensions.applyRegexFiltering data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var nickname: String, var photoUri: String, var phoneNumbers: ArrayList, var emails: ArrayList, var addresses: ArrayList
, var events: ArrayList, var source: String, var starred: Int, val contactId: Int, val thumbnailUri: String, var photo: Bitmap?, var notes: String, - var groups: ArrayList, var organization: Organization, var websites: ArrayList) : Comparable { + var groups: ArrayList, var organization: Organization, var websites: ArrayList, var cleanPhoneNumbers: ArrayList, + var IMs: ArrayList) : + Comparable { companion object { var sorting = 0 var startWithSurname = false @@ -92,15 +94,33 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m } } - fun getHashToCompare(): Int { - val newPhoneNumbers = ArrayList() - phoneNumbers.mapTo(newPhoneNumbers) { PhoneNumber(it.value.replace(PHONE_NUMBER_PATTERN.toRegex(), ""), 0) } - + fun getStringToCompare(): String { val newEmails = ArrayList() - emails.mapTo(newEmails) { Email(it.value, 0) } + emails.mapTo(newEmails) { Email(it.value, 0, "") } return copy(id = 0, prefix = "", firstName = getFullName().toLowerCase(), middleName = "", surname = "", suffix = "", nickname = "", photoUri = "", - phoneNumbers = newPhoneNumbers, events = ArrayList(), addresses = ArrayList(), emails = newEmails, source = "", starred = 0, - contactId = 0, thumbnailUri = "", notes = "", groups = ArrayList(), websites = ArrayList(), organization = Organization("", "")).hashCode() + phoneNumbers = ArrayList(), events = ArrayList(), addresses = ArrayList(), emails = newEmails, source = "", starred = 0, + contactId = 0, thumbnailUri = "", notes = "", groups = ArrayList(), websites = ArrayList(), organization = Organization("", ""), + IMs = ArrayList()).toString() + } + + fun getHashToCompare() = getStringToCompare().hashCode() + + // do a more advanced phone number check here, compare numbers and and search query with dashes, spaces and everything but numbers removed + fun doesContainPhoneNumber(text: String): Boolean { + if (text.isNotEmpty()) { + if (phoneNumbers.any { it.value.contains(text) } || cleanPhoneNumbers.any { it.value.contains(text) }) { + return true + } + } + + val filteredNumber = text.applyRegexFiltering() + if (filteredNumber.isNotEmpty()) { + if (phoneNumbers.any { it.value.contains(filteredNumber) } || cleanPhoneNumbers.any { it.value.contains(filteredNumber) }) { + return true + } + } + + return false } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Email.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Email.kt index 268bf7e9..caeafc0d 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Email.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Email.kt @@ -1,3 +1,3 @@ package com.simplemobiletools.contacts.models -data class Email(var value: String, var type: Int) +data class Email(var value: String, var type: Int, var label: String) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/IM.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/IM.kt new file mode 100644 index 00000000..f646e9f4 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/IM.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.contacts.models + +data class IM(var value: String, var type: Int, var label: String) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/PhoneNumber.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/PhoneNumber.kt index e1eee4cf..1dd39896 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/PhoneNumber.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/PhoneNumber.kt @@ -1,3 +1,3 @@ package com.simplemobiletools.contacts.models -data class PhoneNumber(var value: String, var type: Int) +data class PhoneNumber(var value: String, var type: Int, var label: String) diff --git a/app/src/main/res/drawable-hdpi/ic_clear_char.png b/app/src/main/res/drawable-hdpi/ic_clear_char.png new file mode 100644 index 00000000..3fd9c535 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_clear_char.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_dialpad.png b/app/src/main/res/drawable-hdpi/ic_dialpad.png new file mode 100644 index 00000000..f600b5b7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_dialpad.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_im.png b/app/src/main/res/drawable-hdpi/ic_im.png new file mode 100644 index 00000000..7a16bc7c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_im.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_new_contact.png b/app/src/main/res/drawable-hdpi/ic_new_contact.png new file mode 100644 index 00000000..ded0ae92 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_new_contact.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_clear_char.png b/app/src/main/res/drawable-xhdpi/ic_clear_char.png new file mode 100644 index 00000000..b51790d1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_clear_char.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_dialpad.png b/app/src/main/res/drawable-xhdpi/ic_dialpad.png new file mode 100644 index 00000000..793da560 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_dialpad.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_im.png b/app/src/main/res/drawable-xhdpi/ic_im.png new file mode 100644 index 00000000..175610f8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_im.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_new_contact.png b/app/src/main/res/drawable-xhdpi/ic_new_contact.png new file mode 100644 index 00000000..5637f091 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_new_contact.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_clear_char.png b/app/src/main/res/drawable-xxhdpi/ic_clear_char.png new file mode 100644 index 00000000..e049a108 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_clear_char.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_dialpad.png b/app/src/main/res/drawable-xxhdpi/ic_dialpad.png new file mode 100644 index 00000000..1f6bf856 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_dialpad.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_im.png b/app/src/main/res/drawable-xxhdpi/ic_im.png new file mode 100644 index 00000000..822a47dd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_im.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_new_contact.png b/app/src/main/res/drawable-xxhdpi/ic_new_contact.png new file mode 100644 index 00000000..d33121aa Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_new_contact.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clear_char.png b/app/src/main/res/drawable-xxxhdpi/ic_clear_char.png new file mode 100644 index 00000000..ea552fd3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_clear_char.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_dialpad.png b/app/src/main/res/drawable-xxxhdpi/ic_dialpad.png new file mode 100644 index 00000000..7754a7c9 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_dialpad.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_im.png b/app/src/main/res/drawable-xxxhdpi/ic_im.png new file mode 100644 index 00000000..8d75838c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_im.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_new_contact.png b/app/src/main/res/drawable-xxxhdpi/ic_new_contact.png new file mode 100644 index 00000000..07435b94 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_new_contact.png differ diff --git a/app/src/main/res/layout/activity_dialpad.xml b/app/src/main/res/layout/activity_dialpad.xml new file mode 100644 index 00000000..6378918f --- /dev/null +++ b/app/src/main/res/layout/activity_dialpad.xml @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_edit_contact.xml b/app/src/main/res/layout/activity_edit_contact.xml index 15c77a7c..fa6e8eb7 100644 --- a/app/src/main/res/layout/activity_edit_contact.xml +++ b/app/src/main/res/layout/activity_edit_contact.xml @@ -299,6 +299,44 @@ android:paddingTop="@dimen/medium_margin" android:src="@drawable/ic_plus"/> + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_insert_edit_contact.xml b/app/src/main/res/layout/activity_insert_edit_contact.xml new file mode 100644 index 00000000..50edfe57 --- /dev/null +++ b/app/src/main/res/layout/activity_insert_edit_contact.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1c26ff25..f3b5d8e9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -22,4 +22,16 @@ android:layout_height="match_parent" android:layout_below="@+id/main_tabs_holder"/> + + diff --git a/app/src/main/res/layout/activity_view_contact.xml b/app/src/main/res/layout/activity_view_contact.xml index e471ae49..74ce627d 100644 --- a/app/src/main/res/layout/activity_view_contact.xml +++ b/app/src/main/res/layout/activity_view_contact.xml @@ -241,6 +241,26 @@ android:orientation="vertical" android:paddingLeft="@dimen/small_margin"/> + + + + diff --git a/app/src/main/res/layout/dialog_custom_label.xml b/app/src/main/res/layout/dialog_custom_label.xml new file mode 100644 index 00000000..746ae0cd --- /dev/null +++ b/app/src/main/res/layout/dialog_custom_label.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/layout/dialog_manage_visible_fields.xml b/app/src/main/res/layout/dialog_manage_visible_fields.xml index 599fdb88..477e1210 100644 --- a/app/src/main/res/layout/dialog_manage_visible_fields.xml +++ b/app/src/main/res/layout/dialog_manage_visible_fields.xml @@ -86,6 +86,14 @@ android:paddingTop="@dimen/activity_margin" android:text="@string/addresses"/> + + @@ -31,15 +32,15 @@ android:layout_toRightOf="@+id/contact_tmb" android:ellipsize="end" android:maxLines="1" - android:textSize="@dimen/bigger_text_size" + android:textSize="@dimen/big_text_size" tools:text="John Doe"/> + + + + + + + diff --git a/app/src/main/res/layout/item_recent_call.xml b/app/src/main/res/layout/item_recent_call.xml index 61334288..14c60772 100644 --- a/app/src/main/res/layout/item_recent_call.xml +++ b/app/src/main/res/layout/item_recent_call.xml @@ -14,16 +14,16 @@ + + + + + + + diff --git a/app/src/main/res/menu/menu_dialpad.xml b/app/src/main/res/menu/menu_dialpad.xml new file mode 100644 index 00000000..66fa1d06 --- /dev/null +++ b/app/src/main/res/menu/menu_dialpad.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index d197f75f..8f2f3892 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -15,6 +15,8 @@ Grupa e-poçt göndər %s şəxsinə zng et Lazım olan icazələri istə + Create new contact + Add to an existing contact Yeni kontakt Redaktə et @@ -95,6 +97,10 @@ Kontakt köklərini daxil et Fayl adı (.vcf olmadan) + + Dialpad + Add number to contact + Göstərmək üçün sahəni seç Ön şəkilçi @@ -108,6 +114,7 @@ Vebsaytlar Qruplar Kontakt kökü + Instant messaging (IM) I want to change what fields are visible at contacts. Can I do it? diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 74ec5350..ec3adbe4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -15,6 +15,8 @@ Sende E-Mail an Gruppe %s anrufen Benötigte Berechtigungen anfordern + Create new contact + Add to an existing contact Neuer Kontakt Kontakt bearbeiten @@ -95,6 +97,10 @@ Kontaktquellen einschließen Dateiname (ohne .vcf) + + Dialpad + Add number to contact + Sichtbare Felder auswählen Titel @@ -108,6 +114,7 @@ Webseiten Gruppen Kontaktquelle + Instant messaging (IM) Ich möchte die sichtbaren Kontaktfelder ändern. Kann ich das machen? diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 34fd69ec..b548d329 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -15,6 +15,8 @@ Αποστολή email σε ομάδες Κλήση %s Ζητούνται τα απαιτούμενα δικαιώματα + Create new contact + Add to an existing contact Νέα επαφή Επεξεργασία επαφής @@ -95,6 +97,10 @@ Συμπερίληψη πηγών επαφών Όνομα αρχείου (χωρίς .vcf) + + Dialpad + Add number to contact + Επιλογή εμφάνισης πεδίων Πρόθεμα @@ -108,6 +114,7 @@ Ιστοσελίδα Ομάδες Προέλευση επαφής + Instant messaging (IM) Θέλω να αλλάξω τα πεδία που θα είναι ορατά στις επαφές. Μπορώ να το κάνω? diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c8ecf2f7..b3dc4634 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -14,7 +14,9 @@ Envoyer un SMS au groupe Envoyer un e-mail au groupe Call %s - Request the required permissions + Demander les autorisations requises + Create new contact + Add to an existing contact Nouveau contact Modifier contact @@ -23,7 +25,7 @@ Prénom Deuxième prénom Nom - Nickname + Surnom Pas de groupe @@ -49,12 +51,12 @@ Appeler le contact Voir les détails du contact Configurer l\'affichage des champs des contacts - Try filtering out duplicate contacts - Manage shown tabs + Essayez de filtrer les contacts en double + Gérer les onglets affichés Contacts Favorites - Recent calls - Show a call confirmation dialog before initiating a call + Appels récents + Afficher une boîte de dialogue de confirmation d\'appel avant de lancer un appel E-mail @@ -80,7 +82,7 @@ Ajouter des favoris Ajouter aux favoris Retirer des favoris - You must be at the Edit screen to modify a contact + Vous devez être sur l\'écran Modifier pour modifier un contact Rechercher des contacts @@ -95,6 +97,10 @@ Inclure les sources du contact Nom du fichier (sans .vcf) + + Dialpad + Add number to contact + Sélectionner les champs à afficher Préfixe @@ -108,6 +114,7 @@ Sites web Groupe Source du contact + Messagerie instantanée (MI) Je veux changer quelles champs sont visibles. Est-ce que je peux ? diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index e3b2bb3f..d5c8544e 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -9,12 +9,14 @@ Tvrtka Radno mjesto Web stranica - Send SMS to contacts - Send email to contacts - Send SMS to group - Send email to group - Call %s - Request the required permissions + Pošalji SMS kontaktima + Pošalji e-poštu kontaktima + Pošalji SMS grupi + Pošalji e-poštu grupi + Nazovi %s + Zatraži potrebna dopuštenja + Create new contact + Add to an existing contact Novi kontakt Uredi kontakt @@ -48,13 +50,13 @@ Prilikom dodira kontakta Nazovi kontakt Prikaži pojedinosti o kontaktu - Manage shown contact fields - Try filtering out duplicate contacts - Manage shown tabs - Contacts - Favorites - Recent calls - Show a call confirmation dialog before initiating a call + Upravljaj prikazanim poljima kontakta + Pokušaj filtrirati duple kontakte + Upravljaj prikazanim karticama + Kontakti + Favoriti + Nedavni pozivi + Pokažite dijaloški okvir za potvrdu poziva prije pokretanja poziva E-pošta @@ -80,7 +82,7 @@ Dodaj favorite Dodaj u favorite Ukloni iz favorita - You must be at the Edit screen to modify a contact + Morate biti na zaslonu Uređivanje da biste izmijenili kontakt Pretraži kontakte @@ -95,6 +97,10 @@ Uključi izvore kontakta Naziv datoteke (bez .vcf) + + Dialpad + Add number to contact + Odaberi polja za prikaz Prefiks @@ -108,6 +114,7 @@ Web stranice Grupe Izvori kontakata + Brzo slanje poruka (IM) Želim promijeniti polja koja su vidljiva na kontaktima. Mogu li to napraviti? diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 3235994a..e87a4211 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -15,6 +15,8 @@ グループにメールを送信 Call %s Request the required permissions + Create new contact + Add to an existing contact 新しい連絡先 連絡先を編集 @@ -95,6 +97,10 @@ Include contact sources ファイル名 (.vcfを含めない) + + ダイヤルパッド + Add number to contact + 表示する項目を選択 敬称(名前の前) @@ -108,6 +114,7 @@ ウェブサイト グループ インポート元 + Instant messaging (IM) 連絡先に表示される項目(フィールド)を変更することはできますか? diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 94819d86..4c7bbb68 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -15,6 +15,8 @@ Send email to group Call %s Request the required permissions + Create new contact + Add to an existing contact 새로운 연락처 연락처 수정 @@ -95,6 +97,10 @@ 가져오기 대상 파일이름 (.vcf 확장자 생략) + + Dialpad + Add number to contact + Select fields to show Prefix @@ -108,6 +114,7 @@ Websites Groups Contact source + Instant messaging (IM) I want to change what fields are visible at contacts. Can I do it? diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 4dbd270c..e99c1beb 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -15,6 +15,8 @@ Send email to group Call %s Request the required permissions + Create new contact + Add to an existing contact Naujas kontaktas Redaguoti kontaktą @@ -95,6 +97,10 @@ Įtraukti kontaktų šaltinius Bylos vardas (be .vcf) + + Dialpad + Add number to contact + Pasirinkti rodomus laukelius Priešdėlis @@ -108,6 +114,7 @@ Interneto puslapiai Grupės Kontakto šaltinis + Instant messaging (IM) Noriu pakeisti, kokie laukai yra matomi kontaktuose. Ar galiu tai padaryti? diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index f2a6d47f..de97bde8 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -14,7 +14,9 @@ Enviar SMS para o grupo Enviar e-mail para o grupo Ligar a %s - Request the required permissions + Pedir a permissão necessária + Create new contact + Add to an existing contact Novo contacto Editar contacto @@ -50,7 +52,7 @@ Ver detalhes Gerir campos a exibir Tentar filtrar contactos duplicados - Manage shown tabs + Gerir separadores a exibir Contactos Favoritos Chamadas recentes @@ -76,7 +78,7 @@ Aniversário - Parece que ainda não adicionou contactos como favoritos + Parece que ainda não adicionou contactos aos favoritos Adicionar favoritos Adicionar aos favoritos Remover dos favoritos @@ -95,6 +97,10 @@ Incluir fontes dos contactos Nome do ficheiro (sem .vcf) + + Dialpad + Add number to contact + Selecione os campos a mostrar Prefixo @@ -108,6 +114,7 @@ Site Grupos Origem do contacto + Mensagem instantânea (IM) I want to change what fields are visible at contacts. Can I do it? diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c4cc2259..cd412417 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -15,6 +15,8 @@ Отправить письмо группе Вызов %s Запрос необходимых разрешений + Create new contact + Add to an existing contact Новый контакт Редактировать контакт @@ -95,6 +97,10 @@ Включить источники контактов Имя файла (без .vcf) + + Dialpad + Add number to contact + Выберите отображаемые поля Префикс @@ -108,6 +114,7 @@ Сайты Группы Источник контакта + Мессенджеры (IM) Я хочу изменить поля, отображаемые у контактов. Могу ли я это сделать? diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 6e3f3d25..ac542986 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -15,6 +15,8 @@ Poslať skupine email Zavolať %s Vyžiadať potrebné oprávnenia + Vytvoriť nový kontakt + Pridať k existujúcemu kontaktu Nový kontakt Upraviť kontakt @@ -95,6 +97,10 @@ Zahrnúť zdroje kontaktov Názov súboru (bez .vcf) + + Číselník + Pridať číslo kontaktu + Zvoľte polia na zobrazenie Titul pred menom @@ -108,6 +114,7 @@ Webstránky Skupiny Zdroje kontaktov + Rýchle správy (IM) Chcem upraviť viditeľné polia kontaktov. Dá sa to? diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index c5f3e8a7..b2198c6e 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -15,6 +15,8 @@ Skicka e-post till grupp Ring %s Begär de behörigheter som krävs + Create new contact + Add to an existing contact Ny kontakt Redigera kontakt @@ -95,6 +97,10 @@ Inkludera kontaktkällor Filnamn (utan .vcf) + + Dialpad + Add number to contact + Välj vilka fält som ska visas Prefix @@ -108,6 +114,7 @@ Webbplatser Grupper Kontaktkälla + Instant messaging (IM) I want to change what fields are visible at contacts. Can I do it? diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f849e887..583219d7 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -15,6 +15,8 @@ Gruba e-posta gönder Call %s Request the required permissions + Create new contact + Add to an existing contact Yeni kişi Kişiyi düzenle @@ -95,6 +97,10 @@ Kişi kaynaklarını dahil et Dosya adı (.vcf olmadan) + + Dialpad + Add number to contact + Görüntülenecek alanları seç Önek @@ -108,6 +114,7 @@ Web siteleri Gruplar Kişi kaynağı + Instant messaging (IM) Rehberde görüntülenecek alanları değiştirmek istiyorum. Bunu yapabilir miyim? diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..afdc4448 --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c6f1b48b..1fe54476 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -15,6 +15,8 @@ 發送電子郵件給群組 打電話給 %s 請求必要的權限 + Create new contact + Add to an existing contact 新聯絡人 編輯聯絡人 @@ -95,6 +97,10 @@ 包含聯絡人來源 檔案名稱 (不含.vcf) + + Dialpad + Add number to contact + 選擇要顯示的欄位 前缀 @@ -108,6 +114,7 @@ 網站 群組 聯絡人來源 + Instant messaging (IM) 我想要更改在通訊錄會看到哪些欄位。我能這麼做嗎? diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4061e3b5..b0496177 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -4,4 +4,8 @@ 45dp 40dp 52dp + 56dp + 68dp + 60dp + 34sp diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index aa83af5e..c3d9f092 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -1,7 +1,22 @@ + IM + AIM + Windows Live + Yahoo + Skype + QQ + Hangouts + ICQ + Jabber + + Added a simple dialpad, dialer will come soon + + Added an optional IM field\n + Added support for custom phone number/email/address types + Added an optional Nickname field Allow customizing which tabs are visible\n diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0c4b5f5..53be90a2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,6 +15,8 @@ Send email to group Call %s Request the required permissions + Create new contact + Add to an existing contact New contact Edit contact @@ -95,6 +97,10 @@ Include contact sources Filename (without .vcf) + + Dialpad + Add number to contact + Select fields to show Prefix @@ -108,6 +114,7 @@ Websites Groups Contact source + Instant messaging (IM) I want to change what fields are visible at contacts. Can I do it? diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 0be50415..b7e0377a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -2,4 +2,15 @@ + + + diff --git a/build.gradle b/build.gradle index 1b8fb3c3..76ac6173 100644 --- a/build.gradle +++ b/build.gradle @@ -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.61' + ext.kotlin_version = '1.2.71' repositories { google() @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:3.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7d73f3c2..503fd67b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Dec 10 10:08:39 CET 2017 +#Tue Sep 25 13:09:34 CEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip