diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cc26c16..315a7e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ Changelog ========== +Version 3.3.0 *(2018-02-22)* +---------------------------- + + * Added Address and Notes fields + * Properly handle Get Email intent + * Fixed some glitches at exporting contacts + * Added FAQ + +Version 3.2.0 *(2018-02-11)* +---------------------------- + + * Allow storing contacts in a local database, hidden from other apps + * Added a new screen for viewing contact details + * Increased font size + Version 3.1.4 *(2018-02-03)* ---------------------------- diff --git a/app/build.gradle b/app/build.gradle index 92b6ece5..21c2d2ea 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.simplemobiletools.contacts" minSdkVersion 16 targetSdkVersion 27 - versionCode 9 - versionName "3.1.4" + versionCode 11 + versionName "3.3.0" setProperty("archivesBaseName", "contacts") } @@ -45,8 +45,10 @@ ext { } dependencies { - implementation 'com.simplemobiletools:commons:3.11.3' + implementation 'com.simplemobiletools:commons:3.12.20' 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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 99f7f95d..c3692df2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -172,6 +172,11 @@ android:label="@string/customize_colors" android:parentActivityName=".activities.SettingsActivity"/> + + { @@ -152,6 +154,12 @@ abstract class ContactActivity : SimpleActivity() { else -> R.string.other } + fun getAddressTextId(type: Int) = when (type) { + ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME -> R.string.home + ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK -> R.string.work + else -> R.string.other + } + fun getEventTextId(type: Int) = when (type) { ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY -> R.string.birthday ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY -> R.string.anniversary 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 3b8b0092..38b30572 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/EditContactActivity.kt @@ -22,11 +22,9 @@ import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.extensions.* import com.simplemobiletools.contacts.helpers.* -import com.simplemobiletools.contacts.models.Contact -import com.simplemobiletools.contacts.models.Email -import com.simplemobiletools.contacts.models.Event -import com.simplemobiletools.contacts.models.PhoneNumber +import com.simplemobiletools.contacts.models.* import kotlinx.android.synthetic.main.activity_edit_contact.* +import kotlinx.android.synthetic.main.item_edit_address.view.* import kotlinx.android.synthetic.main.item_edit_email.view.* import kotlinx.android.synthetic.main.item_edit_phone_number.view.* import kotlinx.android.synthetic.main.item_event.view.* @@ -35,6 +33,11 @@ import org.joda.time.format.DateTimeFormat import java.util.* class EditContactActivity : ContactActivity() { + val DEFAULT_EMAIL_TYPE = CommonDataKinds.Email.TYPE_HOME + val DEFAULT_PHONE_NUMBER_TYPE = CommonDataKinds.Phone.TYPE_MOBILE + val DEFAULT_ADDRESS_TYPE = CommonDataKinds.StructuredPostal.TYPE_HOME + val DEFAULT_EVENT_TYPE = CommonDataKinds.Event.TYPE_BIRTHDAY + private val INTENT_TAKE_PHOTO = 1 private val INTENT_CHOOSE_PHOTO = 2 private val INTENT_CROP_PHOTO = 3 @@ -94,7 +97,7 @@ class EditContactActivity : ContactActivity() { super.onActivityResult(requestCode, resultCode, resultData) if (resultCode == RESULT_OK) { when (requestCode) { - INTENT_TAKE_PHOTO, INTENT_CHOOSE_PHOTO -> startCropPhotoIntent(lastPhotoIntentUri!!) + INTENT_TAKE_PHOTO, INTENT_CHOOSE_PHOTO -> startCropPhotoIntent(lastPhotoIntentUri) INTENT_CROP_PHOTO -> updateContactPhoto(lastPhotoIntentUri.toString(), contact_photo) } } @@ -119,7 +122,7 @@ class EditContactActivity : ContactActivity() { } if (contactId != 0) { - contact = ContactsHelper(this).getContactWithId(contactId) + contact = ContactsHelper(this).getContactWithId(contactId, intent.getBooleanExtra(IS_PRIVATE, false)) if (contact == null) { toast(R.string.unknown_error_occurred) finish() @@ -146,10 +149,10 @@ class EditContactActivity : ContactActivity() { contact_photo.background = ColorDrawable(config.primaryColor) - if (contact!!.photoUri.isEmpty()) { + if (contact!!.photoUri.isEmpty() && contact!!.photo == null) { showPhotoPlaceholder(contact_photo) } else { - updateContactPhoto(contact!!.photoUri, contact_photo) + updateContactPhoto(contact!!.photoUri, contact_photo, contact!!.photo) } val textColor = config.textColor @@ -159,7 +162,9 @@ class EditContactActivity : ContactActivity() { 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_notes_image.applyColorFilter(textColor) contact_source_image.applyColorFilter(textColor) val adjustedPrimaryColor = getAdjustedPrimaryColor() @@ -167,6 +172,8 @@ class EditContactActivity : ContactActivity() { 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) @@ -177,6 +184,7 @@ class EditContactActivity : ContactActivity() { 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_toggle_favorite.apply { @@ -190,7 +198,12 @@ class EditContactActivity : ContactActivity() { invalidateOptionsMenu() } - private fun startCropPhotoIntent(uri: Uri) { + private fun startCropPhotoIntent(uri: Uri?) { + if (uri == null) { + toast(R.string.unknown_error_occurred) + return + } + lastPhotoIntentUri = getCachePhotoUri() Intent("com.android.camera.action.CROP").apply { setDataAndType(uri, "image/*") @@ -204,7 +217,11 @@ class EditContactActivity : ContactActivity() { putExtra("scaleUpIfNeeded", "true") clipData = ClipData("Attachment", arrayOf("text/uri-list"), ClipData.Item(lastPhotoIntentUri)) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - startActivityForResult(this, INTENT_CROP_PHOTO) + if (resolveActivity(packageManager) != null) { + startActivityForResult(this, INTENT_CROP_PHOTO) + } else { + toast(R.string.no_app_found) + } } } @@ -218,6 +235,8 @@ class EditContactActivity : ContactActivity() { setupPhoneNumbers() setupEmails() + setupAddresses() + setupNotes() setupEvents() } @@ -251,6 +270,25 @@ class EditContactActivity : ContactActivity() { } } + 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) + } + + addressHolder!!.apply { + contact_address.setText(address.value) + setupAddressTypePicker(contact_address_type, address.type) + } + } + } + + private fun setupNotes() { + contact_notes.setText(contact!!.notes) + } + private fun setupEvents() { contact!!.events.forEachIndexed { index, event -> var eventHolder = contact_events_holder.getChildAt(index) @@ -283,7 +321,7 @@ class EditContactActivity : ContactActivity() { private fun setupNewContact() { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE) supportActionBar?.title = resources.getString(R.string.new_contact) - contact = Contact(0, "", "", "", "", ArrayList(), ArrayList(), ArrayList(), config.lastUsedContactSource, 0, 0, "") + contact = Contact(0, "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), config.lastUsedContactSource, 0, 0, "", null, "") contact_source.text = getPublicContactSource(contact!!.source) contact_source.setOnClickListener { showContactSourcePicker(contact!!.source) { @@ -308,6 +346,13 @@ class EditContactActivity : ContactActivity() { } } + if (contact!!.addresses.isEmpty()) { + val addressHolder = contact_addresses_holder.getChildAt(0) + (addressHolder as? ViewGroup)?.contact_address_type?.apply { + setupAddressTypePicker(this) + } + } + if (contact!!.events.isEmpty()) { val eventHolder = contact_events_holder.getChildAt(0) (eventHolder as? ViewGroup)?.apply { @@ -334,6 +379,15 @@ class EditContactActivity : ContactActivity() { } } + private fun setupAddressTypePicker(addressTypeField: TextView, type: Int = DEFAULT_ADDRESS_TYPE) { + addressTypeField.apply { + setText(getAddressTextId(type)) + setOnClickListener { + showAddressTypePicker(it as TextView) + } + } + } + private fun setupEventTypePicker(eventHolder: ViewGroup, type: Int = DEFAULT_EVENT_TYPE) { eventHolder.contact_event_type.apply { setText(getEventTextId(type)) @@ -409,6 +463,19 @@ class EditContactActivity : ContactActivity() { } } + private fun showAddressTypePicker(addressTypeField: TextView) { + val items = arrayListOf( + RadioItem(CommonDataKinds.StructuredPostal.TYPE_HOME, getString(R.string.home)), + RadioItem(CommonDataKinds.StructuredPostal.TYPE_WORK, getString(R.string.work)), + RadioItem(CommonDataKinds.StructuredPostal.TYPE_OTHER, getString(R.string.other)) + ) + + val currentAddressTypeId = getAddressTypeId(addressTypeField.value) + RadioGroupDialog(this, items, currentAddressTypeId) { + addressTypeField.setText(getAddressTextId(it as Int)) + } + } + private fun showEventTypePicker(eventTypeField: TextView) { val items = arrayListOf( RadioItem(CommonDataKinds.Event.TYPE_BIRTHDAY, getString(R.string.birthday)), @@ -436,9 +503,11 @@ class EditContactActivity : ContactActivity() { photoUri = currentContactPhotoPath phoneNumbers = getFilledPhoneNumbers() emails = getFilledEmails() + addresses = getFilledAddresses() events = getFilledEvents() source = contact!!.source starred = if (isContactStarred()) 1 else 0 + notes = contact_notes.value Thread { config.lastUsedContactSource = source @@ -482,6 +551,21 @@ class EditContactActivity : ContactActivity() { return emails } + private fun getFilledAddresses(): ArrayList
{ + val addresses = ArrayList
() + val addressesCount = contact_addresses_holder.childCount + for (i in 0 until addressesCount) { + val addressHolder = contact_addresses_holder.getChildAt(i) + val address = addressHolder.contact_address.value + val addressType = getAddressTypeId(addressHolder.contact_address_type.value) + + if (address.isNotEmpty()) { + addresses.add(Address(address, addressType)) + } + } + return addresses + } + private fun getFilledEvents(): ArrayList { val unknown = getString(R.string.unknown) val events = ArrayList() @@ -551,6 +635,17 @@ class EditContactActivity : ContactActivity() { } } + private fun addNewAddressField() { + val addressHolder = layoutInflater.inflate(R.layout.item_edit_address, contact_addresses_holder, false) as ViewGroup + updateTextColors(addressHolder) + setupAddressTypePicker(addressHolder.contact_address_type) + contact_addresses_holder.addView(addressHolder) + contact_addresses_holder.onGlobalLayout { + addressHolder.contact_address.requestFocus() + showKeyboard(addressHolder.contact_address) + } + } + private fun addNewEventField() { val eventHolder = layoutInflater.inflate(R.layout.item_event, contact_events_holder, false) as ViewGroup updateTextColors(eventHolder) @@ -577,7 +672,7 @@ class EditContactActivity : ContactActivity() { RadioItem(CHOOSE_PHOTO, getString(R.string.choose_photo)) ) - if (currentContactPhotoPath.isNotEmpty()) { + if (currentContactPhotoPath.isNotEmpty() || contact!!.photo != null) { items.add(RadioItem(REMOVE_PHOTO, getString(R.string.remove_photo))) } @@ -642,4 +737,10 @@ class EditContactActivity : ContactActivity() { getString(R.string.anniversary) -> CommonDataKinds.Event.TYPE_ANNIVERSARY else -> CommonDataKinds.Event.TYPE_OTHER } + + private fun getAddressTypeId(value: String) = when (value) { + getString(R.string.home) -> CommonDataKinds.StructuredPostal.TYPE_HOME + getString(R.string.work) -> CommonDataKinds.StructuredPostal.TYPE_WORK + else -> CommonDataKinds.StructuredPostal.TYPE_OTHER + } } 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 6fc48b2c..aa37373e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt @@ -14,6 +14,8 @@ import android.view.MenuItem import com.simplemobiletools.commons.dialogs.FilePickerDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.commons.models.FAQItem +import com.simplemobiletools.commons.models.Release import com.simplemobiletools.contacts.BuildConfig import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.adapters.ViewPagerAdapter @@ -69,6 +71,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } } storeStateVariables() + checkWhatsNewDialog() } override fun onResume() { @@ -105,8 +108,8 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { if (storedPrimaryColor != configPrimaryColor) { main_tabs_holder.setSelectedTabIndicatorColor(getAdjustedPrimaryColor()) main_tabs_holder.getTabAt(viewpager.currentItem)?.icon?.applyColorFilter(getAdjustedPrimaryColor()) - contacts_fragment.primaryColorChanged(configPrimaryColor) - favorites_fragment.primaryColorChanged(configPrimaryColor) + contacts_fragment.primaryColorChanged() + favorites_fragment.primaryColorChanged() } val configStartNameWithSurname = config.startNameWithSurname @@ -119,12 +122,16 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { if (viewpager.adapter == null) { initFragments() } + contacts_fragment?.initContacts() contacts_fragment?.onActivityResume() favorites_fragment?.initContacts() favorites_fragment?.onActivityResume() } - isFirstResume = false + + if (hasPermission(PERMISSION_WRITE_CONTACTS)) { + isFirstResume = false + } } override fun onPause() { @@ -189,13 +196,13 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { MenuItemCompat.setOnActionExpandListener(searchMenuItem, object : MenuItemCompat.OnActionExpandListener { override fun onMenuItemActionExpand(item: MenuItem?): Boolean { - getCurrentFragment().onSearchOpened() + getCurrentFragment()?.onSearchOpened() isSearchOpen = true return true } override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { - getCurrentFragment().onSearchClosed() + getCurrentFragment()?.onSearchClosed() isSearchOpen = false return true } @@ -284,15 +291,15 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { private fun showSortingDialog() { ChangeSortingDialog(this) { - contacts_fragment.initContacts() - favorites_fragment.initContacts() + contacts_fragment?.initContacts() + favorites_fragment?.initContacts() } } fun showFilterDialog() { FilterContactSourcesDialog(this) { - contacts_fragment.forceListRedraw = true - contacts_fragment.initContacts() + contacts_fragment?.forceListRedraw = true + contacts_fragment?.initContacts() } } @@ -351,7 +358,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { FilePickerDialog(this, pickFile = false, showFAB = true) { ExportContactsDialog(this, it) { file, contactSources -> Thread { - ContactsHelper(this).getContacts { + ContactsHelper(this).getContacts(true) { val contacts = it.filter { contactSources.contains(it.source) } if (contacts.isEmpty()) { toast(R.string.no_entries_for_exporting) @@ -372,7 +379,9 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } private fun launchAbout() { - startAboutActivity(R.string.app_name, LICENSE_KOTLIN or LICENSE_MULTISELECT or LICENSE_JODA or LICENSE_GLIDE, BuildConfig.VERSION_NAME) + val faqItems = arrayListOf(FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons)) + startAboutActivity(R.string.app_name, LICENSE_KOTLIN or LICENSE_MULTISELECT or LICENSE_JODA or LICENSE_GLIDE or LICENSE_GSON or LICENSE_STETHO, + BuildConfig.VERSION_NAME, faqItems) } override fun refreshContacts() { @@ -383,4 +392,12 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { override fun refreshFavorites() { favorites_fragment?.initContacts() } + + private fun checkWhatsNewDialog() { + arrayListOf().apply { + add(Release(10, R.string.release_10)) + add(Release(11, R.string.release_11)) + 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 1d720db2..c6b82f3e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt @@ -4,6 +4,8 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import android.provider.ContactsContract +import android.view.Menu +import android.view.MenuItem import com.simplemobiletools.commons.extensions.appLaunched import com.simplemobiletools.commons.extensions.baseConfig import com.simplemobiletools.commons.extensions.isActivityDestroyed @@ -12,12 +14,17 @@ import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CONTACTS import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.adapters.SelectContactsAdapter +import com.simplemobiletools.contacts.dialogs.ChangeSortingDialog +import com.simplemobiletools.contacts.dialogs.FilterContactSourcesDialog import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.SMT_PRIVATE import com.simplemobiletools.contacts.models.Contact import kotlinx.android.synthetic.main.layout_select_contact.* class SelectContactActivity : SimpleActivity() { + private var isGetEmailIntent = false + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.layout_select_contact) @@ -27,6 +34,7 @@ class SelectContactActivity : SimpleActivity() { if (it) { handlePermission(PERMISSION_WRITE_CONTACTS) { if (it) { + isGetEmailIntent = intent.data == ContactsContract.CommonDataKinds.Email.CONTENT_URI initContacts() } else { toast(R.string.no_contacts_permission) @@ -40,13 +48,46 @@ class SelectContactActivity : SimpleActivity() { } } + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_select_activity, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.sort -> showSortingDialog() + R.id.filter -> showFilterDialog() + else -> return super.onOptionsItemSelected(item) + } + return true + } + + private fun showSortingDialog() { + ChangeSortingDialog(this) { + initContacts() + } + } + + private fun showFilterDialog() { + FilterContactSourcesDialog(this) { + initContacts() + } + } + private fun initContacts() { - ContactsHelper(this).getContacts { - var contacts = it + ContactsHelper(this).getContacts(true) { if (isActivityDestroyed()) { return@getContacts } + var contacts = it.filter { + if (isGetEmailIntent) { + (it.source != SMT_PRIVATE && it.emails.isNotEmpty()) + } else { + true + } + } as ArrayList + val contactSources = config.displayContactSources if (!config.showAllContacts()) { contacts = contacts.filter { contactSources.contains(it.source) } as ArrayList @@ -59,6 +100,7 @@ class SelectContactActivity : SimpleActivity() { select_contact_list.adapter = SelectContactsAdapter(this, contacts, ArrayList(), false) { confirmSelection(it) } + select_contact_fastscroller.allowBubbleDisplay = baseConfig.showInfoBubble select_contact_fastscroller.setViews(select_contact_list) { select_contact_fastscroller.updateBubbleText(contacts[it].getBubbleText()) @@ -68,14 +110,21 @@ class SelectContactActivity : SimpleActivity() { } private fun confirmSelection(contact: Contact) { - val lookupKey = ContactsHelper(this).getContactLookupKey(contact.id.toString()) - val lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey) - Intent().apply { - data = lookupUri + data = getResultUri(contact) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) setResult(RESULT_OK, this) } finish() } + + private fun getResultUri(contact: Contact): Uri { + return if (isGetEmailIntent) { + val emailID = ContactsHelper(this).getContactDataId(contact.id.toString()) + Uri.withAppendedPath(ContactsContract.Data.CONTENT_URI, emailID) + } else { + val lookupKey = ContactsHelper(this).getContactLookupKey(contact.id.toString()) + Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey) + } + } } 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 c86ee0c6..f27ef8d8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt @@ -6,17 +6,18 @@ import android.os.Bundle import android.provider.ContactsContract import android.view.Menu import android.view.MenuItem -import android.view.ViewGroup import android.view.WindowManager import android.widget.RelativeLayout -import android.widget.TextView 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.* +import com.simplemobiletools.contacts.helpers.CONTACT_ID +import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.IS_PRIVATE 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_phone_number.view.* @@ -76,7 +77,7 @@ class ViewContactActivity : ContactActivity() { } if (contactId != 0) { - contact = ContactsHelper(this).getContactWithId(contactId) + contact = ContactsHelper(this).getContactWithId(contactId, intent.getBooleanExtra(IS_PRIVATE, false)) if (contact == null) { toast(R.string.unknown_error_occurred) finish() @@ -89,19 +90,18 @@ class ViewContactActivity : ContactActivity() { return } - setupEditContact() + setupViewContact() - setupTypePickers() contact_send_sms.beVisibleIf(contact!!.phoneNumbers.isNotEmpty()) contact_start_call.beVisibleIf(contact!!.phoneNumbers.isNotEmpty()) contact_send_email.beVisibleIf(contact!!.emails.isNotEmpty()) contact_photo.background = ColorDrawable(config.primaryColor) - if (contact!!.photoUri.isEmpty()) { + if (contact!!.photoUri.isEmpty() && contact!!.photo == null) { showPhotoPlaceholder(contact_photo) } else { - updateContactPhoto(contact!!.photoUri, contact_photo) + updateContactPhoto(contact!!.photoUri, contact_photo, contact!!.photo) } val textColor = config.textColor @@ -113,6 +113,7 @@ class ViewContactActivity : ContactActivity() { contact_email_image.applyColorFilter(textColor) contact_event_image.applyColorFilter(textColor) contact_source_image.applyColorFilter(textColor) + contact_notes_image.applyColorFilter(textColor) contact_send_sms.setOnClickListener { trySendSMS() } contact_start_call.setOnClickListener { tryStartCall(contact!!) } @@ -122,7 +123,7 @@ class ViewContactActivity : ContactActivity() { invalidateOptionsMenu() } - private fun setupEditContact() { + private fun setupViewContact() { window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) contact!!.apply { contact_first_name.text = firstName @@ -150,21 +151,19 @@ class ViewContactActivity : ContactActivity() { setupPhoneNumbers() setupEmails() + setupAddresses() setupEvents() + setupNotes() } private fun setupPhoneNumbers() { + contact_numbers_holder.removeAllViews() val phoneNumbers = contact!!.phoneNumbers - phoneNumbers.forEachIndexed { index, number -> - var numberHolder = contact_numbers_holder.getChildAt(index) - if (numberHolder == null) { - numberHolder = layoutInflater.inflate(R.layout.item_view_phone_number, contact_numbers_holder, false) - contact_numbers_holder.addView(numberHolder) - } - - numberHolder!!.apply { - contact_number.text = number.value - setupPhoneNumberTypePicker(contact_number_type, number.type) + phoneNumbers.forEach { + layoutInflater.inflate(R.layout.item_view_phone_number, contact_numbers_holder, false).apply { + contact_numbers_holder.addView(this) + contact_number.text = it.value + contact_number_type.setText(getPhoneNumberTextId(it.type)) } } @@ -173,17 +172,13 @@ class ViewContactActivity : ContactActivity() { } private fun setupEmails() { + contact_emails_holder.removeAllViews() val emails = contact!!.emails - emails.forEachIndexed { index, email -> - var emailHolder = contact_emails_holder.getChildAt(index) - if (emailHolder == null) { - emailHolder = layoutInflater.inflate(R.layout.item_view_email, contact_emails_holder, false) - contact_emails_holder.addView(emailHolder) - } - - emailHolder!!.apply { - contact_email.text = email.value - setupEmailTypePicker(contact_email_type, email.type) + emails.forEach { + layoutInflater.inflate(R.layout.item_view_email, contact_emails_holder, false).apply { + contact_emails_holder.addView(this) + contact_email.text = it.value + contact_email_type.setText(getEmailTextId(it.type)) } } @@ -191,23 +186,30 @@ class ViewContactActivity : ContactActivity() { contact_emails_holder.beVisibleIf(emails.isNotEmpty()) } - private fun setupEvents() { - val events = contact!!.events - 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) + 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 { + contact_addresses_holder.addView(this) + contact_address.text = it.value + contact_address_type.setText(getAddressTextId(it.type)) } + } - (eventHolder as ViewGroup).apply { - contact_event.apply { - getDateTime(event.value, this) - tag = event.value - alpha = 1f - } + contact_address_image.beVisibleIf(addresses.isNotEmpty()) + contact_addresses_holder.beVisibleIf(addresses.isNotEmpty()) + } - setupEventTypePicker(this, event.type) + 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() } } @@ -216,39 +218,11 @@ class ViewContactActivity : ContactActivity() { contact_events_holder.beVisibleIf(events.isNotEmpty()) } - private fun setupTypePickers() { - if (contact!!.phoneNumbers.isEmpty()) { - val numberHolder = contact_numbers_holder.getChildAt(0) - (numberHolder as? ViewGroup)?.contact_number_type?.apply { - setupPhoneNumberTypePicker(this) - } - } - - if (contact!!.emails.isEmpty()) { - val emailHolder = contact_emails_holder.getChildAt(0) - (emailHolder as? ViewGroup)?.contact_email_type?.apply { - setupEmailTypePicker(this) - } - } - - if (contact!!.events.isEmpty()) { - val eventHolder = contact_events_holder.getChildAt(0) - (eventHolder as? ViewGroup)?.apply { - setupEventTypePicker(this) - } - } - } - - private fun setupPhoneNumberTypePicker(numberTypeField: TextView, type: Int = DEFAULT_PHONE_NUMBER_TYPE) { - numberTypeField.setText(getPhoneNumberTextId(type)) - } - - private fun setupEmailTypePicker(emailTypeField: TextView, type: Int = DEFAULT_EMAIL_TYPE) { - emailTypeField.setText(getEmailTextId(type)) - } - - private fun setupEventTypePicker(eventHolder: ViewGroup, type: Int = DEFAULT_EVENT_TYPE) { - eventHolder.contact_event_type.setText(getEventTextId(type)) + private fun setupNotes() { + val notes = contact!!.notes + contact_notes.text = notes + contact_notes_image.beVisibleIf(notes.isNotEmpty()) + contact_notes.beVisibleIf(notes.isNotEmpty()) } 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 9d49925e..6250fbfe 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt @@ -27,7 +27,7 @@ import com.simplemobiletools.contacts.models.Contact import kotlinx.android.synthetic.main.item_contact_with_number.view.* import java.util.* -class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList, private val listener: RefreshContactsListener?, +class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList, private val listener: RefreshContactsListener?, private val isFavoritesFragment: Boolean, recyclerView: MyRecyclerView, fastScroller: FastScroller, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) { @@ -66,6 +66,10 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList editContact() R.id.cab_select_all -> selectAll() @@ -97,7 +101,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList) { + fun updateItems(newItems: ArrayList) { contactItems = newItems notifyDataSetChanged() finishActMode() @@ -135,19 +139,13 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList() selectedPositions.sortedDescending().forEach { favoritesToRemove.add(contactItems[it]) } contactItems.removeAll(favoritesToRemove) - val favoriteIDsToRemove = ArrayList() - favoritesToRemove.mapTo(favoriteIDsToRemove, { it.contactId.toString() }) - ContactsHelper(activity).removeFavorites(favoriteIDsToRemove) + ContactsHelper(activity).removeFavorites(favoritesToRemove) if (contactItems.isEmpty()) { listener?.refreshFavorites() finishActMode() @@ -157,28 +155,23 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList() - selectedPositions.forEach { newFavorites.add(contactItems[it].contactId.toString()) } + val newFavorites = ArrayList() + selectedPositions.forEach { newFavorites.add(contactItems[it]) } ContactsHelper(activity).addFavorites(newFavorites) listener?.refreshFavorites() finishActMode() } private fun shareContacts() { - if (selectedPositions.isEmpty()) { - return - } - - val contacts = ArrayList() + val contactsIDs = ArrayList() selectedPositions.forEach { - contacts.add(contactItems[it]) + contactsIDs.add(contactItems[it].id) } - activity.shareContacts(contacts) + ContactsHelper(activity).getContacts(true) { + val filtered = it.filter { contactsIDs.contains(it.id) } as ArrayList + activity.shareContacts(filtered) + } } override fun onViewRecycled(holder: ViewHolder?) { @@ -201,16 +194,26 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList { + val options = RequestOptions() + .signature(ObjectKey(contact.photoUri)) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .error(contactDrawable) + .centerCrop() - Glide.with(activity).load(contact.photoUri).transition(DrawableTransitionOptions.withCrossFade()).apply(options).into(contact_tmb) - } else { - contact_tmb.setImageDrawable(contactDrawable) + Glide.with(activity).load(contact.photoUri).transition(DrawableTransitionOptions.withCrossFade()).apply(options).into(contact_tmb) + } + contact.photo != null -> { + val options = RequestOptions() + .signature(ObjectKey(contact.photo!!)) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .error(contactDrawable) + .centerCrop() + + Glide.with(activity).load(contact.photo).transition(DrawableTransitionOptions.withCrossFade()).apply(options).into(contact_tmb) + } + else -> contact_tmb.setImageDrawable(contactDrawable) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/FilterContactSourcesAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/FilterContactSourcesAdapter.kt index 1af2218f..69c97584 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/FilterContactSourcesAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/FilterContactSourcesAdapter.kt @@ -9,6 +9,7 @@ import com.simplemobiletools.commons.interfaces.MyAdapterListener import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.extensions.config +import com.simplemobiletools.contacts.helpers.SMT_PRIVATE import com.simplemobiletools.contacts.models.ContactSource import kotlinx.android.synthetic.main.item_filter_contact_source.view.* import java.util.* @@ -27,6 +28,10 @@ class FilterContactSourcesAdapter(val activity: SimpleActivity, private val cont if (contactSource.name == activity.config.localAccountName && contactSource.type == activity.config.localAccountType) { contactSource.name = activity.getString(R.string.phone_storage) } + + if (contactSource.type == SMT_PRIVATE && displayContactSources.contains(SMT_PRIVATE)) { + selectedPositions.add(index) + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/AddFavoritesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/AddFavoritesDialog.kt index f31f0c4a..e0992e10 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/AddFavoritesDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/AddFavoritesDialog.kt @@ -59,12 +59,11 @@ class AddFavoritesDialog(val activity: SimpleActivity, private val callback: () val allDisplayedContacts = ArrayList() allContacts.mapTo(allDisplayedContacts, { it }) val selectedContacts = (view?.select_contact_list?.adapter as? SelectContactsAdapter)?.getSelectedItemsSet() ?: LinkedHashSet() - val contactIDsToAdd = selectedContacts.map { it.contactId.toString() } as ArrayList - contactsHelper.addFavorites(contactIDsToAdd) + val contactsToAdd = selectedContacts.map { it } as ArrayList + contactsHelper.addFavorites(contactsToAdd) allDisplayedContacts.removeAll(selectedContacts) - val contactIDsToRemove = allDisplayedContacts.map { it.contactId.toString() } as ArrayList - contactsHelper.removeFavorites(contactIDsToRemove) + contactsHelper.removeFavorites(allDisplayedContacts) callback() dialog?.dismiss() diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ExportContactsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ExportContactsDialog.kt index d59ada5d..7aedd1af 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ExportContactsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ExportContactsDialog.kt @@ -8,6 +8,7 @@ import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.adapters.FilterContactSourcesAdapter import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.SMT_PRIVATE import com.simplemobiletools.contacts.models.ContactSource import kotlinx.android.synthetic.main.dialog_export_contacts.view.* import java.io.File @@ -35,6 +36,10 @@ class ExportContactsDialog(val activity: SimpleActivity, val path: String, priva .create().apply { activity.setupDialogStuff(view, this, R.string.export_contacts) { getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + if (view.export_contacts_list.adapter == null) { + return@setOnClickListener + } + val filename = view.export_contacts_filename.value when { filename.isEmpty() -> activity.toast(R.string.empty_name) @@ -48,7 +53,7 @@ class ExportContactsDialog(val activity: SimpleActivity, val path: String, priva val selectedIndexes = (view.export_contacts_list.adapter as FilterContactSourcesAdapter).getSelectedItemsSet() val selectedContactSources = HashSet() selectedIndexes.forEach { - selectedContactSources.add(contactSources[it].name) + selectedContactSources.add(if (contactSources[it].type == SMT_PRIVATE) SMT_PRIVATE else contactSources[it].name) } callback(file, selectedContactSources) dismiss() diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/FilterContactSourcesDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/FilterContactSourcesDialog.kt index a01dc8f8..cc1a9d41 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/FilterContactSourcesDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/FilterContactSourcesDialog.kt @@ -7,6 +7,7 @@ import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.adapters.FilterContactSourcesAdapter import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.SMT_PRIVATE import com.simplemobiletools.contacts.models.ContactSource import kotlinx.android.synthetic.main.dialog_filter_contact_sources.view.* import java.util.* @@ -41,7 +42,7 @@ class FilterContactSourcesDialog(val activity: SimpleActivity, private val callb val selectedIndexes = (view.filter_contact_sources_list.adapter as FilterContactSourcesAdapter).getSelectedItemsSet() val selectedContactSources = HashSet() selectedIndexes.forEach { - selectedContactSources.add(contactSources[it].name) + selectedContactSources.add(if (contactSources[it].type == SMT_PRIVATE) SMT_PRIVATE else contactSources[it].name) } if (activity.config.displayContactSources != selectedContactSources) { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ImportContactsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ImportContactsDialog.kt index 5c872afe..8f2ed22f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ImportContactsDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ImportContactsDialog.kt @@ -9,6 +9,7 @@ import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.extensions.config import com.simplemobiletools.contacts.extensions.getPublicContactSource import com.simplemobiletools.contacts.extensions.showContactSourcePicker +import com.simplemobiletools.contacts.helpers.SMT_PRIVATE import com.simplemobiletools.contacts.helpers.VcfImporter import com.simplemobiletools.contacts.helpers.VcfImporter.ImportResult.IMPORT_FAIL import kotlinx.android.synthetic.main.dialog_import_contacts.view.* @@ -22,7 +23,7 @@ class ImportContactsDialog(val activity: SimpleActivity, val path: String, priva import_contacts_title.text = activity.getPublicContactSource(targetContactSource) import_contacts_title.setOnClickListener { activity.showContactSourcePicker(targetContactSource) { - targetContactSource = it + targetContactSource = if (it == activity.getString(R.string.phone_storage_hidden)) SMT_PRIVATE else it import_contacts_title.text = activity.getPublicContactSource(it) } } 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 02a87f7b..3aa0661c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt @@ -4,8 +4,7 @@ import android.content.Intent import android.net.Uri import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.dialogs.RadioGroupDialog -import com.simplemobiletools.commons.extensions.getFilePublicUri -import com.simplemobiletools.commons.extensions.shareUri +import com.simplemobiletools.commons.extensions.sharePathIntent import com.simplemobiletools.commons.extensions.toast import com.simplemobiletools.commons.helpers.PERMISSION_CALL_PHONE import com.simplemobiletools.commons.models.RadioItem @@ -13,6 +12,7 @@ import com.simplemobiletools.contacts.BuildConfig import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.helpers.ContactsHelper +import com.simplemobiletools.contacts.helpers.SMT_PRIVATE import com.simplemobiletools.contacts.helpers.VcfExporter import com.simplemobiletools.contacts.models.Contact import java.io.File @@ -62,6 +62,8 @@ fun SimpleActivity.showContactSourcePicker(currentSource: String, callback: (new items.add(RadioItem(index, publicAccount)) if (account == currentSource) { currentSourceIndex = index + } else if (currentSource == SMT_PRIVATE && account == getString(R.string.phone_storage_hidden)) { + currentSourceIndex = index } } @@ -73,7 +75,13 @@ fun SimpleActivity.showContactSourcePicker(currentSource: String, callback: (new } } -fun SimpleActivity.getPublicContactSource(source: String) = if (source == config.localAccountName) getString(R.string.phone_storage) else source +fun SimpleActivity.getPublicContactSource(source: String): String { + return when (source) { + config.localAccountName -> getString(R.string.phone_storage) + SMT_PRIVATE -> getString(R.string.phone_storage_hidden) + else -> source + } +} fun BaseSimpleActivity.shareContacts(contacts: ArrayList) { val file = getTempFile() @@ -84,8 +92,7 @@ fun BaseSimpleActivity.shareContacts(contacts: ArrayList) { VcfExporter().exportContacts(this, file, contacts) { if (it == VcfExporter.ExportResult.EXPORT_OK) { - val uri = getFilePublicUri(file, BuildConfig.APPLICATION_ID) - shareUri(uri, BuildConfig.APPLICATION_ID) + sharePathIntent(file.absolutePath, BuildConfig.APPLICATION_ID) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Bitmap.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Bitmap.kt new file mode 100644 index 00000000..55983f24 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Bitmap.kt @@ -0,0 +1,15 @@ +package com.simplemobiletools.contacts.extensions + +import android.graphics.Bitmap +import java.io.ByteArrayOutputStream + +fun Bitmap.getByteArray(): ByteArray { + var baos: ByteArrayOutputStream? = null + try { + baos = ByteArrayOutputStream() + compress(Bitmap.CompressFormat.JPEG, 80, baos) + return baos.toByteArray() + } finally { + baos?.close() + } +} 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 9d5b9aa0..19d18f9b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Context.kt @@ -15,16 +15,18 @@ import com.simplemobiletools.contacts.BuildConfig import com.simplemobiletools.contacts.R import com.simplemobiletools.contacts.activities.EditContactActivity import com.simplemobiletools.contacts.activities.ViewContactActivity -import com.simplemobiletools.contacts.helpers.CONTACT_ID -import com.simplemobiletools.contacts.helpers.Config +import com.simplemobiletools.contacts.helpers.* import com.simplemobiletools.contacts.models.Contact import java.io.File val Context.config: Config get() = Config.newInstance(applicationContext) +val Context.dbHelper: DBHelper get() = DBHelper.newInstance(applicationContext) + fun Context.viewContact(contact: Contact) { Intent(applicationContext, ViewContactActivity::class.java).apply { putExtra(CONTACT_ID, contact.id) + putExtra(IS_PRIVATE, contact.source == SMT_PRIVATE) startActivity(this) } } @@ -32,6 +34,7 @@ fun Context.viewContact(contact: Contact) { fun Context.editContact(contact: Contact) { Intent(applicationContext, EditContactActivity::class.java).apply { putExtra(CONTACT_ID, contact.id) + putExtra(IS_PRIVATE, contact.source == SMT_PRIVATE) startActivity(this) } } @@ -125,3 +128,18 @@ fun Context.getCachePhoto(): File { } fun Context.getCachePhotoUri(file: File = getCachePhoto()) = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", file) + +fun Context.getPhotoThumbnailSize(): Int { + val uri = ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI + val projection = arrayOf(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM) + var cursor: Cursor? = null + try { + cursor = contentResolver.query(uri, projection, null, null, null) + if (cursor?.moveToFirst() == true) { + return cursor.getIntValue(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM) + } + } finally { + cursor?.close() + } + return 0 +} 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 de0ff1ae..a2517ff1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt @@ -59,7 +59,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } } - fun primaryColorChanged(color: Int) { + fun primaryColorChanged() { fragment_fastscroller.updatePrimaryColor() fragment_fastscroller.updateBubblePrimaryColor() } @@ -193,7 +193,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } fun onSearchClosed() { - (fragment_list.adapter as ContactsAdapter).updateItems(contactsIgnoringSearch) + (fragment_list.adapter as? ContactsAdapter)?.updateItems(contactsIgnoringSearch) if (this is FavoritesFragment) { fragment_placeholder.text = activity?.getString(R.string.no_favorites) } 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 d34fbc68..f6613b07 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt @@ -2,18 +2,12 @@ package com.simplemobiletools.contacts.helpers import android.content.Context import com.simplemobiletools.commons.helpers.BaseConfig -import com.simplemobiletools.commons.helpers.SORTING -import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME class Config(context: Context) : BaseConfig(context) { companion object { fun newInstance(context: Context) = Config(context) } - var sorting: Int - get() = prefs.getInt(SORTING, SORT_BY_FIRST_NAME) - set(sorting) = prefs.edit().putInt(SORTING, sorting).apply() - var displayContactSources: Set get() = prefs.getStringSet(DISPLAY_CONTACT_SOURCES, hashSetOf("-1")) set(displayContactSources) = prefs.edit().remove(DISPLAY_CONTACT_SOURCES).putStringSet(DISPLAY_CONTACT_SOURCES, displayContactSources).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 c8dbce1b..b59e18ca 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt @@ -1,7 +1,5 @@ package com.simplemobiletools.contacts.helpers -import android.provider.ContactsContract.CommonDataKinds - // shared prefs const val SHOW_CONTACT_THUMBNAILS = "show_contact_thumbnails" const val SHOW_PHONE_NUMBERS = "show_phone_numbers" @@ -14,7 +12,8 @@ const val LOCAL_ACCOUNT_TYPE = "local_account_type" const val ON_CONTACT_CLICK = "on_contact_click" const val CONTACT_ID = "contact_id" -const val SMT_PRIVATE = "smt_private" +const val SMT_PRIVATE = "smt_private" // used at the contact source of local contacts hidden from other apps +const val IS_PRIVATE = "is_private" // contact photo changes const val PHOTO_ADDED = 1 @@ -22,11 +21,6 @@ const val PHOTO_REMOVED = 2 const val PHOTO_CHANGED = 3 const val PHOTO_UNCHANGED = 4 -// default contact values -const val DEFAULT_EMAIL_TYPE = CommonDataKinds.Email.TYPE_HOME -const val DEFAULT_PHONE_NUMBER_TYPE = CommonDataKinds.Phone.TYPE_MOBILE -const val DEFAULT_EVENT_TYPE = CommonDataKinds.Event.TYPE_BIRTHDAY - // export/import const val BEGIN_VCARD = "BEGIN:VCARD" const val END_VCARD = "END:VCARD" @@ -36,6 +30,8 @@ const val BDAY = "BDAY:" const val ANNIVERSARY = "ANNIVERSARY:" const val PHOTO = "PHOTO" const val EMAIL = "EMAIL" +const val ADR = "ADR" +const val NOTE = "NOTE:" const val ENCODING = "ENCODING" const val BASE64 = "BASE64" const val JPEG = "JPEG" 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 d5d8de29..1c9c62cb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt @@ -9,6 +9,7 @@ import android.graphics.Bitmap import android.net.Uri import android.provider.ContactsContract import android.provider.ContactsContract.CommonDataKinds +import android.provider.ContactsContract.CommonDataKinds.Note import android.provider.MediaStore import android.util.SparseArray import com.simplemobiletools.commons.activities.BaseSimpleActivity @@ -22,12 +23,13 @@ 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.models.* -import java.io.ByteArrayOutputStream -import java.util.* class ContactsHelper(val activity: BaseSimpleActivity) { - fun getContacts(callback: (ArrayList) -> Unit) { + fun getContacts(addOptionalFields: Boolean = false, callback: (ArrayList) -> Unit) { val contacts = SparseArray() Thread { val uri = ContactsContract.Data.CONTENT_URI @@ -48,12 +50,15 @@ class ContactsHelper(val activity: BaseSimpleActivity) { 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 contact = Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId, thumbnailUri) + val notes = "" + val contact = Contact(id, firstName, middleName, surname, photoUri, number, emails, addresses, events, accountName, + starred, contactId, thumbnailUri, null, notes) contacts.put(id, contact) } while (cursor.moveToNext()) } @@ -70,11 +75,38 @@ class ContactsHelper(val activity: BaseSimpleActivity) { 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) + if (addOptionalFields) { + 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) + } + } + + activity.dbHelper.getContacts().forEach { + contacts.put(it.id, it) } val contactsSize = contacts.size() @@ -160,28 +192,75 @@ class ContactsHelper(val activity: BaseSimpleActivity) { return emails } - private fun getEvents(contactId: Int): SparseArray> { - val events = SparseArray>() - val uri = ContactsContract.Data.CONTENT_URI + private fun getAddresses(contactId: Int? = null): SparseArray> { + val addresses = SparseArray>() + val uri = CommonDataKinds.StructuredPostal.CONTENT_URI val projection = arrayOf( - CommonDataKinds.Event.START_DATE, - CommonDataKinds.Event.TYPE + ContactsContract.Data.RAW_CONTACT_ID, + CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, + CommonDataKinds.StructuredPostal.TYPE ) - val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?" - val selectionArgs = arrayOf(contactId.toString(), CommonDataKinds.Event.CONTENT_ITEM_TYPE) + + val selection = if (contactId == null) null else "${ContactsContract.Data.RAW_CONTACT_ID} = ?" + val selectionArgs = if (contactId == null) null else arrayOf(contactId.toString()) + var cursor: Cursor? = null try { cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null) if (cursor?.moveToFirst() == true) { do { - val startDate = cursor.getStringValue(CommonDataKinds.Event.START_DATE) - val type = cursor.getIntValue(CommonDataKinds.Event.TYPE) + val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID) + val address = cursor.getStringValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) + val type = cursor.getIntValue(CommonDataKinds.StructuredPostal.TYPE) - if (events[contactId] == null) { - events.put(contactId, ArrayList()) + if (addresses[id] == null) { + addresses.put(id, ArrayList()) } - events[contactId]!!.add(Event(startDate, type)) + addresses[id]!!.add(Address(address, type)) + } while (cursor.moveToNext()) + } + + } catch (e: Exception) { + activity.showErrorToast(e) + } finally { + cursor?.close() + } + + return addresses + } + + private fun getEvents(contactId: Int? = null): SparseArray> { + val events = SparseArray>() + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf( + ContactsContract.Data.RAW_CONTACT_ID, + CommonDataKinds.Event.START_DATE, + CommonDataKinds.Event.TYPE + ) + + var selection = "${ContactsContract.Data.MIMETYPE} = ?" + var selectionArgs = arrayOf(CommonDataKinds.Event.CONTENT_ITEM_TYPE) + + if (contactId != null) { + selection += " AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?" + selectionArgs = arrayOf(CommonDataKinds.Event.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 startDate = cursor.getStringValue(CommonDataKinds.Event.START_DATE) ?: continue + val type = cursor.getIntValue(CommonDataKinds.Event.TYPE) + + if (events[id] == null) { + events.put(id, ArrayList()) + } + + events[id]!!.add(Event(startDate, type)) } while (cursor.moveToNext()) } } catch (e: Exception) { @@ -193,9 +272,46 @@ class ContactsHelper(val activity: BaseSimpleActivity) { return events } - fun getContactWithId(id: Int): Contact? { + private fun getNotes(contactId: Int? = null): SparseArray { + val notes = SparseArray() + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf( + ContactsContract.Data.RAW_CONTACT_ID, + Note.NOTE + ) + + var selection = "${ContactsContract.Data.MIMETYPE} = ?" + var selectionArgs = arrayOf(Note.CONTENT_ITEM_TYPE) + + if (contactId != null) { + selection += " AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?" + selectionArgs = arrayOf(Note.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 note = cursor.getStringValue(CommonDataKinds.Note.NOTE) ?: continue + notes.put(id, note) + } while (cursor.moveToNext()) + } + } catch (e: Exception) { + activity.showErrorToast(e) + } finally { + cursor?.close() + } + + return notes + } + + fun getContactWithId(id: Int, isLocalPrivate: Boolean): Contact? { if (id == 0) { return null + } else if (isLocalPrivate) { + return activity.dbHelper.getContactWithId(id) } val uri = ContactsContract.Data.CONTENT_URI @@ -212,12 +328,14 @@ class ContactsHelper(val activity: BaseSimpleActivity) { val photoUri = cursor.getStringValue(CommonDataKinds.Phone.PHOTO_URI) ?: "" val number = getPhoneNumbers(id)[id] ?: ArrayList() val emails = getEmails(id)[id] ?: ArrayList() + val addresses = getAddresses(id)[id] ?: ArrayList() val events = getEvents(id)[id] ?: ArrayList() + val notes = getNotes(id)[id] ?: "" 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) ?: "" - return Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId, thumbnailUri) + return Contact(id, firstName, middleName, surname, photoUri, number, emails, addresses, events, accountName, starred, contactId, thumbnailUri, null, notes) } } finally { cursor?.close() @@ -321,7 +439,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } fun updateContact(contact: Contact, photoUpdateStatus: Int): Boolean { - return try { + return if (contact.source == SMT_PRIVATE) { + activity.dbHelper.update(contact) + } else try { activity.toast(R.string.updating) val operations = ArrayList() ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply { @@ -372,6 +492,25 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } } + // delete addresses + ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { + val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? " + val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + withSelection(selection, selectionArgs) + operations.add(build()) + } + + // add addresses + contact.addresses.forEach { + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id) + withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + withValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, it.value) + withValue(CommonDataKinds.StructuredPostal.TYPE, it.type) + operations.add(build()) + } + } + // delete events ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? " @@ -391,6 +530,15 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } } + // notes + ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply { + val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?" + val selectionArgs = arrayOf(contact.id.toString(), Note.CONTENT_ITEM_TYPE) + withSelection(selection, selectionArgs) + withValue(Note.NOTE, contact.notes) + operations.add(build()) + } + // favorite try { val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, contact.contactId.toString()) @@ -420,12 +568,12 @@ class ContactsHelper(val activity: BaseSimpleActivity) { val photoUri = Uri.parse(contact.photoUri) val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri) - val thumbnailSize = getThumbnailSize() + val thumbnailSize = activity.getPhotoThumbnailSize() val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false) - val scaledSizePhotoData = bitmapToByteArray(scaledPhoto) + val scaledSizePhotoData = scaledPhoto.getByteArray() scaledPhoto.recycle() - val fullSizePhotoData = bitmapToByteArray(bitmap) + val fullSizePhotoData = bitmap.getByteArray() bitmap.recycle() ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { @@ -495,6 +643,17 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } } + // addresses + contact.addresses.forEach { + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) + withValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, it.value) + withValue(CommonDataKinds.StructuredPostal.TYPE, it.type) + operations.add(build()) + } + } + // events contact.events.forEach { ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { @@ -506,6 +665,14 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } } + // notes + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply { + withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + withValue(ContactsContract.Data.MIMETYPE, Note.CONTENT_ITEM_TYPE) + withValue(Note.NOTE, contact.notes) + operations.add(build()) + } + // photo (inspired by https://gist.github.com/slightfoot/5985900) var fullSizePhotoData: ByteArray? = null var scaledSizePhotoData: ByteArray? @@ -513,11 +680,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) { val photoUri = Uri.parse(contact.photoUri) val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri) - val thumbnailSize = getThumbnailSize() + val thumbnailSize = activity.getPhotoThumbnailSize() val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false) - scaledSizePhotoData = bitmapToByteArray(scaledPhoto) + scaledSizePhotoData = scaledPhoto.getByteArray() - fullSizePhotoData = bitmapToByteArray(bitmap) + fullSizePhotoData = bitmap.getByteArray() scaledPhoto.recycle() bitmap.recycle() @@ -559,9 +726,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } } - private fun insertLocalContact(contact: Contact): Boolean { - return true - } + private fun insertLocalContact(contact: Contact) = activity.dbHelper.insert(contact) private fun addFullSizePhoto(contactId: Long, fullSizePhotoData: ByteArray) { val baseUri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, contactId) @@ -573,17 +738,6 @@ class ContactsHelper(val activity: BaseSimpleActivity) { fileDescriptor.close() } - private fun bitmapToByteArray(bitmap: Bitmap): ByteArray { - var baos: ByteArrayOutputStream? = null - try { - baos = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos) - return baos.toByteArray() - } finally { - baos?.close() - } - } - fun getContactLookupKey(contactId: String): String { val uri = ContactsContract.Data.CONTENT_URI val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.LOOKUP_KEY) @@ -603,24 +757,45 @@ class ContactsHelper(val activity: BaseSimpleActivity) { return "" } - fun addFavorites(ids: ArrayList) { - toggleFavorites(ids, true) + fun getContactDataId(contactId: String): String { + val uri = ContactsContract.Data.CONTENT_URI + val projection = arrayOf(ContactsContract.Data._ID, ContactsContract.Data.RAW_CONTACT_ID, ContactsContract.Data.MIMETYPE) + val selection = "${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?" + val selectionArgs = arrayOf(CommonDataKinds.Email.CONTENT_ITEM_TYPE, contactId) + + var cursor: Cursor? = null + try { + cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null) + if (cursor?.moveToFirst() == true) { + return cursor.getStringValue(ContactsContract.Data._ID) + } + } finally { + cursor?.close() + } + return "" } - fun removeFavorites(ids: ArrayList) { - toggleFavorites(ids, false) + fun addFavorites(contacts: ArrayList) { + toggleLocalFavorites(contacts, true) + toggleFavorites(contacts, true) } - private fun toggleFavorites(ids: ArrayList, areFavorites: Boolean) { + fun removeFavorites(contacts: ArrayList) { + toggleLocalFavorites(contacts, false) + toggleFavorites(contacts, false) + } + + private fun toggleFavorites(contacts: ArrayList, addToFavorites: Boolean) { val applyBatchSize = 100 try { val operations = ArrayList() - ids.forEach { + contacts.filter { it.source != SMT_PRIVATE }.map { it.contactId.toString() }.forEach { val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, it) ContentProviderOperation.newUpdate(uri).apply { - withValue(ContactsContract.Contacts.STARRED, if (areFavorites) 1 else 0) + withValue(ContactsContract.Contacts.STARRED, if (addToFavorites) 1 else 0) operations.add(build()) } + if (operations.size % applyBatchSize == 0) { activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations) operations.clear() @@ -632,14 +807,28 @@ class ContactsHelper(val activity: BaseSimpleActivity) { } } - fun deleteContact(contact: Contact) = deleteContacts(arrayListOf(contact)) + private fun toggleLocalFavorites(contacts: ArrayList, addToFavorites: Boolean) { + val localContacts = contacts.filter { it.source == SMT_PRIVATE }.map { it.id.toString() }.toTypedArray() + activity.dbHelper.toggleFavorites(localContacts, addToFavorites) + } + + fun deleteContact(contact: Contact) { + if (contact.source == SMT_PRIVATE) { + activity.dbHelper.deleteContact(contact.id) + } else { + deleteContacts(arrayListOf(contact)) + } + } fun deleteContacts(contacts: ArrayList) { + 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.RAW_CONTACT_ID} = ?" - contacts.forEach { + contacts.filter { it.source != SMT_PRIVATE }.forEach { ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply { val selectionArgs = arrayOf(it.id.toString()) withSelection(selection, selectionArgs) @@ -653,19 +842,4 @@ class ContactsHelper(val activity: BaseSimpleActivity) { activity.showErrorToast(e) } } - - private fun getThumbnailSize(): Int { - val uri = ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI - val projection = arrayOf(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM) - var cursor: Cursor? = null - try { - cursor = activity.contentResolver.query(uri, projection, null, null, null) - if (cursor?.moveToFirst() == true) { - return cursor.getIntValue(ContactsContract.DisplayPhoto.THUMBNAIL_MAX_DIM) - } - } finally { - cursor?.close() - } - return 0 - } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt new file mode 100644 index 00000000..2703ffa8 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt @@ -0,0 +1,179 @@ +package com.simplemobiletools.contacts.helpers + +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.provider.MediaStore +import android.text.TextUtils +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.simplemobiletools.commons.extensions.getBlobValue +import com.simplemobiletools.commons.extensions.getIntValue +import com.simplemobiletools.commons.extensions.getStringValue +import com.simplemobiletools.contacts.extensions.getByteArray +import com.simplemobiletools.contacts.extensions.getPhotoThumbnailSize +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_FIRST_NAME = "first_name" + private val COL_MIDDLE_NAME = "middle_name" + private val COL_SURNAME = "surname" + private val COL_PHOTO = "photo" + private val COL_PHONE_NUMBERS = "phone_numbers" + private val COL_EMAILS = "emails" + private val COL_EVENTS = "events" + private val COL_STARRED = "starred" + private val COL_ADDRESSES = "addresses" + private val COL_NOTES = "notes" + + private val FIRST_CONTACT_ID = 1000000 + + private val mDb = writableDatabase + + companion object { + private const val DB_VERSION = 2 + const val DB_NAME = "contacts.db" + var dbInstance: DBHelper? = null + + fun newInstance(context: Context): DBHelper { + if (dbInstance == null) + dbInstance = DBHelper(context) + + return dbInstance!! + } + } + + 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)") + + // 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)") + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + if (oldVersion == 1) { + db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_ADDRESSES TEXT DEFAULT ''") + db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_NOTES TEXT DEFAULT ''") + } + } + + fun insert(contact: Contact): Boolean { + val contactValues = fillContactValues(contact) + val id = mDb.insert(CONTACTS_TABLE_NAME, null, contactValues).toInt() + return id != -1 + } + + fun update(contact: Contact): Boolean { + val contactValues = fillContactValues(contact) + val selection = "$COL_ID = ?" + val selectionArgs = arrayOf(contact.id.toString()) + return mDb.update(CONTACTS_TABLE_NAME, contactValues, selection, selectionArgs) == 1 + } + + fun deleteContact(id: Int) = deleteContacts(arrayOf(id.toString())) + + fun deleteContacts(ids: Array) { + val args = TextUtils.join(", ", ids) + val selection = "$CONTACTS_TABLE_NAME.$COL_ID IN ($args)" + mDb.delete(CONTACTS_TABLE_NAME, selection, null) + } + + private fun fillContactValues(contact: Contact): ContentValues { + return ContentValues().apply { + 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_STARRED, contact.starred) + put(COL_NOTES, contact.notes) + + if (contact.photoUri.isNotEmpty()) { + put(COL_PHOTO, getPhotoByteArray(contact.photoUri)) + } else if (contact.photo == null) { + putNull(COL_PHOTO) + } + } + } + + private fun getPhotoByteArray(uri: String): ByteArray { + val photoUri = Uri.parse(uri) + val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, photoUri) + + val thumbnailSize = context.getPhotoThumbnailSize() + val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize * 2, thumbnailSize * 2, false) + val scaledSizePhotoData = scaledPhoto.getByteArray() + scaledPhoto.recycle() + return scaledSizePhotoData + } + + fun toggleFavorites(ids: Array, addToFavorites: Boolean) { + val contactValues = ContentValues() + contactValues.put(COL_STARRED, if (addToFavorites) 1 else 0) + + val args = TextUtils.join(", ", ids) + val selection = "$COL_ID IN ($args)" + mDb.update(CONTACTS_TABLE_NAME, contactValues, selection, null) + } + + fun getContacts(selection: String? = null, selectionArgs: Array? = null): ArrayList { + 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) + 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 firstName = cursor.getStringValue(COL_FIRST_NAME) + val middleName = cursor.getStringValue(COL_MIDDLE_NAME) + val surname = cursor.getStringValue(COL_SURNAME) + + val phoneNumbersJson = cursor.getStringValue(COL_PHONE_NUMBERS) + val phoneNumbersToken = object : TypeToken>() {}.type + val phoneNumbers = 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 addressesJson = cursor.getStringValue(COL_ADDRESSES) + val addressesToken = object : TypeToken>() {}.type + val addresses = 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 photoByteArray = cursor.getBlobValue(COL_PHOTO) ?: null + val photo = if (photoByteArray?.isNotEmpty() == true) { + BitmapFactory.decodeByteArray(photoByteArray, 0, photoByteArray.size) + } else { + null + } + + val notes = cursor.getStringValue(COL_NOTES) + val starred = cursor.getIntValue(COL_STARRED) + + val contact = Contact(id, firstName, middleName, surname, "", phoneNumbers, emails, addresses, events, SMT_PRIVATE, starred, id, "", photo, notes) + contacts.add(contact) + } + } + return contacts + } + + fun getContactWithId(id: Int): Contact? { + val selection = "$COL_ID = ?" + val selectionArgs = arrayOf(id.toString()) + return getContacts(selection, selectionArgs).firstOrNull() + } +} 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 85fa0bec..ec7e443e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt @@ -8,9 +8,11 @@ import android.util.Base64 import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.getFileOutputStream import com.simplemobiletools.commons.extensions.showErrorToast +import com.simplemobiletools.commons.extensions.toFileDirItem import com.simplemobiletools.commons.extensions.writeLn import com.simplemobiletools.contacts.helpers.VcfExporter.ExportResult.* import com.simplemobiletools.contacts.models.Contact +import java.io.BufferedWriter import java.io.ByteArrayOutputStream import java.io.File @@ -26,7 +28,7 @@ class VcfExporter { fun exportContacts(activity: BaseSimpleActivity, file: File, contacts: ArrayList, callback: (result: ExportResult) -> Unit) { try { - activity.getFileOutputStream(file) { + activity.getFileOutputStream(file.toFileDirItem(activity)) { if (it == null) { callback(EXPORT_FAIL) return@getFileOutputStream @@ -48,31 +50,29 @@ class VcfExporter { out.writeLn("$EMAIL$delimiterType:${it.value}") } + contact.addresses.forEach { + val type = getAddressTypeLabel(it.type) + val delimiterType = if (type.isEmpty()) "" else ";$type" + out.writeLn("$ADR$delimiterType:;;${it.value};;;;") + } + contact.events.forEach { if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) { out.writeLn("$BDAY${it.value}") } } + if (contact.notes.isNotEmpty()) { + out.writeLn("$NOTE${contact.notes.replace("\n", "\\n")}") + } + if (contact.thumbnailUri.isNotEmpty()) { - val firstLine = "$PHOTO;$ENCODING=$BASE64;$JPEG:" val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, Uri.parse(contact.thumbnailUri)) - val byteArrayOutputStream = ByteArrayOutputStream() - bitmap.compress(Bitmap.CompressFormat.JPEG, 85, byteArrayOutputStream) - bitmap.recycle() - val byteArray = byteArrayOutputStream.toByteArray() - val encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP) + addBitmap(bitmap, out) + } - val encodedFirstLineSection = encoded.substring(0, ENCODED_PHOTO_LINE_LENGTH - firstLine.length) - out.writeLn(firstLine + encodedFirstLineSection) - var curStartIndex = encodedFirstLineSection.length - do { - val part = encoded.substring(curStartIndex, Math.min(curStartIndex + ENCODED_PHOTO_LINE_LENGTH - 1, encoded.length)) - out.writeLn(" $part") - curStartIndex += ENCODED_PHOTO_LINE_LENGTH - 1 - } while (curStartIndex < encoded.length) - - out.writeLn("") + if (contact.photo != null) { + addBitmap(contact.photo!!, out) } out.writeLn(END_VCARD) @@ -91,6 +91,26 @@ class VcfExporter { }) } + private fun addBitmap(bitmap: Bitmap, out: BufferedWriter) { + val firstLine = "$PHOTO;$ENCODING=$BASE64;$JPEG:" + val byteArrayOutputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.JPEG, 85, byteArrayOutputStream) + bitmap.recycle() + val byteArray = byteArrayOutputStream.toByteArray() + val encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP) + + val encodedFirstLineSection = encoded.substring(0, ENCODED_PHOTO_LINE_LENGTH - firstLine.length) + out.writeLn(firstLine + encodedFirstLineSection) + var curStartIndex = encodedFirstLineSection.length + do { + val part = encoded.substring(curStartIndex, Math.min(curStartIndex + ENCODED_PHOTO_LINE_LENGTH - 1, encoded.length)) + out.writeLn(" $part") + curStartIndex += ENCODED_PHOTO_LINE_LENGTH - 1 + } while (curStartIndex < encoded.length) + + out.writeLn("") + } + private fun getNames(contact: Contact): String { var result = "" var firstName = contact.firstName @@ -126,4 +146,10 @@ class VcfExporter { CommonDataKinds.Email.TYPE_MOBILE -> MOBILE else -> "" } + + private fun getAddressTypeLabel(type: Int) = when (type) { + CommonDataKinds.StructuredPostal.TYPE_HOME -> HOME + CommonDataKinds.StructuredPostal.TYPE_WORK -> WORK + else -> "" + } } 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 5d991428..e644d5eb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt @@ -10,10 +10,7 @@ import com.simplemobiletools.contacts.activities.SimpleActivity import com.simplemobiletools.contacts.extensions.getCachePhoto import com.simplemobiletools.contacts.extensions.getCachePhotoUri import com.simplemobiletools.contacts.helpers.VcfImporter.ImportResult.* -import com.simplemobiletools.contacts.models.Contact -import com.simplemobiletools.contacts.models.Email -import com.simplemobiletools.contacts.models.Event -import com.simplemobiletools.contacts.models.PhoneNumber +import com.simplemobiletools.contacts.models.* import java.io.File import java.io.FileOutputStream @@ -26,9 +23,11 @@ class VcfImporter(val activity: SimpleActivity) { private var curMiddleName = "" private var curSurname = "" private var curPhotoUri = "" + private var curNotes = "" private var curPhoneNumbers = ArrayList() private var curEmails = ArrayList() private var curEvents = ArrayList() + private var curAddresses = ArrayList
() private var isGettingPhoto = false private var currentPhotoString = StringBuilder() @@ -38,6 +37,9 @@ class VcfImporter(val activity: SimpleActivity) { private var currentNameIsANSI = false private var currentNameString = StringBuilder() + private var isGettingNotes = false + private var currentNotesSB = StringBuilder() + private var contactsImported = 0 private var contactsFailed = 0 @@ -62,13 +64,22 @@ class VcfImporter(val activity: SimpleActivity) { currentNameString.append(line.trimStart('\t')) isGettingName = false parseNames() + } else if (isGettingNotes) { + if (line.startsWith(' ')) { + currentNotesSB.append(line.substring(1)) + } else { + curNotes = currentNotesSB.toString().replace("\\n", "\n").replace("\\,", ",") + isGettingNotes = false + } } when { line.toUpperCase() == BEGIN_VCARD -> resetValues() + line.toUpperCase().startsWith(NOTE) -> addNotes(line.substring(NOTE.length)) line.toUpperCase().startsWith(N) -> addNames(line.substring(N.length)) line.toUpperCase().startsWith(TEL) -> addPhoneNumber(line.substring(TEL.length)) line.toUpperCase().startsWith(EMAIL) -> addEmail(line.substring(EMAIL.length)) + line.toUpperCase().startsWith(ADR) -> addAddress(line.substring(ADR.length)) 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)) @@ -156,6 +167,25 @@ class VcfImporter(val activity: SimpleActivity) { else -> CommonDataKinds.Email.TYPE_OTHER } + private fun addAddress(address: String) { + val addressParts = address.trimStart(';').split(":") + var rawType = addressParts[0] + if (rawType.contains('=')) { + rawType = rawType.split('=').last() + } + val type = getAddressTypeId(rawType.toUpperCase()) + val addresses = addressParts[1].split(";") + if (addresses.size == 7) { + curAddresses.add(Address(addresses[2], type)) + } + } + + private fun getAddressTypeId(type: String) = when (type) { + HOME -> CommonDataKinds.Email.TYPE_HOME + WORK -> CommonDataKinds.Email.TYPE_WORK + else -> CommonDataKinds.Email.TYPE_OTHER + } + private fun addBirthday(birthday: String) { curEvents.add(Event(birthday, CommonDataKinds.Event.TYPE_BIRTHDAY)) } @@ -198,8 +228,13 @@ class VcfImporter(val activity: SimpleActivity) { curPhotoUri = activity.getCachePhotoUri(file).toString() } + private fun addNotes(notes: String) { + currentNotesSB.append(notes) + isGettingNotes = true + } + private fun saveContact(source: String) { - val contact = Contact(0, curFirstName, curMiddleName, curSurname, curPhotoUri, curPhoneNumbers, curEmails, curEvents, source, 0, 0, "") + val contact = Contact(0, curFirstName, curMiddleName, curSurname, curPhotoUri, curPhoneNumbers, curEmails, curAddresses, curEvents, source, 0, 0, "", null, curNotes) if (ContactsHelper(activity).insertContact(contact)) { contactsImported++ } @@ -210,9 +245,11 @@ class VcfImporter(val activity: SimpleActivity) { curMiddleName = "" curSurname = "" curPhotoUri = "" + curNotes = "" curPhoneNumbers = ArrayList() curEmails = ArrayList() curEvents = ArrayList() + curAddresses = ArrayList() isGettingPhoto = false currentPhotoString = StringBuilder() @@ -221,5 +258,8 @@ class VcfImporter(val activity: SimpleActivity) { isGettingName = false currentNameIsANSI = false currentNameString = StringBuilder() + + isGettingNotes = false + currentNotesSB = StringBuilder() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Address.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Address.kt new file mode 100644 index 00000000..7571a228 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Address.kt @@ -0,0 +1,3 @@ +package com.simplemobiletools.contacts.models + +data class Address(var value: String, var type: Int) 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 b41e6e6b..498e1a52 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt @@ -1,12 +1,13 @@ package com.simplemobiletools.contacts.models +import android.graphics.Bitmap 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, - var phoneNumbers: ArrayList, var emails: ArrayList, var events: ArrayList, var source: String, - var starred: Int, val contactId: Int, val thumbnailUri: String) : Comparable { + 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) : Comparable { companion object { var sorting = 0 } diff --git a/app/src/main/res/layout/activity_edit_contact.xml b/app/src/main/res/layout/activity_edit_contact.xml index 6684ce6d..b5c8cc52 100644 --- a/app/src/main/res/layout/activity_edit_contact.xml +++ b/app/src/main/res/layout/activity_edit_contact.xml @@ -213,6 +213,44 @@ android:paddingTop="@dimen/medium_margin" android:src="@drawable/ic_plus"/> + + + + + + + + + + @@ -251,6 +289,30 @@ android:paddingTop="@dimen/medium_margin" android:src="@drawable/ic_plus"/> + + + + - - - - + android:paddingLeft="@dimen/small_margin"/> + android:paddingLeft="@dimen/small_margin"/> - + - + + android:orientation="vertical"/> - + - + + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/> diff --git a/app/src/main/res/layout/dialog_filter_contact_sources.xml b/app/src/main/res/layout/dialog_filter_contact_sources.xml index 4264f88b..432d7ff3 100644 --- a/app/src/main/res/layout/dialog_filter_contact_sources.xml +++ b/app/src/main/res/layout/dialog_filter_contact_sources.xml @@ -8,4 +8,4 @@ android:clipToPadding="false" android:overScrollMode="never" android:paddingTop="@dimen/medium_margin" - app:layoutManager="android.support.v7.widget.LinearLayoutManager"/> + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/> diff --git a/app/src/main/res/layout/fragment_layout.xml b/app/src/main/res/layout/fragment_layout.xml index 029e3861..55e0a9ae 100644 --- a/app/src/main/res/layout/fragment_layout.xml +++ b/app/src/main/res/layout/fragment_layout.xml @@ -40,7 +40,7 @@ android:layout_height="match_parent" android:clipToPadding="false" android:scrollbars="none" - app:layoutManager="android.support.v7.widget.LinearLayoutManager"/> + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/> + + + + + + + diff --git a/app/src/main/res/layout/item_view_address.xml b/app/src/main/res/layout/item_view_address.xml new file mode 100644 index 00000000..fce8cbf1 --- /dev/null +++ b/app/src/main/res/layout/item_view_address.xml @@ -0,0 +1,37 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_view_email.xml b/app/src/main/res/layout/item_view_email.xml index 8c4a2401..9de6e320 100644 --- a/app/src/main/res/layout/item_view_email.xml +++ b/app/src/main/res/layout/item_view_email.xml @@ -28,7 +28,6 @@ android:layout_alignParentRight="true" android:layout_alignTop="@+id/contact_email" android:layout_centerVertical="true" - android:background="?attr/selectableItemBackground" android:gravity="center" android:paddingLeft="@dimen/medium_margin" android:paddingRight="@dimen/medium_margin" diff --git a/app/src/main/res/layout/item_view_phone_number.xml b/app/src/main/res/layout/item_view_phone_number.xml index 8fca1a77..d37bf04e 100644 --- a/app/src/main/res/layout/item_view_phone_number.xml +++ b/app/src/main/res/layout/item_view_phone_number.xml @@ -28,7 +28,6 @@ android:layout_alignParentRight="true" android:layout_alignTop="@+id/contact_number" android:layout_centerVertical="true" - android:background="?attr/selectableItemBackground" android:gravity="center" android:paddingLeft="@dimen/medium_margin" android:paddingRight="@dimen/medium_margin" diff --git a/app/src/main/res/layout/layout_select_contact.xml b/app/src/main/res/layout/layout_select_contact.xml index f904d1d1..d2dde128 100644 --- a/app/src/main/res/layout/layout_select_contact.xml +++ b/app/src/main/res/layout/layout_select_contact.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content" android:clipToPadding="false" android:scrollbars="none" - app:layoutManager="android.support.v7.widget.LinearLayoutManager"/> + app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/> + + + + diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 73c90220..2d9fa6a1 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -4,8 +4,8 @@ Adress Lägger till… Uppdaterar… - Phone storage - Phone storage (not visible by other apps) + Telefonens lagringsutrymme + Telefonens lagringsutrymme (inte synligt för andra appar) Ny kontakt Redigera kontakt @@ -23,10 +23,10 @@ Visa efternamn först Visa telefonnummer i huvudvyn - Show contact thumbnails - On contact click - Call contact - View contact details + Visa kontaktminiatyrer + Vid kontakttryckning + Ring kontakt + Visa kontaktuppgifter E-post diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml new file mode 100644 index 00000000..1943508d --- /dev/null +++ b/app/src/main/res/values/donottranslate.xml @@ -0,0 +1,8 @@ + + + + + Added Address and Notes fields + Allow storing contacts in a local database, hidden from other apps + + diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml new file mode 100644 index 00000000..11ccda4a --- /dev/null +++ b/app/src/main/res/values/integers.xml @@ -0,0 +1,3 @@ + + 128 +