diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc45df2..f21ecede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ Changelog ========== +Version 3.5.1 *(2018-04-10)* +---------------------------- + + * Show the organization name as the contact name in case only that is filled + * Fix "Start name with surname" + +Version 3.5.0 *(2018-04-08)* +---------------------------- + + * Added name prefix/suffix and contact organizations (hidden by default) + * Added a settings item "Manage shown contact fields" for customizing visible contact details, with some fields disabled by default + * Allow using the app without granting Contacts permission, rely on local secret storage only + * Dial the selected contact if Call permission is not granted + * Many performance improvements and smaller bugfixes + Version 3.4.0 *(2018-03-21)* ---------------------------- diff --git a/app/build.gradle b/app/build.gradle index abf62b29..140b0da5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 27 - versionCode 15 - versionName "3.4.0" + versionCode 17 + versionName "3.5.1" setProperty("archivesBaseName", "contacts") } @@ -45,10 +45,9 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:3.16.12' + implementation 'com.simplemobiletools:commons:3.18.23' implementation 'joda-time:joda-time:2.9.9' implementation 'com.facebook.stetho:stetho:1.5.0' - implementation 'com.google.code.gson:gson:2.8.2' debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ContactActivity.kt index 0ef88394..da49b099 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ContactActivity.kt @@ -33,8 +33,9 @@ import java.text.SimpleDateFormat import java.util.* abstract class ContactActivity : SimpleActivity() { - var contact: Contact? = null - var currentContactPhotoPath = "" + protected var contact: Contact? = null + protected var currentContactPhotoPath = "" + protected var showFields = 0 fun showPhotoPlaceholder(photoView: ImageView) { val placeholder = resources.getColoredBitmap(R.drawable.ic_person, config.primaryColor.getContrastColor()) 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 b88e05db..6ba8d2f0 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt @@ -49,30 +49,39 @@ class EditContactActivity : ContactActivity() { private val REMOVE_PHOTO = 3 private val KEY_PHONE = "phone" + private val KEY_NAME = "name" private var wasActivityInitialized = false private var lastPhotoIntentUri: Uri? = null private var isSaving = false + private var isThirdPartyIntent = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_edit_contact) supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_cross) + showFields = config.showContactFields - handlePermission(PERMISSION_READ_CONTACTS) { - if (it) { - handlePermission(PERMISSION_WRITE_CONTACTS) { - if (it) { - initContact() - } else { - toast(R.string.no_contacts_permission) - finish() + val action = intent.action + isThirdPartyIntent = action == Intent.ACTION_EDIT || action == Intent.ACTION_INSERT_OR_EDIT || action == Intent.ACTION_INSERT + if (isThirdPartyIntent) { + handlePermission(PERMISSION_READ_CONTACTS) { + if (it) { + handlePermission(PERMISSION_WRITE_CONTACTS) { + if (it) { + initContact() + } else { + toast(R.string.no_contacts_permission) + finish() + } } + } else { + toast(R.string.no_contacts_permission) + finish() } - } else { - toast(R.string.no_contacts_permission) - finish() } + } else { + initContact() } } @@ -138,10 +147,13 @@ class EditContactActivity : ContactActivity() { setupEditContact() } - if (contact!!.id == 0 && intent.extras?.containsKey(KEY_PHONE) == true && (intent.action == Intent.ACTION_INSERT_OR_EDIT || intent.action == Intent.ACTION_INSERT)) { - val phoneNumber = intent.getStringExtra(KEY_PHONE) + 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)) setupPhoneNumbers() + + val contactFullName = intent.extras.get(KEY_NAME)?.toString() ?: "" + contact_first_name.setText(contactFullName) } setupTypePickers() @@ -162,23 +174,24 @@ class EditContactActivity : ContactActivity() { contact_start_call.applyColorFilter(textColor) contact_send_email.applyColorFilter(textColor) contact_name_image.applyColorFilter(textColor) - contact_number_image.applyColorFilter(textColor) - contact_email_image.applyColorFilter(textColor) - contact_address_image.applyColorFilter(textColor) - contact_event_image.applyColorFilter(textColor) + contact_numbers_image.applyColorFilter(textColor) + contact_emails_image.applyColorFilter(textColor) + contact_addresses_image.applyColorFilter(textColor) + contact_events_image.applyColorFilter(textColor) contact_notes_image.applyColorFilter(textColor) contact_source_image.applyColorFilter(textColor) contact_groups_image.applyColorFilter(textColor) + contact_organization_image.applyColorFilter(textColor) val adjustedPrimaryColor = getAdjustedPrimaryColor() - contact_number_add_new.applyColorFilter(adjustedPrimaryColor) - contact_number_add_new.background.applyColorFilter(textColor) - contact_email_add_new.applyColorFilter(adjustedPrimaryColor) - contact_email_add_new.background.applyColorFilter(textColor) - contact_address_add_new.applyColorFilter(adjustedPrimaryColor) - contact_address_add_new.background.applyColorFilter(textColor) - contact_event_add_new.applyColorFilter(adjustedPrimaryColor) - contact_event_add_new.background.applyColorFilter(textColor) + contact_numbers_add_new.applyColorFilter(adjustedPrimaryColor) + contact_numbers_add_new.background.applyColorFilter(textColor) + contact_emails_add_new.applyColorFilter(adjustedPrimaryColor) + contact_emails_add_new.background.applyColorFilter(textColor) + contact_addresses_add_new.applyColorFilter(adjustedPrimaryColor) + contact_addresses_add_new.background.applyColorFilter(textColor) + contact_events_add_new.applyColorFilter(adjustedPrimaryColor) + contact_events_add_new.background.applyColorFilter(textColor) contact_groups_add_new.applyColorFilter(adjustedPrimaryColor) contact_groups_add_new.background.applyColorFilter(textColor) @@ -187,12 +200,14 @@ class EditContactActivity : ContactActivity() { contact_send_sms.setOnClickListener { trySendSMS() } contact_start_call.setOnClickListener { tryStartCall(contact!!) } contact_send_email.setOnClickListener { trySendEmail() } - contact_number_add_new.setOnClickListener { addNewPhoneNumberField() } - contact_email_add_new.setOnClickListener { addNewEmailField() } - contact_address_add_new.setOnClickListener { addNewAddressField() } - contact_event_add_new.setOnClickListener { addNewEventField() } + contact_numbers_add_new.setOnClickListener { addNewPhoneNumberField() } + contact_emails_add_new.setOnClickListener { addNewEmailField() } + contact_addresses_add_new.setOnClickListener { addNewAddressField() } + contact_events_add_new.setOnClickListener { addNewEventField() } contact_groups_add_new.setOnClickListener { showSelectGroupsDialog() } + setupFieldVisibility() + contact_toggle_favorite.apply { setImageDrawable(getStarDrawable(contact!!.starred == 1)) tag = contact!!.starred @@ -231,94 +246,164 @@ class EditContactActivity : ContactActivity() { } } + private fun setupFieldVisibility() { + if (showFields and (SHOW_PREFIX_FIELD or SHOW_FIRST_NAME_FIELD or SHOW_MIDDLE_NAME_FIELD or SHOW_SURNAME_FIELD or SHOW_SUFFIX_FIELD) == 0) { + contact_name_image.beInvisible() + } + + contact_prefix.beVisibleIf(showFields and SHOW_PREFIX_FIELD != 0) + contact_first_name.beVisibleIf(showFields and SHOW_FIRST_NAME_FIELD != 0) + contact_middle_name.beVisibleIf(showFields and SHOW_MIDDLE_NAME_FIELD != 0) + contact_surname.beVisibleIf(showFields and SHOW_SURNAME_FIELD != 0) + contact_suffix.beVisibleIf(showFields and SHOW_SUFFIX_FIELD != 0) + + contact_source.beVisibleIf(showFields and SHOW_CONTACT_SOURCE_FIELD != 0) + contact_source_image.beVisibleIf(showFields and SHOW_CONTACT_SOURCE_FIELD != 0) + + val arePhoneNumbersVisible = showFields and SHOW_PHONE_NUMBERS_FIELD != 0 + contact_numbers_image.beVisibleIf(arePhoneNumbersVisible) + contact_numbers_holder.beVisibleIf(arePhoneNumbersVisible) + contact_numbers_add_new.beVisibleIf(arePhoneNumbersVisible) + + val areEmailsVisible = showFields and SHOW_EMAILS_FIELD != 0 + contact_emails_image.beVisibleIf(areEmailsVisible) + contact_emails_holder.beVisibleIf(areEmailsVisible) + contact_emails_add_new.beVisibleIf(areEmailsVisible) + + val areAddressesVisible = showFields and SHOW_ADDRESSES_FIELD != 0 + contact_addresses_image.beVisibleIf(areAddressesVisible) + contact_addresses_holder.beVisibleIf(areAddressesVisible) + contact_addresses_add_new.beVisibleIf(areAddressesVisible) + + val isOrganizationVisible = showFields and SHOW_ORGANIZATION_FIELD != 0 + contact_organization_company.beVisibleIf(isOrganizationVisible) + contact_organization_job_position.beVisibleIf(isOrganizationVisible) + contact_organization_image.beVisibleIf(isOrganizationVisible) + + val areEventsVisible = showFields and SHOW_EVENTS_FIELD != 0 + contact_events_image.beVisibleIf(areEventsVisible) + contact_events_holder.beVisibleIf(areEventsVisible) + contact_events_add_new.beVisibleIf(areEventsVisible) + + val areGroupsVisible = showFields and SHOW_GROUPS_FIELD != 0 + contact_groups_image.beVisibleIf(areGroupsVisible) + contact_groups_holder.beVisibleIf(areGroupsVisible) + contact_groups_add_new.beVisibleIf(areGroupsVisible) + + val areNotesVisible = showFields and SHOW_NOTES_FIELD != 0 + contact_notes.beVisibleIf(areNotesVisible) + contact_notes_image.beVisibleIf(areNotesVisible) + } + private fun setupEditContact() { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) supportActionBar?.title = resources.getString(R.string.edit_contact) + contact_prefix.setText(contact!!.prefix) contact_first_name.setText(contact!!.firstName) contact_middle_name.setText(contact!!.middleName) contact_surname.setText(contact!!.surname) + contact_suffix.setText(contact!!.suffix) + contact_source.text = getPublicContactSource(contact!!.source) setupPhoneNumbers() setupEmails() setupAddresses() setupNotes() + setupOrganization() setupEvents() setupGroups() } private fun setupPhoneNumbers() { - contact!!.phoneNumbers.forEachIndexed { index, number -> - var numberHolder = contact_numbers_holder.getChildAt(index) - if (numberHolder == null) { - numberHolder = layoutInflater.inflate(R.layout.item_edit_phone_number, contact_numbers_holder, false) - contact_numbers_holder.addView(numberHolder) - } + if (showFields and SHOW_PHONE_NUMBERS_FIELD != 0) { + contact!!.phoneNumbers.forEachIndexed { index, number -> + var numberHolder = contact_numbers_holder.getChildAt(index) + if (numberHolder == null) { + numberHolder = layoutInflater.inflate(R.layout.item_edit_phone_number, contact_numbers_holder, false) + contact_numbers_holder.addView(numberHolder) + } - numberHolder!!.apply { - contact_number.setText(number.value) - setupPhoneNumberTypePicker(contact_number_type, number.type) + numberHolder!!.apply { + contact_number.setText(number.value) + setupPhoneNumberTypePicker(contact_number_type, number.type) + } } } } private fun setupEmails() { - contact!!.emails.forEachIndexed { index, email -> - var emailHolder = contact_emails_holder.getChildAt(index) - if (emailHolder == null) { - emailHolder = layoutInflater.inflate(R.layout.item_edit_email, contact_emails_holder, false) - contact_emails_holder.addView(emailHolder) - } + if (showFields and SHOW_EMAILS_FIELD != 0) { + contact!!.emails.forEachIndexed { index, email -> + var emailHolder = contact_emails_holder.getChildAt(index) + if (emailHolder == null) { + emailHolder = layoutInflater.inflate(R.layout.item_edit_email, contact_emails_holder, false) + contact_emails_holder.addView(emailHolder) + } - emailHolder!!.apply { - contact_email.setText(email.value) - setupEmailTypePicker(contact_email_type, email.type) + emailHolder!!.apply { + contact_email.setText(email.value) + setupEmailTypePicker(contact_email_type, email.type) + } } } } private fun setupAddresses() { - contact!!.addresses.forEachIndexed { index, address -> - var addressHolder = contact_addresses_holder.getChildAt(index) - if (addressHolder == null) { - addressHolder = layoutInflater.inflate(R.layout.item_edit_address, contact_addresses_holder, false) - contact_addresses_holder.addView(addressHolder) - } + if (showFields and SHOW_ADDRESSES_FIELD != 0) { + contact!!.addresses.forEachIndexed { index, address -> + var addressHolder = contact_addresses_holder.getChildAt(index) + if (addressHolder == null) { + addressHolder = layoutInflater.inflate(R.layout.item_edit_address, contact_addresses_holder, false) + contact_addresses_holder.addView(addressHolder) + } - addressHolder!!.apply { - contact_address.setText(address.value) - setupAddressTypePicker(contact_address_type, address.type) + addressHolder!!.apply { + contact_address.setText(address.value) + setupAddressTypePicker(contact_address_type, address.type) + } } } } private fun setupNotes() { - contact_notes.setText(contact!!.notes) + if (showFields and SHOW_NOTES_FIELD != 0) { + contact_notes.setText(contact!!.notes) + } + } + + private fun setupOrganization() { + if (showFields and SHOW_ORGANIZATION_FIELD != 0) { + contact_organization_company.setText(contact!!.organization.company) + contact_organization_job_position.setText(contact!!.organization.jobPosition) + } } private fun setupEvents() { - contact!!.events.forEachIndexed { index, event -> - var eventHolder = contact_events_holder.getChildAt(index) - if (eventHolder == null) { - eventHolder = layoutInflater.inflate(R.layout.item_event, contact_events_holder, false) - contact_events_holder.addView(eventHolder) - } - - (eventHolder as ViewGroup).apply { - val contactEvent = contact_event.apply { - getDateTime(event.value, this) - tag = event.value - alpha = 1f + if (showFields and SHOW_EVENTS_FIELD != 0) { + contact!!.events.forEachIndexed { index, event -> + var eventHolder = contact_events_holder.getChildAt(index) + if (eventHolder == null) { + eventHolder = layoutInflater.inflate(R.layout.item_event, contact_events_holder, false) + contact_events_holder.addView(eventHolder) } - setupEventTypePicker(this, event.type) + (eventHolder as ViewGroup).apply { + val contactEvent = contact_event.apply { + getDateTime(event.value, this) + tag = event.value + alpha = 1f + } - contact_event_remove.apply { - beVisible() - applyColorFilter(getAdjustedPrimaryColor()) - background.applyColorFilter(config.textColor) - setOnClickListener { - resetContactEvent(contactEvent, this) + setupEventTypePicker(this, event.type) + + contact_event_remove.apply { + beVisible() + applyColorFilter(getAdjustedPrimaryColor()) + background.applyColorFilter(config.textColor) + setOnClickListener { + resetContactEvent(contactEvent, this) + } } } } @@ -326,59 +411,64 @@ class EditContactActivity : ContactActivity() { } private fun setupGroups() { - contact_groups_holder.removeAllViews() - val groups = contact!!.groups - groups.forEachIndexed { index, group -> - var groupHolder = contact_groups_holder.getChildAt(index) - if (groupHolder == null) { - groupHolder = layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false) - contact_groups_holder.addView(groupHolder) - } - - (groupHolder as ViewGroup).apply { - contact_group.apply { - text = group.title - setTextColor(config.textColor) - tag = group.id - alpha = 1f + if (showFields and SHOW_GROUPS_FIELD != 0) { + contact_groups_holder.removeAllViews() + val groups = contact!!.groups + groups.forEachIndexed { index, group -> + var groupHolder = contact_groups_holder.getChildAt(index) + if (groupHolder == null) { + groupHolder = layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false) + contact_groups_holder.addView(groupHolder) } - setOnClickListener { - showSelectGroupsDialog() - } + (groupHolder as ViewGroup).apply { + contact_group.apply { + text = group.title + setTextColor(config.textColor) + tag = group.id + alpha = 1f + } - contact_group_remove.apply { - beVisible() - applyColorFilter(getAdjustedPrimaryColor()) - background.applyColorFilter(config.textColor) setOnClickListener { - removeGroup(group.id) + showSelectGroupsDialog() + } + + contact_group_remove.apply { + beVisible() + applyColorFilter(getAdjustedPrimaryColor()) + background.applyColorFilter(config.textColor) + setOnClickListener { + removeGroup(group.id) + } } } } - } - if (groups.isEmpty()) { - layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false).apply { - contact_group.apply { - alpha = 0.5f - text = getString(R.string.no_groups) - setTextColor(config.textColor) - } + if (groups.isEmpty()) { + layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false).apply { + contact_group.apply { + alpha = 0.5f + text = getString(R.string.no_groups) + setTextColor(config.textColor) + } - contact_groups_holder.addView(this) - contact_group_remove.beGone() - setOnClickListener { - showSelectGroupsDialog() + contact_groups_holder.addView(this) + contact_group_remove.beGone() + setOnClickListener { + showSelectGroupsDialog() + } } } } } private fun setupNewContact() { - window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) + //window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) supportActionBar?.title = resources.getString(R.string.new_contact) - contact = Contact(0, "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), config.lastUsedContactSource, 0, 0, "", null, "", ArrayList()) + val contactSource = if (hasContactPermissions()) config.lastUsedContactSource else SMT_PRIVATE + val organization = Organization("", "") + contact = Contact(0, "", "", "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), contactSource, 0, 0, "", null, "", + ArrayList(), organization, ArrayList()) contact_source.text = getPublicContactSource(contact!!.source) contact_source.setOnClickListener { showContactSourcePicker(contact!!.source) { @@ -583,9 +673,11 @@ class EditContactActivity : ContactActivity() { contact!!.apply { val oldPhotoUri = photoUri + prefix = contact_prefix.value firstName = contact_first_name.value middleName = contact_middle_name.value surname = contact_surname.value + suffix = contact_suffix.value photoUri = currentContactPhotoPath phoneNumbers = getFilledPhoneNumbers() emails = getFilledEmails() @@ -595,6 +687,10 @@ class EditContactActivity : ContactActivity() { starred = if (isContactStarred()) 1 else 0 notes = contact_notes.value + val company = contact_organization_company.value + val jobPosition = contact_organization_job_position.value + organization = Organization(company, jobPosition) + Thread { config.lastUsedContactSource = source if (id == 0) { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt index 03136965..2ee2ab73 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt @@ -67,6 +67,7 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh group_contacts_list.beVisibleIf(groupContacts.isNotEmpty()) Contact.sorting = config.sorting + Contact.startWithSurname = config.startNameWithSurname groupContacts.sort() updateContacts(groupContacts) @@ -90,7 +91,6 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh ON_CLICK_EDIT_CONTACT -> editContact(it as Contact) } }.apply { - setupDragListener(true) addVerticalDividers(true) group_contacts_list.adapter = this } @@ -110,9 +110,11 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh } override fun removeFromGroup(contacts: ArrayList) { - ContactsHelper(this).removeContactsFromGroup(contacts, group.id) - if (groupContacts.size == 0) { - refreshContacts() - } + Thread { + removeContactsFromGroup(contacts, group.id) + if (groupContacts.size == 0) { + refreshContacts() + } + }.start() } } 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 3997c5b0..664acf60 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -36,11 +36,12 @@ import kotlinx.android.synthetic.main.fragment_groups.* import java.io.FileOutputStream class MainActivity : SimpleActivity(), RefreshContactsListener { - private var isFirstResume = true private var isSearchOpen = false private var searchMenuItem: MenuItem? = null + private var werePermissionsHandled = false + private var isFirstResume = true + private var isGettingContacts = false - private var storedUseEnglish = false private var storedTextColor = 0 private var storedBackgroundColor = 0 private var storedPrimaryColor = 0 @@ -58,19 +59,15 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { dbHelper handlePermission(PERMISSION_READ_CONTACTS) { + werePermissionsHandled = true if (it) { handlePermission(PERMISSION_WRITE_CONTACTS) { - if (it) { - storeLocalAccountData() - initFragments() - } else { - toast(R.string.no_contacts_permission) - finish() - } + storeLocalAccountData() + initFragments() } } else { - toast(R.string.no_contacts_permission) - finish() + storeLocalAccountData() + initFragments() } } storeStateVariables() @@ -79,13 +76,8 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { override fun onResume() { super.onResume() - if (storedUseEnglish != config.useEnglish) { - restartActivity() - return - } - if (storedShowPhoneNumbers != config.showPhoneNumbers) { - restartActivity() + System.exit(0) return } @@ -126,20 +118,18 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { favorites_fragment?.startNameWithSurnameChanged(configStartNameWithSurname) } - if (!isFirstResume) { + if (werePermissionsHandled && !isFirstResume) { if (viewpager.adapter == null) { initFragments() + } else { + refreshContacts(ALL_TABS_MASK) } getAllFragments().forEach { it?.onActivityResume() } - refreshContacts(ALL_TABS_MASK) - } - - if (hasPermission(PERMISSION_WRITE_CONTACTS)) { - isFirstResume = false } + isFirstResume = false } override fun onPause() { @@ -179,7 +169,6 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { private fun storeStateVariables() { config.apply { - storedUseEnglish = useEnglish storedTextColor = textColor storedBackgroundColor = backgroundColor storedPrimaryColor = primaryColor @@ -308,6 +297,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { if (intent?.action == Intent.ACTION_VIEW && intent.data != null) { tryImportContactsFromFile(intent.data) + intent.data = null } } @@ -400,17 +390,23 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } private fun launchAbout() { - val faqItems = arrayListOf(FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons)) + val faqItems = arrayListOf( + FAQItem(R.string.faq_1_title, R.string.faq_1_text), + FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons) + ) + startAboutActivity(R.string.app_name, LICENSE_MULTISELECT or LICENSE_JODA or LICENSE_GLIDE or LICENSE_GSON or LICENSE_STETHO, BuildConfig.VERSION_NAME, faqItems) } override fun refreshContacts(refreshTabsMask: Int) { - if (isActivityDestroyed()) { + if (isActivityDestroyed() || isGettingContacts) { return } + isGettingContacts = true ContactsHelper(this).getContacts { + isGettingContacts = false if (isActivityDestroyed()) { return@getContacts } @@ -443,6 +439,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { arrayListOf().apply { add(Release(10, R.string.release_10)) add(Release(11, R.string.release_11)) + add(Release(16, R.string.release_16)) checkWhatsNew(this, BuildConfig.VERSION_CODE) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt index 601f6e85..675e2ad1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt @@ -103,6 +103,7 @@ class SelectContactActivity : SimpleActivity() { } Contact.sorting = config.sorting + Contact.startWithSurname = config.startNameWithSurname contacts.sort() runOnUiThread { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt index bfc35114..9b76f9eb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt @@ -4,9 +4,9 @@ import android.os.Bundle import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.beVisibleIf import com.simplemobiletools.commons.extensions.updateTextColors -import com.simplemobiletools.commons.extensions.useEnglishToggled import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.dialogs.ManageVisibleFieldsDialog import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.helpers.ON_CLICK_CALL_CONTACT import com.simplemobiletools.contacts.helpers.ON_CLICK_EDIT_CONTACT @@ -24,6 +24,7 @@ class SettingsActivity : SimpleActivity() { super.onResume() setupCustomizeColors() + setupManageShownContactFields() setupUseEnglish() setupAvoidWhatsNew() setupShowInfoBubble() @@ -40,13 +41,19 @@ class SettingsActivity : SimpleActivity() { } } + private fun setupManageShownContactFields() { + settings_manage_contact_fields_holder.setOnClickListener { + ManageVisibleFieldsDialog(this) + } + } + private fun setupUseEnglish() { settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en") settings_use_english.isChecked = config.useEnglish settings_use_english_holder.setOnClickListener { settings_use_english.toggle() config.useEnglish = settings_use_english.isChecked - useEnglishToggled() + System.exit(0) } } 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 d9c17a46..09ec8269 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt @@ -12,25 +12,39 @@ import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.extensions.* -import com.simplemobiletools.contacts.helpers.CONTACT_ID -import com.simplemobiletools.contacts.helpers.ContactsHelper -import com.simplemobiletools.contacts.helpers.IS_PRIVATE +import com.simplemobiletools.contacts.helpers.* import kotlinx.android.synthetic.main.activity_view_contact.* 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_phone_number.view.* +import kotlinx.android.synthetic.main.item_website.view.* class ViewContactActivity : ContactActivity() { + private var isViewIntent = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_view_contact) + showFields = config.showContactFields } override fun onResume() { super.onResume() - tryInitContact() + isViewIntent = intent.action == ContactsContract.QuickContact.ACTION_QUICK_CONTACT || intent.action == Intent.ACTION_VIEW + if (isViewIntent) { + handlePermission(PERMISSION_READ_CONTACTS) { + if (it) { + initContact() + } else { + toast(R.string.no_contacts_permission) + finish() + } + } + } else { + initContact() + } } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -48,22 +62,10 @@ class ViewContactActivity : ContactActivity() { return true } - private fun tryInitContact() { - handlePermission(PERMISSION_READ_CONTACTS) { - if (it) { - initContact() - } else { - toast(R.string.no_contacts_permission) - finish() - } - } - } - private fun initContact() { var wasLookupKeyUsed = false var contactId = intent.getIntExtra(CONTACT_ID, 0) - val action = intent.action - if (contactId == 0 && (action == ContactsContract.QuickContact.ACTION_QUICK_CONTACT || action == Intent.ACTION_VIEW)) { + if (contactId == 0 && isViewIntent) { val data = intent.data if (data != null) { val rawId = if (data.path.contains("lookup")) { @@ -117,11 +119,13 @@ class ViewContactActivity : ContactActivity() { contact_start_call.applyColorFilter(textColor) contact_send_email.applyColorFilter(textColor) contact_name_image.applyColorFilter(textColor) - contact_number_image.applyColorFilter(textColor) - contact_email_image.applyColorFilter(textColor) - contact_event_image.applyColorFilter(textColor) + contact_numbers_image.applyColorFilter(textColor) + contact_emails_image.applyColorFilter(textColor) + contact_events_image.applyColorFilter(textColor) contact_source_image.applyColorFilter(textColor) contact_notes_image.applyColorFilter(textColor) + contact_organization_image.applyColorFilter(textColor) + contact_websites_image.applyColorFilter(textColor) contact_groups_image.applyColorFilter(textColor) contact_send_sms.setOnClickListener { trySendSMS() } @@ -134,135 +138,223 @@ class ViewContactActivity : ContactActivity() { private fun setupViewContact() { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) - contact!!.apply { - contact_first_name.text = firstName - contact_first_name.beVisibleIf(firstName.isNotEmpty()) - - contact_middle_name.text = middleName - contact_middle_name.beVisibleIf(middleName.isNotEmpty()) - - contact_surname.text = surname - contact_surname.beVisibleIf(surname.isNotEmpty()) - - if (firstName.isEmpty() && middleName.isEmpty() && surname.isEmpty()) { - contact_name_image.beInvisible() - (contact_photo.layoutParams as RelativeLayout.LayoutParams).bottomMargin = resources.getDimension(R.dimen.medium_margin).toInt() - } - contact_source.text = getPublicContactSource(source) - } + setupFavorite() + setupNames() + setupPhoneNumbers() + setupEmails() + setupAddresses() + setupEvents() + setupNotes() + setupOrganization() + setupWebsites() + setupGroups() + setupContactSource() + } + private fun setupFavorite() { contact_toggle_favorite.apply { beVisible() setImageDrawable(getStarDrawable(contact!!.starred == 1)) tag = contact!!.starred applyColorFilter(config.textColor) } + } - setupPhoneNumbers() - setupEmails() - setupAddresses() - setupEvents() - setupNotes() - setupGroups() + private fun setupNames() { + contact!!.apply { + contact_prefix.text = prefix + contact_prefix.beVisibleIf(prefix.isNotEmpty() && showFields and SHOW_PREFIX_FIELD != 0) + + contact_first_name.text = firstName + contact_first_name.beVisibleIf(firstName.isNotEmpty() && showFields and SHOW_FIRST_NAME_FIELD != 0) + + contact_middle_name.text = middleName + contact_middle_name.beVisibleIf(middleName.isNotEmpty() && showFields and SHOW_MIDDLE_NAME_FIELD != 0) + + contact_surname.text = surname + contact_surname.beVisibleIf(surname.isNotEmpty() && showFields and SHOW_SURNAME_FIELD != 0) + + contact_suffix.text = suffix + contact_suffix.beVisibleIf(suffix.isNotEmpty() && showFields and SHOW_SUFFIX_FIELD != 0) + + if (contact_prefix.isGone() && contact_first_name.isGone() && contact_middle_name.isGone() && contact_surname.isGone() && contact_suffix.isGone()) { + contact_name_image.beInvisible() + (contact_photo.layoutParams as RelativeLayout.LayoutParams).bottomMargin = resources.getDimension(R.dimen.medium_margin).toInt() + } + } } private fun setupPhoneNumbers() { contact_numbers_holder.removeAllViews() val phoneNumbers = contact!!.phoneNumbers - phoneNumbers.forEach { - layoutInflater.inflate(R.layout.item_view_phone_number, contact_numbers_holder, false).apply { - val phoneNumber = it - contact_numbers_holder.addView(this) - contact_number.text = phoneNumber.value - contact_number_type.setText(getPhoneNumberTextId(phoneNumber.type)) + if (phoneNumbers.isNotEmpty() && showFields and SHOW_PHONE_NUMBERS_FIELD != 0) { + phoneNumbers.forEach { + layoutInflater.inflate(R.layout.item_view_phone_number, contact_numbers_holder, false).apply { + val phoneNumber = it + contact_numbers_holder.addView(this) + contact_number.text = phoneNumber.value + contact_number_type.setText(getPhoneNumberTextId(phoneNumber.type)) - setOnClickListener { - startCallIntent(phoneNumber.value) + setOnClickListener { + startCallIntent(phoneNumber.value) + } } } + contact_numbers_image.beVisible() + contact_numbers_holder.beVisible() + } else { + contact_numbers_image.beGone() + contact_numbers_holder.beGone() } - - contact_number_image.beVisibleIf(phoneNumbers.isNotEmpty()) - contact_numbers_holder.beVisibleIf(phoneNumbers.isNotEmpty()) } private fun setupEmails() { contact_emails_holder.removeAllViews() val emails = contact!!.emails - emails.forEach { - layoutInflater.inflate(R.layout.item_view_email, contact_emails_holder, false).apply { - val email = it - contact_emails_holder.addView(this) - contact_email.text = email.value - contact_email_type.setText(getEmailTextId(email.type)) + if (emails.isNotEmpty() && showFields and SHOW_EMAILS_FIELD != 0) { + emails.forEach { + layoutInflater.inflate(R.layout.item_view_email, contact_emails_holder, false).apply { + val email = it + contact_emails_holder.addView(this) + contact_email.text = email.value + contact_email_type.setText(getEmailTextId(email.type)) - setOnClickListener { - sendEmailIntent(email.value) + setOnClickListener { + sendEmailIntent(email.value) + } } } + contact_emails_image.beVisible() + contact_emails_holder.beVisible() + } else { + contact_emails_image.beGone() + contact_emails_holder.beGone() } - - contact_email_image.beVisibleIf(emails.isNotEmpty()) - contact_emails_holder.beVisibleIf(emails.isNotEmpty()) } private fun setupAddresses() { contact_addresses_holder.removeAllViews() val addresses = contact!!.addresses - addresses.forEach { - layoutInflater.inflate(R.layout.item_view_address, contact_addresses_holder, false).apply { - val address = it - contact_addresses_holder.addView(this) - contact_address.text = address.value - contact_address_type.setText(getAddressTextId(address.type)) + if (addresses.isNotEmpty() && showFields and SHOW_ADDRESSES_FIELD != 0) { + addresses.forEach { + layoutInflater.inflate(R.layout.item_view_address, contact_addresses_holder, false).apply { + val address = it + contact_addresses_holder.addView(this) + contact_address.text = address.value + contact_address_type.setText(getAddressTextId(address.type)) - setOnClickListener { - sendAddressIntent(address.value) + setOnClickListener { + sendAddressIntent(address.value) + } } } + contact_addresses_image.beVisible() + contact_addresses_holder.beVisible() + } else { + contact_addresses_image.beGone() + contact_addresses_holder.beGone() } - - contact_address_image.beVisibleIf(addresses.isNotEmpty()) - contact_addresses_holder.beVisibleIf(addresses.isNotEmpty()) } private fun setupEvents() { contact_events_holder.removeAllViews() val events = contact!!.events - events.forEach { - layoutInflater.inflate(R.layout.item_event, contact_events_holder, false).apply { - contact_events_holder.addView(this) - contact_event.alpha = 1f - getDateTime(it.value, contact_event) - contact_event_type.setText(getEventTextId(it.type)) - contact_event_remove.beGone() + if (events.isNotEmpty() && showFields and SHOW_EVENTS_FIELD != 0) { + events.forEach { + layoutInflater.inflate(R.layout.item_event, contact_events_holder, false).apply { + contact_events_holder.addView(this) + contact_event.alpha = 1f + getDateTime(it.value, contact_event) + contact_event_type.setText(getEventTextId(it.type)) + contact_event_remove.beGone() + } } + contact_events_image.beVisible() + contact_events_holder.beVisible() + } else { + contact_events_image.beGone() + contact_events_holder.beGone() } - - contact_event_image.beVisibleIf(events.isNotEmpty()) - contact_events_holder.beVisibleIf(events.isNotEmpty()) } private fun setupNotes() { val notes = contact!!.notes - contact_notes.text = notes - contact_notes_image.beVisibleIf(notes.isNotEmpty()) - contact_notes.beVisibleIf(notes.isNotEmpty()) + if (notes.isNotEmpty() && showFields and SHOW_NOTES_FIELD != 0) { + contact_notes.text = notes + contact_notes_image.beVisible() + contact_notes.beVisible() + } else { + contact_notes_image.beGone() + contact_notes.beGone() + } + } + + private fun setupOrganization() { + val organization = contact!!.organization + if (!organization.isEmpty() && showFields and SHOW_ORGANIZATION_FIELD != 0) { + contact_organization_company.text = organization.company + contact_organization_job_position.text = organization.jobPosition + contact_organization_image.beGoneIf(organization.isEmpty()) + contact_organization_company.beGoneIf(organization.company.isEmpty()) + contact_organization_job_position.beGoneIf(organization.jobPosition.isEmpty()) + } else { + contact_organization_image.beGone() + contact_organization_company.beGone() + contact_organization_job_position.beGone() + } + } + + private fun setupWebsites() { + contact_websites_holder.removeAllViews() + val websites = contact!!.websites + if (websites.isNotEmpty() && showFields and SHOW_WEBSITES_FIELD != 0) { + websites.forEach { + val url = it + layoutInflater.inflate(R.layout.item_website, contact_websites_holder, false).apply { + contact_websites_holder.addView(this) + contact_website.text = url + + setOnClickListener { + openWebsiteIntent(url) + } + } + } + contact_websites_image.beVisible() + contact_websites_holder.beVisible() + } else { + contact_websites_image.beGone() + contact_websites_holder.beGone() + } } private fun setupGroups() { contact_groups_holder.removeAllViews() val groups = contact!!.groups - groups.forEach { - layoutInflater.inflate(R.layout.item_view_group, contact_groups_holder, false).apply { - val group = it - contact_groups_holder.addView(this) - contact_group.text = group.title + if (groups.isNotEmpty() && showFields and SHOW_GROUPS_FIELD != 0) { + groups.forEach { + layoutInflater.inflate(R.layout.item_view_group, contact_groups_holder, false).apply { + val group = it + contact_groups_holder.addView(this) + contact_group.text = group.title + } } + contact_groups_image.beVisible() + contact_groups_holder.beVisible() + } else { + contact_groups_image.beGone() + contact_groups_holder.beGone() } + } - contact_groups_image.beVisibleIf(groups.isNotEmpty()) - contact_groups_holder.beVisibleIf(groups.isNotEmpty()) + private fun setupContactSource() { + if (showFields and SHOW_CONTACT_SOURCE_FIELD != 0) { + contact_source.text = getPublicContactSource(contact!!.source) + contact_source_image.beVisible() + contact_source.beVisible() + } else { + contact_source_image.beGone() + contact_source.beGone() + } } private fun getStarDrawable(on: Boolean) = resources.getDrawable(if (on) R.drawable.ic_star_on_big else R.drawable.ic_star_off_big) 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 7cc74c73..347a6f88 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt @@ -47,6 +47,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList - activity.shareContacts(filtered) - } + val filtered = contactItems.filter { contactsIDs.contains(it.id) } as ArrayList + activity.shareContacts(filtered) } override fun onViewRecycled(holder: ViewHolder) { @@ -235,7 +234,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList, val refreshListener: RefreshContactsListener?, recyclerView: MyRecyclerView, fastScroller: FastScroller, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { - private var config = activity.config private var smallPadding = activity.resources.getDimension(R.dimen.small_margin).toInt() private var bigPadding = activity.resources.getDimension(R.dimen.normal_margin).toInt() - var showContactThumbnails = config.showContactThumbnails + var showContactThumbnails = activity.config.showContactThumbnails + + init { + setupDragListener(true) + } override fun getActionMenuId() = R.menu.cab_groups diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/SelectContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/SelectContactsAdapter.kt index 4e118f24..59471c1b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/SelectContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/SelectContactsAdapter.kt @@ -30,7 +30,6 @@ class SelectContactsAdapter(val activity: SimpleActivity, val contacts: List Unit)?) : RecyclerView.ViewHolder(view) { - fun bindView(contact: Contact, startNameWithSurname: Boolean, contactDrawable: Drawable, config: Config, showContactThumbnails: Boolean, + fun bindView(contact: Contact, contactDrawable: Drawable, config: Config, showContactThumbnails: Boolean, smallPadding: Int, bigPadding: Int): View { itemView.apply { contact_checkbox.beVisibleIf(showCheckbox) contact_checkbox.setColors(config.textColor, context.getAdjustedPrimaryColor(), config.backgroundColor) val textColor = config.textColor - contact_name.text = contact.getFullName(startNameWithSurname) + contact_name.text = contact.getFullName() contact_name.setTextColor(textColor) contact_name.setPadding(if (showContactThumbnails) smallPadding else bigPadding, smallPadding, smallPadding, 0) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt new file mode 100644 index 00000000..cb0b8424 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt @@ -0,0 +1,56 @@ +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.views.MyAppCompatCheckbox +import com.simplemobiletools.contacts.R +import com.simplemobiletools.contacts.extensions.config +import com.simplemobiletools.contacts.helpers.* + +class ManageVisibleFieldsDialog(val activity: BaseSimpleActivity) { + private var view = activity.layoutInflater.inflate(R.layout.dialog_manage_visible_fields, null) + private val fields = LinkedHashMap() + + init { + fields.apply { + put(SHOW_PREFIX_FIELD, R.id.manage_visible_fields_prefix) + put(SHOW_FIRST_NAME_FIELD, R.id.manage_visible_fields_first_name) + put(SHOW_MIDDLE_NAME_FIELD, R.id.manage_visible_fields_middle_name) + put(SHOW_SURNAME_FIELD, R.id.manage_visible_fields_surname) + put(SHOW_SUFFIX_FIELD, R.id.manage_visible_fields_suffix) + 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_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) + put(SHOW_WEBSITES_FIELD, R.id.manage_visible_fields_websites) + put(SHOW_GROUPS_FIELD, R.id.manage_visible_fields_groups) + put(SHOW_CONTACT_SOURCE_FIELD, R.id.manage_visible_fields_contact_source) + } + + val showContactFields = activity.config.showContactFields + for ((key, value) in fields) { + view.findViewById(value).isChecked = showContactFields and key != 0 + } + + AlertDialog.Builder(activity) + .setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() }) + .setNegativeButton(R.string.cancel, null) + .create().apply { + activity.setupDialogStuff(view, this) + } + } + + private fun dialogConfirmed() { + var result = 0 + for ((key, value) in fields) { + if (view.findViewById(value).isChecked) { + result += key + } + } + + activity.config.showContactFields = result + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/SelectContactsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/SelectContactsDialog.kt index 7f5e259d..358a7dcd 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/SelectContactsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/SelectContactsDialog.kt @@ -29,6 +29,7 @@ class SelectContactsDialog(val activity: SimpleActivity, initialContacts: ArrayL } Contact.sorting = activity.config.sorting + Contact.startWithSurname = activity.config.startNameWithSurname allContacts.sort() activity.runOnUiThread { 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 5815f5a1..71af206e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt @@ -20,14 +20,13 @@ import java.io.File fun SimpleActivity.startCallIntent(recipient: String) { handlePermission(PERMISSION_CALL_PHONE) { - if (it) { - Intent(Intent.ACTION_CALL).apply { - data = Uri.fromParts("tel", recipient, null) - if (resolveActivity(packageManager) != null) { - startActivity(this) - } else { - toast(R.string.no_app_found) - } + val action = if (it) Intent.ACTION_CALL else Intent.ACTION_DIAL + Intent(action).apply { + data = Uri.fromParts("tel", recipient, null) + if (resolveActivity(packageManager) != null) { + startActivity(this) + } else { + toast(R.string.no_app_found) } } } @@ -127,7 +126,7 @@ fun BaseSimpleActivity.addContactsToGroup(contacts: ArrayList, groupId: fun BaseSimpleActivity.removeContactsFromGroup(contacts: ArrayList, groupId: Long) { val publicContacts = contacts.filter { it.source != SMT_PRIVATE } val privateContacts = contacts.filter { it.source == SMT_PRIVATE } - if (publicContacts.isNotEmpty()) { + if (publicContacts.isNotEmpty() && hasContactPermissions()) { ContactsHelper(this).removeContactsFromGroup(contacts, groupId) } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Context.kt index 8731909a..dfcafded 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Context.kt @@ -9,7 +9,10 @@ import android.os.Build import android.provider.ContactsContract import android.support.v4.content.FileProvider import com.simplemobiletools.commons.extensions.getIntValue +import com.simplemobiletools.commons.extensions.hasPermission import com.simplemobiletools.commons.extensions.toast +import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS +import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CONTACTS import com.simplemobiletools.commons.helpers.isLollipopPlus import com.simplemobiletools.contacts.BuildConfig import com.simplemobiletools.contacts.R @@ -65,11 +68,23 @@ fun Context.sendAddressIntent(address: String) { val location = Uri.encode(address) val uri = Uri.parse("geo:0,0?q=$location") - val intent = Intent(Intent.ACTION_VIEW, uri) - if (intent.resolveActivity(packageManager) != null) { - startActivity(intent) - } else { - toast(R.string.no_app_found) + Intent(Intent.ACTION_VIEW, uri).apply { + if (resolveActivity(packageManager) != null) { + startActivity(this) + } else { + toast(R.string.no_app_found) + } + } +} + +fun Context.openWebsiteIntent(url: String) { + Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(url) + if (resolveActivity(packageManager) != null) { + startActivity(this) + } else { + toast(R.string.no_app_found) + } } } @@ -150,8 +165,11 @@ fun Context.getPhotoThumbnailSize(): Int { if (cursor?.moveToFirst() == true) { return cursor.getIntValue(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM) } + } catch (ignored: Exception) { } finally { cursor?.close() } return 0 } + +fun Context.hasContactPermissions() = hasPermission(PERMISSION_READ_CONTACTS) && hasPermission(PERMISSION_WRITE_CONTACTS) 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 1adc2d25..2bbcdd25 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -92,6 +92,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } Contact.sorting = config.sorting + Contact.startWithSurname = config.startNameWithSurname contacts.sort() allContacts = contacts @@ -149,7 +150,6 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) activity!!.startActivity(this) } }.apply { - setupDragListener(true) addVerticalDividers(true) fragment_list.adapter = this } @@ -190,7 +190,6 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) ON_CLICK_EDIT_CONTACT -> context!!.editContact(it as Contact) } }.apply { - setupDragListener(true) addVerticalDividers(true) fragment_list.adapter = this } @@ -235,16 +234,20 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) fun onSearchQueryChanged(text: String) { (fragment_list.adapter as? ContactsAdapter)?.apply { val filtered = contactsIgnoringSearch.filter { - it.getFullName(startNameWithSurname).contains(text, true) || + it.getFullName().contains(text, true) || it.phoneNumbers.any { it.value.contains(text, true) } || it.emails.any { it.value.contains(text, true) } || it.addresses.any { it.value.contains(text, true) } || - it.notes.contains(text, true) + it.notes.contains(text, true) || + it.organization.company.contains(text, true) || + it.organization.jobPosition.contains(text, true) || + it.websites.any { it.contains(text, true) } } as ArrayList Contact.sorting = config.sorting + Contact.startWithSurname = config.startNameWithSurname filtered.sort() - filtered.sortBy { !it.getFullName(startNameWithSurname).startsWith(text, true) } + filtered.sortBy { !it.getFullName().startsWith(text, true) } if (filtered.isEmpty() && this@MyViewPagerFragment is FavoritesFragment) { fragment_placeholder.text = activity.getString(R.string.no_items_found) diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt index 81c3bf65..ec516108 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt @@ -41,4 +41,9 @@ class Config(context: Context) : BaseConfig(context) { var onContactClick: Int get() = prefs.getInt(ON_CONTACT_CLICK, ON_CLICK_VIEW_CONTACT) set(onContactClick) = prefs.edit().putInt(ON_CONTACT_CLICK, onContactClick).apply() + + var showContactFields: Int + get() = prefs.getInt(SHOW_CONTACT_FIELDS, SHOW_FIRST_NAME_FIELD or SHOW_SURNAME_FIELD or SHOW_PHONE_NUMBERS_FIELD or SHOW_EMAILS_FIELD or + SHOW_ADDRESSES_FIELD or SHOW_EVENTS_FIELD or SHOW_NOTES_FIELD or SHOW_GROUPS_FIELD or SHOW_CONTACT_SOURCE_FIELD) + set(showContactFields) = prefs.edit().putInt(SHOW_CONTACT_FIELDS, showContactFields).apply() } 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 6a7555f6..03e65cbe 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -9,6 +9,7 @@ const val LAST_USED_CONTACT_SOURCE = "last_used_contact_source" const val LOCAL_ACCOUNT_NAME = "local_account_name" const val LOCAL_ACCOUNT_TYPE = "local_account_type" const val ON_CONTACT_CLICK = "on_contact_click" +const val SHOW_CONTACT_FIELDS = "show_contact_fields" const val CONTACT_ID = "contact_id" const val SMT_PRIVATE = "smt_private" // used at the contact source of local contacts hidden from other apps @@ -43,6 +44,8 @@ const val PHOTO = "PHOTO" const val EMAIL = "EMAIL" const val ADR = "ADR" const val NOTE = "NOTE:" +const val ORG = "ORG:" +const val TITLE = "TITLE:" const val ENCODING = "ENCODING" const val BASE64 = "BASE64" const val JPEG = "JPEG" @@ -64,3 +67,19 @@ const val VOICE = "VOICE" const val ON_CLICK_CALL_CONTACT = 1 const val ON_CLICK_VIEW_CONTACT = 2 const val ON_CLICK_EDIT_CONTACT = 3 + +// visible fields filtering +const val SHOW_PREFIX_FIELD = 1 +const val SHOW_FIRST_NAME_FIELD = 2 +const val SHOW_MIDDLE_NAME_FIELD = 4 +const val SHOW_SURNAME_FIELD = 8 +const val SHOW_SUFFIX_FIELD = 16 +const val SHOW_PHONE_NUMBERS_FIELD = 32 +const val SHOW_EMAILS_FIELD = 64 +const val SHOW_ADDRESSES_FIELD = 128 +const val SHOW_EVENTS_FIELD = 256 +const val SHOW_NOTES_FIELD = 512 +const val SHOW_ORGANIZATION_FIELD = 1024 +const val SHOW_GROUPS_FIELD = 2048 +const val SHOW_CONTACT_SOURCE_FIELD = 4096 +const val SHOW_WEBSITES_FIELD = 8192 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 06c00a22..8804a680 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -20,87 +20,15 @@ import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME import com.simplemobiletools.commons.helpers.SORT_BY_SURNAME import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.contacts.R -import com.simplemobiletools.contacts.extensions.config -import com.simplemobiletools.contacts.extensions.dbHelper -import com.simplemobiletools.contacts.extensions.getByteArray -import com.simplemobiletools.contacts.extensions.getPhotoThumbnailSize +import com.simplemobiletools.contacts.extensions.* import com.simplemobiletools.contacts.models.* class ContactsHelper(val activity: BaseSimpleActivity) { + private val BATCH_SIZE = 100 fun getContacts(callback: (ArrayList) -> Unit) { - val contacts = SparseArray() Thread { - val uri = ContactsContract.Data.CONTENT_URI - val projection = getContactProjection() - val selection = "${ContactsContract.Data.MIMETYPE} = ?" - val selectionArgs = arrayOf(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) - val sortOrder = getSortString() - - var cursor: Cursor? = null - try { - cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, sortOrder) - if (cursor?.moveToFirst() == true) { - do { - val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) - val firstName = cursor.getStringValue(CommonDataKinds.StructuredName.GIVEN_NAME) ?: "" - val middleName = cursor.getStringValue(CommonDataKinds.StructuredName.MIDDLE_NAME) ?: "" - val surname = cursor.getStringValue(CommonDataKinds.StructuredName.FAMILY_NAME) ?: "" - val photoUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_URI) ?: "" - val number = ArrayList() // proper value is obtained below - val emails = ArrayList() - val addresses = ArrayList
() - val events = ArrayList() - val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: "" - val starred = cursor.getIntValue(CommonDataKinds.StructuredName.STARRED) - val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) - val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: "" - val notes = "" - val groups = ArrayList() - val contact = Contact(id, firstName, middleName, surname, photoUri, number, emails, addresses, events, accountName, - starred, contactId, thumbnailUri, null, notes, groups) - contacts.put(id, contact) - } while (cursor.moveToNext()) - } - } catch (e: Exception) { - activity.showErrorToast(e) - } finally { - cursor?.close() - } - - val phoneNumbers = getPhoneNumbers() - var size = phoneNumbers.size() - for (i in 0 until size) { - val key = phoneNumbers.keyAt(i) - contacts[key]?.phoneNumbers = phoneNumbers.valueAt(i) - } - - val emails = getEmails() - size = emails.size() - for (i in 0 until size) { - val key = emails.keyAt(i) - contacts[key]?.emails = emails.valueAt(i) - } - - val addresses = getAddresses() - size = addresses.size() - for (i in 0 until size) { - val key = addresses.keyAt(i) - contacts[key]?.addresses = addresses.valueAt(i) - } - - val events = getEvents() - size = events.size() - for (i in 0 until size) { - val key = events.keyAt(i) - contacts[key]?.events = events.valueAt(i) - } - - val notes = getNotes() - size = notes.size() - for (i in 0 until size) { - val key = notes.keyAt(i) - contacts[key]?.notes = notes.valueAt(i) - } + val contacts = SparseArray() + getDeviceContacts(contacts) activity.dbHelper.getContacts(activity).forEach { contacts.put(it.id, it) @@ -113,7 +41,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) { // groups are obtained with contactID, not rawID, so assign them to proper contacts like this val groups = getContactGroups(getStoredGroups()) - size = groups.size() + val size = groups.size() for (i in 0 until size) { val key = groups.keyAt(i) resultContacts.firstOrNull { it.contactId == key }?.groups = groups.valueAt(i) @@ -125,6 +53,102 @@ class ContactsHelper(val activity: BaseSimpleActivity) { }.start() } + private fun getDeviceContacts(contacts: SparseArray) { + if (!activity.hasContactPermissions()) { + return + } + + val uri = ContactsContract.Data.CONTENT_URI + val projection = getContactProjection() + val selection = "${ContactsContract.Data.MIMETYPE} = ?" + val selectionArgs = arrayOf(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + val sortOrder = getSortString() + + var cursor: Cursor? = null + try { + cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, sortOrder) + if (cursor?.moveToFirst() == true) { + do { + val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) + val prefix = cursor.getStringValue(CommonDataKinds.StructuredName.PREFIX) ?: "" + val firstName = cursor.getStringValue(CommonDataKinds.StructuredName.GIVEN_NAME) ?: "" + val middleName = cursor.getStringValue(CommonDataKinds.StructuredName.MIDDLE_NAME) ?: "" + val surname = cursor.getStringValue(CommonDataKinds.StructuredName.FAMILY_NAME) ?: "" + val suffix = cursor.getStringValue(CommonDataKinds.StructuredName.SUFFIX) ?: "" + val photoUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_URI) ?: "" + val number = ArrayList() // proper value is obtained below + val emails = ArrayList() + val addresses = ArrayList
() + val events = ArrayList() + val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: "" + val starred = cursor.getIntValue(CommonDataKinds.StructuredName.STARRED) + val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) + val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: "" + val notes = "" + val groups = ArrayList() + val organization = Organization("", "") + val websites = ArrayList() + val contact = Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events, + accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites) + contacts.put(id, contact) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + activity.showErrorToast(e) + } finally { + cursor?.close() + } + + val phoneNumbers = getPhoneNumbers() + var size = phoneNumbers.size() + for (i in 0 until size) { + val key = phoneNumbers.keyAt(i) + contacts[key]?.phoneNumbers = phoneNumbers.valueAt(i) + } + + val emails = getEmails() + size = emails.size() + for (i in 0 until size) { + val key = emails.keyAt(i) + contacts[key]?.emails = emails.valueAt(i) + } + + val addresses = getAddresses() + size = addresses.size() + for (i in 0 until size) { + val key = addresses.keyAt(i) + contacts[key]?.addresses = addresses.valueAt(i) + } + + val events = getEvents() + size = events.size() + for (i in 0 until size) { + val key = events.keyAt(i) + contacts[key]?.events = events.valueAt(i) + } + + val notes = getNotes() + size = notes.size() + for (i in 0 until size) { + val key = notes.keyAt(i) + contacts[key]?.notes = notes.valueAt(i) + } + + val organizations = getOrganizations() + size = organizations.size() + for (i in 0 until size) { + val key = organizations.keyAt(i) + contacts[key]?.organization = organizations.valueAt(i) + } + + val websites = getWebsites() + size = websites.size() + for (i in 0 until size) { + val key = websites.keyAt(i) + contacts[key]?.websites = websites.valueAt(i) + } + } + private fun getPhoneNumbers(contactId: Int? = null): SparseArray> { val phoneNumbers = SparseArray>() val uri = CommonDataKinds.Phone.CONTENT_URI @@ -218,7 +242,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) { if (cursor?.moveToFirst() == true) { do { val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) - val address = cursor.getStringValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) + val address = cursor.getStringValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) ?: "" val type = cursor.getIntValue(CommonDataKinds.StructuredPostal.TYPE) if (addresses[id] == null) { @@ -315,8 +339,90 @@ class ContactsHelper(val activity: BaseSimpleActivity) { return notes } + private fun getOrganizations(contactId: Int? = null): SparseArray { + val organizations = SparseArray() + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf( + ContactsContract.Data.RAW_CONTACT_ID, + CommonDataKinds.Organization.COMPANY, + CommonDataKinds.Organization.TITLE + ) + + var selection = "${ContactsContract.Data.MIMETYPE} = ?" + var selectionArgs = arrayOf(CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + + if (contactId != null) { + selection += " AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?" + selectionArgs = arrayOf(CommonDataKinds.Organization.CONTENT_ITEM_TYPE, contactId.toString()) + } + + 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 company = cursor.getStringValue(CommonDataKinds.Organization.COMPANY) ?: continue + val title = cursor.getStringValue(CommonDataKinds.Organization.TITLE) ?: continue + val organization = Organization(company, title) + organizations.put(id, organization) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + activity.showErrorToast(e) + } finally { + cursor?.close() + } + + return organizations + } + + private fun getWebsites(contactId: Int? = null): SparseArray> { + val websites = SparseArray>() + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf( + ContactsContract.Data.RAW_CONTACT_ID, + CommonDataKinds.Website.URL + ) + + var selection = "${ContactsContract.Data.MIMETYPE} = ?" + var selectionArgs = arrayOf(CommonDataKinds.Website.CONTENT_ITEM_TYPE) + + if (contactId != null) { + selection += " AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?" + selectionArgs = arrayOf(CommonDataKinds.Website.CONTENT_ITEM_TYPE, contactId.toString()) + } + + 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 url = cursor.getStringValue(CommonDataKinds.Website.URL) ?: continue + + if (websites[id] == null) { + websites.put(id, ArrayList()) + } + + websites[id]!!.add(url) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + activity.showErrorToast(e) + } finally { + cursor?.close() + } + + return websites + } + private fun getContactGroups(storedGroups: ArrayList, contactId: Int? = null): SparseArray> { val groups = SparseArray>() + if (!activity.hasContactPermissions()) { + return groups + } + val uri = ContactsContract.Data.CONTENT_URI val projection = arrayOf( ContactsContract.Data.CONTACT_ID, @@ -357,7 +463,17 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } fun getStoredGroups(): ArrayList { + val groups = getDeviceStoredGroups() + groups.addAll(activity.dbHelper.getGroups()) + return groups + } + + fun getDeviceStoredGroups(): ArrayList { val groups = ArrayList() + if (!activity.hasContactPermissions()) { + return groups + } + val uri = ContactsContract.Groups.CONTENT_URI val projection = arrayOf( ContactsContract.Groups._ID, @@ -374,7 +490,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) { if (cursor?.moveToFirst() == true) { do { val id = cursor.getLongValue(ContactsContract.Groups._ID) - val title = cursor.getStringValue(ContactsContract.Groups.TITLE) + val title = cursor.getStringValue(ContactsContract.Groups.TITLE) ?: continue val systemId = cursor.getStringValue(ContactsContract.Groups.SYSTEM_ID) if (groups.map { it.title }.contains(title) && systemId != null) { @@ -389,8 +505,6 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } finally { cursor?.close() } - - groups.addAll(activity.dbHelper.getGroups()) return groups } @@ -477,9 +591,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) { cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null) if (cursor?.moveToFirst() == true) { val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) + val prefix = cursor.getStringValue(CommonDataKinds.StructuredName.PREFIX) ?: "" val firstName = cursor.getStringValue(CommonDataKinds.StructuredName.GIVEN_NAME) ?: "" val middleName = cursor.getStringValue(CommonDataKinds.StructuredName.MIDDLE_NAME) ?: "" val surname = cursor.getStringValue(CommonDataKinds.StructuredName.FAMILY_NAME) ?: "" + val suffix = cursor.getStringValue(CommonDataKinds.StructuredName.SUFFIX) ?: "" val photoUri = cursor.getStringValue(CommonDataKinds.Phone.PHOTO_URI) ?: "" val number = getPhoneNumbers(id)[id] ?: ArrayList() val emails = getEmails(id)[id] ?: ArrayList() @@ -491,8 +607,10 @@ class ContactsHelper(val activity: BaseSimpleActivity) { val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID) val groups = getContactGroups(storedGroups, contactId)[contactId] ?: ArrayList() val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: "" - return Contact(id, firstName, middleName, surname, photoUri, number, emails, addresses, events, accountName, starred, contactId, - thumbnailUri, null, notes, groups) + val organization = getOrganizations(id)[id] ?: Organization("", "") + val websites = getWebsites(id)[id] ?: ArrayList() + return Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events, accountName, + starred, contactId, thumbnailUri, null, notes, groups, organization, websites) } } finally { cursor?.close() @@ -502,37 +620,44 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } fun getContactSources(callback: (ArrayList) -> Unit) { - val sources = LinkedHashSet() Thread { - val uri = ContactsContract.RawContacts.CONTENT_URI - val projection = arrayOf(ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE) - - var cursor: Cursor? = null - try { - cursor = activity.contentResolver.query(uri, projection, null, null, null) - if (cursor?.moveToFirst() == true) { - do { - val name = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: continue - val type = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_TYPE) ?: continue - val contactSource = ContactSource(name, type) - sources.add(contactSource) - } while (cursor.moveToNext()) - } - } catch (e: Exception) { - activity.showErrorToast(e) - } finally { - cursor?.close() - } - - if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) { - sources.add(ContactSource("", "")) - } - + val sources = LinkedHashSet() + getDeviceContactSources(sources) sources.add(ContactSource(activity.getString(R.string.phone_storage_hidden), SMT_PRIVATE)) callback(ArrayList(sources)) }.start() } + private fun getDeviceContactSources(sources: LinkedHashSet) { + if (!activity.hasContactPermissions()) { + return + } + + val uri = ContactsContract.RawContacts.CONTENT_URI + val projection = arrayOf(ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE) + + var cursor: Cursor? = null + try { + cursor = activity.contentResolver.query(uri, projection, null, null, null) + if (cursor?.moveToFirst() == true) { + do { + val name = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: continue + val type = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_TYPE) ?: continue + val contactSource = ContactSource(name, type) + sources.add(contactSource) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + activity.showErrorToast(e) + } finally { + cursor?.close() + } + + if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) { + sources.add(ContactSource("", "")) + } + } + private fun getContactSourceType(accountName: String): String { if (accountName.isEmpty()) { return "" @@ -558,9 +683,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) { private fun getContactProjection() = arrayOf( ContactsContract.Data.CONTACT_ID, ContactsContract.Data.RAW_CONTACT_ID, + CommonDataKinds.StructuredName.PREFIX, CommonDataKinds.StructuredName.GIVEN_NAME, CommonDataKinds.StructuredName.MIDDLE_NAME, CommonDataKinds.StructuredName.FAMILY_NAME, + CommonDataKinds.StructuredName.SUFFIX, CommonDataKinds.StructuredName.PHOTO_URI, CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI, CommonDataKinds.StructuredName.STARRED, @@ -613,9 +740,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) { val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?" val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) withSelection(selection, selectionArgs) + withValue(CommonDataKinds.StructuredName.PREFIX, contact.prefix) withValue(CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName) withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName) withValue(CommonDataKinds.StructuredName.FAMILY_NAME, contact.surname) + withValue(CommonDataKinds.StructuredName.SUFFIX, contact.suffix) operations.add(build()) } @@ -704,6 +833,18 @@ class ContactsHelper(val activity: BaseSimpleActivity) { operations.add(build()) } + // organization + ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply { + val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?" + val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + withSelection(selection, selectionArgs) + withValue(CommonDataKinds.Organization.COMPANY, contact.organization.company) + withValue(CommonDataKinds.Organization.TYPE, CommonDataKinds.Organization.TYPE_WORK) + withValue(CommonDataKinds.Organization.TITLE, contact.organization.jobPosition) + withValue(CommonDataKinds.Organization.TYPE, CommonDataKinds.Organization.TYPE_WORK) + operations.add(build()) + } + // delete groups val relevantGroupIDs = getStoredGroups().map { it.id } if (relevantGroupIDs.isNotEmpty()) { @@ -795,6 +936,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) { withValue(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId) operations.add(build()) } + + if (operations.size % BATCH_SIZE == 0) { + activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) + operations.clear() + } } activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) } @@ -808,6 +954,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) { withSelection(selection, selectionArgs) operations.add(build()) } + + if (operations.size % BATCH_SIZE == 0) { + activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) + operations.clear() + } } activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) } @@ -829,9 +980,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) { ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + withValue(CommonDataKinds.StructuredName.PREFIX, contact.prefix) withValue(CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName) withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName) withValue(CommonDataKinds.StructuredName.FAMILY_NAME, contact.surname) + withValue(CommonDataKinds.StructuredName.SUFFIX, contact.suffix) operations.add(build()) } @@ -887,6 +1040,17 @@ class ContactsHelper(val activity: BaseSimpleActivity) { operations.add(build()) } + // organization + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE) + withValue(CommonDataKinds.Organization.COMPANY, contact.organization.company) + withValue(CommonDataKinds.Organization.TYPE, CommonDataKinds.Organization.TYPE_WORK) + withValue(CommonDataKinds.Organization.TITLE, contact.organization.jobPosition) + withValue(CommonDataKinds.Organization.TYPE, CommonDataKinds.Organization.TYPE_WORK) + operations.add(build()) + } + // groups contact.groups.forEach { ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { @@ -1000,16 +1164,19 @@ class ContactsHelper(val activity: BaseSimpleActivity) { fun addFavorites(contacts: ArrayList) { toggleLocalFavorites(contacts, true) - toggleFavorites(contacts, true) + if (activity.hasContactPermissions()) { + toggleFavorites(contacts, true) + } } fun removeFavorites(contacts: ArrayList) { toggleLocalFavorites(contacts, false) - toggleFavorites(contacts, false) + if (activity.hasContactPermissions()) { + toggleFavorites(contacts, false) + } } private fun toggleFavorites(contacts: ArrayList, addToFavorites: Boolean) { - val applyBatchSize = 100 try { val operations = ArrayList() contacts.filter { it.source != SMT_PRIVATE }.map { it.contactId.toString() }.forEach { @@ -1019,7 +1186,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) { operations.add(build()) } - if (operations.size % applyBatchSize == 0) { + if (operations.size % BATCH_SIZE == 0) { activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) operations.clear() } @@ -1044,25 +1211,27 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } fun deleteContacts(contacts: ArrayList) { - val localContacts = contacts.filter { it.source == SMT_PRIVATE }.map { it.id.toString() }.toTypedArray() - activity.dbHelper.deleteContacts(localContacts) + Thread { + val localContacts = contacts.filter { it.source == SMT_PRIVATE }.map { it.id.toString() }.toTypedArray() + activity.dbHelper.deleteContacts(localContacts) - try { - val contactIDs = HashSet() - val operations = ArrayList() - val selection = "${ContactsContract.Data.CONTACT_ID} = ?" - contacts.filter { it.source != SMT_PRIVATE }.forEach { - ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { - val selectionArgs = arrayOf(it.contactId.toString()) - withSelection(selection, selectionArgs) - operations.add(this.build()) + try { + val contactIDs = HashSet() + val operations = ArrayList() + val selection = "${ContactsContract.Data.CONTACT_ID} = ?" + contacts.filter { it.source != SMT_PRIVATE }.forEach { + ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { + val selectionArgs = arrayOf(it.contactId.toString()) + withSelection(selection, selectionArgs) + operations.add(this.build()) + } + contactIDs.add(it.id.toString()) } - contactIDs.add(it.id.toString()) - } - activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) - } catch (e: Exception) { - activity.showErrorToast(e) - } + activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) + } catch (e: Exception) { + activity.showErrorToast(e) + } + }.start() } } 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 0067b33e..d0fe214e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt @@ -23,9 +23,11 @@ import com.simplemobiletools.contacts.models.* class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { private val CONTACTS_TABLE_NAME = "contacts" private val COL_ID = "id" + private val COL_PREFIX = "prefix" private val COL_FIRST_NAME = "first_name" private val COL_MIDDLE_NAME = "middle_name" private val COL_SURNAME = "surname" + private val COL_SUFFIX = "suffix" private val COL_PHOTO = "photo" private val COL_PHONE_NUMBERS = "phone_numbers" private val COL_EMAILS = "emails" @@ -33,6 +35,8 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont private val COL_STARRED = "starred" private val COL_ADDRESSES = "addresses" private val COL_NOTES = "notes" + private val COL_COMPANY = "company" + private val COL_JOB_POSITION = "job_position" private val COL_GROUPS = "groups" private val GROUPS_TABLE_NAME = "groups" @@ -43,9 +47,10 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont private val mDb = writableDatabase companion object { - private const val DB_VERSION = 3 + private const val DB_VERSION = 4 const val DB_NAME = "contacts.db" var dbInstance: DBHelper? = null + var gson = Gson() fun newInstance(context: Context): DBHelper { if (dbInstance == null) @@ -58,7 +63,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont override fun onCreate(db: SQLiteDatabase) { 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_ADDRESSES TEXT, $COL_NOTES TEXT, $COL_GROUPS TEXT, $COL_PREFIX TEXT, $COL_SUFFIX TEXT, $COL_COMPANY TEXT, $COL_JOB_POSITION 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)") @@ -76,6 +81,13 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont createGroupsTable(db) db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_GROUPS TEXT DEFAULT ''") } + + if (oldVersion < 4) { + db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_PREFIX TEXT DEFAULT ''") + db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_SUFFIX TEXT DEFAULT ''") + db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_COMPANY TEXT DEFAULT ''") + db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_JOB_POSITION TEXT DEFAULT ''") + } } private fun createGroupsTable(db: SQLiteDatabase) { @@ -108,16 +120,20 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont private fun fillContactValues(contact: Contact): ContentValues { return ContentValues().apply { + put(COL_PREFIX, contact.prefix) put(COL_FIRST_NAME, contact.firstName) put(COL_MIDDLE_NAME, contact.middleName) put(COL_SURNAME, contact.surname) - put(COL_PHONE_NUMBERS, Gson().toJson(contact.phoneNumbers)) - put(COL_EMAILS, Gson().toJson(contact.emails)) - put(COL_ADDRESSES, Gson().toJson(contact.addresses)) - put(COL_EVENTS, Gson().toJson(contact.events)) + put(COL_SUFFIX, contact.suffix) + put(COL_PHONE_NUMBERS, gson.toJson(contact.phoneNumbers)) + put(COL_EMAILS, gson.toJson(contact.emails)) + put(COL_ADDRESSES, gson.toJson(contact.addresses)) + put(COL_EVENTS, gson.toJson(contact.events)) put(COL_STARRED, contact.starred) put(COL_NOTES, contact.notes) - put(COL_GROUPS, Gson().toJson(contact.groups.map { it.id })) + put(COL_GROUPS, gson.toJson(contact.groups.map { it.id })) + put(COL_COMPANY, contact.organization.company) + put(COL_JOB_POSITION, contact.organization.jobPosition) if (contact.photoUri.isNotEmpty()) { put(COL_PHOTO, getPhotoByteArray(contact.photoUri)) @@ -166,7 +182,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont fun deleteGroup(id: Long) = deleteGroups(arrayOf(id.toString())) - fun deleteGroups(ids: Array) { + private fun deleteGroups(ids: Array) { val args = TextUtils.join(", ", ids) val selection = "$GROUPS_TABLE_NAME.$COL_ID IN ($args)" mDb.delete(GROUPS_TABLE_NAME, selection, null) @@ -209,7 +225,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont } } - fun updateContactGroups(contact: Contact, groupIds: ArrayList) { + private fun updateContactGroups(contact: Contact, groupIds: ArrayList) { val contactValues = fillContactGroupValues(groupIds) val selection = "$COL_ID = ?" val selectionArgs = arrayOf(contact.id.toString()) @@ -218,39 +234,47 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont private fun fillContactGroupValues(groupIds: ArrayList): ContentValues { return ContentValues().apply { - put(COL_GROUPS, Gson().toJson(groupIds)) + put(COL_GROUPS, gson.toJson(groupIds)) } } - fun getContacts(activity: BaseSimpleActivity, selection: String? = null, selectionArgs: Array? = null): ArrayList { val storedGroups = ContactsHelper(activity).getStoredGroups() val contacts = ArrayList() - val projection = arrayOf(COL_ID, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_PHONE_NUMBERS, COL_EMAILS, COL_EVENTS, COL_STARRED, - COL_PHOTO, COL_ADDRESSES, COL_NOTES, COL_GROUPS) + val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_PHONE_NUMBERS, COL_EMAILS, + COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION) + + val phoneNumbersToken = object : TypeToken>() {}.type + val emailsToken = object : TypeToken>() {}.type + val addressesToken = object : TypeToken>() {}.type + val eventsToken = object : TypeToken>() {}.type + val groupIdsToken = object : TypeToken>() {}.type + val cursor = mDb.query(CONTACTS_TABLE_NAME, projection, selection, selectionArgs, null, null, null) cursor.use { while (cursor.moveToNext()) { val id = cursor.getIntValue(COL_ID) + val prefix = cursor.getStringValue(COL_PREFIX) val firstName = cursor.getStringValue(COL_FIRST_NAME) val middleName = cursor.getStringValue(COL_MIDDLE_NAME) val surname = cursor.getStringValue(COL_SURNAME) + val suffix = cursor.getStringValue(COL_SUFFIX) val phoneNumbersJson = cursor.getStringValue(COL_PHONE_NUMBERS) - val phoneNumbersToken = object : TypeToken>() {}.type - val phoneNumbers = Gson().fromJson>(phoneNumbersJson, phoneNumbersToken) ?: ArrayList(1) + val phoneNumbers = if (phoneNumbersJson == "[]") ArrayList() else gson.fromJson>(phoneNumbersJson, phoneNumbersToken) + ?: ArrayList(1) val emailsJson = cursor.getStringValue(COL_EMAILS) - val emailsToken = object : TypeToken>() {}.type - val emails = Gson().fromJson>(emailsJson, emailsToken) ?: ArrayList(1) + val emails = if (emailsJson == "[]") ArrayList() else gson.fromJson>(emailsJson, emailsToken) + ?: ArrayList(1) val addressesJson = cursor.getStringValue(COL_ADDRESSES) - val addressesToken = object : TypeToken>() {}.type - val addresses = Gson().fromJson>(addressesJson, addressesToken) ?: ArrayList(1) + val addresses = if (addressesJson == "[]") ArrayList() else gson.fromJson>(addressesJson, addressesToken) + ?: ArrayList(1) val eventsJson = cursor.getStringValue(COL_EVENTS) - val eventsToken = object : TypeToken>() {}.type - val events = Gson().fromJson>(eventsJson, eventsToken) ?: ArrayList(1) + val events = if (eventsJson == "[]") ArrayList() else gson.fromJson>(eventsJson, eventsToken) + ?: ArrayList(1) val photoByteArray = cursor.getBlobValue(COL_PHOTO) ?: null val photo = if (photoByteArray?.isNotEmpty() == true) { @@ -263,11 +287,18 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont val starred = cursor.getIntValue(COL_STARRED) val groupIdsJson = cursor.getStringValue(COL_GROUPS) - val groupIdsToken = object : TypeToken>() {}.type - val groupIds = Gson().fromJson>(groupIdsJson, groupIdsToken) ?: ArrayList(1) + val groupIds = if (groupIdsJson == "[]") ArrayList() else gson.fromJson>(groupIdsJson, groupIdsToken) + ?: ArrayList(1) val groups = storedGroups.filter { groupIds.contains(it.id) } as ArrayList - val contact = Contact(id, firstName, middleName, surname, "", phoneNumbers, emails, addresses, events, SMT_PRIVATE, starred, id, "", photo, notes, groups) + val company = cursor.getStringValue(COL_COMPANY) + val jobPosition = cursor.getStringValue(COL_JOB_POSITION) + val organization = Organization(company, jobPosition) + + val websites = ArrayList() + + val contact = Contact(id, prefix, firstName, middleName, surname, suffix, "", phoneNumbers, emails, addresses, events, + SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites) 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 ec7e443e..80e503eb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt @@ -66,6 +66,11 @@ class VcfExporter { out.writeLn("$NOTE${contact.notes.replace("\n", "\\n")}") } + if (!contact.organization.isEmpty()) { + out.writeLn("$ORG${contact.organization.company.replace("\n", "\\n")}") + out.writeLn("$TITLE${contact.organization.jobPosition.replace("\n", "\\n")}") + } + if (contact.thumbnailUri.isNotEmpty()) { val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, Uri.parse(contact.thumbnailUri)) addBitmap(bitmap, out) @@ -116,17 +121,23 @@ class VcfExporter { var firstName = contact.firstName var surName = contact.surname var middleName = contact.middleName + var prefix = contact.prefix + var suffix = contact.suffix if (QuotedPrintable.urlEncode(firstName) != firstName || QuotedPrintable.urlEncode(surName) != surName - || QuotedPrintable.urlEncode(middleName) != middleName) { + || QuotedPrintable.urlEncode(middleName) != middleName + || QuotedPrintable.urlEncode(prefix) != prefix + || QuotedPrintable.urlEncode(suffix) != suffix) { firstName = QuotedPrintable.encode(firstName) surName = QuotedPrintable.encode(surName) middleName = QuotedPrintable.encode(middleName) + prefix = QuotedPrintable.encode(prefix) + suffix = QuotedPrintable.encode(suffix) result += ";CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE" } - return "$result:$surName;$firstName;$middleName;;" + return "$result:$surName;$firstName;$middleName;$prefix;$suffix" } private fun getPhoneNumberLabel(type: Int) = when (type) { 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 602e5160..c1938d19 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -19,16 +19,21 @@ class VcfImporter(val activity: SimpleActivity) { IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL } + private var curPrefix = "" private var curFirstName = "" private var curMiddleName = "" private var curSurname = "" + private var curSuffix = "" private var curPhotoUri = "" private var curNotes = "" + private var curCompany = "" + private var curJobPosition = "" private var curPhoneNumbers = ArrayList() private var curEmails = ArrayList() private var curEvents = ArrayList() private var curAddresses = ArrayList
() private var curGroups = ArrayList() + private var curWebsites = ArrayList() private var isGettingPhoto = false private var currentPhotoString = StringBuilder() @@ -84,6 +89,8 @@ class VcfImporter(val activity: SimpleActivity) { line.toUpperCase().startsWith(BDAY) -> addBirthday(line.substring(BDAY.length)) line.toUpperCase().startsWith(ANNIVERSARY) -> addAnniversary(line.substring(ANNIVERSARY.length)) line.toUpperCase().startsWith(PHOTO) -> addPhoto(line.substring(PHOTO.length)) + line.toUpperCase().startsWith(ORG) -> addCompany(line.substring(ORG.length)) + line.toUpperCase().startsWith(TITLE) -> addJobPosition(line.substring(TITLE.length)) line.toUpperCase() == END_VCARD -> saveContact(targetContactSource) isGettingPhoto -> currentPhotoString.append(line.trim()) } @@ -118,6 +125,8 @@ class VcfImporter(val activity: SimpleActivity) { curFirstName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[1]) else nameParts[1] if (nameParts.size > 2) { curMiddleName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[2]) else nameParts[2] + curPrefix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[3]) else nameParts[3] + curSuffix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[4]) else nameParts[4] } } @@ -234,25 +243,39 @@ class VcfImporter(val activity: SimpleActivity) { isGettingNotes = true } + private fun addCompany(company: String) { + curCompany = company + } + + private fun addJobPosition(jobPosition: String) { + curJobPosition = jobPosition + } + private fun saveContact(source: String) { - val contact = Contact(0, curFirstName, curMiddleName, curSurname, curPhotoUri, curPhoneNumbers, curEmails, curAddresses, curEvents, - source, 0, 0, "", null, curNotes, curGroups) + val organization = Organization(curCompany, curJobPosition) + val contact = Contact(0, curPrefix, curFirstName, curMiddleName, curSurname, curSuffix, curPhotoUri, curPhoneNumbers, curEmails, curAddresses, curEvents, + source, 0, 0, "", null, curNotes, curGroups, organization, curWebsites) if (ContactsHelper(activity).insertContact(contact)) { contactsImported++ } } private fun resetValues() { + curPrefix = "" curFirstName = "" curMiddleName = "" curSurname = "" + curSuffix = "" curPhotoUri = "" curNotes = "" + curCompany = "" + curJobPosition = "" curPhoneNumbers = ArrayList() curEmails = ArrayList() curEvents = ArrayList() curAddresses = ArrayList() curGroups = ArrayList() + curWebsites = ArrayList() isGettingPhoto = false currentPhotoString = StringBuilder() 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 488f4a14..a4a422a3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -5,19 +5,50 @@ 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 -data class Contact(val id: Int, var firstName: String, var middleName: String, var surname: String, var photoUri: String, +data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: 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) : Comparable { + var groups: ArrayList, var organization: Organization, var websites: ArrayList) : Comparable { companion object { var sorting = 0 + var startWithSurname = false } override fun compareTo(other: Contact): Int { - var result = when { - sorting and SORT_BY_FIRST_NAME != 0 -> compareStrings(firstName, other.firstName) - sorting and SORT_BY_MIDDLE_NAME != 0 -> compareStrings(middleName, other.middleName) - else -> compareStrings(surname, other.surname) + val firstString: String + val secondString: String + + when { + sorting and SORT_BY_FIRST_NAME != 0 -> { + firstString = firstName + secondString = other.firstName + } + sorting and SORT_BY_MIDDLE_NAME != 0 -> { + firstString = middleName + secondString = other.middleName + } + else -> { + firstString = surname + secondString = other.surname + } + } + + var result = if (firstString.firstOrNull()?.isLetter() == true && secondString.firstOrNull()?.isLetter() == false) { + -1 + } else if (firstString.firstOrNull()?.isLetter() == false && secondString.firstOrNull()?.isLetter() == true) { + 1 + } else { + if (firstString.isEmpty() && secondString.isNotEmpty()) { + 1 + } else if (firstString.isNotEmpty() && secondString.isEmpty()) { + -1 + } else { + if (firstString.toLowerCase() == secondString.toLowerCase()) { + getFullName().compareTo(other.getFullName()) + } else { + firstString.toLowerCase().compareTo(secondString.toLowerCase()) + } + } } if (sorting and SORT_DESCENDING != 0) { @@ -33,28 +64,21 @@ data class Contact(val id: Int, var firstName: String, var middleName: String, v else -> surname } - fun getFullName(startWithSurname: Boolean): String { + fun getFullName(): String { var firstPart = if (startWithSurname) surname else firstName if (middleName.isNotEmpty()) { firstPart += " $middleName" } - val lastPart = if (startWithSurname) firstName else surname - return "$firstPart $lastPart".trim() - } - private fun compareStrings(first: String, second: String): Int { - return if (first.firstOrNull()?.isLetter() == true && second.firstOrNull()?.isLetter() == false) { - -1 - } else if (first.firstOrNull()?.isLetter() == false && second.firstOrNull()?.isLetter() == true) { - 1 + val lastPart = if (startWithSurname) firstName else surname + val suffixComma = if (suffix.isEmpty()) "" else ", $suffix" + val fullName = "$prefix $firstPart $lastPart$suffixComma".trim() + return if (fullName.isEmpty()) { + var fullOrganization = if (organization.jobPosition.isEmpty()) "" else "${organization.jobPosition}, " + fullOrganization += organization.company + fullOrganization.trim().trimEnd(',') } else { - if (first.isEmpty() && second.isNotEmpty()) { - 1 - } else if (first.isNotEmpty() && second.isEmpty()) { - -1 - } else { - first.toLowerCase().compareTo(second.toLowerCase()) - } + fullName } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Organization.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Organization.kt new file mode 100644 index 00000000..fbb7b69c --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Organization.kt @@ -0,0 +1,5 @@ +package com.simplemobiletools.contacts.models + +data class Organization(var company: String, var jobPosition: String) { + fun isEmpty() = company.isEmpty() && jobPosition.isEmpty() +} diff --git a/app/src/main/res/drawable-hdpi/ic_business.png b/app/src/main/res/drawable-hdpi/ic_business.png new file mode 100644 index 00000000..d10ebb76 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_business.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_link.png b/app/src/main/res/drawable-hdpi/ic_link.png new file mode 100644 index 00000000..cf0ce383 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_link.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_business.png b/app/src/main/res/drawable-xhdpi/ic_business.png new file mode 100644 index 00000000..e5630455 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_business.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_link.png b/app/src/main/res/drawable-xhdpi/ic_link.png new file mode 100644 index 00000000..302e5d78 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_link.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_business.png b/app/src/main/res/drawable-xxhdpi/ic_business.png new file mode 100644 index 00000000..7dfc8dc5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_business.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_link.png b/app/src/main/res/drawable-xxhdpi/ic_link.png new file mode 100644 index 00000000..6845cbba Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_link.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_business.png b/app/src/main/res/drawable-xxxhdpi/ic_business.png new file mode 100644 index 00000000..c9aea72c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_business.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_link.png b/app/src/main/res/drawable-xxxhdpi/ic_link.png new file mode 100644 index 00000000..2f793953 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_link.png differ diff --git a/app/src/main/res/layout/activity_edit_contact.xml b/app/src/main/res/layout/activity_edit_contact.xml index f7a2fa11..68f6ac87 100644 --- a/app/src/main/res/layout/activity_edit_contact.xml +++ b/app/src/main/res/layout/activity_edit_contact.xml @@ -18,7 +18,8 @@ + android:layout_height="@dimen/contact_photo_size" + android:layout_marginBottom="@dimen/normal_margin"/> + + + + @@ -162,21 +195,21 @@ @@ -200,7 +232,7 @@ @@ -238,7 +270,7 @@ @@ -276,7 +308,7 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 35f4ccf3..d1fedd67 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -33,6 +33,28 @@ + + + + + + + android:layout_height="@dimen/contact_photo_size" + android:layout_marginBottom="@dimen/normal_margin"/> + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_manage_visible_fields.xml b/app/src/main/res/layout/dialog_manage_visible_fields.xml new file mode 100644 index 00000000..2b8bbf80 --- /dev/null +++ b/app/src/main/res/layout/dialog_manage_visible_fields.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_website.xml b/app/src/main/res/layout/item_website.xml new file mode 100644 index 00000000..39d029bc --- /dev/null +++ b/app/src/main/res/layout/item_website.xml @@ -0,0 +1,14 @@ + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 28f5755a..d8499871 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -6,6 +6,9 @@ Aktualisiere… Gerätespeicher Gerätespeicher (nicht sichtbar für andere Apps) + Company + Job position + Website Neuer Kontakt Kontakt bearbeiten @@ -16,15 +19,15 @@ Familienname - No groups - Create a new group - Remove from group - This group is empty - Add contacts - There are no contact groups on the device - Create group - Add to group - Create group under account + Keine Gruppen + Eine neue Gruppe erstellen + Von Gruppe entfernen + Diese Gruppe ist leer + Kontakte hinzufügen + Es sind keine Kontaktgruppen auf diesem Gerät vorhanden + Erstelle Gruppe + Zu Gruppe hinzufügen + Gruppe in diesem Konto erstellen Foto machen @@ -40,6 +43,7 @@ Kontaktdetails ansehen Show favorites tab Show groups tab + Manage shown contact fields Email @@ -79,6 +83,24 @@ Kontaktquellen einschließen Dateiname (ohne .vcf) + + Select fields to show + Prefix + Suffix + Phone numbers + Emails + Addresses + Events (birthdays, anniversaries) + Notes + Organization + Websites + Groups + Contact source + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + Eine App zum Verwalten von Kontakten, ganz ohne Werbung. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index d4ff97bb..d7b4b75f 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -2,10 +2,13 @@ Απλές Επαφές Επαφές Διεύθυνση - Εισαγωγή... - Ενημέρωση... + Εισαγωγή… + Ενημέρωση… Μνήμη τηλεφώνου Μνήμη τηλεφώνου (δεν είναι ορατή από άλλες εφαρμογές) + Company + Job position + Website Νέα επαφή Επεξεργασία επαφής @@ -40,6 +43,7 @@ Εμφάνιση λεπτομερειών επαφής Εμφάνιση καρτέλας αγαπημένων Εμφάνιση καρτέλας ομάδων + Manage shown contact fields Email @@ -79,6 +83,24 @@ Συμπερίληψη πηγών επαφών Όνομα αρχείου (χωρίς .vcf) + + Select fields to show + Prefix + Suffix + Phone numbers + Emails + Addresses + Events (birthdays, anniversaries) + Notes + Organization + Websites + Groups + Contact source + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + Μια εφαρμογή επαφών για να διαχειρίζεσαι τις επαφές σου χωρίς διαφημίσεις. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 65a677e0..9fb63361 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -6,25 +6,28 @@ Mise à jour… Stockage du téléphone Stockage du téléphone (non visible par d\'autres applis) + Société + Poste + Website Nouveau contact Modifier contact Sélectionner un contact Sélectionner des contacts Prénom - Nom - Surnom + Deuxième prénom + Nom - No groups - Create a new group - Remove from group - This group is empty - Add contacts - There are no contact groups on the device - Create group - Add to group - Create group under account + Pas de groupe + Créer un nouveau groupe + Enlever du groupe + Ce groupe est vide + Ajout contacts + Il n\'y a pas de groupes de contacts sur l\'appareil + Créer un groupe + Ajouter à un groupe + Créer un groupe pris en compte Prendre une photo @@ -32,14 +35,15 @@ Supprimer la photo - Commencer le nom par le surnom + Commencer le nom par le nom de famille Afficher les numéros de téléphone sur l\'écran principal Afficher les vignettes des contacts Sur appui du contact Appeler le contact Voir les détails du contact - Show favorites tab - Show groups tab + Afficher l\'onglet favoris + Afficher l\'onglet groupes + Configurer l\'affichage des champs de contact E-mail @@ -79,6 +83,24 @@ Inclure les sources du contact Nom du fichier (sans .vcf) + + Sélectionner les champs à afficher + Préfixe + Suffixe + Numéros de téléphone + E-mails + Adresses + Évènements (naissances, anniversaires) + Notes + Organisation + Websites + Groupe + Source du contact + + + Je veux changer quelles champs sont visibles. Est-ce que je peux ? + Oui, tout ce que vous avez à faire c\'est d\'aller dans Paramètres -> Configurer l\'affichage des champs de contact. Ici vous pouvez sélectionner quelles champs vous voulez afficher. Certains sont désactivés par défaut, ainsi vous pourrez y trouver des nouveaux champs. + Une appli de contacts pour gérer vos contacts sans pubs. diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml new file mode 100644 index 00000000..76955392 --- /dev/null +++ b/app/src/main/res/values-hr/strings.xml @@ -0,0 +1,121 @@ + + Jednostavni kontakti + Kontakti + Adresa + Dodavanje… + Ažuriranje… + Pohrana na telefonu + Pohrana na telefonu (nije vidljiva drugim aplikacijama)) + Company + Job position + Website + + Novi kontakt + Uredi kontakt + Odaberi kontakt + Odaberi kontakte + Ime + Srednje ime + Prezime + + + Nema grupa + Stvori novu grupu + Ukloni iz grupe + Ova grupa je prazna + Dodaj kontakte + Na uređaju nema grupa kontakata + Stvori grupu + Dodaj u grupu + Stvorite grupu pod računom + + + Uslikaj + Odaberi fotografiju + Ukloni fotografiju + + + Započnite imena s prezimenima + Prikaži telefonske brojeve na glavnom zaslonu + Prikaži sličice kontakata + Prilikom dodira kontakta + Nazovi kontakt + Prikaži pojedinosti o kontaktu + Prikaži karticu favorita + Prikaži karticu grupa + Manage shown contact fields + + + E-pošta + Kućni + Posao + Ostalo + + + Broj + Mobilni + Glavni + Poslovni fax + Kućni fax + Pager + Nije pronađen nijedan telefonski broj + + + Rođendan + Obljetnica + + + Čini se da još niste dodali nijedan kontakt u favorite. + Dodaj favorite + Dodaj u favorite + Ukloni iz favorita + + + Pretraži kontakte + Pretraži favorite + + + Uvezi kontakte + Izvezi kontakte + Uvoz kontakata iz .vcf datoteke + Izvoz kontakata u .vcf datoteku + Ciljani izvor kontakta + Uključi izvore kontakta + Naziv datoteke (bez .vcf) + + + Select fields to show + Prefix + Suffix + Phone numbers + Emails + Addresses + Events (birthdays, anniversaries) + Notes + Organization + Websites + Groups + Contact source + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + + + + Aplikacija za upravljanje kontaktima bez oglasa. + + Jednostavna aplikacija za izradu ili upravljanje kontaktima iz bilo kojeg izvora. Kontakti mogu biti pohranjeni samo na Vašem uređaju, ali i sinkronizirani putem Googlea ili drugih računa. Možete prikazati svoje omiljene kontakte na zasebnom popisu. + + Možete ju koristiti za upravljanje e-poštom i događajima. Ima mogućnost sortirati/filtrirati višestrukim parametrima, po želji može prikazati prezime prvo, zatim ime. + + Ne sadrži oglase ili nepotrebne dozvole. Aplikacije je otvorenog koda, pruža prilagodljive boje. + + Ova je aplikacija samo dio većeg broja aplikacija. Možete pronaći ostatak na http://www.simplemobiletools.com + + + + diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index dfb8692a..7ccd0798 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -6,6 +6,9 @@ 수정중… Phone storage Phone storage (not visible by other apps) + Company + Job position + Website 새로운 연락처 연락처 수정 @@ -40,6 +43,7 @@ View contact details Show favorites tab Show groups tab + Manage shown contact fields 이메일 @@ -79,6 +83,24 @@ 가져오기 대상 파일이름 (.vcf 확장자 생략) + + Select fields to show + Prefix + Suffix + Phone numbers + Emails + Addresses + Events (birthdays, anniversaries) + Notes + Organization + Websites + Groups + Contact source + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + 광고가 없는 연락처 관리 애플리케이션입니다. diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index b6e898db..164db0a4 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -6,6 +6,9 @@ Atnaujinama… Telefono atmintis Telefono atmintis (nematoma kitų programėlių) + Company + Job position + Website Naujas kontaktas Redaguoti kontaktą @@ -18,13 +21,13 @@ Nėra grupių Sukurti naują grupę - Remove from group - This group is empty - Add contacts - There are no contact groups on the device - Create group - Add to group - Create group under account + Pašalinti iš grupės + Ši grupė tuščia + Įtraukti kontaktus + Šiame įrenginyje nėra kontaktų grupių + Sukurti grupę + Įtraukti į grupę + Sukurti grupę paskyroje Nufotografuoti @@ -40,6 +43,7 @@ Žiūrėti kontakto detales Rodyti mėgiamiausiųjų skirtuką Rodyti grupių skirtuką + Manage shown contact fields Elektroninis paštas @@ -79,6 +83,24 @@ Įtraukti kontaktų šaltinius Bylos vardas (be .vcf) + + Select fields to show + Prefix + Suffix + Phone numbers + Emails + Addresses + Events (birthdays, anniversaries) + Notes + Organization + Websites + Groups + Contact source + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + Kontaktų programėlė įrenginio kontaktų tvarkymui, be reklamų. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 8c1521ee..56d5f5fe 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,11 +1,14 @@ Simple Contacts Contactos - Enederço + Endereço A inserir… A atualizar… Armazenamento do telefone Armazenamento do telefone (não visível por outras alicações) + Organização + Cargo + Website Novo contacto Editar contacto @@ -16,15 +19,15 @@ Apelido - No groups - Create a new group - Remove from group - This group is empty - Add contacts - There are no contact groups on the device - Create group - Add to group - Create group under account + Não há grupos + Criar um novo grupo + Remover do grupo + Este grupo está vazio + Adicionar contactos + Não existem grupos de contactos neste dispositivo + Criar um grupo + Adicionar ao grupo + Criar grupo para a conta Tirar foto @@ -38,8 +41,9 @@ Ao clicar no contacto Ligar Ver detalhes - Show favorites tab - Show groups tab + Mostrar favoritos + Mostrar grupos + Gerir campos a exibir E-mail @@ -79,17 +83,35 @@ Incluir fontes dos contactos Nome do ficheiro (sem .vcf) + + Selecione os campos a mostrar + Prefixo + Sufixo + Número de telefone + E-mail + Endereço + Eventos (data de nascimento, aniversário) + Notas + Organização + Websites + Grupos + Fonte do contacto + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + - A contacts app for managing your contacts without ads. + Uma aplicação para gerir os seus contactos. - A simple app for creating or managing your contacts from any source. The contacts can be stored on your device only, but also synchronized via Google, or other accounts. You can display your favorite contacts on a separate list. + Uma aplicação básica para criar e gerir contactos. Pode utilizar a aplicação guardar os contactos localmente, na sua conta Google ou em outro tipo de contas. Pode mostrar os contactos favoritos numa lista distinta. - You can use it for managing user emails and events too. It has the ability to sort/filter by multiple parameters, optionally display surname as the first name. + Também pode utilizar a aplicação para gerir endereços de e-mail e eventos. Tem a capacidade de ordenar/filtrar por diversos parâmetros e também a possibilidade de diversas formas de exibição dos contactos. - Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors. + Não contém anúncios ou permissões desnecessárias. É totalmente open source e permite personalização de cores. - This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com + Esta aplicação é apenas parte de um conjunto mais vasto de aplicações. Saiba mais em http://www.simplemobiletools.com Эл. почта @@ -79,6 +83,24 @@ Включить источники контактов Имя файла (без .vcf) + + Select fields to show + Prefix + Suffix + Phone numbers + Emails + Addresses + Events (birthdays, anniversaries) + Notes + Organization + Websites + Groups + Contact source + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + Приложение для управления контактами без рекламы. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 6e19d57b..be962c4e 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -6,6 +6,9 @@ Upravuje sa… Úložisko mobilu Úložisko mobilu (neviditeľné pre ostatné apky) + Firma + Pracovná pozícia + Website Nový kontakt Upraviť kontakt @@ -40,6 +43,7 @@ Zobraziť údaje kontaktu Zobraziť okno s obľúbenými Zobraziť okno so skupinami + Spravovať zobrazené polia kontaktov Email @@ -79,6 +83,24 @@ Zahrnúť zdroje kontaktov Názov súboru (bez .vcf) + + Zvoľte polia na zobrazenie + Titul pred menom + Titul za priezviskom + Telefónne čísla + Emaily + Adresy + Udalosti (narodeniny, výročia) + Poznámky + Firma + Webstránky + Skupiny + Zdroje kontaktov + + + Chcem upraviť viditeľné polia kontaktov. Dá sa to? + Áno, stačí ísť do Nastavenia -> Spravovať zobrazené polia kontaktov. Tam si viete zvoliť, ktoré polia majú byť viditeľné. Niektoré sú v predvolenom stave vypnuté, čiže tam môžete objaviť aj nové. + Aplikácia pre správu vašich kontaktov bez reklám. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index de16ba0c..4fa202ed 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -6,6 +6,9 @@ Uppdaterar… Telefonens lagringsutrymme Telefonens lagringsutrymme (inte synligt för andra appar) + Company + Job position + Website Ny kontakt Redigera kontakt @@ -16,15 +19,15 @@ Efternamn - No groups - Create a new group - Remove from group - This group is empty - Add contacts - There are no contact groups on the device - Create group - Add to group - Create group under account + Inga grupper + Skapa en ny grupp + Ta bort från grupp + Denna grupp är tom + Lägg till kontakter + Det finns inga kontaktgrupper på enheten + Skapa grupp + Lägg till i grupp + Skapa gruppen i kontot Ta foto @@ -38,8 +41,9 @@ Vid kontakttryckning Ring kontakt Visa kontaktuppgifter - Show favorites tab - Show groups tab + Visa fliken Favoriter + Visa fliken Grupper + Manage shown contact fields E-post @@ -79,6 +83,24 @@ Inkludera kontaktkällor Filnamn (utan .vcf) + + Select fields to show + Prefix + Suffix + Phone numbers + Emails + Addresses + Events (birthdays, anniversaries) + Notes + Organization + Websites + Groups + Contact source + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + En app för att hantera dina kontakter utan reklam. diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 9cc524f0..e10f2970 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -6,6 +6,9 @@ 更新中… 手機空間 手機空間 (其他程式不可見) + 公司 + 職位 + Website 新聯絡人 編輯聯絡人 @@ -24,7 +27,7 @@ 裝置內沒有聯絡人群組 建立群組 添加到群組 - Create group under account + 在帳號下建立群組 拍照 @@ -40,9 +43,10 @@ 顯示聯絡人資料 顯示我的最愛頁面 顯示群組頁面 + 管理顯示的聯絡人欄位 - 信箱 + 電子信箱 住家 工作 其它 @@ -79,6 +83,24 @@ 包含聯絡人來源 檔案名稱 (不含.vcf) + + 選擇要顯示的欄位 + 前缀 + 後綴 + 電話號碼 + 電子信箱 + 地址 + 活動 (生日、紀念日) + 筆記 + 組織 + Websites + 群組 + 聯絡人來源 + + + 我想要更改在通訊錄會看到哪些欄位。我能這麼做嗎? + 可以,你要做的是到[設定] -> [管理顯示的聯絡人欄位]。在那裡,你可以選擇應該看到什麼欄位。其中有些甚至預設是關閉的,所以你可能會在那裡發現一些新的。 + 一個用來管理聯絡人,且沒有廣告的通訊錄應用程式。 diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index fda11285..613319f9 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -2,6 +2,10 @@ + + Added name prefix/suffix and contact organizations\n + Added a settings item \"Manage shown contact fields\" for customizing visible contact details, with some fields disabled by default + Added Address and Notes fields Allow storing contacts in a local database, hidden from other apps diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec6cf97d..a62b2013 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,9 @@ Updating… Phone storage Phone storage (not visible by other apps) + Company + Job position + Website New contact Edit contact @@ -40,6 +43,7 @@ View contact details Show favorites tab Show groups tab + Manage shown contact fields Email @@ -79,6 +83,24 @@ Include contact sources Filename (without .vcf) + + Select fields to show + Prefix + Suffix + Phone numbers + Emails + Addresses + Events (birthdays, anniversaries) + Notes + Organization + Websites + Groups + Contact source + + + I want to change what fields are visible at contacts. Can I do it? + Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there. + A contacts app for managing your contacts without ads. diff --git a/build.gradle b/build.gradle index 73d77ca8..3437621a 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.30' + ext.kotlin_version = '1.2.31' repositories { google() @@ -9,7 +9,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong