Merge pull request #2 from SimpleMobileTools/master

update
This commit is contained in:
solokot 2018-01-28 10:17:43 +03:00 committed by GitHub
commit b0b6d48f2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1151 additions and 226 deletions

View File

@ -1,6 +1,23 @@
Changelog
==========
Version 3.1.2 *(2018-01-23)*
----------------------------
* Properly handle vcf files exported from Thunderbird
* Misc smaller improvements
Version 3.1.1 *(2018-01-22)*
----------------------------
* An f-droid build test version
Version 3.1.0 *(2018-01-16)*
----------------------------
* Added contact import/export functions
* Allow sharing contacts
Version 3.0.3 *(2018-01-03)*
----------------------------

View File

@ -8,16 +8,19 @@ You can use it for managing user emails and events too. It has the ability to so
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.contacts'><img src='http://simplemobiletools.github.io/assets/public/google-play.png' alt='Get it on Google Play' height='45' /></a>
<a href='https://f-droid.org/app/com.simplemobiletools.contacts'><img src='http://simplemobiletools.github.io/assets/public/f-droid.png' alt='Get it on F-Droid' height='45' /></a>
<img alt="App image" src="screenshots/app.png" width="250" />
<img alt="App image" src="screenshots/app_2.png" width="250" />
<img alt="App image" src="screenshots/app_3.png" width="250" />
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.contacts'><img src='http://simplemobiletools.github.io/assets/public/google-play.png' alt='Get it on Google Play' height='45' /></a>
<a href='https://f-droid.org/packages/com.simplemobiletools.contacts'><img src='http://simplemobiletools.github.io/assets/public/f-droid.png' alt='Get it on F-Droid' height='45' /></a>
<div style="display:flex;">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app.png" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_2.png" width="30%">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app_3.png" width="30%">
</div>
License
-------
Copyright 2017 SimpleMobileTools
Copyright 2017-present SimpleMobileTools
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -4,13 +4,14 @@ apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
buildToolsVersion "27.0.3"
defaultConfig {
applicationId "com.simplemobiletools.contacts"
minSdkVersion 16
targetSdkVersion 27
versionCode 4
versionName "3.0.3"
versionCode 7
versionName "3.1.2"
setProperty("archivesBaseName", "contacts")
}
@ -41,11 +42,11 @@ ext {
}
dependencies {
implementation 'com.simplemobiletools:commons:3.5.7'
implementation 'com.simplemobiletools:commons:3.8.2'
implementation 'joda-time:joda-time:2.9.9'
//debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
//releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
}
Properties props = new Properties()

View File

@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission
android:name="android.permission.USE_FINGERPRINT"
@ -40,6 +41,16 @@
<intent-filter>
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:mimeType="text/directory"/>
<data android:mimeType="text/vcard"/>
<data android:mimeType="text/x-vcard"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity

View File

@ -2,16 +2,17 @@ package com.simplemobiletools.contacts
import android.app.Application
import com.simplemobiletools.commons.extensions.checkUseEnglish
import com.squareup.leakcanary.LeakCanary
class App : Application() {
override fun onCreate() {
super.onCreate()
/*if (BuildConfig.DEBUG) {
if (BuildConfig.DEBUG) {
if (LeakCanary.isInAnalyzerProcess(this)) {
return
}
LeakCanary.install(this)
}*/
}
checkUseEnglish()
}

View File

@ -8,8 +8,8 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
import android.provider.MediaStore
import android.support.v4.content.FileProvider
import android.view.Menu
import android.view.MenuItem
import android.view.ViewGroup
@ -31,7 +31,6 @@ import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CONTACTS
import com.simplemobiletools.commons.helpers.getDateFormats
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.contacts.BuildConfig
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.extensions.*
import com.simplemobiletools.contacts.helpers.*
@ -45,16 +44,11 @@ import kotlinx.android.synthetic.main.item_event.view.*
import kotlinx.android.synthetic.main.item_phone_number.view.*
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class ContactActivity : SimpleActivity() {
private val DEFAULT_EMAIL_TYPE = ContactsContract.CommonDataKinds.Email.TYPE_HOME
private val DEFAULT_PHONE_NUMBER_TYPE = ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
private val DEFAULT_EVENT_TYPE = ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY
private val INTENT_TAKE_PHOTO = 1
private val INTENT_CHOOSE_PHOTO = 2
private val INTENT_CROP_PHOTO = 3
@ -93,6 +87,35 @@ class ContactActivity : SimpleActivity() {
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_contact, menu)
if (wasActivityInitialized) {
menu.findItem(R.id.delete).isVisible = contact?.id != 0
menu.findItem(R.id.share).isVisible = contact?.id != 0
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save -> saveContact()
R.id.delete -> deleteContact()
R.id.share -> shareContact()
else -> return super.onOptionsItemSelected(item)
}
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (resultCode == RESULT_OK) {
when (requestCode) {
INTENT_TAKE_PHOTO, INTENT_CHOOSE_PHOTO -> startCropPhotoIntent(lastPhotoIntentUri!!)
INTENT_CROP_PHOTO -> updateContactPhoto(lastPhotoIntentUri.toString())
}
}
}
private fun initContact() {
var contactId = intent.getIntExtra(CONTACT_ID, 0)
val action = intent.action
@ -179,33 +202,6 @@ class ContactActivity : SimpleActivity() {
invalidateOptionsMenu()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_contact, menu)
if (wasActivityInitialized) {
menu.findItem(R.id.delete).isVisible = contact?.id != 0
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save -> saveContact()
R.id.delete -> deleteContact()
else -> return super.onOptionsItemSelected(item)
}
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (resultCode == RESULT_OK) {
when (requestCode) {
INTENT_TAKE_PHOTO, INTENT_CHOOSE_PHOTO -> startCropPhotoIntent(lastPhotoIntentUri!!)
INTENT_CROP_PHOTO -> updateContactPhoto(lastPhotoIntentUri.toString())
}
}
}
private fun startCropPhotoIntent(uri: Uri) {
lastPhotoIntentUri = getCachePhotoUri()
Intent("com.android.camera.action.CROP").apply {
@ -306,9 +302,17 @@ class ContactActivity : SimpleActivity() {
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(), "", 0, 0)
contact = Contact(0, "", "", "", "", ArrayList(), ArrayList(), ArrayList(), "", 0, 0, "")
contact_source.text = config.lastUsedContactSource
contact_source.setOnClickListener { showAccountSourcePicker() }
contact_source.setOnClickListener {
showContactSourcePicker(contact_source.value) {
contact_source.text = it
}
}
}
private fun shareContact() {
shareContacts(arrayListOf(contact!!))
}
private fun showPhotoPlaceholder() {
@ -453,14 +457,14 @@ class ContactActivity : SimpleActivity() {
private fun showNumberTypePicker(numberTypeField: TextView) {
val items = arrayListOf(
RadioItem(ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE, getString(R.string.mobile)),
RadioItem(ContactsContract.CommonDataKinds.Phone.TYPE_HOME, getString(R.string.home)),
RadioItem(ContactsContract.CommonDataKinds.Phone.TYPE_WORK, getString(R.string.work)),
RadioItem(ContactsContract.CommonDataKinds.Phone.TYPE_MAIN, getString(R.string.main_number)),
RadioItem(ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK, getString(R.string.work_fax)),
RadioItem(ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME, getString(R.string.home_fax)),
RadioItem(ContactsContract.CommonDataKinds.Phone.TYPE_PAGER, getString(R.string.pager)),
RadioItem(ContactsContract.CommonDataKinds.Phone.TYPE_OTHER, getString(R.string.other))
RadioItem(CommonDataKinds.Phone.TYPE_MOBILE, getString(R.string.mobile)),
RadioItem(CommonDataKinds.Phone.TYPE_HOME, getString(R.string.home)),
RadioItem(CommonDataKinds.Phone.TYPE_WORK, getString(R.string.work)),
RadioItem(CommonDataKinds.Phone.TYPE_MAIN, getString(R.string.main_number)),
RadioItem(CommonDataKinds.Phone.TYPE_FAX_WORK, getString(R.string.work_fax)),
RadioItem(CommonDataKinds.Phone.TYPE_FAX_HOME, getString(R.string.home_fax)),
RadioItem(CommonDataKinds.Phone.TYPE_PAGER, getString(R.string.pager)),
RadioItem(CommonDataKinds.Phone.TYPE_OTHER, getString(R.string.other))
)
val currentNumberTypeId = getPhoneNumberTypeId(numberTypeField.value)
@ -471,10 +475,10 @@ class ContactActivity : SimpleActivity() {
private fun showEmailTypePicker(emailTypeField: TextView) {
val items = arrayListOf(
RadioItem(ContactsContract.CommonDataKinds.Email.TYPE_HOME, getString(R.string.home)),
RadioItem(ContactsContract.CommonDataKinds.Email.TYPE_WORK, getString(R.string.work)),
RadioItem(ContactsContract.CommonDataKinds.Email.TYPE_MOBILE, getString(R.string.mobile)),
RadioItem(ContactsContract.CommonDataKinds.Email.TYPE_OTHER, getString(R.string.other))
RadioItem(CommonDataKinds.Email.TYPE_HOME, getString(R.string.home)),
RadioItem(CommonDataKinds.Email.TYPE_WORK, getString(R.string.work)),
RadioItem(CommonDataKinds.Email.TYPE_MOBILE, getString(R.string.mobile)),
RadioItem(CommonDataKinds.Email.TYPE_OTHER, getString(R.string.other))
)
val currentEmailTypeId = getEmailTypeId(emailTypeField.value)
@ -485,9 +489,9 @@ class ContactActivity : SimpleActivity() {
private fun showEventTypePicker(eventTypeField: TextView) {
val items = arrayListOf(
RadioItem(ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY, getString(R.string.birthday)),
RadioItem(ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY, getString(R.string.anniversary)),
RadioItem(ContactsContract.CommonDataKinds.Event.TYPE_OTHER, getString(R.string.other))
RadioItem(CommonDataKinds.Event.TYPE_BIRTHDAY, getString(R.string.birthday)),
RadioItem(CommonDataKinds.Event.TYPE_ANNIVERSARY, getString(R.string.anniversary)),
RadioItem(CommonDataKinds.Event.TYPE_OTHER, getString(R.string.other))
)
val currentEventTypeId = getEventTypeId(eventTypeField.value)
@ -579,6 +583,7 @@ class ContactActivity : SimpleActivity() {
private fun insertNewContact() {
isSaving = true
toast(R.string.inserting)
if (ContactsHelper(this@ContactActivity).insertContact(contact!!)) {
finish()
} else {
@ -643,27 +648,6 @@ class ContactActivity : SimpleActivity() {
}
}
private fun showAccountSourcePicker() {
ContactsHelper(this).getContactSources {
val items = ArrayList<RadioItem>()
val sources = it
val currentSource = contact_source.value
var currentSourceIndex = -1
sources.forEachIndexed { index, account ->
items.add(RadioItem(index, account))
if (account == currentSource) {
currentSourceIndex = index
}
}
runOnUiThread {
RadioGroupDialog(this, items, currentSourceIndex) {
contact_source.text = sources[it as Int]
}
}
}
}
private fun toggleFavorite() {
val isStarred = isContactStarred()
contact_toggle_favorite.apply {
@ -757,62 +741,51 @@ class ContactActivity : SimpleActivity() {
}
}
private fun getCachePhotoUri(): Uri {
val imagesFolder = File(cacheDir, "my_cache")
if (!imagesFolder.exists()) {
imagesFolder.mkdirs()
}
val file = File(imagesFolder, "Photo_${System.currentTimeMillis()}.jpg")
file.createNewFile()
return FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", file)
}
private fun getPhoneNumberTextId(type: Int) = when (type) {
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE -> R.string.mobile
ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> R.string.home
ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> R.string.work
ContactsContract.CommonDataKinds.Phone.TYPE_MAIN -> R.string.main_number
ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK -> R.string.work_fax
ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME -> R.string.home_fax
ContactsContract.CommonDataKinds.Phone.TYPE_PAGER -> R.string.pager
CommonDataKinds.Phone.TYPE_MOBILE -> R.string.mobile
CommonDataKinds.Phone.TYPE_HOME -> R.string.home
CommonDataKinds.Phone.TYPE_WORK -> R.string.work
CommonDataKinds.Phone.TYPE_MAIN -> R.string.main_number
CommonDataKinds.Phone.TYPE_FAX_WORK -> R.string.work_fax
CommonDataKinds.Phone.TYPE_FAX_HOME -> R.string.home_fax
CommonDataKinds.Phone.TYPE_PAGER -> R.string.pager
else -> R.string.other
}
private fun getPhoneNumberTypeId(value: String) = when (value) {
getString(R.string.mobile) -> ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE
getString(R.string.home) -> ContactsContract.CommonDataKinds.Phone.TYPE_HOME
getString(R.string.work) -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK
getString(R.string.main_number) -> ContactsContract.CommonDataKinds.Phone.TYPE_MAIN
getString(R.string.work_fax) -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK
getString(R.string.home_fax) -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME
getString(R.string.pager) -> ContactsContract.CommonDataKinds.Phone.TYPE_PAGER
else -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER
getString(R.string.mobile) -> CommonDataKinds.Phone.TYPE_MOBILE
getString(R.string.home) -> CommonDataKinds.Phone.TYPE_HOME
getString(R.string.work) -> CommonDataKinds.Phone.TYPE_WORK
getString(R.string.main_number) -> CommonDataKinds.Phone.TYPE_MAIN
getString(R.string.work_fax) -> CommonDataKinds.Phone.TYPE_FAX_WORK
getString(R.string.home_fax) -> CommonDataKinds.Phone.TYPE_FAX_HOME
getString(R.string.pager) -> CommonDataKinds.Phone.TYPE_PAGER
else -> CommonDataKinds.Phone.TYPE_OTHER
}
private fun getEmailTextId(type: Int) = when (type) {
ContactsContract.CommonDataKinds.Email.TYPE_HOME -> R.string.home
ContactsContract.CommonDataKinds.Email.TYPE_WORK -> R.string.work
ContactsContract.CommonDataKinds.Email.TYPE_MOBILE -> R.string.mobile
CommonDataKinds.Email.TYPE_HOME -> R.string.home
CommonDataKinds.Email.TYPE_WORK -> R.string.work
CommonDataKinds.Email.TYPE_MOBILE -> R.string.mobile
else -> R.string.other
}
private fun getEmailTypeId(value: String) = when (value) {
getString(R.string.home) -> ContactsContract.CommonDataKinds.Email.TYPE_HOME
getString(R.string.work) -> ContactsContract.CommonDataKinds.Email.TYPE_WORK
getString(R.string.mobile) -> ContactsContract.CommonDataKinds.Email.TYPE_MOBILE
else -> ContactsContract.CommonDataKinds.Email.TYPE_OTHER
getString(R.string.home) -> CommonDataKinds.Email.TYPE_HOME
getString(R.string.work) -> CommonDataKinds.Email.TYPE_WORK
getString(R.string.mobile) -> CommonDataKinds.Email.TYPE_MOBILE
else -> CommonDataKinds.Email.TYPE_OTHER
}
private fun getEventTextId(type: Int) = when (type) {
ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY -> R.string.birthday
ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY -> R.string.anniversary
CommonDataKinds.Event.TYPE_BIRTHDAY -> R.string.birthday
CommonDataKinds.Event.TYPE_ANNIVERSARY -> R.string.anniversary
else -> R.string.other
}
private fun getEventTypeId(value: String) = when (value) {
getString(R.string.birthday) -> ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY
getString(R.string.anniversary) -> ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY
else -> ContactsContract.CommonDataKinds.Event.TYPE_OTHER
getString(R.string.birthday) -> CommonDataKinds.Event.TYPE_BIRTHDAY
getString(R.string.anniversary) -> CommonDataKinds.Event.TYPE_ANNIVERSARY
else -> CommonDataKinds.Event.TYPE_OTHER
}
}

View File

@ -4,25 +4,34 @@ import android.app.SearchManager
import android.content.Context
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.support.v4.view.MenuItemCompat
import android.support.v4.view.ViewPager
import android.support.v7.widget.SearchView
import android.view.Menu
import android.view.MenuItem
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.contacts.BuildConfig
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.adapters.ViewPagerAdapter
import com.simplemobiletools.contacts.dialogs.ChangeSortingDialog
import com.simplemobiletools.contacts.dialogs.ExportContactsDialog
import com.simplemobiletools.contacts.dialogs.FilterContactSourcesDialog
import com.simplemobiletools.contacts.dialogs.ImportContactsDialog
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.getTempFile
import com.simplemobiletools.contacts.extensions.onTabSelectionChanged
import com.simplemobiletools.contacts.helpers.ContactsHelper
import com.simplemobiletools.contacts.helpers.VcfExporter
import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
import com.simplemobiletools.contacts.models.Contact
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_contacts.*
import kotlinx.android.synthetic.main.fragment_favorites.*
import java.io.FileOutputStream
class MainActivity : SimpleActivity(), RefreshContactsListener {
private var isFirstResume = true
@ -102,10 +111,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
if (viewpager.adapter == null) {
initFragments()
}
contacts_fragment.initContacts()
contacts_fragment.onActivityResume()
favorites_fragment.initContacts()
favorites_fragment.onActivityResume()
contacts_fragment?.initContacts()
contacts_fragment?.onActivityResume()
favorites_fragment?.initContacts()
favorites_fragment?.onActivityResume()
}
isFirstResume = false
}
@ -130,6 +139,8 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
when (item.itemId) {
R.id.sort -> showSortingDialog()
R.id.filter -> showFilterDialog()
R.id.import_contacts -> tryImportContacts()
R.id.export_contacts -> tryExportContacts()
R.id.settings -> startActivity(Intent(applicationContext, SettingsActivity::class.java))
R.id.about -> launchAbout()
else -> return super.onOptionsItemSelected(item)
@ -156,9 +167,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
isSubmitButtonEnabled = false
queryHint = getString(if (viewpager.currentItem == 0) R.string.search_contacts else R.string.search_favorites)
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
override fun onQueryTextSubmit(query: String) = false
override fun onQueryTextChange(newText: String): Boolean {
if (isSearchOpen) {
@ -232,6 +241,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
it.icon?.applyColorFilter(getAdjustedPrimaryColor())
}
)
if (intent?.action == Intent.ACTION_VIEW && intent.data != null) {
tryImportContactsFromFile(intent.data)
}
}
private fun showSortingDialog() {
@ -243,10 +256,86 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
fun showFilterDialog() {
FilterContactSourcesDialog(this) {
contacts_fragment.forceListRedraw = true
contacts_fragment.initContacts()
}
}
private fun tryImportContacts() {
handlePermission(PERMISSION_READ_STORAGE) {
if (it) {
importContacts()
}
}
}
private fun importContacts() {
FilePickerDialog(this) {
showImportContactsDialog(it)
}
}
private fun showImportContactsDialog(path: String) {
ImportContactsDialog(this, path) {
if (it) {
runOnUiThread {
refreshContacts()
}
}
}
}
private fun tryImportContactsFromFile(uri: Uri) {
when {
uri.scheme == "file" -> showImportContactsDialog(uri.path)
uri.scheme == "content" -> {
val tempFile = getTempFile()
if (tempFile == null) {
toast(R.string.unknown_error_occurred)
return
}
val inputStream = contentResolver.openInputStream(uri)
val out = FileOutputStream(tempFile)
inputStream.copyTo(out)
showImportContactsDialog(tempFile.absolutePath)
}
else -> toast(R.string.invalid_file_format)
}
}
private fun tryExportContacts() {
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
exportContacts()
}
}
}
private fun exportContacts() {
FilePickerDialog(this, pickFile = false, showFAB = true) {
ExportContactsDialog(this, it) { file, contactSources ->
Thread {
ContactsHelper(this).getContacts {
val contacts = it.filter { contactSources.contains(it.source) }
if (contacts.isEmpty()) {
toast(R.string.no_entries_for_exporting)
} else {
toast(R.string.exporting)
VcfExporter().exportContacts(this, file, contacts as ArrayList<Contact>) {
toast(when (it) {
VcfExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
VcfExporter.ExportResult.EXPORT_PARTIAL -> R.string.exporting_some_entries_failed
else -> R.string.exporting_failed
})
}
}
}
}.start()
}
}
}
private fun launchAbout() {
startAboutActivity(R.string.app_name, LICENSE_KOTLIN or LICENSE_MULTISELECT or LICENSE_JODA or LICENSE_GLIDE, BuildConfig.VERSION_NAME)
}

View File

@ -13,11 +13,13 @@ import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.getColoredDrawableWithColor
import com.simplemobiletools.commons.extensions.isActivityDestroyed
import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.activities.SimpleActivity
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.openContact
import com.simplemobiletools.contacts.extensions.shareContacts
import com.simplemobiletools.contacts.helpers.ContactsHelper
import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
import com.simplemobiletools.contacts.models.Contact
@ -25,8 +27,8 @@ import kotlinx.android.synthetic.main.item_contact_with_number.view.*
import java.util.*
class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList<Contact>, val listener: RefreshContactsListener?,
val isFavoritesFragment: Boolean, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) :
MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
val isFavoritesFragment: Boolean, recyclerView: MyRecyclerView, fastScroller: FastScroller, itemClick: (Any) -> Unit) :
MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
lateinit private var contactDrawable: Drawable
var config = activity.config
@ -61,9 +63,10 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList<Co
when (id) {
R.id.cab_edit -> editContact()
R.id.cab_select_all -> selectAll()
R.id.cab_delete -> askConfirmDelete()
R.id.cab_add_to_favorites -> addToFavorites()
R.id.cab_share -> shareContacts()
R.id.cab_remove -> removeFavorites()
R.id.cab_delete -> askConfirmDelete()
}
}
@ -160,10 +163,23 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: MutableList<Co
finishActMode()
}
private fun shareContacts() {
if (selectedPositions.isEmpty()) {
return
}
val contacts = ArrayList<Contact>()
selectedPositions.forEach {
contacts.add(contactItems[it])
}
activity.shareContacts(contacts)
}
override fun onViewRecycled(holder: ViewHolder?) {
super.onViewRecycled(holder)
if (!activity.isActivityDestroyed()) {
Glide.with(activity).clear(holder?.itemView?.contact_tmb)
Glide.with(activity).clear(holder?.itemView?.contact_tmb!!)
}
}

View File

@ -124,6 +124,6 @@ class SelectContactsAdapter(val activity: SimpleActivity, val contacts: List<Con
override fun onViewRecycled(holder: ViewHolder?) {
super.onViewRecycled(holder)
Glide.with(activity).clear(holder?.itemView?.contact_tmb)
Glide.with(activity).clear(holder?.itemView?.contact_tmb!!)
}
}

View File

@ -0,0 +1,54 @@
package com.simplemobiletools.contacts.dialogs
import android.support.v7.app.AlertDialog
import android.view.ViewGroup
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.contacts.R
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 kotlinx.android.synthetic.main.dialog_export_contacts.view.*
import java.io.File
class ExportContactsDialog(val activity: SimpleActivity, val path: String, val callback: (file: File, contactSources: HashSet<String>) -> Unit) {
init {
val view = (activity.layoutInflater.inflate(R.layout.dialog_export_contacts, null) as ViewGroup).apply {
export_contacts_folder.text = activity.humanizePath(path)
export_contacts_filename.setText("contacts_${System.currentTimeMillis() / 1000}")
ContactsHelper(activity).getContactSources {
activity.runOnUiThread {
export_contacts_list.adapter = FilterContactSourcesAdapter(activity, it, activity.config.displayContactSources)
}
}
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.export_contacts) {
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val filename = view.export_contacts_filename.value
when {
filename.isEmpty() -> activity.toast(R.string.empty_name)
filename.isAValidFilename() -> {
val file = File(path, "$filename.vcf")
if (file.exists()) {
activity.toast(R.string.name_taken)
return@setOnClickListener
}
val contactSources = (view.export_contacts_list.adapter as FilterContactSourcesAdapter).getSelectedItemsSet()
callback(file, contactSources)
dismiss()
}
else -> activity.toast(R.string.invalid_name)
}
}
}
}
}
}

View File

@ -0,0 +1,52 @@
package com.simplemobiletools.contacts.dialogs
import android.support.v7.app.AlertDialog
import android.view.ViewGroup
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.extensions.value
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.activities.SimpleActivity
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.showContactSourcePicker
import com.simplemobiletools.contacts.helpers.VcfImporter
import com.simplemobiletools.contacts.helpers.VcfImporter.ImportResult.IMPORT_FAIL
import kotlinx.android.synthetic.main.dialog_import_contacts.view.*
class ImportContactsDialog(val activity: SimpleActivity, val path: String, val callback: (refreshView: Boolean) -> Unit) {
init {
val view = (activity.layoutInflater.inflate(R.layout.dialog_import_contacts, null) as ViewGroup).apply {
import_contacts_title.text = activity.config.lastUsedContactSource
import_contacts_title.setOnClickListener {
activity.showContactSourcePicker(import_contacts_title.value) {
import_contacts_title.text = it
}
}
}
AlertDialog.Builder(activity)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this, R.string.import_contacts) {
getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
activity.toast(R.string.importing)
Thread {
val result = VcfImporter(activity).importContacts(path, view.import_contacts_title.value)
handleParseResult(result)
dismiss()
}.start()
}
}
}
}
private fun handleParseResult(result: VcfImporter.ImportResult) {
activity.toast(when (result) {
VcfImporter.ImportResult.IMPORT_OK -> R.string.importing_successful
VcfImporter.ImportResult.IMPORT_PARTIAL -> R.string.importing_some_entries_failed
else -> R.string.importing_failed
})
callback(result != IMPORT_FAIL)
}
}

View File

@ -3,12 +3,19 @@ package com.simplemobiletools.contacts.extensions
import android.content.Intent
import android.net.Uri
import com.simplemobiletools.commons.R
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.toast
import com.simplemobiletools.commons.helpers.PERMISSION_CALL_PHONE
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.contacts.BuildConfig
import com.simplemobiletools.contacts.activities.SimpleActivity
import com.simplemobiletools.contacts.helpers.ContactsHelper
import com.simplemobiletools.contacts.helpers.VcfExporter
import com.simplemobiletools.contacts.models.Contact
import java.io.File
fun SimpleActivity.startCallIntent(recipient: String) {
handlePermission(PERMISSION_CALL_PHONE) {
@ -40,3 +47,50 @@ fun SimpleActivity.tryStartCall(contact: Contact) {
}
}
}
fun SimpleActivity.showContactSourcePicker(currentSource: String, callback: (newSource: String) -> Unit) {
ContactsHelper(this).getContactSources {
val items = ArrayList<RadioItem>()
val sources = it
var currentSourceIndex = -1
sources.forEachIndexed { index, account ->
items.add(RadioItem(index, account))
if (account == currentSource) {
currentSourceIndex = index
}
}
runOnUiThread {
RadioGroupDialog(this, items, currentSourceIndex) {
callback(sources[it as Int])
}
}
}
}
fun BaseSimpleActivity.shareContacts(contacts: ArrayList<Contact>) {
val file = getTempFile()
if (file == null) {
toast(R.string.unknown_error_occurred)
return
}
VcfExporter().exportContacts(this, file, contacts) {
if (it == VcfExporter.ExportResult.EXPORT_OK) {
val uri = getFilePublicUri(file, BuildConfig.APPLICATION_ID)
shareUri(uri, BuildConfig.APPLICATION_ID)
}
}
}
fun BaseSimpleActivity.getTempFile(): File? {
val folder = File(cacheDir, "contacts")
if (!folder.exists()) {
if (!folder.mkdir()) {
toast(R.string.unknown_error_occurred)
return null
}
}
return File(folder, "contacts.vcf")
}

View File

@ -7,14 +7,17 @@ import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.provider.ContactsContract
import android.support.v4.content.FileProvider
import com.simplemobiletools.commons.R
import com.simplemobiletools.commons.extensions.getIntValue
import com.simplemobiletools.commons.extensions.isLollipopPlus
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.contacts.BuildConfig
import com.simplemobiletools.contacts.activities.ContactActivity
import com.simplemobiletools.contacts.helpers.CONTACT_ID
import com.simplemobiletools.contacts.helpers.Config
import com.simplemobiletools.contacts.models.Contact
import java.io.File
val Context.config: Config get() = Config.newInstance(applicationContext)
@ -94,3 +97,16 @@ fun lookupContactUri(lookup: String, context: Context): Uri {
val lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookup)
return ContactsContract.Contacts.lookupContact(context.contentResolver, lookupUri)
}
fun Context.getCachePhoto(): File {
val imagesFolder = File(cacheDir, "my_cache")
if (!imagesFolder.exists()) {
imagesFolder.mkdirs()
}
val file = File(imagesFolder, "Photo_${System.currentTimeMillis()}.jpg")
file.createNewFile()
return file
}
fun Context.getCachePhotoUri(file: File = getCachePhoto()) = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.provider", file)

View File

@ -26,6 +26,8 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
private var contactsIgnoringSearch = ArrayList<Contact>()
lateinit private var config: Config
var forceListRedraw = false
fun setupFragment(activity: MainActivity) {
config = activity.config
if (this.activity == null) {
@ -114,8 +116,9 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
fragment_list.beVisibleIf(contacts.isNotEmpty())
val currAdapter = fragment_list.adapter
if (currAdapter == null) {
ContactsAdapter(activity as SimpleActivity, contacts, activity, this is FavoritesFragment, fragment_list) {
if (currAdapter == null || forceListRedraw) {
forceListRedraw = false
ContactsAdapter(activity as SimpleActivity, contacts, activity, this is FavoritesFragment, fragment_list, fragment_fastscroller) {
if (config.callContact) {
val contact = it as Contact
if (contact.phoneNumbers.isNotEmpty()) {
@ -132,6 +135,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
fragment_list.adapter = this
}
fragment_fastscroller.setScrollTo(0)
fragment_fastscroller.setViews(fragment_list) {
val item = (fragment_list.adapter as ContactsAdapter).contactItems.getOrNull(it)
fragment_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
@ -165,16 +169,24 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
filtered.sort()
filtered.sortBy { !it.getFullName(startNameWithSurname).startsWith(text, true) }
if (filtered.isEmpty() && this@MyViewPagerFragment is FavoritesFragment) {
fragment_placeholder.text = activity.getString(R.string.no_items_found)
}
fragment_placeholder.beVisibleIf(filtered.isEmpty())
updateItems(filtered)
}
}
fun onSearchOpened() {
contactsIgnoringSearch = (fragment_list.adapter as ContactsAdapter).contactItems as ArrayList
contactsIgnoringSearch = (fragment_list.adapter as? ContactsAdapter)?.contactItems as ArrayList
}
fun onSearchClosed() {
(fragment_list.adapter as ContactsAdapter).updateItems(contactsIgnoringSearch)
if (this is FavoritesFragment) {
fragment_placeholder.text = activity?.getString(R.string.no_favorites)
}
}
private fun updateViewStuff() {

View File

@ -1,5 +1,7 @@
package com.simplemobiletools.contacts.helpers
import android.provider.ContactsContract.CommonDataKinds
// shared prefs
val CALL_CONTACT_ON_CLICK = "call_contact_on_click"
val SHOW_PHONE_NUMBERS = "show_phone_numbers"
@ -15,3 +17,35 @@ val PHOTO_ADDED = 1
val PHOTO_REMOVED = 2
val PHOTO_CHANGED = 3
val PHOTO_UNCHANGED = 4
// default contact values
val DEFAULT_EMAIL_TYPE = CommonDataKinds.Email.TYPE_HOME
val DEFAULT_PHONE_NUMBER_TYPE = CommonDataKinds.Phone.TYPE_MOBILE
val DEFAULT_EVENT_TYPE = CommonDataKinds.Event.TYPE_BIRTHDAY
// export/import
val BEGIN_VCARD = "BEGIN:VCARD"
val END_VCARD = "END:VCARD"
val N = "N:"
val TEL = "TEL"
val BDAY = "BDAY:"
val ANNIVERSARY = "ANNIVERSARY:"
val PHOTO = "PHOTO"
val EMAIL = "EMAIL"
val ENCODING = "ENCODING"
val BASE64 = "BASE64"
val JPEG = "JPEG"
val VERSION_2_1 = "VERSION:2.1"
// phone number/email types
val CELL = "CELL"
val WORK = "WORK"
val HOME = "HOME"
val PREF = "PREF"
val MAIN = "MAIN"
val FAX = "FAX"
val WORK_FAX = "WORK;FAX"
val HOME_FAX = "HOME;FAX"
val PAGER = "PAGER"
val MOBILE = "MOBILE"
val VOICE = "VOICE"

View File

@ -8,6 +8,7 @@ import android.database.Cursor
import android.graphics.Bitmap
import android.net.Uri
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
import android.provider.MediaStore
import android.util.SparseArray
import com.simplemobiletools.commons.activities.BaseSimpleActivity
@ -35,7 +36,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val uri = ContactsContract.Data.CONTENT_URI
val projection = getContactProjection()
val selection = "${ContactsContract.Data.MIMETYPE} = ?"
val selectionArgs = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
val selectionArgs = arrayOf(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
val sortOrder = getSortString()
var cursor: Cursor? = null
@ -44,17 +45,18 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
val firstName = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME) ?: ""
val middleName = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME) ?: ""
val surname = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME) ?: ""
val photoUri = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.PHOTO_URI) ?: ""
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<PhoneNumber>() // proper value is obtained below
val emails = ArrayList<Email>()
val events = ArrayList<Event>()
val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME)
val starred = cursor.getIntValue(ContactsContract.CommonDataKinds.StructuredName.STARRED)
val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: ""
val starred = cursor.getIntValue(CommonDataKinds.StructuredName.STARRED)
val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID)
val contact = Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId)
val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
val contact = Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId, thumbnailUri)
contacts.put(id, contact)
} while (cursor.moveToNext())
}
@ -88,11 +90,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
private fun getPhoneNumbers(contactId: Int? = null): SparseArray<ArrayList<PhoneNumber>> {
val phoneNumbers = SparseArray<ArrayList<PhoneNumber>>()
val uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
val uri = CommonDataKinds.Phone.CONTENT_URI
val projection = arrayOf(
ContactsContract.Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.Phone.NUMBER,
ContactsContract.CommonDataKinds.Phone.TYPE
CommonDataKinds.Phone.NUMBER,
CommonDataKinds.Phone.TYPE
)
val selection = if (contactId == null) null else "${ContactsContract.Data.RAW_CONTACT_ID} = ?"
@ -104,16 +106,19 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
val number = cursor.getStringValue(ContactsContract.CommonDataKinds.Phone.NUMBER)
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Phone.TYPE)
val number = cursor.getStringValue(CommonDataKinds.Phone.NUMBER) ?: continue
val type = cursor.getIntValue(CommonDataKinds.Phone.TYPE)
if (phoneNumbers[id] == null) {
phoneNumbers.put(id, ArrayList())
}
phoneNumbers[id].add(PhoneNumber(number, type))
val phoneNumber = PhoneNumber(number, type)
phoneNumbers[id].add(phoneNumber)
} while (cursor.moveToNext())
}
} catch (e: Exception) {
activity.showErrorToast(e)
} finally {
cursor?.close()
}
@ -122,11 +127,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
private fun getEmails(contactId: Int? = null): SparseArray<ArrayList<Email>> {
val emails = SparseArray<ArrayList<Email>>()
val uri = ContactsContract.CommonDataKinds.Email.CONTENT_URI
val uri = CommonDataKinds.Email.CONTENT_URI
val projection = arrayOf(
ContactsContract.Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.Email.DATA,
ContactsContract.CommonDataKinds.Email.TYPE
CommonDataKinds.Email.DATA,
CommonDataKinds.Email.TYPE
)
val selection = if (contactId == null) null else "${ContactsContract.Data.RAW_CONTACT_ID} = ?"
@ -138,8 +143,8 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
val email = cursor.getStringValue(ContactsContract.CommonDataKinds.Email.DATA)
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Email.TYPE)
val email = cursor.getStringValue(CommonDataKinds.Email.DATA) ?: continue
val type = cursor.getIntValue(CommonDataKinds.Email.TYPE)
if (emails[id] == null) {
emails.put(id, ArrayList())
@ -148,6 +153,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
emails[id]!!.add(Email(email, type))
} while (cursor.moveToNext())
}
} catch (e: Exception) {
activity.showErrorToast(e)
} finally {
cursor?.close()
}
@ -159,18 +167,18 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val events = SparseArray<ArrayList<Event>>()
val uri = ContactsContract.Data.CONTENT_URI
val projection = arrayOf(
ContactsContract.CommonDataKinds.Event.START_DATE,
ContactsContract.CommonDataKinds.Event.TYPE
CommonDataKinds.Event.START_DATE,
CommonDataKinds.Event.TYPE
)
val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
val selectionArgs = arrayOf(contactId.toString(), ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)
val selectionArgs = arrayOf(contactId.toString(), CommonDataKinds.Event.CONTENT_ITEM_TYPE)
var cursor: Cursor? = null
try {
cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val startDate = cursor.getStringValue(ContactsContract.CommonDataKinds.Event.START_DATE)
val type = cursor.getIntValue(ContactsContract.CommonDataKinds.Event.TYPE)
val startDate = cursor.getStringValue(CommonDataKinds.Event.START_DATE)
val type = cursor.getIntValue(CommonDataKinds.Event.TYPE)
if (events[contactId] == null) {
events.put(contactId, ArrayList())
@ -196,22 +204,23 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val uri = ContactsContract.Data.CONTENT_URI
val projection = getContactProjection()
val selection = "${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?"
val selectionArgs = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id.toString())
val selectionArgs = arrayOf(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id.toString())
var cursor: Cursor? = null
try {
cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
val firstName = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME) ?: ""
val middleName = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME) ?: ""
val surname = cursor.getStringValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME) ?: ""
val photoUri = cursor.getStringValue(ContactsContract.CommonDataKinds.Phone.PHOTO_URI) ?: ""
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.Phone.PHOTO_URI) ?: ""
val number = getPhoneNumbers(id)[id] ?: ArrayList()
val emails = getEmails(id)[id] ?: ArrayList()
val events = getEvents(id)[id] ?: ArrayList()
val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME)
val starred = cursor.getIntValue(ContactsContract.CommonDataKinds.StructuredName.STARRED)
val accountName = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: ""
val starred = cursor.getIntValue(CommonDataKinds.StructuredName.STARRED)
val contactId = cursor.getIntValue(ContactsContract.Data.CONTACT_ID)
return Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId)
val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
return Contact(id, firstName, middleName, surname, photoUri, number, emails, events, accountName, starred, contactId, thumbnailUri)
}
} finally {
cursor?.close()
@ -264,21 +273,22 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
private fun getContactProjection() = arrayOf(
ContactsContract.Data.CONTACT_ID,
ContactsContract.Data.RAW_CONTACT_ID,
ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,
ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME,
ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME,
ContactsContract.CommonDataKinds.StructuredName.PHOTO_URI,
ContactsContract.CommonDataKinds.StructuredName.STARRED,
CommonDataKinds.StructuredName.GIVEN_NAME,
CommonDataKinds.StructuredName.MIDDLE_NAME,
CommonDataKinds.StructuredName.FAMILY_NAME,
CommonDataKinds.StructuredName.PHOTO_URI,
CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI,
CommonDataKinds.StructuredName.STARRED,
ContactsContract.RawContacts.ACCOUNT_NAME
)
private fun getSortString(): String {
val sorting = activity.config.sorting
var sort = when {
sorting and SORT_BY_FIRST_NAME != 0 -> "${ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME} COLLATE NOCASE"
sorting and SORT_BY_MIDDLE_NAME != 0 -> "${ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME} COLLATE NOCASE"
sorting and SORT_BY_SURNAME != 0 -> "${ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME} COLLATE NOCASE"
else -> ContactsContract.CommonDataKinds.Phone.NUMBER
sorting and SORT_BY_FIRST_NAME != 0 -> "${CommonDataKinds.StructuredName.GIVEN_NAME} COLLATE NOCASE"
sorting and SORT_BY_MIDDLE_NAME != 0 -> "${CommonDataKinds.StructuredName.MIDDLE_NAME} COLLATE NOCASE"
sorting and SORT_BY_SURNAME != 0 -> "${CommonDataKinds.StructuredName.FAMILY_NAME} COLLATE NOCASE"
else -> CommonDataKinds.Phone.NUMBER
}
if (sorting and SORT_DESCENDING != 0) {
@ -294,18 +304,18 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val operations = ArrayList<ContentProviderOperation>()
ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply {
val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
val selectionArgs = arrayOf(contact.id.toString(), ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
withSelection(selection, selectionArgs)
withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName)
withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName)
withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.surname)
withValue(CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName)
withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName)
withValue(CommonDataKinds.StructuredName.FAMILY_NAME, contact.surname)
operations.add(build())
}
// delete phone numbers
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? "
val selectionArgs = arrayOf(contact.id.toString(), ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
withSelection(selection, selectionArgs)
operations.add(build())
}
@ -314,9 +324,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
contact.phoneNumbers.forEach {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, it.value)
withValue(ContactsContract.CommonDataKinds.Phone.TYPE, it.type)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Phone.NUMBER, it.value)
withValue(CommonDataKinds.Phone.TYPE, it.type)
operations.add(build())
}
}
@ -324,7 +334,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
// delete emails
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? "
val selectionArgs = arrayOf(contact.id.toString(), ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.Email.CONTENT_ITEM_TYPE)
withSelection(selection, selectionArgs)
operations.add(build())
}
@ -333,9 +343,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
contact.emails.forEach {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Email.DATA, it.value)
withValue(ContactsContract.CommonDataKinds.Email.TYPE, it.type)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Email.DATA, it.value)
withValue(CommonDataKinds.Email.TYPE, it.type)
operations.add(build())
}
}
@ -343,7 +353,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
// delete events
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? "
val selectionArgs = arrayOf(contact.id.toString(), ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)
val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.Event.CONTENT_ITEM_TYPE)
withSelection(selection, selectionArgs)
operations.add(build())
}
@ -352,9 +362,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
contact.events.forEach {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Event.START_DATE, it.value)
withValue(ContactsContract.CommonDataKinds.Event.TYPE, it.type)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Event.START_DATE, it.value)
withValue(CommonDataKinds.Event.TYPE, it.type)
operations.add(build())
}
}
@ -398,8 +408,8 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, scaledSizePhotoData)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Photo.PHOTO, scaledSizePhotoData)
operations.add(build())
}
@ -411,7 +421,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
private fun removePhoto(contact: Contact, operations: ArrayList<ContentProviderOperation>): ArrayList<ContentProviderOperation> {
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
val selectionArgs = arrayOf(contact.id.toString(), ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
withSelection(selection, selectionArgs)
operations.add(build())
}
@ -421,7 +431,6 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
fun insertContact(contact: Contact): Boolean {
return try {
activity.toast(R.string.inserting)
val operations = ArrayList<ContentProviderOperation>()
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI).apply {
withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.source)
@ -432,10 +441,10 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
// names
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName)
withValue(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName)
withValue(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, contact.surname)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName)
withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName)
withValue(CommonDataKinds.StructuredName.FAMILY_NAME, contact.surname)
operations.add(build())
}
@ -443,9 +452,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
contact.phoneNumbers.forEach {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, it.value)
withValue(ContactsContract.CommonDataKinds.Phone.TYPE, it.type)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Phone.NUMBER, it.value)
withValue(CommonDataKinds.Phone.TYPE, it.type)
operations.add(build())
}
}
@ -454,9 +463,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
contact.emails.forEach {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Email.DATA, it.value)
withValue(ContactsContract.CommonDataKinds.Email.TYPE, it.type)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Email.DATA, it.value)
withValue(CommonDataKinds.Email.TYPE, it.type)
operations.add(build())
}
}
@ -465,9 +474,9 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
contact.events.forEach {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Event.START_DATE, it.value)
withValue(ContactsContract.CommonDataKinds.Event.TYPE, it.type)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Event.START_DATE, it.value)
withValue(CommonDataKinds.Event.TYPE, it.type)
operations.add(build())
}
}
@ -482,15 +491,15 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val thumbnailSize = getThumbnailSize()
val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false)
scaledSizePhotoData = bitmapToByteArray(scaledPhoto)
scaledPhoto.recycle()
fullSizePhotoData = bitmapToByteArray(bitmap)
scaledPhoto.recycle()
bitmap.recycle()
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
withValue(ContactsContract.CommonDataKinds.Photo.PHOTO, scaledSizePhotoData)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Photo.PHOTO, scaledSizePhotoData)
operations.add(build())
}
}
@ -540,7 +549,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val uri = ContactsContract.Data.CONTENT_URI
val projection = arrayOf(ContactsContract.Data.CONTACT_ID, ContactsContract.Data.LOOKUP_KEY)
val selection = "${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?"
val selectionArgs = arrayOf(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, contactId)
val selectionArgs = arrayOf(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, contactId)
var cursor: Cursor? = null
try {
cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)

View File

@ -0,0 +1,106 @@
package com.simplemobiletools.contacts.helpers
import android.graphics.Bitmap
import android.net.Uri
import android.provider.ContactsContract.CommonDataKinds
import android.provider.MediaStore
import android.util.Base64
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.getFileOutputStream
import com.simplemobiletools.commons.extensions.writeLn
import com.simplemobiletools.contacts.helpers.VcfExporter.ExportResult.*
import com.simplemobiletools.contacts.models.Contact
import java.io.ByteArrayOutputStream
import java.io.File
class VcfExporter {
private val ENCODED_PHOTO_LINE_LENGTH = 74
enum class ExportResult {
EXPORT_FAIL, EXPORT_OK, EXPORT_PARTIAL
}
private var contactsExported = 0
private var contactsFailed = 0
fun exportContacts(activity: BaseSimpleActivity, file: File, contacts: ArrayList<Contact>, callback: (result: ExportResult) -> Unit) {
activity.getFileOutputStream(file) {
if (it == null) {
callback(EXPORT_FAIL)
return@getFileOutputStream
}
it.bufferedWriter().use { out ->
for (contact in contacts) {
out.writeLn(BEGIN_VCARD)
out.writeLn(VERSION_2_1)
out.writeLn("$N${contact.surname};${contact.firstName};${contact.middleName};;")
contact.phoneNumbers.forEach {
out.writeLn("$TEL;${getPhoneNumberLabel(it.type)}:${it.value}")
}
contact.emails.forEach {
val type = getEmailTypeLabel(it.type)
val delimiterType = if (type.isEmpty()) "" else ";$type"
out.writeLn("$EMAIL$delimiterType:${it.value}")
}
contact.events.forEach {
if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) {
out.writeLn("$BDAY${it.value}")
}
}
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)
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("")
}
out.writeLn(END_VCARD)
contactsExported++
}
}
}
callback(when {
contactsExported == 0 -> EXPORT_FAIL
contactsFailed > 0 -> EXPORT_PARTIAL
else -> EXPORT_OK
})
}
private fun getPhoneNumberLabel(type: Int) = when (type) {
CommonDataKinds.Phone.TYPE_MOBILE -> CELL
CommonDataKinds.Phone.TYPE_HOME -> HOME
CommonDataKinds.Phone.TYPE_WORK -> WORK
CommonDataKinds.Phone.TYPE_MAIN -> PREF
CommonDataKinds.Phone.TYPE_FAX_WORK -> WORK_FAX
CommonDataKinds.Phone.TYPE_FAX_HOME -> HOME_FAX
CommonDataKinds.Phone.TYPE_PAGER -> PAGER
else -> VOICE
}
private fun getEmailTypeLabel(type: Int) = when (type) {
CommonDataKinds.Email.TYPE_HOME -> HOME
CommonDataKinds.Email.TYPE_WORK -> WORK
CommonDataKinds.Email.TYPE_MOBILE -> MOBILE
else -> ""
}
}

View File

@ -0,0 +1,202 @@
package com.simplemobiletools.contacts.helpers
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.provider.ContactsContract.CommonDataKinds
import android.util.Base64
import android.widget.Toast
import com.simplemobiletools.commons.extensions.showErrorToast
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 java.io.File
import java.io.FileOutputStream
class VcfImporter(val activity: SimpleActivity) {
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL
}
private var curFirstName = ""
private var curMiddleName = ""
private var curSurname = ""
private var curPhotoUri = ""
private var curPhoneNumbers = ArrayList<PhoneNumber>()
private var curEmails = ArrayList<Email>()
private var curEvents = ArrayList<Event>()
private var isGettingPhoto = false
private var currentPhotoString = StringBuilder()
private var currentPhotoCompressionFormat = Bitmap.CompressFormat.JPEG
private var contactsImported = 0
private var contactsFailed = 0
fun importContacts(path: String, targetContactSource: String): ImportResult {
try {
val inputStream = if (path.contains("/")) {
File(path).inputStream()
} else {
activity.assets.open(path)
}
inputStream.bufferedReader().use {
while (true) {
val line = it.readLine() ?: break
if (line.trim().isEmpty()) {
if (isGettingPhoto) {
savePhoto()
isGettingPhoto = false
}
continue
}
when {
line.toUpperCase() == BEGIN_VCARD -> resetValues()
line.toUpperCase().startsWith(N) -> parseNames(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(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() == END_VCARD -> saveContact(targetContactSource)
isGettingPhoto -> currentPhotoString.append(line.trim())
}
}
}
} catch (e: Exception) {
activity.showErrorToast(e, Toast.LENGTH_LONG)
contactsFailed++
}
return when {
contactsImported == 0 -> IMPORT_FAIL
contactsFailed > 0 -> IMPORT_PARTIAL
else -> IMPORT_OK
}
}
private fun parseNames(names: String) {
val nameParts = names.split(";")
curSurname = nameParts[0]
curFirstName = nameParts[1]
if (nameParts.size > 2) {
curMiddleName = nameParts[2]
}
}
private fun addPhoneNumber(phoneNumber: String) {
val phoneParts = phoneNumber.trimStart(';').split(":")
var rawType = phoneParts[0]
var subType = ""
if (rawType.contains('=')) {
val types = rawType.split('=')
if (types.any { it.contains(';') }) {
subType = types[1].split(';')[0]
}
rawType = types.last()
}
val type = getPhoneNumberTypeId(rawType.toUpperCase(), subType)
val value = phoneParts[1]
curPhoneNumbers.add(PhoneNumber(value, type))
}
private fun getPhoneNumberTypeId(type: String, subType: String) = when (type) {
CELL -> CommonDataKinds.Phone.TYPE_MOBILE
HOME -> CommonDataKinds.Phone.TYPE_HOME
WORK -> CommonDataKinds.Phone.TYPE_WORK
PREF, MAIN -> CommonDataKinds.Phone.TYPE_MAIN
WORK_FAX -> CommonDataKinds.Phone.TYPE_FAX_WORK
HOME_FAX -> CommonDataKinds.Phone.TYPE_FAX_HOME
FAX -> if (subType == WORK) CommonDataKinds.Phone.TYPE_FAX_WORK else CommonDataKinds.Phone.TYPE_FAX_HOME
PAGER -> CommonDataKinds.Phone.TYPE_PAGER
else -> CommonDataKinds.Phone.TYPE_OTHER
}
private fun addEmail(email: String) {
val emailParts = email.trimStart(';').split(":")
var rawType = emailParts[0]
if (rawType.contains('=')) {
rawType = rawType.split('=').last()
}
val type = getEmailTypeId(rawType.toUpperCase())
val value = emailParts[1]
curEmails.add(Email(value, type))
}
private fun getEmailTypeId(type: String) = when (type) {
HOME -> CommonDataKinds.Email.TYPE_HOME
WORK -> CommonDataKinds.Email.TYPE_WORK
MOBILE -> CommonDataKinds.Email.TYPE_MOBILE
else -> CommonDataKinds.Email.TYPE_OTHER
}
private fun addBirthday(birthday: String) {
curEvents.add(Event(birthday, CommonDataKinds.Event.TYPE_BIRTHDAY))
}
private fun addAnniversary(anniversary: String) {
curEvents.add(Event(anniversary, CommonDataKinds.Event.TYPE_ANNIVERSARY))
}
private fun addPhoto(photo: String) {
val photoParts = photo.trimStart(';').split(';')
if (photoParts.size == 2) {
val typeParts = photoParts[1].split(':')
currentPhotoCompressionFormat = getPhotoCompressionFormat(typeParts[0])
val encoding = photoParts[0].split('=').last()
if (encoding == BASE64) {
isGettingPhoto = true
currentPhotoString.append(typeParts[1].trim())
}
}
}
private fun getPhotoCompressionFormat(type: String) = when (type.toLowerCase()) {
"png" -> Bitmap.CompressFormat.PNG
"webp" -> Bitmap.CompressFormat.WEBP
else -> Bitmap.CompressFormat.JPEG
}
private fun savePhoto() {
val file = activity.getCachePhoto()
val imageAsBytes = Base64.decode(currentPhotoString.toString().toByteArray(), Base64.DEFAULT)
val bitmap = BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.size)
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(file)
bitmap.compress(currentPhotoCompressionFormat, 100, fileOutputStream)
} finally {
fileOutputStream?.close()
}
curPhotoUri = activity.getCachePhotoUri(file).toString()
}
private fun saveContact(source: String) {
val contact = Contact(0, curFirstName, curMiddleName, curSurname, curPhotoUri, curPhoneNumbers, curEmails, curEvents, source, 0, 0, "")
if (ContactsHelper(activity).insertContact(contact)) {
contactsImported++
}
}
private fun resetValues() {
curFirstName = ""
curMiddleName = ""
curSurname = ""
curPhotoUri = ""
curPhoneNumbers = ArrayList()
curEmails = ArrayList()
curEvents = ArrayList()
isGettingPhoto = false
currentPhotoString = StringBuilder()
currentPhotoCompressionFormat = Bitmap.CompressFormat.JPEG
}
}

View File

@ -6,7 +6,7 @@ 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<PhoneNumber>, var emails: ArrayList<Email>, var events: ArrayList<Event>, var source: String,
var starred: Int, val contactId: Int) : Comparable<Contact> {
var starred: Int, val contactId: Int, val thumbnailUri: String) : Comparable<Contact> {
companion object {
var sorting: Int = 0
}

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/export_contacts_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/export_contacts_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_margin"
android:paddingRight="@dimen/activity_margin"
android:paddingTop="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/export_contacts_folder_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/folder"
android:textSize="@dimen/smaller_text_size"/>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/export_contacts_folder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:layout_marginLeft="@dimen/activity_margin"
android:paddingBottom="@dimen/small_margin"
android:paddingRight="@dimen/small_margin"
android:paddingTop="@dimen/small_margin"/>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/export_contacts_filename_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/filename_without_vcf"
android:textSize="@dimen/smaller_text_size"/>
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/export_contacts_filename"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:layout_marginLeft="@dimen/activity_margin"
android:paddingRight="@dimen/small_margin"
android:paddingTop="@dimen/normal_margin"
android:textSize="@dimen/normal_text_size"/>
<ImageView
android:id="@+id/export_contacts_divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginBottom="@dimen/medium_margin"
android:layout_marginTop="@dimen/medium_margin"
android:background="@color/divider_grey"
android:importantForAccessibility="no"/>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/export_contacts_pick_sources_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/include_contact_sources"
android:textSize="@dimen/smaller_text_size"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/export_contacts_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingTop="@dimen/medium_margin"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"/>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/import_contacts_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_margin"
android:paddingRight="@dimen/activity_margin"
android:paddingTop="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/import_contacts_source_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/target_contact_source"
android:textSize="@dimen/smaller_text_size"/>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/import_contacts_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="@dimen/activity_margin"
tools:text="Phone"/>
</LinearLayout>

View File

@ -16,6 +16,11 @@
android:icon="@drawable/ic_star_on"
android:title="@string/add_to_favorites"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/cab_share"
android:icon="@drawable/ic_share"
android:title="@string/share"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/cab_delete"
android:icon="@drawable/ic_delete"

View File

@ -17,6 +17,14 @@
android:icon="@drawable/ic_filter"
android:title="@string/filter"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/import_contacts"
android:title="@string/import_contacts_from_vcf"
app:showAsAction="never"/>
<item
android:id="@+id/export_contacts"
android:title="@string/export_contacts_to_vcf"
app:showAsAction="never"/>
<item
android:id="@+id/settings"
android:title="@string/settings"

View File

@ -11,4 +11,9 @@
android:icon="@drawable/ic_delete"
android:title="@string/delete"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/share"
android:icon="@drawable/ic_share"
android:title="@string/share"
app:showAsAction="ifRoom"/>
</menu>

View File

@ -21,8 +21,9 @@
<!-- Settings -->
<string name="call_contact_on_click">Kontakt bei Klick anrufen</string>
<string name="start_name_with_surname">Namen mit Vornamen beginnen</string>
<string name="start_name_with_surname">Namen mit Nachnamen beginnen</string>
<string name="show_phone_numbers">Zeige Telefonnummern im Hauptmenü</string>
<string name="show_contact_avatars">Show contact avatars</string>
<!-- Emails -->
<string name="email">Email</string>
@ -53,6 +54,15 @@
<string name="search_contacts">Kontakte durchsuchen</string>
<string name="search_favorites">Favoriten durchsuchen</string>
<!-- Export / Import -->
<string name="import_contacts">Kontakte importieren</string>
<string name="export_contacts">Kontakte exportieren</string>
<string name="import_contacts_from_vcf">Kontakte aus .vcf Datei importieren</string>
<string name="export_contacts_to_vcf">Kontakte in .vcf Datei exportieren</string>
<string name="target_contact_source">Zielkontaktquelle</string>
<string name="include_contact_sources">Kontaktquellen einschließen</string>
<string name="filename_without_vcf">Dateiname (ohne .vcf)</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">Eine App zum Verwalten von Kontakten, ganz ohne Werbung.</string>

View File

@ -3,7 +3,7 @@
<string name="app_launcher_name">연락처</string>
<string name="address">주소</string>
<string name="inserting">등록중…</string>
<string name="updating">갱신중…</string>
<string name="updating">수정중…</string>
<string name="missing_contact_source">연락처가 포함된 계정을 선택하세요.</string>
<string name="new_contact">새로운 연락처</string>
@ -23,6 +23,7 @@
<string name="call_contact_on_click">클릭으로 전화걸기</string>
<string name="start_name_with_surname">성을 먼저 표시</string>
<string name="show_phone_numbers">메인 스크린에 전화번호 표시</string>
<string name="show_contact_avatars">Show contact avatars</string>
<!-- Emails -->
<string name="email">이메일</string>
@ -53,6 +54,15 @@
<string name="search_contacts">연락처 검색</string>
<string name="search_favorites">자주쓰는 연락처 검색</string>
<!-- Export / Import -->
<string name="import_contacts">연락처 가져오기</string>
<string name="export_contacts">연락처 내보내기</string>
<string name="import_contacts_from_vcf">.vcf file에서 가져오기</string>
<string name="export_contacts_to_vcf">.vcf file로 내보내기</string>
<string name="target_contact_source">내보내기 대상</string>
<string name="include_contact_sources">가져오기 대상</string>
<string name="filename_without_vcf">파일이름 (.vcf 확장자 생략)</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">광고가 없는 연락처 관리 애플리케이션입니다.</string>
@ -60,7 +70,7 @@
연락처를 생성하고 관리하는 간단한 앱입니다. 연락처는 기본적으로 기기에만 저장되며 Google 또는 다른 계정을 통해 동기화 할 수도 있습니다. 즐겨 찾는 연락처는 별도의 목록에 표시 할 수 있습니다.
다양한 조건으로 정렬 및 필터링이 가능하며 성과 이름을 표시하는 옵션도 제공합니다. 또한 애플리케이션을 이용해 사용자 이메일 및 이벤트 관리를 할 수도 있습니다.
광고가 포함되어 있거나, 불필요한 권한을 요청하지 않습니다. 이 앱의 모든 소스는 오픈소스이며, 사용자가 직접 애플리케이션의 컬러를 설정 할 수 있습니다.
이 앱은 다양한 시리즈의 모바일앱 중 하나입니다. 나머지는 http://www.simplemobiletools.com 에서 찾아 보실 수 있습니다.

View File

@ -23,6 +23,7 @@
<string name="call_contact_on_click">Call contact on click</string>
<string name="start_name_with_surname">Start name with surname</string>
<string name="show_phone_numbers">Show phone numbers on the main screen</string>
<string name="show_contact_avatars">Show contact avatars</string>
<!-- Emails -->
<string name="email">E-mail</string>
@ -53,6 +54,15 @@
<string name="search_contacts">Search contacts</string>
<string name="search_favorites">Search favorites</string>
<!-- Export / Import -->
<string name="import_contacts">Import contacts</string>
<string name="export_contacts">Export contacts</string>
<string name="import_contacts_from_vcf">Import contacts from a .vcf file</string>
<string name="export_contacts_to_vcf">Export contacts to a .vcf file</string>
<string name="target_contact_source">Target contact source</string>
<string name="include_contact_sources">Include contact sources</string>
<string name="filename_without_vcf">Filename (without .vcf)</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">A contacts app for managing your contacts without ads.</string>

View File

@ -23,9 +23,10 @@
<string name="call_contact_on_click">Вызывать контакт при нажатии</string>
<string name="start_name_with_surname">Отображать сначала фамилии</string>
<string name="show_phone_numbers">Отображать номера телефонов на главном экране</string>
<string name="show_contact_avatars">Show contact avatars</string>
<!-- Emails -->
<string name="email">Эл.почта</string>
<string name="email">Эл. почта</string>
<string name="home">Домашний</string>
<string name="work">Рабочий</string>
<string name="other">Другой</string>
@ -53,6 +54,15 @@
<string name="search_contacts">Поиск контактов</string>
<string name="search_favorites">Поиск избранных</string>
<!-- Export / Import -->
<string name="import_contacts">Импортировать контакты</string>
<string name="export_contacts">Экспортировать контакты</string>
<string name="import_contacts_from_vcf">Импортировать контакты из .vcf файла</string>
<string name="export_contacts_to_vcf">Экспортировать контакты в .vcf файл</string>
<string name="target_contact_source">Назначенный к обновлению источник контактов</string>
<string name="include_contact_sources">Включить источники контактов</string>
<string name="filename_without_vcf">Имя файла (без .vcf)</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">Приложение для управления контактами без рекламы.</string>

View File

@ -23,6 +23,7 @@
<string name="call_contact_on_click">Zavolať kontakt po kliknutí</string>
<string name="start_name_with_surname">Začať meno priezviskom</string>
<string name="show_phone_numbers">Zobraziť telefónne čísla na hlavnej obrazovke</string>
<string name="show_contact_avatars">Zobraziť obrázky kontaktov</string>
<!-- Emails -->
<string name="email">Email</string>
@ -53,6 +54,15 @@
<string name="search_contacts">Hľadať v kontaktoch</string>
<string name="search_favorites">Hľadať medzi obľúbenými</string>
<!-- Export / Import -->
<string name="import_contacts">Importovať kontakty</string>
<string name="export_contacts">Exportovať kontakty</string>
<string name="import_contacts_from_vcf">Importovať kontakty z .vcf súboru</string>
<string name="export_contacts_to_vcf">Exportovať kontakty do .vcf súboru</string>
<string name="target_contact_source">Cieľový zdroj kontaktov</string>
<string name="include_contact_sources">Zahrnúť zdroje kontaktov</string>
<string name="filename_without_vcf">Názov súboru (bez .vcf)</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">Aplikácia pre správu vašich kontaktov bez reklám.</string>

View File

@ -0,0 +1,83 @@
<resources>
<string name="app_name">Simple Contacts</string>
<string name="app_launcher_name">Kontakter</string>
<string name="address">Adress</string>
<string name="inserting">Lägger till…</string>
<string name="updating">Uppdaterar…</string>
<string name="missing_contact_account">Du måste välja vilket konto kontakten ska tillhöra</string>
<string name="new_contact">Ny kontakt</string>
<string name="edit_contact">Redigera kontakt</string>
<string name="select_contact">Välj kontakt</string>
<string name="select_contacts">Välj kontakter</string>
<string name="first_name">Förnamn</string>
<string name="middle_name">Mellannamn</string>
<string name="surname">Efternamn</string>
<!-- Photo -->
<string name="take_photo">Ta foto</string>
<string name="choose_photo">Välj foto</string>
<string name="remove_photo">Ta bort foto</string>
<!-- Settings -->
<string name="call_contact_on_click">Ring kontakter när jag trycker på dem</string>
<string name="start_name_with_surname">Visa efternamn först</string>
<string name="show_phone_numbers">Visa telefonnummer i huvudvyn</string>
<string name="show_contact_avatars">Show contact avatars</string>
<!-- Emails -->
<string name="email">E-post</string>
<string name="home">Hem</string>
<string name="work">Arbete</string>
<string name="other">Annat</string>
<!-- Phone numbers -->
<string name="number">Nummer</string>
<string name="mobile">Mobil</string>
<string name="main_number">Primärt nummer</string>
<string name="work_fax">Arbetsfax</string>
<string name="home_fax">Hemfax</string>
<string name="pager">Personsökare</string>
<string name="no_phone_number_found">Inget telefonnummer hittades</string>
<!-- Events -->
<string name="birthday">Födelsedag</string>
<string name="anniversary">Årsdag</string>
<!-- Favorites -->
<string name="no_favorites">Det verkar som att du inte har lagt till några favoritkontakter ännu.</string>
<string name="add_favorites">Lägg till favoriter</string>
<string name="add_to_favorites">Lägg till i favoriter</string>
<string name="remove_from_favorites">Ta bort från favoriter</string>
<!-- Search -->
<string name="search_contacts">Sök efter kontakter</string>
<string name="search_favorites">Sök efter favoriter</string>
<!-- Export / Import -->
<string name="import_contacts">Importera kontakter</string>
<string name="export_contacts">Exportera kontakter</string>
<string name="import_contacts_from_vcf">Importera kontakter från en .vcf-fil</string>
<string name="export_contacts_to_vcf">Exportera kontakter till en .vcf-fil</string>
<string name="target_contact_source">Målkontaktkälla</string>
<string name="include_contact_sources">Inkludera kontaktkällor</string>
<string name="filename_without_vcf">Filnamn (utan .vcf)</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">En app för att hantera dina kontakter utan reklam.</string>
<string name="app_long_description">
En enkel app för att skapa och hantera kontakter från olika källor. Kontakterna kan lagras bara på din enhet, men kan också synkroniseras med ditt Google-konto eller andra konton. Du kan visa dina favoritkontakter i en separat lista.
Du kan också använda den för att hantera dina kontakters e-postadresser och händelser. Den kan sortera och filtrera efter flera parametrar. Den kan också visa efternamn först.
Innehåller ingen reklam eller onödiga behörigheter. Den har helt öppen källkod och anpassningsbara färger.
Denna app är bara en del av en större serie appar. Du hittar resten av dem på http://www.simplemobiletools.com
</string>
<!--
Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
-->
</resources>

View File

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Default colors -->
<color name="default_text_color">@color/theme_dark_text_color</color>
<color name="default_background_color">@color/theme_dark_background_color</color>
</resources>

View File

@ -23,6 +23,7 @@
<string name="call_contact_on_click">Call contact on click</string>
<string name="start_name_with_surname">Start name with surname</string>
<string name="show_phone_numbers">Show phone numbers on the main screen</string>
<string name="show_contact_avatars">Show contact avatars</string>
<!-- Emails -->
<string name="email">Email</string>
@ -53,6 +54,15 @@
<string name="search_contacts">Search contacts</string>
<string name="search_favorites">Search favorites</string>
<!-- Export / Import -->
<string name="import_contacts">Import contacts</string>
<string name="export_contacts">Export contacts</string>
<string name="import_contacts_from_vcf">Import contacts from a .vcf file</string>
<string name="export_contacts_to_vcf">Export contacts to a .vcf file</string>
<string name="target_contact_source">Target contact source</string>
<string name="include_contact_sources">Include contact sources</string>
<string name="filename_without_vcf">Filename (without .vcf)</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">A contacts app for managing your contacts without ads.</string>

View File

@ -3,4 +3,5 @@
<external-path name="external_files" path="."/>
<root-path name="external_files" path="/storage/" />
<cache-path name="cache_files" path="my_cache/" />
<cache-path name="shared_contacts" path="contacts/"/>
</paths>

View File

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

View File

@ -0,0 +1,7 @@
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.
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.
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1 @@
A contacts app for managing your contacts without ads.

View File

@ -0,0 +1 @@
Simple Contacts

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB