diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9854e29..3c6a8c2c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,57 @@
Changelog
==========
+Version 4.2.2 *(2018-08-13)*
+----------------------------
+
+ * Added an optional Nickname field
+ * Improved searching and sorting UTF8 characters
+ * Fixed updating Notes and Organization fields
+
+Version 4.2.1 *(2018-08-05)*
+----------------------------
+
+ * Added some stability and light theme UX fixes
+
+Version 4.2.0 *(2018-08-04)*
+----------------------------
+
+ * Added a Recent Calls tab
+ * Allow customizing which tabs are visible
+ * Added an optional call confirmation dialog
+ * Fixed some glitches related to company contacts
+ * Some other performance and stability improvements
+
+Version 4.1.0 *(2018-07-16)*
+----------------------------
+
+ * Fixed a couple issues related to importing contacts from .vcf files
+ * Couple other UX and stability improvements
+
+Version 4.0.5 *(2018-07-05)*
+----------------------------
+
+ * Make duplicate contact filtering more agressive
+ * Couple UX and stability improvements
+
+Version 4.0.4 *(2018-06-19)*
+----------------------------
+
+ * Make "Try filtering out duplicate contacts" more agressive
+ * Ignore hidden contact fields, do not wipe them
+ * Prefer the contacts Mobile number at sending batch SMS
+ * Added a couple stability improvements
+
+Version 4.0.3 *(2018-05-13)*
+----------------------------
+
+ * Show a couple additional contact sources
+
+Version 4.0.2 *(2018-05-12)*
+----------------------------
+
+ * Make sure all relevant contact sources are visible
+
Version 4.0.1 *(2018-05-09)*
----------------------------
diff --git a/app/build.gradle b/app/build.gradle
index ca15f081..dd3e3290 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,15 +3,15 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 27
- buildToolsVersion "27.0.3"
+ compileSdkVersion 28
+ buildToolsVersion "28.0.2"
defaultConfig {
applicationId "com.simplemobiletools.contacts"
minSdkVersion 16
- targetSdkVersion 27
- versionCode 21
- versionName "4.0.1"
+ targetSdkVersion 28
+ versionCode 29
+ versionName "4.2.2"
setProperty("archivesBaseName", "contacts")
}
@@ -45,9 +45,11 @@ ext {
}
dependencies {
- implementation 'com.simplemobiletools:commons:4.0.0'
+ implementation 'com.simplemobiletools:commons:4.6.15'
implementation 'joda-time:joda-time:2.9.9'
implementation 'com.facebook.stetho:stetho:1.5.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+ compile 'com.googlecode.ez-vcard:ez-vcard:0.10.4'
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion"
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion"
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index e69de29b..50dfb901 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -0,0 +1,5 @@
+# ez-vcard
+-keep,includedescriptorclasses class ezvcard.property.** { *; }
+-keep enum ezvcard.VCardVersion { *; }
+-dontwarn ezvcard.io.json.**
+-dontwarn freemarker.**
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6d7fec68..4422660d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,6 +11,8 @@
+
+
-
-
-
-
+ android:theme="@style/SplashTheme"/>
@@ -191,19 +189,6 @@
android:resource="@xml/provider_paths"/>
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
saveContact()
R.id.share -> shareContact()
@@ -232,7 +233,11 @@ class EditContactActivity : ContactActivity() {
Intent().apply {
action = Intent.ACTION_EDIT
data = getContactPublicUri(contact!!)
- startActivity(this)
+ if (resolveActivity(packageManager) != null) {
+ startActivity(this)
+ } else {
+ toast(R.string.no_app_found)
+ }
}
}
@@ -264,6 +269,7 @@ class EditContactActivity : ContactActivity() {
}
private fun setupFieldVisibility() {
+ val showFields = config.showContactFields
if (showFields and (SHOW_PREFIX_FIELD or SHOW_FIRST_NAME_FIELD or SHOW_MIDDLE_NAME_FIELD or SHOW_SURNAME_FIELD or SHOW_SUFFIX_FIELD) == 0) {
contact_name_image.beInvisible()
}
@@ -273,6 +279,7 @@ class EditContactActivity : ContactActivity() {
contact_middle_name.beVisibleIf(showFields and SHOW_MIDDLE_NAME_FIELD != 0)
contact_surname.beVisibleIf(showFields and SHOW_SURNAME_FIELD != 0)
contact_suffix.beVisibleIf(showFields and SHOW_SUFFIX_FIELD != 0)
+ contact_nickname.beVisibleIf(showFields and SHOW_NICKNAME_FIELD != 0)
contact_source.beVisibleIf(showFields and SHOW_CONTACT_SOURCE_FIELD != 0)
contact_source_image.beVisibleIf(showFields and SHOW_CONTACT_SOURCE_FIELD != 0)
@@ -340,112 +347,99 @@ class EditContactActivity : ContactActivity() {
contact_middle_name.setText(middleName)
contact_surname.setText(surname)
contact_suffix.setText(suffix)
+ contact_nickname.setText(nickname)
}
}
private fun setupPhoneNumbers() {
- if (showFields and SHOW_PHONE_NUMBERS_FIELD != 0) {
- contact!!.phoneNumbers.forEachIndexed { index, number ->
- var numberHolder = contact_numbers_holder.getChildAt(index)
- if (numberHolder == null) {
- numberHolder = layoutInflater.inflate(R.layout.item_edit_phone_number, contact_numbers_holder, false)
- contact_numbers_holder.addView(numberHolder)
- }
+ contact!!.phoneNumbers.forEachIndexed { index, number ->
+ var numberHolder = contact_numbers_holder.getChildAt(index)
+ if (numberHolder == null) {
+ numberHolder = layoutInflater.inflate(R.layout.item_edit_phone_number, contact_numbers_holder, false)
+ contact_numbers_holder.addView(numberHolder)
+ }
- numberHolder!!.apply {
- contact_number.setText(number.value)
- setupPhoneNumberTypePicker(contact_number_type, number.type)
- }
+ numberHolder!!.apply {
+ contact_number.setText(number.value)
+ setupPhoneNumberTypePicker(contact_number_type, number.type)
}
}
}
private fun setupEmails() {
- if (showFields and SHOW_EMAILS_FIELD != 0) {
- contact!!.emails.forEachIndexed { index, email ->
- var emailHolder = contact_emails_holder.getChildAt(index)
- if (emailHolder == null) {
- emailHolder = layoutInflater.inflate(R.layout.item_edit_email, contact_emails_holder, false)
- contact_emails_holder.addView(emailHolder)
- }
+ contact!!.emails.forEachIndexed { index, email ->
+ var emailHolder = contact_emails_holder.getChildAt(index)
+ if (emailHolder == null) {
+ emailHolder = layoutInflater.inflate(R.layout.item_edit_email, contact_emails_holder, false)
+ contact_emails_holder.addView(emailHolder)
+ }
- emailHolder!!.apply {
- contact_email.setText(email.value)
- setupEmailTypePicker(contact_email_type, email.type)
- }
+ emailHolder!!.apply {
+ contact_email.setText(email.value)
+ setupEmailTypePicker(contact_email_type, email.type)
}
}
}
private fun setupAddresses() {
- if (showFields and SHOW_ADDRESSES_FIELD != 0) {
- contact!!.addresses.forEachIndexed { index, address ->
- var addressHolder = contact_addresses_holder.getChildAt(index)
- if (addressHolder == null) {
- addressHolder = layoutInflater.inflate(R.layout.item_edit_address, contact_addresses_holder, false)
- contact_addresses_holder.addView(addressHolder)
- }
+ contact!!.addresses.forEachIndexed { index, address ->
+ var addressHolder = contact_addresses_holder.getChildAt(index)
+ if (addressHolder == null) {
+ addressHolder = layoutInflater.inflate(R.layout.item_edit_address, contact_addresses_holder, false)
+ contact_addresses_holder.addView(addressHolder)
+ }
- addressHolder!!.apply {
- contact_address.setText(address.value)
- setupAddressTypePicker(contact_address_type, address.type)
- }
+ addressHolder!!.apply {
+ contact_address.setText(address.value)
+ setupAddressTypePicker(contact_address_type, address.type)
}
}
}
private fun setupNotes() {
- if (showFields and SHOW_NOTES_FIELD != 0) {
- contact_notes.setText(contact!!.notes)
- }
+ contact_notes.setText(contact!!.notes)
}
private fun setupOrganization() {
- if (showFields and SHOW_ORGANIZATION_FIELD != 0) {
- contact_organization_company.setText(contact!!.organization.company)
- contact_organization_job_position.setText(contact!!.organization.jobPosition)
- }
+ contact_organization_company.setText(contact!!.organization.company)
+ contact_organization_job_position.setText(contact!!.organization.jobPosition)
}
private fun setupWebsites() {
- if (showFields and SHOW_WEBSITES_FIELD != 0) {
- contact!!.websites.forEachIndexed { index, website ->
- var websitesHolder = contact_websites_holder.getChildAt(index)
- if (websitesHolder == null) {
- websitesHolder = layoutInflater.inflate(R.layout.item_edit_website, contact_websites_holder, false)
- contact_websites_holder.addView(websitesHolder)
- }
-
- websitesHolder!!.contact_website.setText(website)
+ contact!!.websites.forEachIndexed { index, website ->
+ var websitesHolder = contact_websites_holder.getChildAt(index)
+ if (websitesHolder == null) {
+ websitesHolder = layoutInflater.inflate(R.layout.item_edit_website, contact_websites_holder, false)
+ contact_websites_holder.addView(websitesHolder)
}
+
+ websitesHolder!!.contact_website.setText(website)
}
}
private fun setupEvents() {
- if (showFields and SHOW_EVENTS_FIELD != 0) {
- contact!!.events.forEachIndexed { index, event ->
- var eventHolder = contact_events_holder.getChildAt(index)
- if (eventHolder == null) {
- eventHolder = layoutInflater.inflate(R.layout.item_event, contact_events_holder, false)
- contact_events_holder.addView(eventHolder)
+ contact!!.events.forEachIndexed { index, event ->
+ var eventHolder = contact_events_holder.getChildAt(index)
+ if (eventHolder == null) {
+ eventHolder = layoutInflater.inflate(R.layout.item_event, contact_events_holder, false)
+ contact_events_holder.addView(eventHolder)
+ }
+
+ (eventHolder as ViewGroup).apply {
+ val contactEvent = contact_event.apply {
+ event.value.getDateTimeFromDateString(this)
+ tag = event.value
+ alpha = 1f
}
- (eventHolder as ViewGroup).apply {
- val contactEvent = contact_event.apply {
- getDateTime(event.value, this)
- tag = event.value
- alpha = 1f
- }
+ setupEventTypePicker(this, event.type)
- setupEventTypePicker(this, event.type)
-
- contact_event_remove.apply {
- beVisible()
- applyColorFilter(getAdjustedPrimaryColor())
- background.applyColorFilter(config.textColor)
- setOnClickListener {
- resetContactEvent(contactEvent, this)
- }
+ contact_event_remove.apply {
+ beVisible()
+ applyColorFilter(getAdjustedPrimaryColor())
+ background.applyColorFilter(config.textColor)
+ setOnClickListener {
+ resetContactEvent(contactEvent, this)
}
}
}
@@ -453,52 +447,50 @@ class EditContactActivity : ContactActivity() {
}
private fun setupGroups() {
- if (showFields and SHOW_GROUPS_FIELD != 0) {
- contact_groups_holder.removeAllViews()
- val groups = contact!!.groups
- groups.forEachIndexed { index, group ->
- var groupHolder = contact_groups_holder.getChildAt(index)
- if (groupHolder == null) {
- groupHolder = layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false)
- contact_groups_holder.addView(groupHolder)
+ contact_groups_holder.removeAllViews()
+ val groups = contact!!.groups
+ groups.forEachIndexed { index, group ->
+ var groupHolder = contact_groups_holder.getChildAt(index)
+ if (groupHolder == null) {
+ groupHolder = layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false)
+ contact_groups_holder.addView(groupHolder)
+ }
+
+ (groupHolder as ViewGroup).apply {
+ contact_group.apply {
+ text = group.title
+ setTextColor(config.textColor)
+ tag = group.id
+ alpha = 1f
}
- (groupHolder as ViewGroup).apply {
- contact_group.apply {
- text = group.title
- setTextColor(config.textColor)
- tag = group.id
- alpha = 1f
- }
+ setOnClickListener {
+ showSelectGroupsDialog()
+ }
+ contact_group_remove.apply {
+ beVisible()
+ applyColorFilter(getAdjustedPrimaryColor())
+ background.applyColorFilter(config.textColor)
setOnClickListener {
- showSelectGroupsDialog()
- }
-
- contact_group_remove.apply {
- beVisible()
- applyColorFilter(getAdjustedPrimaryColor())
- background.applyColorFilter(config.textColor)
- setOnClickListener {
- removeGroup(group.id)
- }
+ removeGroup(group.id)
}
}
}
+ }
- if (groups.isEmpty()) {
- layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false).apply {
- contact_group.apply {
- alpha = 0.5f
- text = getString(R.string.no_groups)
- setTextColor(config.textColor)
- }
+ if (groups.isEmpty()) {
+ layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false).apply {
+ contact_group.apply {
+ alpha = 0.5f
+ text = getString(R.string.no_groups)
+ setTextColor(config.textColor)
+ }
- contact_groups_holder.addView(this)
- contact_group_remove.beGone()
- setOnClickListener {
- showSelectGroupsDialog()
- }
+ contact_groups_holder.addView(this)
+ contact_group_remove.beGone()
+ setOnClickListener {
+ showSelectGroupsDialog()
}
}
}
@@ -513,8 +505,8 @@ class EditContactActivity : ContactActivity() {
supportActionBar?.title = resources.getString(R.string.new_contact)
originalContactSource = if (hasContactPermissions()) config.lastUsedContactSource else SMT_PRIVATE
val organization = Organization("", "")
- contact = Contact(0, "", "", "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), originalContactSource, 0, 0, "", null, "",
- ArrayList(), organization, ArrayList())
+ contact = Contact(0, "", "", "", "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), originalContactSource, 0, 0, "",
+ null, "", ArrayList(), organization, ArrayList())
contact_source.text = getPublicContactSource(contact!!.source)
}
@@ -603,7 +595,7 @@ class EditContactActivity : ContactActivity() {
}
}
- val date = getDateTime(eventField.tag?.toString() ?: "")
+ val date = (eventField.tag?.toString() ?: "").getDateTimeFromDateString()
DatePickerDialog(this, getDialogTheme(), setDateListener, date.year, date.monthOfYear - 1, date.dayOfMonth).show()
}
@@ -713,7 +705,7 @@ class EditContactActivity : ContactActivity() {
}
private fun saveContact() {
- if (isSaving || contact == null) {
+ if (isSaving) {
return
}
@@ -725,6 +717,7 @@ class EditContactActivity : ContactActivity() {
middleName = contact_middle_name.value
surname = contact_surname.value
suffix = contact_suffix.value
+ nickname = contact_nickname.value
photoUri = currentContactPhotoPath
phoneNumbers = getFilledPhoneNumbers()
emails = getFilledEmails()
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt
index c81d309b..3716007b 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt
@@ -3,12 +3,17 @@ package com.simplemobiletools.contacts.activities
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
-import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.extensions.beVisibleIf
+import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor
+import com.simplemobiletools.commons.extensions.underlineText
+import com.simplemobiletools.commons.extensions.updateTextColors
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.adapters.ContactsAdapter
import com.simplemobiletools.contacts.dialogs.SelectContactsDialog
import com.simplemobiletools.contacts.extensions.*
-import com.simplemobiletools.contacts.helpers.*
+import com.simplemobiletools.contacts.helpers.ContactsHelper
+import com.simplemobiletools.contacts.helpers.GROUP
+import com.simplemobiletools.contacts.helpers.LOCATION_GROUP_CONTACTS
import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
import com.simplemobiletools.contacts.interfaces.RemoveFromGroupListener
import com.simplemobiletools.contacts.models.Contact
@@ -102,24 +107,13 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh
val currAdapter = group_contacts_list.adapter
if (currAdapter == null) {
ContactsAdapter(this, contacts, this, LOCATION_GROUP_CONTACTS, this, group_contacts_list, group_contacts_fastscroller) {
- when (config.onContactClick) {
- ON_CLICK_CALL_CONTACT -> {
- val contact = it as Contact
- if (contact.phoneNumbers.isNotEmpty()) {
- tryStartCall(it)
- } else {
- toast(R.string.no_phone_number_found)
- }
- }
- ON_CLICK_VIEW_CONTACT -> viewContact(it as Contact)
- ON_CLICK_EDIT_CONTACT -> editContact(it as Contact)
- }
+ contactClicked(it as Contact)
}.apply {
addVerticalDividers(true)
group_contacts_list.adapter = this
}
- group_contacts_fastscroller.setScrollTo(0)
+ group_contacts_fastscroller.setScrollToY(0)
group_contacts_fastscroller.setViews(group_contacts_list) {
val item = (group_contacts_list.adapter as ContactsAdapter).contactItems.getOrNull(it)
group_contacts_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
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 90f0f9e4..19c92a75 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt
@@ -1,15 +1,13 @@
package com.simplemobiletools.contacts.activities
-import android.Manifest
import android.app.SearchManager
import android.content.Context
import android.content.Intent
-import android.content.pm.PackageManager
import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
-import android.support.v4.app.ActivityCompat
-import android.support.v4.content.ContextCompat
+import android.os.Handler
import android.support.v4.view.MenuItemCompat
import android.support.v4.view.ViewPager
import android.support.v7.widget.SearchView
@@ -30,6 +28,7 @@ import com.simplemobiletools.contacts.dialogs.ImportContactsDialog
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.dbHelper
import com.simplemobiletools.contacts.extensions.getTempFile
+import com.simplemobiletools.contacts.fragments.MyViewPagerFragment
import com.simplemobiletools.contacts.helpers.*
import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
import com.simplemobiletools.contacts.models.Contact
@@ -37,6 +36,7 @@ import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_contacts.*
import kotlinx.android.synthetic.main.fragment_favorites.*
import kotlinx.android.synthetic.main.fragment_groups.*
+import kotlinx.android.synthetic.main.fragment_recents.*
import java.io.FileOutputStream
class MainActivity : SimpleActivity(), RefreshContactsListener {
@@ -45,6 +45,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
private var werePermissionsHandled = false
private var isFirstResume = true
private var isGettingContacts = false
+ private var handledShowTabs = 0
private var storedTextColor = 0
private var storedBackgroundColor = 0
@@ -53,6 +54,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
private var storedShowPhoneNumbers = false
private var storedStartNameWithSurname = false
private var storedFilterDuplicates = true
+ private var storedShowTabs = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -63,26 +65,36 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
// just get a reference to the database to make sure it is created properly
dbHelper
+ handlePermission(PERMISSION_READ_CALL_LOG) {
+ if (it) {
+ handlePermission(PERMISSION_WRITE_CALL_LOG) {
+ checkContactPermissions()
+ }
+ } else {
+ checkContactPermissions()
+ }
+ }
+
+ storeStateVariables()
+ checkWhatsNewDialog()
+ }
+
+ private fun checkContactPermissions() {
handlePermission(PERMISSION_READ_CONTACTS) {
werePermissionsHandled = true
if (it) {
handlePermission(PERMISSION_WRITE_CONTACTS) {
// workaround for upgrading from version 3.x to 4.x as we added a new permission from an already granted permissions group
- val hasGetAccountsPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS) == PackageManager.PERMISSION_GRANTED
- if (!hasGetAccountsPermission) {
- ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.GET_ACCOUNTS), 34)
+ handlePermission(PERMISSION_GET_ACCOUNTS) {
+ storeLocalAccountData()
+ initFragments()
}
-
- storeLocalAccountData()
- initFragments()
}
} else {
storeLocalAccountData()
initFragments()
}
}
- storeStateVariables()
- checkWhatsNewDialog()
}
override fun onResume() {
@@ -92,6 +104,12 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
return
}
+ if (storedShowTabs != config.showTabs) {
+ config.lastUsedViewPagerPage = 0
+ System.exit(0)
+ return
+ }
+
val configShowContactThumbnails = config.showContactThumbnails
if (storedShowContactThumbnails != configShowContactThumbnails) {
getAllFragments().forEach {
@@ -138,10 +156,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
initFragments()
} else {
refreshContacts(ALL_TABS_MASK)
- }
- getAllFragments().forEach {
- it?.onActivityResume()
+ getAllFragments().forEach {
+ it?.onActivityResume()
+ }
}
}
isFirstResume = false
@@ -159,11 +177,12 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu, menu)
- val currentPage = viewpager?.currentItem
+ val currentFragment = getCurrentFragment()
+
menu.apply {
- findItem(R.id.search).isVisible = currentPage != LOCATION_GROUPS_TAB
- findItem(R.id.sort).isVisible = currentPage != LOCATION_GROUPS_TAB
- findItem(R.id.filter).isVisible = currentPage != LOCATION_GROUPS_TAB
+ findItem(R.id.search).isVisible = currentFragment != groups_fragment && currentFragment != recents_fragment
+ findItem(R.id.sort).isVisible = currentFragment != groups_fragment && currentFragment != recents_fragment
+ findItem(R.id.filter).isVisible = currentFragment != groups_fragment
}
setupSearch(menu)
return true
@@ -191,6 +210,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
storedShowPhoneNumbers = showPhoneNumbers
storedStartNameWithSurname = startNameWithSurname
storedFilterDuplicates = filterDuplicates
+ storedShowTabs = showTabs
}
}
@@ -200,7 +220,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
(searchMenuItem!!.actionView as SearchView).apply {
setSearchableInfo(searchManager.getSearchableInfo(componentName))
isSubmitButtonEnabled = false
- queryHint = getString(if (viewpager.currentItem == 0) R.string.search_contacts else R.string.search_favorites)
+ queryHint = getString(if (getCurrentFragment() == contacts_fragment) R.string.search_contacts else R.string.search_favorites)
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String) = false
@@ -228,13 +248,30 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
})
}
- private fun getCurrentFragment() = when (viewpager.currentItem) {
- 0 -> contacts_fragment
- 1 -> favorites_fragment
- else -> groups_fragment
+ private fun getCurrentFragment(): MyViewPagerFragment? {
+ val showTabs = config.showTabs
+ val fragments = arrayListOf()
+ if (showTabs and CONTACTS_TAB_MASK != 0) {
+ fragments.add(contacts_fragment)
+ }
+
+ if (showTabs and FAVORITES_TAB_MASK != 0) {
+ fragments.add(favorites_fragment)
+ }
+
+ if (showTabs and RECENTS_TAB_MASK != 0) {
+ fragments.add(recents_fragment)
+ }
+
+ if (showTabs and GROUPS_TAB_MASK != 0) {
+ fragments.add(groups_fragment)
+ }
+
+ return fragments[viewpager.currentItem]
}
private fun setupTabColors() {
+ handledShowTabs = config.showTabs
val lastUsedPage = config.lastUsedViewPagerPage
main_tabs_holder.apply {
background = ColorDrawable(config.backgroundColor)
@@ -250,10 +287,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
private fun storeLocalAccountData() {
if (config.localAccountType == "-1") {
- ContactsHelper(this).getContactSources {
+ ContactsHelper(this).getContactSources { sources ->
var localAccountType = ""
var localAccountName = ""
- it.forEach {
+ sources.forEach {
if (localAccountTypes.contains(it.type)) {
localAccountType = it.type
localAccountName = it.name
@@ -266,11 +303,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
}
}
- private fun getInactiveTabIndexes(activeIndex: Int) = arrayListOf(0, 1, 2).filter { it != activeIndex }
+ private fun getInactiveTabIndexes(activeIndex: Int) = arrayListOf(0, 1, 2, 3).filter { it != activeIndex }
private fun initFragments() {
- refreshContacts(ALL_TABS_MASK)
- viewpager.offscreenPageLimit = 2
+ viewpager.offscreenPageLimit = 3
viewpager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
if (isSearchOpen) {
@@ -291,11 +327,19 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
}
})
+ viewpager.onGlobalLayout {
+ refreshContacts(ALL_TABS_MASK)
+ }
+
main_tabs_holder.onTabSelectionChanged(
tabUnselectedAction = {
it.icon?.applyColorFilter(config.textColor)
},
tabSelectedAction = {
+ if (isSearchOpen) {
+ getCurrentFragment()?.onSearchQueryChanged("")
+ searchMenuItem?.collapseActionView()
+ }
viewpager.currentItem = it.position
it.icon?.applyColorFilter(getAdjustedPrimaryColor())
}
@@ -305,6 +349,37 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
tryImportContactsFromFile(intent.data)
intent.data = null
}
+
+ main_tabs_holder.removeAllTabs()
+ var skippedTabs = 0
+ tabsList.forEachIndexed { index, value ->
+ if (config.showTabs and value == 0) {
+ skippedTabs++
+ } else {
+ main_tabs_holder.addTab(main_tabs_holder.newTab().setIcon(getTabIcon(index)), index - skippedTabs, config.lastUsedViewPagerPage == index - skippedTabs)
+ }
+ }
+
+ // selecting the proper tab sometimes glitches, add an extra selector to make sure we have it right
+ main_tabs_holder.onGlobalLayout {
+ Handler().postDelayed({
+ main_tabs_holder.getTabAt(config.lastUsedViewPagerPage)?.select()
+ invalidateOptionsMenu()
+ }, 100L)
+ }
+
+ main_tabs_holder.beVisibleIf(skippedTabs < 3)
+ }
+
+ private fun getTabIcon(position: Int): Drawable {
+ val drawableId = when (position) {
+ LOCATION_CONTACTS_TAB -> R.drawable.ic_person
+ LOCATION_FAVORITES_TAB -> R.drawable.ic_star_on
+ LOCATION_RECENTS_TAB -> R.drawable.ic_clock
+ else -> R.drawable.ic_group
+ }
+
+ return resources.getColoredDrawableWithColor(drawableId, config.textColor)
}
private fun showSortingDialog() {
@@ -354,10 +429,14 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
return
}
- val inputStream = contentResolver.openInputStream(uri)
- val out = FileOutputStream(tempFile)
- inputStream.copyTo(out)
- showImportContactsDialog(tempFile.absolutePath)
+ try {
+ val inputStream = contentResolver.openInputStream(uri)
+ val out = FileOutputStream(tempFile)
+ inputStream.copyTo(out)
+ showImportContactsDialog(tempFile.absolutePath)
+ } catch (e: Exception) {
+ showErrorToast(e)
+ }
}
else -> toast(R.string.invalid_file_format)
}
@@ -375,13 +454,13 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
FilePickerDialog(this, pickFile = false, showFAB = true) {
ExportContactsDialog(this, it) { file, contactSources ->
Thread {
- ContactsHelper(this).getContacts {
- val contacts = it.filter { contactSources.contains(it.source) }
+ ContactsHelper(this).getContacts { allContacts ->
+ val contacts = allContacts.filter { contactSources.contains(it.source) }
if (contacts.isEmpty()) {
toast(R.string.no_entries_for_exporting)
} else {
- VcfExporter().exportContacts(this, file, contacts as ArrayList, true) {
- toast(when (it) {
+ VcfExporter().exportContacts(this, file, contacts as ArrayList, true) { result ->
+ toast(when (result) {
VcfExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
VcfExporter.ExportResult.EXPORT_PARTIAL -> R.string.exporting_some_entries_failed
else -> R.string.exporting_failed
@@ -395,32 +474,33 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
}
private fun launchAbout() {
+ val licenses = LICENSE_MULTISELECT or LICENSE_JODA or LICENSE_GLIDE or LICENSE_GSON or LICENSE_STETHO
+
val faqItems = arrayListOf(
FAQItem(R.string.faq_1_title, R.string.faq_1_text),
FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons)
)
- startAboutActivity(R.string.app_name, LICENSE_MULTISELECT or LICENSE_JODA or LICENSE_GLIDE or LICENSE_GSON or LICENSE_STETHO,
- BuildConfig.VERSION_NAME, faqItems)
+ startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
}
override fun refreshContacts(refreshTabsMask: Int) {
if (isActivityDestroyed() || isGettingContacts) {
return
}
-
isGettingContacts = true
+
+ if (viewpager.adapter == null) {
+ viewpager.adapter = ViewPagerAdapter(this)
+ viewpager.currentItem = config.lastUsedViewPagerPage
+ }
+
ContactsHelper(this).getContacts {
isGettingContacts = false
if (isActivityDestroyed()) {
return@getContacts
}
- if (viewpager.adapter == null) {
- viewpager.adapter = ViewPagerAdapter(this, it)
- viewpager.currentItem = config.lastUsedViewPagerPage
- }
-
if (refreshTabsMask and CONTACTS_TAB_MASK != 0) {
contacts_fragment?.refreshContacts(it)
}
@@ -429,6 +509,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
favorites_fragment?.refreshContacts(it)
}
+ if (refreshTabsMask and RECENTS_TAB_MASK != 0) {
+ recents_fragment?.refreshContacts(it)
+ }
+
if (refreshTabsMask and GROUPS_TAB_MASK != 0) {
if (refreshTabsMask == GROUPS_TAB_MASK) {
groups_fragment.skipHashComparing = true
@@ -436,15 +520,25 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
groups_fragment?.refreshContacts(it)
}
}
+
+ if (refreshTabsMask and RECENTS_TAB_MASK != 0) {
+ ContactsHelper(this).getRecents {
+ runOnUiThread {
+ recents_fragment?.updateRecentCalls(it)
+ }
+ }
+ }
}
- private fun getAllFragments() = arrayListOf(contacts_fragment, favorites_fragment, groups_fragment)
+ private fun getAllFragments() = arrayListOf(contacts_fragment, favorites_fragment, recents_fragment, groups_fragment)
private fun checkWhatsNewDialog() {
arrayListOf().apply {
add(Release(10, R.string.release_10))
add(Release(11, R.string.release_11))
add(Release(16, R.string.release_16))
+ add(Release(27, R.string.release_27))
+ add(Release(29, R.string.release_29))
checkWhatsNew(this, BuildConfig.VERSION_CODE)
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt
index 1cf3bb24..37811085 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SettingsActivity.kt
@@ -3,10 +3,13 @@ package com.simplemobiletools.contacts.activities
import android.os.Bundle
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.beVisibleIf
+import com.simplemobiletools.commons.extensions.isThankYouInstalled
+import com.simplemobiletools.commons.extensions.launchPurchaseThankYouIntent
import com.simplemobiletools.commons.extensions.updateTextColors
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.dialogs.ManageVisibleFieldsDialog
+import com.simplemobiletools.contacts.dialogs.ManageVisibleTabsDialog
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.helpers.ON_CLICK_CALL_CONTACT
import com.simplemobiletools.contacts.helpers.ON_CLICK_EDIT_CONTACT
@@ -23,8 +26,10 @@ class SettingsActivity : SimpleActivity() {
override fun onResume() {
super.onResume()
+ setupPurchaseThankYou()
setupCustomizeColors()
setupManageShownContactFields()
+ setupManageShownTabs()
setupUseEnglish()
setupAvoidWhatsNew()
setupShowInfoBubble()
@@ -32,10 +37,18 @@ class SettingsActivity : SimpleActivity() {
setupShowPhoneNumbers()
setupStartNameWithSurname()
setupFilterDuplicates()
+ setupShowCallConfirmation()
setupOnContactClick()
updateTextColors(settings_holder)
}
+ private fun setupPurchaseThankYou() {
+ settings_purchase_thank_you_holder.beVisibleIf(config.appRunCount > 10 && !isThankYouInstalled())
+ settings_purchase_thank_you_holder.setOnClickListener {
+ launchPurchaseThankYouIntent()
+ }
+ }
+
private fun setupCustomizeColors() {
settings_customize_colors_holder.setOnClickListener {
startCustomizationActivity()
@@ -48,6 +61,12 @@ class SettingsActivity : SimpleActivity() {
}
}
+ private fun setupManageShownTabs() {
+ settings_manage_tabs_holder.setOnClickListener {
+ ManageVisibleTabsDialog(this)
+ }
+ }
+
private fun setupUseEnglish() {
settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en")
settings_use_english.isChecked = config.useEnglish
@@ -126,4 +145,12 @@ class SettingsActivity : SimpleActivity() {
ON_CLICK_VIEW_CONTACT -> R.string.view_contact
else -> R.string.edit_contact
})
+
+ private fun setupShowCallConfirmation() {
+ settings_show_call_confirmation.isChecked = config.showCallConfirmation
+ settings_show_call_confirmation_holder.setOnClickListener {
+ settings_show_call_confirmation.toggle()
+ config.showCallConfirmation = settings_show_call_confirmation.isChecked
+ }
+ }
}
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 a0cd2ade..3b41f777 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt
@@ -23,6 +23,7 @@ import kotlinx.android.synthetic.main.item_website.view.*
class ViewContactActivity : ContactActivity() {
private var isViewIntent = false
+ private var showFields = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -57,6 +58,10 @@ class ViewContactActivity : ContactActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (contact == null) {
+ return true
+ }
+
when (item.itemId) {
R.id.edit -> editContact(contact!!)
R.id.share -> shareContact()
@@ -160,7 +165,11 @@ class ViewContactActivity : ContactActivity() {
Intent().apply {
action = ContactsContract.QuickContact.ACTION_QUICK_CONTACT
data = getContactPublicUri(contact!!)
- startActivity(this)
+ if (resolveActivity(packageManager) != null) {
+ startActivity(this)
+ } else {
+ toast(R.string.no_app_found)
+ }
}
}
@@ -193,7 +202,11 @@ class ViewContactActivity : ContactActivity() {
contact_suffix.text = suffix
contact_suffix.beVisibleIf(suffix.isNotEmpty() && showFields and SHOW_SUFFIX_FIELD != 0)
- if (contact_prefix.isGone() && contact_first_name.isGone() && contact_middle_name.isGone() && contact_surname.isGone() && contact_suffix.isGone()) {
+ contact_nickname.text = nickname
+ contact_nickname.beVisibleIf(nickname.isNotEmpty() && showFields and SHOW_NICKNAME_FIELD != 0)
+
+ if (contact_prefix.isGone() && contact_first_name.isGone() && contact_middle_name.isGone() && contact_surname.isGone() && contact_suffix.isGone()
+ && contact_nickname.isGone()) {
contact_name_image.beInvisible()
(contact_photo.layoutParams as RelativeLayout.LayoutParams).bottomMargin = resources.getDimension(R.dimen.medium_margin).toInt()
}
@@ -280,7 +293,7 @@ class ViewContactActivity : ContactActivity() {
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)
+ it.value.getDateTimeFromDateString(contact_event)
contact_event_type.setText(getEventTextId(it.type))
contact_event_remove.beGone()
}
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 fd9db502..39bd3220 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt
@@ -70,10 +70,10 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList
+ val view = holder.bindView(contact, true, true) { itemView, layoutPosition ->
setupView(itemView, contact)
}
bindViewHolder(holder, position, view)
@@ -145,7 +147,10 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList()
selectedPositions.sortedDescending().forEach {
- contactsToRemove.add(contactItems[it])
+ val contact = contactItems.getOrNull(it)
+ if (contact != null) {
+ contactsToRemove.add(contact)
+ }
}
contactItems.removeAll(contactsToRemove)
@@ -155,7 +160,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList, val
override fun prepareActionMode(menu: Menu) {
menu.apply {
- findItem(R.id.cab_edit).isVisible = isOneItemSelected()
+ findItem(R.id.cab_rename).isVisible = isOneItemSelected()
}
}
- override fun prepareItemSelection(view: View) {}
+ override fun prepareItemSelection(viewHolder: ViewHolder) {}
- override fun markItemSelection(select: Boolean, view: View?) {
- view?.group_frame?.isSelected = select
+ override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {
+ viewHolder?.itemView?.group_frame?.isSelected = select
}
override fun actionItemPressed(id: Int) {
@@ -53,7 +53,7 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val
}
when (id) {
- R.id.cab_edit -> editGroup()
+ R.id.cab_rename -> renameGroup()
R.id.cab_select_all -> selectAll()
R.id.cab_delete -> askConfirmDelete()
}
@@ -61,11 +61,13 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val
override fun getSelectableItemCount() = groups.size
+ override fun getIsItemSelectable(position: Int) = true
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_group, parent)
override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
val group = groups[position]
- val view = holder.bindView(group, true) { itemView, layoutPosition ->
+ val view = holder.bindView(group, true, true) { itemView, layoutPosition ->
setupView(itemView, group)
}
bindViewHolder(holder, position, view)
@@ -80,7 +82,7 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val
fastScroller?.measureRecyclerView()
}
- private fun editGroup() {
+ private fun renameGroup() {
RenameGroupDialog(activity, groups[selectedPositions.first()]) {
finishActMode()
refreshListener?.refreshContacts(GROUPS_TAB_MASK)
@@ -89,11 +91,11 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val
private fun askConfirmDelete() {
ConfirmationDialog(activity) {
- deleteContacts()
+ deleteGroups()
}
}
- private fun deleteContacts() {
+ private fun deleteGroups() {
if (selectedPositions.isEmpty()) {
return
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt
new file mode 100644
index 00000000..e54d8d2b
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/RecentCallsAdapter.kt
@@ -0,0 +1,119 @@
+package com.simplemobiletools.contacts.adapters
+
+import android.view.Menu
+import android.view.View
+import android.view.ViewGroup
+import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
+import com.simplemobiletools.commons.dialogs.ConfirmationDialog
+import com.simplemobiletools.commons.extensions.beVisibleIf
+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.helpers.ContactsHelper
+import com.simplemobiletools.contacts.helpers.RECENTS_TAB_MASK
+import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
+import com.simplemobiletools.contacts.models.RecentCall
+import kotlinx.android.synthetic.main.item_recent_call.view.*
+import java.util.*
+
+class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList, val refreshListener: RefreshContactsListener?, recyclerView: MyRecyclerView,
+ fastScroller: FastScroller, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
+ private val showPhoneNumbers = activity.config.showPhoneNumbers
+
+ init {
+ setupDragListener(true)
+ }
+
+ override fun getActionMenuId() = R.menu.cab_recent_calls
+
+ override fun prepareActionMode(menu: Menu) {}
+
+ override fun prepareItemSelection(viewHolder: ViewHolder) {}
+
+ override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {
+ viewHolder?.itemView?.recent_call_frame?.isSelected = select
+ }
+
+ override fun actionItemPressed(id: Int) {
+ if (selectedPositions.isEmpty()) {
+ return
+ }
+
+ when (id) {
+ R.id.cab_select_all -> selectAll()
+ R.id.cab_delete -> askConfirmDelete()
+ }
+ }
+
+ override fun getSelectableItemCount() = recentCalls.size
+
+ override fun getIsItemSelectable(position: Int) = true
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_recent_call, parent)
+
+ override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
+ val recentCall = recentCalls[position]
+ val view = holder.bindView(recentCall, true, true) { itemView, layoutPosition ->
+ setupView(itemView, recentCall)
+ }
+ bindViewHolder(holder, position, view)
+ }
+
+ override fun getItemCount() = recentCalls.size
+
+ fun updateItems(newItems: ArrayList) {
+ recentCalls = newItems
+ notifyDataSetChanged()
+ finishActMode()
+ fastScroller?.measureRecyclerView()
+ }
+
+ private fun askConfirmDelete() {
+ ConfirmationDialog(activity) {
+ deleteRecentCalls()
+ }
+ }
+
+ private fun deleteRecentCalls() {
+ if (selectedPositions.isEmpty()) {
+ return
+ }
+
+ val callsToRemove = ArrayList()
+ selectedPositions.sortedDescending().forEach {
+ val call = recentCalls[it]
+ callsToRemove.add(call)
+ }
+ ContactsHelper(activity).removeRecentCalls(callsToRemove.map { it.id } as ArrayList)
+ recentCalls.removeAll(callsToRemove)
+
+ if (recentCalls.isEmpty()) {
+ refreshListener?.refreshContacts(RECENTS_TAB_MASK)
+ finishActMode()
+ } else {
+ removeSelectedItems()
+ }
+ }
+
+ private fun setupView(view: View, recentCall: RecentCall) {
+ view.apply {
+ recent_call_name.apply {
+ text = recentCall.name ?: recentCall.number
+ setTextColor(textColor)
+ }
+
+ recent_call_number.apply {
+ beVisibleIf(showPhoneNumbers && recentCall.name != null)
+ text = recentCall.number
+ setTextColor(textColor)
+ }
+
+ recent_call_date_time.apply {
+ text = recentCall.dateTime
+ setTextColor(textColor)
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt
index beb17f42..c78ffaef 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt
@@ -5,19 +5,22 @@ import android.view.View
import android.view.ViewGroup
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.activities.MainActivity
+import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.fragments.MyViewPagerFragment
-import com.simplemobiletools.contacts.models.Contact
+import com.simplemobiletools.contacts.helpers.*
-class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList) : PagerAdapter() {
+class ViewPagerAdapter(val activity: MainActivity) : PagerAdapter() {
+ private val showTabs = activity.config.showTabs
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val layout = getFragment(position)
val view = activity.layoutInflater.inflate(layout, container, false)
container.addView(view)
+
(view as MyViewPagerFragment).apply {
setupFragment(activity)
- refreshContacts(contacts)
}
+
return view
}
@@ -25,12 +28,28 @@ class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList R.layout.fragment_contacts
- 1 -> R.layout.fragment_favorites
- else -> R.layout.fragment_groups
+ private fun getFragment(position: Int): Int {
+ val fragments = arrayListOf()
+ if (showTabs and CONTACTS_TAB_MASK != 0) {
+ fragments.add(R.layout.fragment_contacts)
+ }
+
+ if (showTabs and FAVORITES_TAB_MASK != 0) {
+ fragments.add(R.layout.fragment_favorites)
+ }
+
+ if (showTabs and RECENTS_TAB_MASK != 0) {
+ fragments.add(R.layout.fragment_recents)
+ }
+
+ if (showTabs and GROUPS_TAB_MASK != 0) {
+ fragments.add(R.layout.fragment_groups)
+ }
+
+ return fragments[position]
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt
new file mode 100644
index 00000000..40f3ef89
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CallConfirmationDialog.kt
@@ -0,0 +1,33 @@
+package com.simplemobiletools.contacts.dialogs
+
+import android.support.v7.app.AlertDialog
+import android.view.animation.AnimationUtils
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.applyColorFilter
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.extensions.config
+import com.simplemobiletools.contacts.models.Contact
+import kotlinx.android.synthetic.main.dialog_call_confirmation.view.*
+
+class CallConfirmationDialog(val activity: BaseSimpleActivity, val contact: Contact, private val callback: () -> Unit) {
+ private var view = activity.layoutInflater.inflate(R.layout.dialog_call_confirmation, null)
+
+ init {
+ view.call_confirm_phone.applyColorFilter(activity.config.textColor)
+ AlertDialog.Builder(activity)
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ val title = String.format(activity.getString(R.string.call_person), contact.getFullName())
+ activity.setupDialogStuff(view, this, titleText = title) {
+ view.call_confirm_phone.apply {
+ startAnimation(AnimationUtils.loadAnimation(activity, R.anim.pulsing_animation))
+ setOnClickListener {
+ callback.invoke()
+ dismiss()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt
index cb0b8424..9560f4b2 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleFieldsDialog.kt
@@ -19,6 +19,7 @@ class ManageVisibleFieldsDialog(val activity: BaseSimpleActivity) {
put(SHOW_MIDDLE_NAME_FIELD, R.id.manage_visible_fields_middle_name)
put(SHOW_SURNAME_FIELD, R.id.manage_visible_fields_surname)
put(SHOW_SUFFIX_FIELD, R.id.manage_visible_fields_suffix)
+ put(SHOW_NICKNAME_FIELD, R.id.manage_visible_fields_nickname)
put(SHOW_PHONE_NUMBERS_FIELD, R.id.manage_visible_fields_phone_numbers)
put(SHOW_EMAILS_FIELD, R.id.manage_visible_fields_emails)
put(SHOW_ADDRESSES_FIELD, R.id.manage_visible_fields_addresses)
@@ -36,7 +37,7 @@ class ManageVisibleFieldsDialog(val activity: BaseSimpleActivity) {
}
AlertDialog.Builder(activity)
- .setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() })
+ .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
.setNegativeButton(R.string.cancel, null)
.create().apply {
activity.setupDialogStuff(view, this)
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt
new file mode 100644
index 00000000..241c62cf
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/ManageVisibleTabsDialog.kt
@@ -0,0 +1,50 @@
+package com.simplemobiletools.contacts.dialogs
+
+import android.support.v7.app.AlertDialog
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import com.simplemobiletools.commons.views.MyAppCompatCheckbox
+import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.extensions.config
+import com.simplemobiletools.contacts.helpers.*
+
+class ManageVisibleTabsDialog(val activity: BaseSimpleActivity) {
+ private var view = activity.layoutInflater.inflate(R.layout.dialog_manage_visible_tabs, null)
+ private val tabs = LinkedHashMap()
+
+ init {
+ tabs.apply {
+ put(CONTACTS_TAB_MASK, R.id.manage_visible_tabs_contacts)
+ put(FAVORITES_TAB_MASK, R.id.manage_visible_tabs_favorites)
+ put(RECENTS_TAB_MASK, R.id.manage_visible_tabs_recents)
+ put(GROUPS_TAB_MASK, R.id.manage_visible_tabs_groups)
+ }
+
+ val showTabs = activity.config.showTabs
+ for ((key, value) in tabs) {
+ view.findViewById(value).isChecked = showTabs and key != 0
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+
+ private fun dialogConfirmed() {
+ var result = 0
+ for ((key, value) in tabs) {
+ if (view.findViewById(value).isChecked) {
+ result += key
+ }
+ }
+
+ if (result == 0) {
+ result = ALL_TABS_MASK
+ }
+
+ activity.config.showTabs = result
+ }
+}
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 7044edc6..a63eb96d 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt
@@ -14,9 +14,8 @@ import com.simplemobiletools.commons.models.RadioItem
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.dialogs.CallConfirmationDialog
+import com.simplemobiletools.contacts.helpers.*
import com.simplemobiletools.contacts.models.Contact
import com.simplemobiletools.contacts.models.ContactSource
import java.io.File
@@ -36,6 +35,16 @@ fun SimpleActivity.startCallIntent(recipient: String) {
}
fun SimpleActivity.tryStartCall(contact: Contact) {
+ if (config.showCallConfirmation) {
+ CallConfirmationDialog(this, contact) {
+ startCall(contact)
+ }
+ } else {
+ startCall(contact)
+ }
+}
+
+fun SimpleActivity.startCall(contact: Contact) {
val numbers = contact.phoneNumbers
if (numbers.size == 1) {
startCallIntent(numbers.first().value)
@@ -105,10 +114,10 @@ fun BaseSimpleActivity.shareContacts(contacts: ArrayList) {
fun BaseSimpleActivity.sendSMSToContacts(contacts: ArrayList) {
val numbers = StringBuilder()
contacts.forEach {
- it.phoneNumbers.forEach {
- if (it.value.isNotEmpty()) {
- numbers.append("${it.value};")
- }
+ val number = it.phoneNumbers.firstOrNull { it.type == ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE }
+ ?: it.phoneNumbers.firstOrNull()
+ if (number != null) {
+ numbers.append("${number.value};")
}
val uriString = "smsto:${numbers.toString().trimEnd(';')}"
@@ -191,3 +200,17 @@ fun Activity.getVisibleContactSources(): ArrayList {
sourceNames.removeAll(config.ignoredContactSources)
return sourceNames
}
+
+fun SimpleActivity.contactClicked(contact: Contact) {
+ when (config.onContactClick) {
+ ON_CLICK_CALL_CONTACT -> {
+ if (contact.phoneNumbers.isNotEmpty()) {
+ tryStartCall(contact)
+ } else {
+ toast(R.string.no_phone_number_found)
+ }
+ }
+ ON_CLICK_VIEW_CONTACT -> viewContact(contact)
+ ON_CLICK_EDIT_CONTACT -> editContact(contact)
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt
new file mode 100644
index 00000000..6134b1ad
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/String.kt
@@ -0,0 +1,34 @@
+package com.simplemobiletools.contacts.extensions
+
+import android.widget.TextView
+import com.simplemobiletools.commons.helpers.getDateFormats
+import org.joda.time.DateTime
+import org.joda.time.format.DateTimeFormat
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.*
+
+fun String.getDateTimeFromDateString(viewToUpdate: TextView? = null): DateTime {
+ val dateFormats = getDateFormats()
+ var date = DateTime()
+ for (format in dateFormats) {
+ try {
+ date = DateTime.parse(this, DateTimeFormat.forPattern(format))
+
+ val formatter = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault())
+ var localPattern = (formatter as SimpleDateFormat).toLocalizedPattern()
+
+ val hasYear = format.contains("y")
+ if (!hasYear) {
+ localPattern = localPattern.replace("y", "").trim()
+ date = date.withYear(DateTime().year)
+ }
+
+ val formattedString = date.toString(localPattern)
+ viewToUpdate?.text = formattedString
+ break
+ } catch (ignored: Exception) {
+ }
+ }
+ return date
+}
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 853b1292..7afecd04 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt
@@ -15,7 +15,10 @@ import com.simplemobiletools.contacts.activities.MainActivity
import com.simplemobiletools.contacts.activities.SimpleActivity
import com.simplemobiletools.contacts.adapters.ContactsAdapter
import com.simplemobiletools.contacts.adapters.GroupsAdapter
-import com.simplemobiletools.contacts.extensions.*
+import com.simplemobiletools.contacts.adapters.RecentCallsAdapter
+import com.simplemobiletools.contacts.extensions.config
+import com.simplemobiletools.contacts.extensions.contactClicked
+import com.simplemobiletools.contacts.extensions.getVisibleContactSources
import com.simplemobiletools.contacts.helpers.*
import com.simplemobiletools.contacts.models.Contact
import com.simplemobiletools.contacts.models.Group
@@ -47,21 +50,28 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
fragment_placeholder_2.underlineText()
updateViewStuff()
- if (this is FavoritesFragment) {
- fragment_placeholder.text = activity.getString(R.string.no_favorites)
- fragment_placeholder_2.text = activity.getString(R.string.add_favorites)
- } else if (this is GroupsFragment) {
- fragment_placeholder.text = activity.getString(R.string.no_group_created)
- fragment_placeholder_2.text = activity.getString(R.string.create_group)
+ when {
+ this is FavoritesFragment -> {
+ fragment_placeholder.text = activity.getString(R.string.no_favorites)
+ fragment_placeholder_2.text = activity.getString(R.string.add_favorites)
+ }
+ this is GroupsFragment -> {
+ fragment_placeholder.text = activity.getString(R.string.no_group_created)
+ fragment_placeholder_2.text = activity.getString(R.string.create_group)
+ }
+ this is RecentsFragment -> {
+ fragment_fab.beGone()
+ fragment_placeholder_2.text = activity.getString(R.string.request_the_required_permissions)
+ }
}
}
}
fun textColorChanged(color: Int) {
- if (this is GroupsFragment) {
- (fragment_list.adapter as GroupsAdapter).updateTextColor(color)
- } else {
- (fragment_list.adapter as ContactsAdapter).apply {
+ when {
+ this is GroupsFragment -> (fragment_list.adapter as GroupsAdapter).updateTextColor(color)
+ this is RecentsFragment -> (fragment_list.adapter as RecentCallsAdapter).updateTextColor(color)
+ else -> (fragment_list.adapter as ContactsAdapter).apply {
updateTextColor(color)
initDrawables()
}
@@ -77,7 +87,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
fun startNameWithSurnameChanged(startNameWithSurname: Boolean) {
- if (this !is GroupsFragment) {
+ if (this !is GroupsFragment && this !is RecentsFragment) {
(fragment_list.adapter as ContactsAdapter).apply {
config.sorting = if (startNameWithSurname) SORT_BY_SURNAME else SORT_BY_FIRST_NAME
this@MyViewPagerFragment.activity!!.refreshContacts(CONTACTS_TAB_MASK or FAVORITES_TAB_MASK)
@@ -86,6 +96,13 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
fun refreshContacts(contacts: ArrayList) {
+ if ((config.showTabs and CONTACTS_TAB_MASK == 0 && this is ContactsFragment) ||
+ (config.showTabs and FAVORITES_TAB_MASK == 0 && this is FavoritesFragment) ||
+ (config.showTabs and RECENTS_TAB_MASK == 0 && this is RecentsFragment) ||
+ (config.showTabs and GROUPS_TAB_MASK == 0 && this is GroupsFragment)) {
+ return
+ }
+
if (config.lastUsedContactSource.isEmpty()) {
val grouped = contacts.groupBy { it.source }.maxWith(compareBy { it.value.size })
config.lastUsedContactSource = grouped?.key ?: ""
@@ -99,6 +116,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
val filtered = when {
this is GroupsFragment -> contacts
this is FavoritesFragment -> contacts.filter { it.starred == 1 } as ArrayList
+ this is RecentsFragment -> ArrayList()
else -> {
val contactSources = activity!!.getVisibleContactSources()
contacts.filter { contactSources.contains(it.source) } as ArrayList
@@ -117,9 +135,13 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
private fun setupContacts(contacts: ArrayList) {
if (this is GroupsFragment) {
setupGroupsAdapter(contacts)
- } else {
+ } else if (this !is RecentsFragment) {
setupContactsFavoritesAdapter(contacts)
}
+
+ if (this is ContactsFragment || this is FavoritesFragment) {
+ contactsIgnoringSearch = (fragment_list?.adapter as? ContactsAdapter)?.contactItems ?: ArrayList()
+ }
}
private fun setupGroupsAdapter(contacts: ArrayList) {
@@ -150,7 +172,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
fragment_list.adapter = this
}
- fragment_fastscroller.setScrollTo(0)
+ fragment_fastscroller.setScrollToY(0)
fragment_fastscroller.setViews(fragment_list) {
val item = (fragment_list.adapter as GroupsAdapter).groups.getOrNull(it)
fragment_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
@@ -164,33 +186,19 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
private fun setupContactsFavoritesAdapter(contacts: ArrayList) {
- fragment_placeholder_2.beVisibleIf(contacts.isEmpty())
- fragment_placeholder.beVisibleIf(contacts.isEmpty())
- fragment_list.beVisibleIf(contacts.isNotEmpty())
-
+ setupViewVisibility(contacts)
val currAdapter = fragment_list.adapter
if (currAdapter == null || forceListRedraw) {
forceListRedraw = false
val location = if (this is FavoritesFragment) LOCATION_FAVORITES_TAB else LOCATION_CONTACTS_TAB
ContactsAdapter(activity as SimpleActivity, contacts, activity, location, null, fragment_list, fragment_fastscroller) {
- when (config.onContactClick) {
- ON_CLICK_CALL_CONTACT -> {
- val contact = it as Contact
- if (contact.phoneNumbers.isNotEmpty()) {
- (activity as SimpleActivity).tryStartCall(it)
- } else {
- activity!!.toast(R.string.no_phone_number_found)
- }
- }
- ON_CLICK_VIEW_CONTACT -> context!!.viewContact(it as Contact)
- ON_CLICK_EDIT_CONTACT -> context!!.editContact(it as Contact)
- }
+ activity?.contactClicked(it as Contact)
}.apply {
addVerticalDividers(true)
fragment_list.adapter = this
}
- fragment_fastscroller.setScrollTo(0)
+ fragment_fastscroller.setScrollToY(0)
fragment_fastscroller.setViews(fragment_list) {
val item = (fragment_list.adapter as ContactsAdapter).contactItems.getOrNull(it)
fragment_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
@@ -211,7 +219,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
showContactThumbnails = showThumbnails
notifyDataSetChanged()
}
- } else {
+ } else if (this !is RecentsFragment) {
(fragment_list.adapter as? ContactsAdapter)?.apply {
showContactThumbnails = showThumbnails
notifyDataSetChanged()
@@ -228,38 +236,44 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
fun onSearchQueryChanged(text: String) {
+ val shouldNormalize = text.normalizeString() == text
(fragment_list.adapter as? ContactsAdapter)?.apply {
val filtered = contactsIgnoringSearch.filter {
- it.getFullName().contains(text, true) ||
+ getProperText(it.getFullName(), shouldNormalize).contains(text, true) ||
+ getProperText(it.nickname, shouldNormalize).contains(text, true) ||
it.phoneNumbers.any { it.value.contains(text, true) } ||
it.emails.any { it.value.contains(text, true) } ||
- it.addresses.any { it.value.contains(text, true) } ||
- it.notes.contains(text, true) ||
- it.organization.company.contains(text, true) ||
- it.organization.jobPosition.contains(text, true) ||
+ it.addresses.any { getProperText(it.value, shouldNormalize).contains(text, true) } ||
+ getProperText(it.notes, shouldNormalize).contains(text, true) ||
+ getProperText(it.organization.company, shouldNormalize).contains(text, true) ||
+ getProperText(it.organization.jobPosition, shouldNormalize).contains(text, true) ||
it.websites.any { it.contains(text, true) }
} as ArrayList
Contact.sorting = config.sorting
Contact.startWithSurname = config.startNameWithSurname
filtered.sort()
- filtered.sortBy { !it.getFullName().startsWith(text, true) }
+ filtered.sortBy { !getProperText(it.getFullName(), shouldNormalize).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, text)
+ updateItems(filtered, text.normalizeString())
}
}
+ private fun getProperText(text: String, shouldNormalize: Boolean) = if (shouldNormalize) text.normalizeString() else text
+
fun onSearchOpened() {
contactsIgnoringSearch = (fragment_list?.adapter as? ContactsAdapter)?.contactItems ?: ArrayList()
}
fun onSearchClosed() {
(fragment_list.adapter as? ContactsAdapter)?.updateItems(contactsIgnoringSearch)
+ setupViewVisibility(contactsIgnoringSearch)
+
if (this is FavoritesFragment) {
fragment_placeholder.text = activity?.getString(R.string.no_favorites)
}
@@ -272,6 +286,12 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
fragment_placeholder_2.setTextColor(context.getAdjustedPrimaryColor())
}
+ private fun setupViewVisibility(contacts: ArrayList) {
+ fragment_placeholder_2.beVisibleIf(contacts.isEmpty())
+ fragment_placeholder.beVisibleIf(contacts.isEmpty())
+ fragment_list.beVisibleIf(contacts.isNotEmpty())
+ }
+
abstract fun fabClicked()
abstract fun placeholderClicked()
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt
new file mode 100644
index 00000000..c1319997
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/RecentsFragment.kt
@@ -0,0 +1,79 @@
+package com.simplemobiletools.contacts.fragments
+
+import android.content.Context
+import android.content.Intent
+import android.util.AttributeSet
+import com.simplemobiletools.commons.extensions.beVisibleIf
+import com.simplemobiletools.commons.extensions.hasPermission
+import com.simplemobiletools.commons.extensions.isActivityDestroyed
+import com.simplemobiletools.commons.helpers.PERMISSION_READ_CALL_LOG
+import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALL_LOG
+import com.simplemobiletools.contacts.activities.EditContactActivity
+import com.simplemobiletools.contacts.adapters.RecentCallsAdapter
+import com.simplemobiletools.contacts.extensions.contactClicked
+import com.simplemobiletools.contacts.helpers.IS_FROM_SIMPLE_CONTACTS
+import com.simplemobiletools.contacts.helpers.KEY_PHONE
+import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN
+import com.simplemobiletools.contacts.helpers.RECENTS_TAB_MASK
+import com.simplemobiletools.contacts.models.Contact
+import com.simplemobiletools.contacts.models.RecentCall
+import kotlinx.android.synthetic.main.fragment_layout.view.*
+
+class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet) {
+ override fun fabClicked() {}
+
+ override fun placeholderClicked() {
+ activity!!.handlePermission(PERMISSION_WRITE_CALL_LOG) {
+ if (it) {
+ activity!!.handlePermission(PERMISSION_READ_CALL_LOG) {
+ activity?.refreshContacts(RECENTS_TAB_MASK)
+ }
+ }
+ }
+ }
+
+ fun updateRecentCalls(recentCalls: ArrayList) {
+ if (activity == null || activity!!.isActivityDestroyed()) {
+ return
+ }
+
+ fragment_placeholder.beVisibleIf(recentCalls.isEmpty())
+ fragment_placeholder_2.beVisibleIf(recentCalls.isEmpty() && !activity!!.hasPermission(PERMISSION_WRITE_CALL_LOG))
+ fragment_list.beVisibleIf(recentCalls.isNotEmpty())
+
+ val currAdapter = fragment_list.adapter
+ if (currAdapter == null) {
+ RecentCallsAdapter(activity!!, recentCalls, activity, fragment_list, fragment_fastscroller) {
+ val recentCall = (it as RecentCall).number.replace(PHONE_NUMBER_PATTERN.toRegex(), "")
+ var selectedContact: Contact? = null
+ for (contact in allContacts) {
+ if (contact.phoneNumbers.any { it.value.replace(PHONE_NUMBER_PATTERN.toRegex(), "") == recentCall }) {
+ selectedContact = contact
+ break
+ }
+ }
+
+ if (selectedContact != null) {
+ activity?.contactClicked(selectedContact)
+ } else {
+ Intent(context, EditContactActivity::class.java).apply {
+ action = Intent.ACTION_INSERT
+ putExtra(KEY_PHONE, recentCall)
+ putExtra(IS_FROM_SIMPLE_CONTACTS, true)
+ context.startActivity(this)
+ }
+ }
+ }.apply {
+ addVerticalDividers(true)
+ fragment_list.adapter = this
+ }
+
+ fragment_fastscroller.setViews(fragment_list) {
+ val item = (fragment_list.adapter as RecentCallsAdapter).recentCalls.getOrNull(it)
+ fragment_fastscroller.updateBubbleText(item?.name ?: item?.number ?: "")
+ }
+ } else {
+ (currAdapter as RecentCallsAdapter).updateItems(recentCalls)
+ }
+ }
+}
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 de1bb860..2370665b 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Config.kt
@@ -48,4 +48,12 @@ class Config(context: Context) : BaseConfig(context) {
var filterDuplicates: Boolean
get() = prefs.getBoolean(FILTER_DUPLICATES, true)
set(filterDuplicates) = prefs.edit().putBoolean(FILTER_DUPLICATES, filterDuplicates).apply()
+
+ var showTabs: Int
+ get() = prefs.getInt(SHOW_TABS, ALL_TABS_MASK)
+ set(showTabs) = prefs.edit().putInt(SHOW_TABS, showTabs).apply()
+
+ var showCallConfirmation: Boolean
+ get() = prefs.getBoolean(SHOW_CALL_CONFIRMATION, false)
+ set(showCallConfirmation) = prefs.edit().putBoolean(SHOW_CALL_CONFIRMATION, showCallConfirmation).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 5409d2dd..0f56d22b 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt
@@ -12,23 +12,39 @@ const val LOCAL_ACCOUNT_NAME = "local_account_name"
const val LOCAL_ACCOUNT_TYPE = "local_account_type"
const val ON_CONTACT_CLICK = "on_contact_click"
const val SHOW_CONTACT_FIELDS = "show_contact_fields"
+const val SHOW_TABS = "show_tabs"
const val FILTER_DUPLICATES = "filter_duplicates"
+const val SHOW_CALL_CONFIRMATION = "show_call_confirmation"
const val CONTACT_ID = "contact_id"
const val SMT_PRIVATE = "smt_private" // used at the contact source of local contacts hidden from other apps
const val IS_PRIVATE = "is_private"
const val GROUP = "group"
const val FIRST_GROUP_ID = 10000
+const val PHONE_NUMBER_PATTERN = "\\D+"
+const val IS_FROM_SIMPLE_CONTACTS = "is_from_simple_contacts"
+
+// extras used at third party intents
+const val KEY_PHONE = "phone"
+const val KEY_NAME = "name"
const val LOCATION_CONTACTS_TAB = 0
const val LOCATION_FAVORITES_TAB = 1
-const val LOCATION_GROUPS_TAB = 2
-const val LOCATION_GROUP_CONTACTS = 3
+const val LOCATION_RECENTS_TAB = 2
+const val LOCATION_GROUPS_TAB = 3
+const val LOCATION_GROUP_CONTACTS = 4
const val CONTACTS_TAB_MASK = 1
const val FAVORITES_TAB_MASK = 2
-const val GROUPS_TAB_MASK = 4
-const val ALL_TABS_MASK = 7
+const val RECENTS_TAB_MASK = 4
+const val GROUPS_TAB_MASK = 8
+const val ALL_TABS_MASK = 15
+
+val tabsList = arrayListOf(CONTACTS_TAB_MASK,
+ FAVORITES_TAB_MASK,
+ RECENTS_TAB_MASK,
+ GROUPS_TAB_MASK
+)
// contact photo changes
const val PHOTO_ADDED = 1
@@ -36,25 +52,6 @@ const val PHOTO_REMOVED = 2
const val PHOTO_CHANGED = 3
const val PHOTO_UNCHANGED = 4
-// export/import
-const val BEGIN_VCARD = "BEGIN:VCARD"
-const val END_VCARD = "END:VCARD"
-const val N = "N"
-const val TEL = "TEL"
-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 ORG = "ORG:"
-const val TITLE = "TITLE:"
-const val URL = "URL:"
-const val ENCODING = "ENCODING"
-const val BASE64 = "BASE64"
-const val JPEG = "JPEG"
-const val VERSION_2_1 = "VERSION:2.1"
-
// phone number/email types
const val CELL = "CELL"
const val WORK = "WORK"
@@ -66,7 +63,6 @@ const val WORK_FAX = "WORK;FAX"
const val HOME_FAX = "HOME;FAX"
const val PAGER = "PAGER"
const val MOBILE = "MOBILE"
-const val VOICE = "VOICE"
const val ON_CLICK_CALL_CONTACT = 1
const val ON_CLICK_VIEW_CONTACT = 2
@@ -87,6 +83,7 @@ const val SHOW_ORGANIZATION_FIELD = 1024
const val SHOW_GROUPS_FIELD = 2048
const val SHOW_CONTACT_SOURCE_FIELD = 4096
const val SHOW_WEBSITES_FIELD = 8192
+const val SHOW_NICKNAME_FIELD = 16384
const val DEFAULT_EMAIL_TYPE = CommonDataKinds.Email.TYPE_HOME
const val DEFAULT_PHONE_NUMBER_TYPE = CommonDataKinds.Phone.TYPE_MOBILE
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 e52181fa..17d26c16 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt
@@ -1,26 +1,29 @@
package com.simplemobiletools.contacts.helpers
+import android.accounts.Account
import android.accounts.AccountManager
+import android.annotation.SuppressLint
import android.app.Activity
import android.content.*
import android.database.Cursor
import android.graphics.Bitmap
import android.net.Uri
+import android.provider.CallLog
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
+import android.provider.ContactsContract.CommonDataKinds.Nickname
import android.provider.ContactsContract.CommonDataKinds.Note
import android.provider.MediaStore
import android.text.TextUtils
import android.util.SparseArray
import com.simplemobiletools.commons.extensions.*
-import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME
-import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME
-import com.simplemobiletools.commons.helpers.SORT_BY_SURNAME
-import com.simplemobiletools.commons.helpers.SORT_DESCENDING
+import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.extensions.*
import com.simplemobiletools.contacts.models.*
import com.simplemobiletools.contacts.overloads.times
+import java.text.SimpleDateFormat
+import java.util.*
class ContactsHelper(val activity: Activity) {
private val BATCH_SIZE = 100
@@ -61,6 +64,33 @@ class ContactsHelper(val activity: Activity) {
}.start()
}
+ private fun getContentResolverAccounts(): HashSet {
+ val uri = ContactsContract.Data.CONTENT_URI
+ val projection = arrayOf(
+ ContactsContract.RawContacts.ACCOUNT_NAME,
+ ContactsContract.RawContacts.ACCOUNT_TYPE
+ )
+
+ val sources = HashSet()
+ var cursor: Cursor? = null
+ try {
+ cursor = activity.contentResolver.query(uri, projection, null, null, null)
+ if (cursor?.moveToFirst() == true) {
+ do {
+ val name = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: ""
+ val type = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_TYPE) ?: ""
+ val source = ContactSource(name, type)
+ sources.add(source)
+ } while (cursor.moveToNext())
+ }
+ } catch (e: Exception) {
+ } finally {
+ cursor?.close()
+ }
+
+ return sources
+ }
+
private fun getDeviceContacts(contacts: SparseArray) {
if (!activity.hasContactPermissions()) {
return
@@ -83,6 +113,7 @@ class ContactsHelper(val activity: Activity) {
val middleName = cursor.getStringValue(CommonDataKinds.StructuredName.MIDDLE_NAME) ?: ""
val surname = cursor.getStringValue(CommonDataKinds.StructuredName.FAMILY_NAME) ?: ""
val suffix = cursor.getStringValue(CommonDataKinds.StructuredName.SUFFIX) ?: ""
+ val nickname = ""
val photoUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_URI) ?: ""
val number = ArrayList() // proper value is obtained below
val emails = ArrayList()
@@ -96,8 +127,8 @@ class ContactsHelper(val activity: Activity) {
val groups = ArrayList()
val organization = Organization("", "")
val websites = ArrayList()
- val contact = Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events,
- accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites)
+ val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses,
+ events, accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites)
contacts.put(id, contact)
} while (cursor.moveToNext())
@@ -115,6 +146,13 @@ class ContactsHelper(val activity: Activity) {
contacts[key]?.phoneNumbers = phoneNumbers.valueAt(i)
}
+ val nicknames = getNicknames()
+ size = nicknames.size()
+ for (i in 0 until size) {
+ val key = nicknames.keyAt(i)
+ contacts[key]?.nickname = nicknames.valueAt(i)
+ }
+
val emails = getEmails()
size = emails.size()
for (i in 0 until size) {
@@ -196,6 +234,36 @@ class ContactsHelper(val activity: Activity) {
return phoneNumbers
}
+ private fun getNicknames(contactId: Int? = null): SparseArray {
+ val nicknames = SparseArray()
+ val uri = ContactsContract.Data.CONTENT_URI
+ val projection = arrayOf(
+ ContactsContract.Data.RAW_CONTACT_ID,
+ Nickname.NAME
+ )
+
+ val selection = getSourcesSelection(true, contactId != null)
+ val selectionArgs = getSourcesSelectionArgs(Nickname.CONTENT_ITEM_TYPE, contactId)
+
+ var cursor: Cursor? = null
+ try {
+ cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ if (cursor?.moveToFirst() == true) {
+ do {
+ val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
+ val nickname = cursor.getStringValue(Nickname.NAME) ?: continue
+ nicknames.put(id, nickname)
+ } while (cursor.moveToNext())
+ }
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ } finally {
+ cursor?.close()
+ }
+
+ return nicknames
+ }
+
private fun getEmails(contactId: Int? = null): SparseArray> {
val emails = SparseArray>()
val uri = CommonDataKinds.Email.CONTENT_URI
@@ -251,7 +319,7 @@ class ContactsHelper(val activity: Activity) {
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
- val address = cursor.getStringValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) ?: ""
+ val address = cursor.getStringValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS) ?: continue
val type = cursor.getIntValue(CommonDataKinds.StructuredPostal.TYPE)
if (addresses[id] == null) {
@@ -324,7 +392,7 @@ class ContactsHelper(val activity: Activity) {
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
- val note = cursor.getStringValue(CommonDataKinds.Note.NOTE) ?: continue
+ val note = cursor.getStringValue(Note.NOTE) ?: continue
notes.put(id, note)
} while (cursor.moveToNext())
}
@@ -355,8 +423,12 @@ class ContactsHelper(val activity: Activity) {
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
- val company = cursor.getStringValue(CommonDataKinds.Organization.COMPANY) ?: continue
- val title = cursor.getStringValue(CommonDataKinds.Organization.TITLE) ?: continue
+ val company = cursor.getStringValue(CommonDataKinds.Organization.COMPANY) ?: ""
+ val title = cursor.getStringValue(CommonDataKinds.Organization.TITLE) ?: ""
+ if (company.isEmpty() && title.isEmpty()) {
+ continue
+ }
+
val organization = Organization(company, title)
organizations.put(id, organization)
} while (cursor.moveToNext())
@@ -621,6 +693,7 @@ class ContactsHelper(val activity: Activity) {
val middleName = cursor.getStringValue(CommonDataKinds.StructuredName.MIDDLE_NAME) ?: ""
val surname = cursor.getStringValue(CommonDataKinds.StructuredName.FAMILY_NAME) ?: ""
val suffix = cursor.getStringValue(CommonDataKinds.StructuredName.SUFFIX) ?: ""
+ val nickname = getNicknames(id)[id] ?: ""
val photoUri = cursor.getStringValue(CommonDataKinds.Phone.PHOTO_URI) ?: ""
val number = getPhoneNumbers(id)[id] ?: ArrayList()
val emails = getEmails(id)[id] ?: ArrayList()
@@ -634,8 +707,8 @@ class ContactsHelper(val activity: Activity) {
val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
val organization = getOrganizations(id)[id] ?: Organization("", "")
val websites = getWebsites(id)[id] ?: ArrayList()
- return Contact(id, prefix, firstName, middleName, surname, suffix, photoUri, number, emails, addresses, events, accountName,
- starred, contactId, thumbnailUri, null, notes, groups, organization, websites)
+ return Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, events,
+ accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites)
}
} finally {
cursor?.close()
@@ -662,14 +735,19 @@ class ContactsHelper(val activity: Activity) {
return sources
}
- val accountManager = AccountManager.get(activity)
- accountManager.accounts.filter { it.name.contains("@") || localAccountTypes.contains(it.type) }.forEach {
+ val accounts = AccountManager.get(activity).accounts
+ accounts.forEach {
if (ContentResolver.getIsSyncable(it, ContactsContract.AUTHORITY) == 1) {
val contactSource = ContactSource(it.name, it.type)
sources.add(contactSource)
}
}
+ val contentResolverAccounts = getContentResolverAccounts().filter {
+ it.name.isNotEmpty() && it.type.isNotEmpty() && !accounts.contains(Account(it.name, it.type))
+ }
+ sources.addAll(contentResolverAccounts)
+
if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) {
sources.add(ContactSource("", ""))
}
@@ -747,6 +825,22 @@ class ContactsHelper(val activity: Activity) {
operations.add(build())
}
+ // delete nickname
+ ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
+ val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? "
+ val selectionArgs = arrayOf(contact.id.toString(), Nickname.CONTENT_ITEM_TYPE)
+ withSelection(selection, selectionArgs)
+ operations.add(build())
+ }
+
+ // add nickname
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id)
+ withValue(ContactsContract.Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE)
+ withValue(Nickname.NAME, contact.nickname)
+ operations.add(build())
+ }
+
// delete phone numbers
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? "
@@ -823,20 +917,34 @@ class ContactsHelper(val activity: Activity) {
}
}
- // notes
- ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply {
- val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
+ // delete notes
+ ContentProviderOperation.newDelete(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)
+ operations.add(build())
+ }
+
+ // add notes
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id)
+ withValue(ContactsContract.Data.MIMETYPE, Note.CONTENT_ITEM_TYPE)
withValue(Note.NOTE, contact.notes)
operations.add(build())
}
- // organization
- ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply {
- val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
+ // delete organization
+ 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.Organization.CONTENT_ITEM_TYPE)
withSelection(selection, selectionArgs)
+ operations.add(build())
+ }
+
+ // add organization
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id)
+ withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Organization.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Organization.COMPANY, contact.organization.company)
withValue(CommonDataKinds.Organization.TYPE, DEFAULT_ORGANIZATION_TYPE)
withValue(CommonDataKinds.Organization.TITLE, contact.organization.jobPosition)
@@ -960,7 +1068,12 @@ class ContactsHelper(val activity: Activity) {
operations.clear()
}
}
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+
+ try {
+ activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ }
}
fun removeContactsFromGroup(contacts: ArrayList, groupId: Long) {
@@ -1006,6 +1119,14 @@ class ContactsHelper(val activity: Activity) {
operations.add(build())
}
+ // nickname
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ withValue(ContactsContract.Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE)
+ withValue(Nickname.NAME, contact.nickname)
+ operations.add(build())
+ }
+
// phone numbers
contact.phoneNumbers.forEach {
ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
@@ -1262,7 +1383,92 @@ class ContactsHelper(val activity: Activity) {
}
}
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ if (activity.hasPermission(PERMISSION_WRITE_CONTACTS)) {
+ activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ }
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ }
+ }.start()
+ }
+
+ @SuppressLint("MissingPermission")
+ fun getRecents(callback: (ArrayList) -> Unit) {
+ Thread {
+ val calls = ArrayList()
+ if (!activity.hasPermission(PERMISSION_WRITE_CALL_LOG) || !activity.hasPermission(PERMISSION_READ_CALL_LOG)) {
+ callback(calls)
+ return@Thread
+ }
+
+ val uri = CallLog.Calls.CONTENT_URI
+ val projection = arrayOf(
+ CallLog.Calls._ID,
+ CallLog.Calls.NUMBER,
+ CallLog.Calls.DATE,
+ CallLog.Calls.CACHED_NAME
+ )
+
+ val sorting = "${CallLog.Calls._ID} DESC LIMIT 100"
+ val currentDate = Date(System.currentTimeMillis())
+ val currentYear = SimpleDateFormat("yyyy", Locale.getDefault()).format(currentDate)
+ val todayDate = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(currentDate)
+ val yesterdayDate = SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(Date(System.currentTimeMillis() - DAY_SECONDS * 1000))
+ val yesterday = activity.getString(R.string.yesterday)
+ var prevNumber = ""
+
+ var cursor: Cursor? = null
+ try {
+ cursor = activity.contentResolver.query(uri, projection, null, null, sorting)
+ if (cursor?.moveToFirst() == true) {
+ do {
+ val id = cursor.getIntValue(CallLog.Calls._ID)
+ val number = cursor.getStringValue(CallLog.Calls.NUMBER)
+ val date = cursor.getLongValue(CallLog.Calls.DATE)
+ val name = cursor.getStringValue(CallLog.Calls.CACHED_NAME)
+ if (number == prevNumber) {
+ continue
+ }
+
+ var formattedDate = SimpleDateFormat("dd MMM yyyy, HH:mm", Locale.getDefault()).format(Date(date))
+ val datePart = formattedDate.substring(0, 11)
+ when {
+ datePart == todayDate -> formattedDate = formattedDate.substring(12)
+ datePart == yesterdayDate -> formattedDate = yesterday + formattedDate.substring(11)
+ formattedDate.substring(7, 11) == currentYear -> formattedDate = formattedDate.substring(0, 6) + formattedDate.substring(11)
+ }
+
+ prevNumber = number
+ val recentCall = RecentCall(id, number, formattedDate, name)
+ calls.add(recentCall)
+ } while (cursor.moveToNext())
+ }
+ } finally {
+ cursor?.close()
+ }
+ callback(calls)
+ }.start()
+ }
+
+ fun removeRecentCalls(ids: ArrayList) {
+ Thread {
+ try {
+ val operations = ArrayList()
+ val selection = "${CallLog.Calls._ID} = ?"
+ ids.forEach {
+ ContentProviderOperation.newDelete(CallLog.Calls.CONTENT_URI).apply {
+ val selectionArgs = arrayOf(it.toString())
+ withSelection(selection, selectionArgs)
+ operations.add(build())
+ }
+
+ if (operations.size % BATCH_SIZE == 0) {
+ activity.contentResolver.applyBatch(CallLog.AUTHORITY, operations)
+ operations.clear()
+ }
+ }
+
+ activity.contentResolver.applyBatch(CallLog.AUTHORITY, operations)
} catch (e: Exception) {
activity.showErrorToast(e)
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt
index 63690935..860b3698 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt
@@ -28,6 +28,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
private val COL_MIDDLE_NAME = "middle_name"
private val COL_SURNAME = "surname"
private val COL_SUFFIX = "suffix"
+ private val COL_NICKNAME = "nickname"
private val COL_PHOTO = "photo"
private val COL_PHONE_NUMBERS = "phone_numbers"
private val COL_EMAILS = "emails"
@@ -48,9 +49,9 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
private val mDb = writableDatabase
companion object {
- private const val DB_VERSION = 5
const val DB_NAME = "contacts.db"
- var dbInstance: DBHelper? = null
+ private const val DB_VERSION = 6
+ private var dbInstance: DBHelper? = null
var gson = Gson()
fun newInstance(context: Context): DBHelper {
@@ -65,7 +66,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
db.execSQL("CREATE TABLE $CONTACTS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_FIRST_NAME TEXT, $COL_MIDDLE_NAME TEXT, " +
"$COL_SURNAME TEXT, $COL_PHOTO BLOB, $COL_PHONE_NUMBERS TEXT, $COL_EMAILS TEXT, $COL_EVENTS TEXT, $COL_STARRED INTEGER, " +
"$COL_ADDRESSES TEXT, $COL_NOTES TEXT, $COL_GROUPS TEXT, $COL_PREFIX TEXT, $COL_SUFFIX TEXT, $COL_COMPANY TEXT, $COL_JOB_POSITION TEXT," +
- "$COL_WEBSITES TEXT)")
+ "$COL_WEBSITES TEXT, $COL_NICKNAME 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)")
@@ -94,6 +95,10 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
if (oldVersion < 5) {
db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_WEBSITES TEXT DEFAULT ''")
}
+
+ if (oldVersion < 6) {
+ db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_NICKNAME TEXT DEFAULT ''")
+ }
}
private fun createGroupsTable(db: SQLiteDatabase) {
@@ -135,6 +140,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
put(COL_MIDDLE_NAME, contact.middleName)
put(COL_SURNAME, contact.surname)
put(COL_SUFFIX, contact.suffix)
+ put(COL_NICKNAME, contact.nickname)
put(COL_PHONE_NUMBERS, gson.toJson(contact.phoneNumbers))
put(COL_EMAILS, gson.toJson(contact.emails))
put(COL_ADDRESSES, gson.toJson(contact.addresses))
@@ -252,8 +258,8 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
fun getContacts(activity: Activity, selection: String? = null, selectionArgs: Array? = null): ArrayList {
val storedGroups = ContactsHelper(activity).getStoredGroups()
val contacts = ArrayList()
- val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_PHONE_NUMBERS, COL_EMAILS,
- COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION, COL_WEBSITES)
+ val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_NICKNAME, COL_PHONE_NUMBERS,
+ COL_EMAILS, COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION, COL_WEBSITES)
val phoneNumbersToken = object : TypeToken>() {}.type
val emailsToken = object : TypeToken>() {}.type
@@ -271,6 +277,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
val middleName = cursor.getStringValue(COL_MIDDLE_NAME)
val surname = cursor.getStringValue(COL_SURNAME)
val suffix = cursor.getStringValue(COL_SUFFIX)
+ val nickname = cursor.getStringValue(COL_NICKNAME)
val phoneNumbersJson = cursor.getStringValue(COL_PHONE_NUMBERS)
val phoneNumbers = if (phoneNumbersJson == "[]") ArrayList() else gson.fromJson>(phoneNumbersJson, phoneNumbersToken)
@@ -290,7 +297,11 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
val photoByteArray = cursor.getBlobValue(COL_PHOTO) ?: null
val photo = if (photoByteArray?.isNotEmpty() == true) {
- BitmapFactory.decodeByteArray(photoByteArray, 0, photoByteArray.size)
+ try {
+ BitmapFactory.decodeByteArray(photoByteArray, 0, photoByteArray.size)
+ } catch (e: OutOfMemoryError) {
+ null
+ }
} else {
null
}
@@ -311,8 +322,8 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
val websites = if (websitesJson == "[]") ArrayList() else gson.fromJson>(websitesJson, websitesToken)
?: ArrayList(1)
- val contact = Contact(id, prefix, firstName, middleName, surname, suffix, "", phoneNumbers, emails, addresses, events,
- SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites)
+ val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, "", phoneNumbers, emails, addresses,
+ events, SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites)
contacts.add(contact)
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt
index 88f077f3..4832c2ac 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfExporter.kt
@@ -1,22 +1,30 @@
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.*
+import com.simplemobiletools.commons.extensions.getFileOutputStream
+import com.simplemobiletools.commons.extensions.showErrorToast
+import com.simplemobiletools.commons.extensions.toFileDirItem
+import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.extensions.getByteArray
+import com.simplemobiletools.contacts.extensions.getDateTimeFromDateString
import com.simplemobiletools.contacts.helpers.VcfExporter.ExportResult.EXPORT_FAIL
import com.simplemobiletools.contacts.models.Contact
-import java.io.BufferedWriter
-import java.io.ByteArrayOutputStream
+import ezvcard.Ezvcard
+import ezvcard.VCard
+import ezvcard.parameter.AddressType
+import ezvcard.parameter.EmailType
+import ezvcard.parameter.ImageType
+import ezvcard.parameter.TelephoneType
+import ezvcard.property.*
+import ezvcard.util.PartialDate
import java.io.File
+import java.util.*
class VcfExporter {
- private val ENCODED_PHOTO_LINE_LENGTH = 74
-
enum class ExportResult {
EXPORT_FAIL, EXPORT_OK, EXPORT_PARTIAL
}
@@ -36,61 +44,93 @@ class VcfExporter {
activity.toast(R.string.exporting)
}
- it.bufferedWriter().use { out ->
- for (contact in contacts) {
- out.writeLn(BEGIN_VCARD)
- out.writeLn(VERSION_2_1)
- out.writeLn("$N${getNames(contact)}")
+ val cards = ArrayList()
+ for (contact in contacts) {
+ val card = VCard()
+ StructuredName().apply {
+ prefixes.add(contact.prefix)
+ given = contact.firstName
+ additionalNames.add(contact.middleName)
+ family = contact.surname
+ suffixes.add(contact.suffix)
+ card.structuredName = this
+ }
- contact.phoneNumbers.forEach {
- out.writeLn("$TEL;${getPhoneNumberLabel(it.type)}:${it.value}")
- }
+ if (contact.nickname.isNotEmpty()) {
+ card.setNickname(contact.nickname)
+ }
- contact.emails.forEach {
- val type = getEmailTypeLabel(it.type)
- val delimiterType = if (type.isEmpty()) "" else ";$type"
- out.writeLn("$EMAIL$delimiterType:${it.value}")
- }
+ contact.phoneNumbers.forEach {
+ val phoneNumber = Telephone(it.value)
+ phoneNumber.types.add(TelephoneType.find(getPhoneNumberLabel(it.type)))
+ card.addTelephoneNumber(phoneNumber)
+ }
- contact.addresses.forEach {
- val type = getAddressTypeLabel(it.type)
- val delimiterType = if (type.isEmpty()) "" else ";$type"
- out.writeLn("$ADR$delimiterType:;;${it.value.replace("\n", "\\n")};;;;")
- }
+ contact.emails.forEach {
+ val email = Email(it.value)
+ email.types.add(EmailType.find(getEmailTypeLabel(it.type)))
+ card.addEmail(email)
+ }
- contact.events.forEach {
- if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) {
- out.writeLn("$BDAY${it.value}")
+ contact.events.forEach {
+ if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY || it.type == CommonDataKinds.Event.TYPE_ANNIVERSARY) {
+ val dateTime = it.value.getDateTimeFromDateString()
+ if (it.value.startsWith("--")) {
+ val partialDate = PartialDate.builder().year(null).month(dateTime.monthOfYear - 1).date(dateTime.dayOfMonth).build()
+ if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) {
+ card.birthdays.add(Birthday(partialDate))
+ } else {
+ card.anniversaries.add(Anniversary(partialDate))
+ }
+ } else {
+ Calendar.getInstance().apply {
+ clear()
+ set(Calendar.YEAR, dateTime.year)
+ set(Calendar.MONTH, dateTime.monthOfYear - 1)
+ set(Calendar.DAY_OF_MONTH, dateTime.dayOfMonth)
+ if (it.type == CommonDataKinds.Event.TYPE_BIRTHDAY) {
+ card.birthdays.add(Birthday(time))
+ } else {
+ card.anniversaries.add(Anniversary(time))
+ }
+ }
}
}
-
- if (contact.notes.isNotEmpty()) {
- out.writeLn("$NOTE${contact.notes.replace("\n", "\\n")}")
- }
-
- if (!contact.organization.isEmpty()) {
- out.writeLn("$ORG${contact.organization.company.replace("\n", "\\n")}")
- out.writeLn("$TITLE${contact.organization.jobPosition.replace("\n", "\\n")}")
- }
-
- contact.websites.forEach {
- out.writeLn("$URL$it")
- }
-
- if (contact.thumbnailUri.isNotEmpty()) {
- val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, Uri.parse(contact.thumbnailUri))
- addBitmap(bitmap, out)
- }
-
- if (contact.photo != null) {
- addBitmap(contact.photo!!, out)
- }
-
- out.writeLn(END_VCARD)
- contactsExported++
}
+
+ contact.addresses.forEach {
+ val address = Address()
+ address.streetAddress = it.value
+ address.types.add(AddressType.find(getAddressTypeLabel(it.type)))
+ card.addAddress(address)
+ }
+
+ if (contact.notes.isNotEmpty()) {
+ card.addNote(contact.notes)
+ }
+
+ if (!contact.organization.isEmpty()) {
+ val organization = Organization()
+ organization.values.add(contact.organization.company)
+ card.organization = organization
+ card.titles.add(Title(contact.organization.jobPosition))
+ }
+
+ contact.websites.forEach {
+ card.addUrl(it)
+ }
+
+ if (contact.thumbnailUri.isNotEmpty()) {
+ val photoByteArray = MediaStore.Images.Media.getBitmap(activity.contentResolver, Uri.parse(contact.thumbnailUri)).getByteArray()
+ val photo = Photo(photoByteArray, ImageType.JPEG)
+ card.addPhoto(photo)
+ }
+
+ cards.add(card)
+ contactsExported++
}
+ Ezvcard.write(cards).go(file)
} catch (e: Exception) {
activity.showErrorToast(e)
}
@@ -103,71 +143,24 @@ 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
- var surName = contact.surname
- var middleName = contact.middleName
- var prefix = contact.prefix
- var suffix = contact.suffix
-
- if (QuotedPrintable.urlEncode(firstName) != firstName
- || QuotedPrintable.urlEncode(surName) != surName
- || QuotedPrintable.urlEncode(middleName) != middleName
- || QuotedPrintable.urlEncode(prefix) != prefix
- || QuotedPrintable.urlEncode(suffix) != suffix) {
- firstName = QuotedPrintable.encode(firstName)
- surName = QuotedPrintable.encode(surName)
- middleName = QuotedPrintable.encode(middleName)
- prefix = QuotedPrintable.encode(prefix)
- suffix = QuotedPrintable.encode(suffix)
- result += ";CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE"
- }
-
- return "$result:$surName;$firstName;$middleName;$prefix;$suffix"
- }
-
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
+ else -> HOME
}
private fun getEmailTypeLabel(type: Int) = when (type) {
- CommonDataKinds.Email.TYPE_HOME -> HOME
CommonDataKinds.Email.TYPE_WORK -> WORK
CommonDataKinds.Email.TYPE_MOBILE -> MOBILE
- else -> ""
+ else -> HOME
}
private fun getAddressTypeLabel(type: Int) = when (type) {
- CommonDataKinds.StructuredPostal.TYPE_HOME -> HOME
CommonDataKinds.StructuredPostal.TYPE_WORK -> WORK
- else -> ""
+ else -> HOME
}
}
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 85561636..d247b0ba 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/VcfImporter.kt
@@ -3,7 +3,6 @@ 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
@@ -11,40 +10,19 @@ import com.simplemobiletools.contacts.extensions.getCachePhoto
import com.simplemobiletools.contacts.extensions.getCachePhotoUri
import com.simplemobiletools.contacts.helpers.VcfImporter.ImportResult.*
import com.simplemobiletools.contacts.models.*
+import ezvcard.Ezvcard
+import org.joda.time.DateTime
+import org.joda.time.format.DateTimeFormat
import java.io.File
import java.io.FileOutputStream
+import java.util.*
class VcfImporter(val activity: SimpleActivity) {
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL
}
- private var curPrefix = ""
- private var curFirstName = ""
- private var curMiddleName = ""
- private var curSurname = ""
- private var curSuffix = ""
- private var curPhotoUri = ""
- private var curNotes = ""
- private var curCompany = ""
- private var curJobPosition = ""
- private var curPhoneNumbers = ArrayList()
- private var curEmails = ArrayList()
- private var curEvents = ArrayList()
- private var curAddresses = ArrayList()
- private var curGroups = ArrayList()
- private var curWebsites = ArrayList()
-
- private var isGettingPhoto = false
- private var currentPhotoString = StringBuilder()
- private var currentPhotoCompressionFormat = Bitmap.CompressFormat.JPEG
-
- private var isGettingName = false
- private var currentNameIsANSI = false
- private var currentNameString = StringBuilder()
-
- private var isGettingNotes = false
- private var currentNotesSB = StringBuilder()
+ private val PATTERN = "EEE MMM dd HH:mm:ss 'GMT'ZZ YYYY"
private var contactsImported = 0
private var contactsFailed = 0
@@ -57,45 +35,70 @@ class VcfImporter(val activity: SimpleActivity) {
activity.assets.open(path)
}
- inputStream.bufferedReader().use {
- while (true) {
- val line = it.readLine() ?: break
- if (line.trim().isEmpty()) {
- if (isGettingPhoto) {
- savePhoto()
- isGettingPhoto = false
- }
- continue
- } else if (line.startsWith('\t') && isGettingName) {
- 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
- }
- }
+ val ezContacts = Ezvcard.parse(inputStream).all()
+ for (ezContact in ezContacts) {
+ val structuredName = ezContact.structuredName
+ val prefix = structuredName?.prefixes?.firstOrNull() ?: ""
+ val firstName = structuredName?.given ?: ""
+ val middleName = structuredName?.additionalNames?.firstOrNull() ?: ""
+ val surname = structuredName?.family ?: ""
+ val suffix = structuredName?.suffixes?.firstOrNull() ?: ""
+ val nickname = ezContact.nickname?.values?.firstOrNull() ?: ""
+ val photoUri = ""
- 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))
- line.toUpperCase().startsWith(ORG) -> addCompany(line.substring(ORG.length))
- line.toUpperCase().startsWith(TITLE) -> addJobPosition(line.substring(TITLE.length))
- line.toUpperCase().startsWith(URL) -> addWebsite(line.substring(URL.length))
- line.toUpperCase() == END_VCARD -> saveContact(targetContactSource)
- isGettingPhoto -> currentPhotoString.append(line.trim())
+ val phoneNumbers = ArrayList()
+ ezContact.telephoneNumbers.forEach {
+ val type = getPhoneNumberTypeId(it.types.firstOrNull()?.value ?: MOBILE)
+ val number = it.text
+ phoneNumbers.add(PhoneNumber(number, type))
+ }
+
+ val emails = ArrayList()
+ ezContact.emails.forEach {
+ val type = getEmailTypeId(it.types.firstOrNull()?.value ?: HOME)
+ val email = it.value
+ emails.add(Email(email, type))
+ }
+
+ val addresses = ArrayList()
+ ezContact.addresses.forEach {
+ val type = getAddressTypeId(it.types.firstOrNull()?.value ?: HOME)
+ val address = it.streetAddress
+ if (address?.isNotEmpty() == true) {
+ addresses.add(Address(address, type))
}
}
+
+ val events = ArrayList()
+ ezContact.birthdays.forEach {
+ val event = Event(formatDateToDayCode(it.date), CommonDataKinds.Event.TYPE_BIRTHDAY)
+ events.add(event)
+ }
+
+ ezContact.anniversaries.forEach {
+ val event = Event(formatDateToDayCode(it.date), CommonDataKinds.Event.TYPE_ANNIVERSARY)
+ events.add(event)
+ }
+
+ val starred = 0
+ val contactId = 0
+ val notes = ezContact.notes.firstOrNull()?.value ?: ""
+ val groups = ArrayList()
+ val company = ezContact.organization?.values?.firstOrNull() ?: ""
+ val jobPosition = ezContact.titles?.firstOrNull()?.value ?: ""
+ val organization = Organization(company, jobPosition)
+ val websites = ezContact.urls.map { it.value } as ArrayList
+
+ val photoData = ezContact.photos.firstOrNull()?.data
+ val photo = null
+ val thumbnailUri = savePhoto(photoData)
+
+ val contact = Contact(0, prefix, firstName, middleName, surname, suffix, nickname, photoUri, phoneNumbers, emails, addresses, events,
+ targetContactSource, starred, contactId, thumbnailUri, photo, notes, groups, organization, websites)
+
+ if (ContactsHelper(activity).insertContact(contact)) {
+ contactsImported++
+ }
}
} catch (e: Exception) {
activity.showErrorToast(e, Toast.LENGTH_LONG)
@@ -109,188 +112,51 @@ class VcfImporter(val activity: SimpleActivity) {
}
}
- private fun addNames(names: String) {
- val parts = names.split(":")
- currentNameIsANSI = parts.first().toUpperCase().contains("QUOTED-PRINTABLE")
- currentNameString.append(parts[1].trimEnd('='))
- if (!isGettingName && currentNameIsANSI && names.endsWith('=')) {
- isGettingName = true
- } else {
- parseNames()
- }
+ private fun formatDateToDayCode(date: Date): String {
+ val dateTime = DateTime.parse(date.toString(), DateTimeFormat.forPattern(PATTERN))
+ return dateTime.toString("yyyy-MM-dd")
}
- private fun parseNames() {
- val nameParts = currentNameString.split(";")
- curSurname = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[0]) else nameParts[0]
- curFirstName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[1]) else nameParts[1]
- if (nameParts.size > 2) {
- curMiddleName = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[2]) else nameParts[2]
- curPrefix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[3]) else nameParts[3]
- curSuffix = if (currentNameIsANSI) QuotedPrintable.decode(nameParts[4]) else nameParts[4]
- }
- }
-
- 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) {
+ private fun getPhoneNumberTypeId(type: String) = when (type.toUpperCase()) {
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
+ FAX -> CommonDataKinds.Phone.TYPE_FAX_WORK
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) {
+ private fun getEmailTypeId(type: String) = when (type.toUpperCase()) {
HOME -> CommonDataKinds.Email.TYPE_HOME
WORK -> CommonDataKinds.Email.TYPE_WORK
MOBILE -> CommonDataKinds.Email.TYPE_MOBILE
else -> CommonDataKinds.Email.TYPE_OTHER
}
- 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].replace("\\n", "\n"), type))
- }
- }
-
- private fun getAddressTypeId(type: String) = when (type) {
+ private fun getAddressTypeId(type: String) = when (type.toUpperCase()) {
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))
- }
-
- 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 savePhoto(byteArray: ByteArray?): String {
+ if (byteArray == null) {
+ return ""
}
- }
- 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)
+ val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(file)
- bitmap.compress(currentPhotoCompressionFormat, 100, fileOutputStream)
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream)
} finally {
fileOutputStream?.close()
}
- curPhotoUri = activity.getCachePhotoUri(file).toString()
- }
-
- private fun addNotes(notes: String) {
- currentNotesSB.append(notes)
- isGettingNotes = true
- }
-
- private fun addCompany(company: String) {
- curCompany = company
- }
-
- private fun addJobPosition(jobPosition: String) {
- curJobPosition = jobPosition
- }
-
- private fun addWebsite(website: String) {
- curWebsites.add(website)
- }
-
- private fun saveContact(source: String) {
- val organization = Organization(curCompany, curJobPosition)
- val contact = Contact(0, curPrefix, curFirstName, curMiddleName, curSurname, curSuffix, curPhotoUri, curPhoneNumbers, curEmails, curAddresses, curEvents,
- source, 0, 0, "", null, curNotes, curGroups, organization, curWebsites)
- if (ContactsHelper(activity).insertContact(contact)) {
- contactsImported++
- }
- }
-
- private fun resetValues() {
- curPrefix = ""
- curFirstName = ""
- curMiddleName = ""
- curSurname = ""
- curSuffix = ""
- curPhotoUri = ""
- curNotes = ""
- curCompany = ""
- curJobPosition = ""
- curPhoneNumbers = ArrayList()
- curEmails = ArrayList()
- curEvents = ArrayList()
- curAddresses = ArrayList()
- curGroups = ArrayList()
- curWebsites = ArrayList()
-
- isGettingPhoto = false
- currentPhotoString = StringBuilder()
- currentPhotoCompressionFormat = Bitmap.CompressFormat.JPEG
-
- isGettingName = false
- currentNameIsANSI = false
- currentNameString = StringBuilder()
-
- isGettingNotes = false
- currentNotesSB = StringBuilder()
+ return activity.getCachePhotoUri(file).toString()
}
}
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 5e16348a..6a18a265 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Contact.kt
@@ -1,39 +1,48 @@
package com.simplemobiletools.contacts.models
import android.graphics.Bitmap
+import com.simplemobiletools.commons.extensions.normalizeString
import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME
import com.simplemobiletools.commons.helpers.SORT_BY_MIDDLE_NAME
import com.simplemobiletools.commons.helpers.SORT_DESCENDING
+import com.simplemobiletools.contacts.helpers.PHONE_NUMBER_PATTERN
-data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var photoUri: String,
- var phoneNumbers: ArrayList, var emails: ArrayList, var addresses: ArrayList, var events: ArrayList,
- var source: String, var starred: Int, val contactId: Int, val thumbnailUri: String, var photo: Bitmap?, var notes: String,
+data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var nickname: String,
+ var photoUri: String, var phoneNumbers: ArrayList, var emails: ArrayList, var addresses: ArrayList,
+ var events: ArrayList, var source: String, var starred: Int, val contactId: Int, val thumbnailUri: String, var photo: Bitmap?, var notes: String,
var groups: ArrayList, var organization: Organization, var websites: ArrayList) : Comparable {
companion object {
var sorting = 0
var startWithSurname = false
- val pattern = "\\D+".toRegex()
}
override fun compareTo(other: Contact): Int {
- val firstString: String
- val secondString: String
+ var firstString: String
+ var secondString: String
when {
sorting and SORT_BY_FIRST_NAME != 0 -> {
- firstString = firstName
- secondString = other.firstName
+ firstString = firstName.normalizeString()
+ secondString = other.firstName.normalizeString()
}
sorting and SORT_BY_MIDDLE_NAME != 0 -> {
- firstString = middleName
- secondString = other.middleName
+ firstString = middleName.normalizeString()
+ secondString = other.middleName.normalizeString()
}
else -> {
- firstString = surname
- secondString = other.surname
+ firstString = surname.normalizeString()
+ secondString = other.surname.normalizeString()
}
}
+ if (firstString.isEmpty() && firstName.isEmpty() && middleName.isEmpty() && surname.isEmpty() && organization.company.isNotEmpty()) {
+ firstString = organization.company.normalizeString()
+ }
+
+ if (secondString.isEmpty() && other.firstName.isEmpty() && other.middleName.isEmpty() && other.surname.isEmpty() && other.organization.company.isNotEmpty()) {
+ secondString = other.organization.company.normalizeString()
+ }
+
var result = if (firstString.firstOrNull()?.isLetter() == true && secondString.firstOrNull()?.isLetter() == false) {
-1
} else if (firstString.firstOrNull()?.isLetter() == false && secondString.firstOrNull()?.isLetter() == true) {
@@ -45,9 +54,9 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m
-1
} else {
if (firstString.toLowerCase() == secondString.toLowerCase()) {
- getFullName().compareTo(other.getFullName())
+ getFullName().compareTo(other.getFullName(), true)
} else {
- firstString.toLowerCase().compareTo(secondString.toLowerCase())
+ firstString.compareTo(secondString, true)
}
}
}
@@ -85,8 +94,13 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m
fun getHashToCompare(): Int {
val newPhoneNumbers = ArrayList()
- phoneNumbers.mapTo(newPhoneNumbers, { PhoneNumber(it.value.replace(pattern, ""), 0) })
- return copy(id = 0, prefix = "", firstName = getFullName().toLowerCase(), middleName = "", surname = "", suffix = "", photoUri = "",
- phoneNumbers = newPhoneNumbers, source = "", starred = 0, contactId = 0, thumbnailUri = "", notes = "").hashCode()
+ phoneNumbers.mapTo(newPhoneNumbers) { PhoneNumber(it.value.replace(PHONE_NUMBER_PATTERN.toRegex(), ""), 0) }
+
+ val newEmails = ArrayList()
+ emails.mapTo(newEmails) { Email(it.value, 0) }
+
+ return copy(id = 0, prefix = "", firstName = getFullName().toLowerCase(), middleName = "", surname = "", suffix = "", nickname = "", photoUri = "",
+ phoneNumbers = newPhoneNumbers, events = ArrayList(), addresses = ArrayList(), emails = newEmails, source = "", starred = 0,
+ contactId = 0, thumbnailUri = "", notes = "", groups = ArrayList(), websites = ArrayList(), organization = Organization("", "")).hashCode()
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt
new file mode 100644
index 00000000..e009be76
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/RecentCall.kt
@@ -0,0 +1,3 @@
+package com.simplemobiletools.contacts.models
+
+data class RecentCall(var id: Int, var number: String, var dateTime: String, var name: String?)
diff --git a/app/src/main/res/anim/pulsing_animation.xml b/app/src/main/res/anim/pulsing_animation.xml
new file mode 100644
index 00000000..119d4b04
--- /dev/null
+++ b/app/src/main/res/anim/pulsing_animation.xml
@@ -0,0 +1,14 @@
+
+
diff --git a/app/src/main/res/drawable-hdpi/ic_clock.png b/app/src/main/res/drawable-hdpi/ic_clock.png
new file mode 100644
index 00000000..43b00e0a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_clock.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_phone_big.png b/app/src/main/res/drawable-hdpi/ic_phone_big.png
new file mode 100644
index 00000000..61d59bd4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_phone_big.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_clock.png b/app/src/main/res/drawable-xhdpi/ic_clock.png
new file mode 100644
index 00000000..9d64be88
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_clock.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_phone_big.png b/app/src/main/res/drawable-xhdpi/ic_phone_big.png
new file mode 100644
index 00000000..3aae5d70
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_phone_big.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_clock.png b/app/src/main/res/drawable-xxhdpi/ic_clock.png
new file mode 100644
index 00000000..a29eea53
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_clock.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_phone_big.png b/app/src/main/res/drawable-xxhdpi/ic_phone_big.png
new file mode 100644
index 00000000..aaa1b757
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_phone_big.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clock.png b/app/src/main/res/drawable-xxxhdpi/ic_clock.png
new file mode 100644
index 00000000..bfd0dc3e
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_clock.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_phone_big.png b/app/src/main/res/drawable-xxxhdpi/ic_phone_big.png
new file mode 100644
index 00000000..707350b0
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_phone_big.png differ
diff --git a/app/src/main/res/layout/activity_edit_contact.xml b/app/src/main/res/layout/activity_edit_contact.xml
index 534ac995..15c77a7c 100644
--- a/app/src/main/res/layout/activity_edit_contact.xml
+++ b/app/src/main/res/layout/activity_edit_contact.xml
@@ -170,6 +170,22 @@
android:textCursorDrawable="@null"
android:textSize="@dimen/bigger_text_size"/>
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 95c89e52..1c26ff25 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -14,27 +14,7 @@
app:tabIndicatorColor="@android:color/white"
app:tabIndicatorHeight="2dp"
app:tabMinWidth="150dp"
- app:tabSelectedTextColor="@android:color/white">
-
-
-
-
-
-
-
-
+ app:tabSelectedTextColor="@android:color/white"/>
@@ -11,6 +12,28 @@
android:layout_height="wrap_content"
android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+ android:text="@string/use_english_language"
+ app:switchPadding="@dimen/medium_margin"/>
@@ -97,7 +143,8 @@
android:clickable="false"
android:paddingLeft="@dimen/medium_margin"
android:paddingStart="@dimen/medium_margin"
- android:text="@string/avoid_whats_new"/>
+ android:text="@string/avoid_whats_new"
+ app:switchPadding="@dimen/medium_margin"/>
@@ -120,7 +167,8 @@
android:clickable="false"
android:paddingLeft="@dimen/medium_margin"
android:paddingStart="@dimen/medium_margin"
- android:text="@string/show_info_bubble"/>
+ android:text="@string/show_info_bubble"
+ app:switchPadding="@dimen/medium_margin"/>
@@ -143,7 +191,8 @@
android:clickable="false"
android:paddingLeft="@dimen/medium_margin"
android:paddingStart="@dimen/medium_margin"
- android:text="@string/show_contact_thumbnails"/>
+ android:text="@string/show_contact_thumbnails"
+ app:switchPadding="@dimen/medium_margin"/>
@@ -166,7 +215,8 @@
android:clickable="false"
android:paddingLeft="@dimen/medium_margin"
android:paddingStart="@dimen/medium_margin"
- android:text="@string/show_phone_numbers"/>
+ android:text="@string/show_phone_numbers"
+ app:switchPadding="@dimen/medium_margin"/>
@@ -189,7 +239,8 @@
android:clickable="false"
android:paddingLeft="@dimen/medium_margin"
android:paddingStart="@dimen/medium_margin"
- android:text="@string/start_name_with_surname"/>
+ android:text="@string/start_name_with_surname"
+ app:switchPadding="@dimen/medium_margin"/>
@@ -212,7 +263,32 @@
android:clickable="false"
android:paddingLeft="@dimen/medium_margin"
android:paddingStart="@dimen/medium_margin"
- android:text="@string/filter_duplicates"/>
+ android:text="@string/filter_duplicates"
+ app:switchPadding="@dimen/medium_margin"/>
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_view_contact.xml b/app/src/main/res/layout/activity_view_contact.xml
index 8d50e8cf..e471ae49 100644
--- a/app/src/main/res/layout/activity_view_contact.xml
+++ b/app/src/main/res/layout/activity_view_contact.xml
@@ -166,6 +166,21 @@
android:singleLine="true"
android:textSize="@dimen/bigger_text_size"/>
+
+
diff --git a/app/src/main/res/layout/dialog_call_confirmation.xml b/app/src/main/res/layout/dialog_call_confirmation.xml
new file mode 100644
index 00000000..9d406f6b
--- /dev/null
+++ b/app/src/main/res/layout/dialog_call_confirmation.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_manage_visible_fields.xml b/app/src/main/res/layout/dialog_manage_visible_fields.xml
index 2b8bbf80..599fdb88 100644
--- a/app/src/main/res/layout/dialog_manage_visible_fields.xml
+++ b/app/src/main/res/layout/dialog_manage_visible_fields.xml
@@ -54,6 +54,14 @@
android:paddingTop="@dimen/activity_margin"
android:text="@string/suffix"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_recents.xml b/app/src/main/res/layout/fragment_recents.xml
new file mode 100644
index 00000000..4c7e37b1
--- /dev/null
+++ b/app/src/main/res/layout/fragment_recents.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_recent_call.xml b/app/src/main/res/layout/item_recent_call.xml
new file mode 100644
index 00000000..61334288
--- /dev/null
+++ b/app/src/main/res/layout/item_recent_call.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/cab.xml b/app/src/main/res/menu/cab.xml
index d775dbc5..ae59e0ae 100644
--- a/app/src/main/res/menu/cab.xml
+++ b/app/src/main/res/menu/cab.xml
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
-
-
+
diff --git a/app/src/main/res/menu/menu_view_contact.xml b/app/src/main/res/menu/menu_view_contact.xml
index a4436f7f..0ed4b3ec 100644
--- a/app/src/main/res/menu/menu_view_contact.xml
+++ b/app/src/main/res/menu/menu_view_contact.xml
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml
index ddd51757..dab4c0c5 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml
index 0bb69cf2..37bf057f 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml
index aa10bae8..3e4d0696 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml
index 8d2fc23f..9786d7bf 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml
index 89f7a275..afb3d0d3 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml
index 0822e55f..1846b81d 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml
index 0735af68..4152801a 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml
index 06e60b88..e55d1092 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml
index 4765f7ec..40d07450 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml
index 37f5249f..601d8170 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml
index eed925f1..01f2fead 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml
index a051acce..d37b24c4 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml
index d6df0e23..9fd7bc5b 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml
index 4b6fc188..d2adf9a0 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml
index e7e2cb97..32c838cd 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml
index 972ad3de..a6e93599 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml
index c3020ef7..18492d28 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml
index 0c336264..854427e0 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml
@@ -2,4 +2,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
new file mode 100644
index 00000000..d197f75f
--- /dev/null
+++ b/app/src/main/res/values-az/strings.xml
@@ -0,0 +1,133 @@
+
+ Sadə Kontaktlar
+ Kontaktlar
+ Ünvan
+ Daxil edilir…
+ Yenilənir…
+ Telefon yaddaşı
+ Telefon yaddaşı (digər tətbiqlərə görünmür)
+ Şirkət
+ İş vəziyyəti
+ Vebsayt
+ Kontaktlara SMS göndər
+ Kontaktlara e-poçt göndər
+ Grupa SMS göndər
+ Grupa e-poçt göndər
+ %s şəxsinə zng et
+ Lazım olan icazələri istə
+
+ Yeni kontakt
+ Redaktə et
+ Kontakt seç
+ Kontaktları seç
+ Ad
+ Orta Ad
+ Soyad
+ Nickname
+
+
+ Qruplar yoxdur
+ Yeni qrup yarat
+ Qrupdan sil
+ Bu qrup boşdur
+ Kontaktlar əlavə et
+ Cihazda heçbir kontakt qrupu yoxdur
+ Qrup yarat
+ Qrupa əlavə et
+ Hesab altında qrup yarat
+
+
+ Şəkil çək
+ Şəkil seç
+ Şəkli sil
+
+
+ Ada soyaddan başla
+ Telefon nömrələrini əsas ekranda göstər
+ Kontakt görüntülərini göstər
+ Kontakta toxunduqda
+ Kontakta zəng et
+ Kontakt detallarına bax
+ Göstərilən kontakt sahəsini idarə et
+ Təkrarlanmış kontaktları filtrləməyə çalış
+ Göstərilən nişanları idarə et
+ Kontaktlar
+ Sevimlilər
+ Hazırki zənglər
+ Zəngə başlamazdan əvvəl zəng təsdiq pəncərəsi göstər
+
+
+ E-poçt
+ Ev
+ İş
+ Başqa
+
+
+ Nömrə
+ Mobil
+ Əsas
+ İş Faksı
+ Ev Faksı
+ Zəng cihazı
+ Heçbir telefon nömrəsi tapılmadı
+
+
+ Ad günü
+ İl dönümü
+
+
+ Görünür, hələlik heçbir sevimli kontakt əlavə etməmisiniz.
+ Sevimlilər əlavə et
+ Sevimlilərə əlavə et
+ Sevimlilərdən sil
+ Kontaktı dəyişmək üçün İdarə et ekranında olmalısınız
+
+
+ Kontaktları axtar
+ Sevimliləri axtar
+
+
+ Kontaktları daxil et
+ Kontaktları xaric et
+ .vcf faylından kontaktları daxil et
+ .vcf faylından kontaktları xaric et
+ Kontakt kökünü nişanla
+ Kontakt köklərini daxil et
+ Fayl adı (.vcf olmadan)
+
+
+ Göstərmək üçün sahəni seç
+ Ön şəkilçi
+ Orta şəkilçi
+ Telefon nömrələri
+ E-poçtlar
+ Ünvanlar
+ Hadisələr (ad günləri, il dönümləri)
+ Qeydlər
+ Təşkilat
+ Vebsaytlar
+ Qruplar
+ Kontakt kökü
+
+
+ I want to change what fields are visible at contacts. Can I do it?
+ Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there.
+
+
+
+ A contacts app for managing your contacts without ads.
+
+ 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 https://www.simplemobiletools.com
+
+
+
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 58b24d96..74ec5350 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -6,13 +6,15 @@
Aktualisiere…
Gerätespeicher
Gerätespeicher (nicht sichtbar für andere Apps)
- Company
- Job position
- Website
- Send SMS to contacts
- Send email to contacts
- Send SMS to group
- Send email to group
+ Unternehmen
+ Arbeitsstelle
+ Webseite
+ Sende SMS an Kontakte
+ Sende E-Mail an Kontakte
+ Sende SMS an Gruppe
+ Sende E-Mail an Gruppe
+ %s anrufen
+ Benötigte Berechtigungen anfordern
Neuer Kontakt
Kontakt bearbeiten
@@ -20,12 +22,13 @@
Kontakte auswählen
Vorname
Zweiter Vorname
- Familienname
+ Nachname
+ Spitzname
Keine Gruppen
Eine neue Gruppe erstellen
- Von Gruppe entfernen
+ Aus Gruppe entfernen
Diese Gruppe ist leer
Kontakte hinzufügen
Es sind keine Kontaktgruppen auf diesem Gerät vorhanden
@@ -34,24 +37,27 @@
Gruppe in diesem Konto erstellen
- Foto machen
+ Foto aufnehmen
Foto auswählen
Foto entfernen
Namen mit Nachnamen beginnen
Zeige Telefonnummern im Hauptmenü
- Zeige Vorschaubilder für Kontakte
+ Zeige Vorschaubilder der Kontakte
Beim Klicken auf den Kontakt
Kontakt anrufen
- Kontaktdetails ansehen
- Show favorites tab
- Show groups tab
- Manage shown contact fields
- Try filtering out duplicate contacts
+ Kontaktdetails anzeigen
+ Bearbeite sichtbare Kontaktfelder
+ Versuche Kontaktduplikate herauszufiltern
+ Anzuzeigende Tabs festlegen
+ Kontakte
+ Favoriten
+ Anrufliste
+ Bestätigungsdialog zeigen, bevor ein Anruf durchgeführt wird
- Email
+ E-Mail
Privat
Arbeit
Sonstiges
@@ -74,7 +80,7 @@
Favoriten hinzufügen
Zu Favoriten hinzufügen
Aus Favoriten entfernen
- You must be at the Edit screen to modify a contact
+ Sie müssen sich im Bearbeitungsmodus befinden, um einen Kontakt zu bearbeiten
Kontakte durchsuchen
@@ -90,34 +96,34 @@
Dateiname (ohne .vcf)
- Select fields to show
- Prefix
+ Sichtbare Felder auswählen
+ Titel
Suffix
- Phone numbers
- Emails
- Addresses
- Events (birthdays, anniversaries)
- Notes
- Organization
- Websites
- Groups
- Contact source
+ Telefonnummern
+ E-Mails
+ Addressen
+ Termine (Geburtstage, Jahrestage)
+ Notizen
+ Organisation
+ Webseiten
+ Gruppen
+ Kontaktquelle
- I want to change what fields are visible at contacts. Can I do it?
- Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there.
+ Ich möchte die sichtbaren Kontaktfelder ändern. Kann ich das machen?
+ Ja, alles, was Sie tun müssen ist folgendes: Gehen Sie zu Einstellungen -> Bearbeite sichtbare Kontaktfelder. Hier können die sichtbaren Felder ausgewählt werden. Einige sind standardmäßig deaktiviert, weshalb hier neue gefunden werden können.
- Eine App zum Verwalten von Kontakten, ganz ohne Werbung.
+ Eine Kontakte-App zur Verwaltung Ihrer Kontake. Ohne Werbung.
- Eine schlichte App um Kontakte aus allen Quellen zu verwalten und neue zu erstellen. Die Kontakte können nur auf deinem Gerät gespeichert werden, aber auch über Google oder andere Dienste synchronisiert werden. Deine wichtigsten Kontakte werden in einer separaten Liste angezeigt.
+ Eine einfache App, mit der Sie Kontakte erstellen oder von jeder Quelle verwalten können. Die Kontakte können entweder nur auf Ihrem Gerät gespeichert, oder mittels Google oder anderer Konten synchronisiert werden. Sie können Ihre Lieblingskontake in einer separaten Liste anzeigen.
- Du kannst diese App auch dazu nutzen, um die Email-Adressen und Termine von Kontakten zu verwalten. Sie hat die Möglichkeit mithilfe von mehreren Parametern zu sortieren/filtern, optional auch den Familiennamen als Vornamen anzuzeigen.
+ Sie können es auch zur Verwaltung von Nutzer-E-Mails und Ereignisse nutzen. Es kann nach mehreren Parametern sortieren oder filtern, oder optional den Nachnamen zuerst anzeigen.
- Beinhaltet keine Werbung oder unnötige Berechtigungen. Sie ist komplett Open Source, alle verwendeten Farben sind anpassbar.
+ Enthält keine Werbung oder unnötige Berechtigungen. Vollständig quelloffen. Die Farben sind anpassbar.
- Diese App ist nur eine aus einer größeren Serie von schlichten Apps. Der Rest davon findet sich auf https://www.simplemobiletools.com
+ Diese App ist nur ein Teil einer größeren App-Familie. Die übrigen finden Sie unter https://www.simplemobiletools.com
Δεν υπάρχουν ομάδες
@@ -31,7 +34,7 @@
Δεν υπάρχουν ομάδες επαφών στη συσκευή
Δημιουργία ομάδας
Προσθήκη σε ομάδα
- Δημιουργία ομάδας κάτω από λογαριασμό
+ Δημιουργία ομάδας υπο ενός λογαριασμού
Λήψη φωτογραφίας
@@ -39,20 +42,23 @@
Αφαίρεση φωτογραφίας
- Το όνομα ξεκινά με το επώνυμο
+ Εμφάνιση πρώτα το επώνυμο
Εμφάνιση τηλεφωνικών αριθμών στην κύρια οθόνη
Εμφάνιση μικρογραφιών επαφής
Στην επιλογή επαφής
Κλήση επαφής
Εμφάνιση λεπτομερειών επαφής
- Εμφάνιση καρτέλας αγαπημένων
- Εμφάνιση καρτέλας ομάδων
- Manage shown contact fields
- Try filtering out duplicate contacts
+ Διαχείριση εμφανιζόμενων πεδίων επαφής
+ Δοκιμάστε το φιλτράρισμα διπλών επαφών
+ Διαχείριση εμφανιζόμενων καρτελών
+ Επαφές
+ Αγαπημένες
+ Πρόσφατες Κλήσεις
+ Εμφάνιση διαλόγου επιβεβαίωσης πριν από την έναρξη μιας κλήσης
Email
- Σπίτι
+ Οικία
Εργασία
Άλλο
@@ -60,8 +66,8 @@
Αριθμός
Κινητό
Κύριο
- Φαξ εργασίας
- Φαξ σπιτιού
+ Φαξ Εργασίας
+ Φαξ Οικίας
Βομβητής
Δεν βρέθηκε τηλεφωνικός αριθμός
@@ -70,11 +76,11 @@
Επέτειος
- Φαίνεται ότι δεν έχεις προσθέσει αγαπημένες επαφές ακόμα.
+ Φαίνεται ότι δεν έχετε προσθέσει αγαπημένες επαφές ακόμη.
Προσθήκη αγαπημένων
- Προσθήκη στα αγαπημένα
+ Προσθήκη στις αγαπημένες
Αφαίρεση από τα αγαπημένα
- You must be at the Edit screen to modify a contact
+ Πρέπει να είστε στην οθόνη "Επεξεργασία" για να τροποποιήσετε μια επαφή
Αναζήτηση επαφών
@@ -90,34 +96,34 @@
Όνομα αρχείου (χωρίς .vcf)
- Select fields to show
- Prefix
- Suffix
- Phone numbers
+ Επιλογή εμφάνισης πεδίων
+ Πρόθεμα
+ Κατάληξη
+ Αριθμοί Τηλεφώνων
Emails
- Addresses
- Events (birthdays, anniversaries)
- Notes
- Organization
- Websites
- Groups
- Contact source
+ Διευθύνσεις
+ Εκδηλώσεις (γενέθλια, επετείους)
+ Σημειώσεις
+ Εταιρεία
+ Ιστοσελίδα
+ Ομάδες
+ Προέλευση επαφής
- I want to change what fields are visible at contacts. Can I do it?
- Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there.
+ Θέλω να αλλάξω τα πεδία που θα είναι ορατά στις επαφές. Μπορώ να το κάνω?
+ Ναι, το μόνο που έχετε να κάνετε είναι να μεταβείτε στις Ρυθμίσεις -> Διαχείριση εμφανιζόμενων πεδίων επαφής. Εκεί μπορείτε να επιλέξετε ποια πεδία θα πρέπει να είναι ορατά. Κάποια από αυτά είναι ακόμη και απενεργοποιημένα από προεπιλογή, επομένως ίσως βρείτε κάποια νέα εκεί.
- Μια εφαρμογή επαφών για να διαχειρίζεσαι τις επαφές σου χωρίς διαφημίσεις.
+ Μια εφαρμογή για την διαχείρηση των επαφών σου χωρίς διαφημίσεις.
Μια απλή εφαρμογή για δημιουργία και διαχείριση των επαφών σου από κάθε πηγή. Οι επαφές μπορεί να είναι αποθηκευμένες μόνο στη συσκευή σου, αλλά μπορούν να συγχρονίζονται στο Google, ή σε κάποιο άλλο λογαριασμό. Μπορείς να εμφανίσεις τις αγαπημένες σου επαφές σε ξεχωριστή λίστα.
- Μπορείς να τη χρησιμοποιήσεις για τη διαχείριση των email των χρηστών και τα γεγονότα. Έχει τη δυνατότητα ταξινόμησης/φιλτραρίσματος με διάφορες παραμέτρους, προαιρετικά να εμφανίζεται το επώνυμο σαν όνομα.
+ Μπορείτε να τη χρησιμοποιήσετε για τη διαχείριση των email και εκδηλώσεων επίσης. Έχει τη δυνατότητα ταξινόμησης/φιλτραρίσματος με διάφορες παραμέτρους, προαιρετικά να εμφανίζεται το επώνυμο πρώτα ή το όνομα.
- Δεν περιέχει διαφημίσεις ή περιττές άδειες. Είναι πλήρως ανοικτού κώδικα, παρέχει δυνατότητα προσαρμογής των χρωμάτων.
+ Δεν περιέχει διαφημίσεις ή περιττές άδειες. Έιναι όλη ανοιχτού κώδικα και παρέχει προσαρμόσιμα χρώματα για την εφαρμογή.
- Αυτή η εφαρμογή είναι ένα μικρό κομμάτι μιας μεγαλύτερης συλλογής εφαρμογών. Μπορείς να βρεις τις υπόλοιπες στο https://www.simplemobiletools.com
+ Αυτή η εφαρμογή είναι μέρος μιας σειράς εφαρμογών. Μπορείτε να βρείτε τις υπόλοιπες στο https://www.simplemobiletools.com
Pas de groupe
@@ -45,10 +48,13 @@
Sur appui du contact
Appeler le contact
Voir les détails du contact
- Afficher l\'onglet favoris
- Afficher l\'onglet groupes
Configurer l\'affichage des champs des contacts
Try filtering out duplicate contacts
+ Manage shown tabs
+ Contacts
+ Favorites
+ Recent calls
+ Show a call confirmation dialog before initiating a call
E-mail
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 08d418e7..e3b2bb3f 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -13,6 +13,8 @@
Send email to contacts
Send SMS to group
Send email to group
+ Call %s
+ Request the required permissions
Novi kontakt
Uredi kontakt
@@ -21,6 +23,7 @@
Ime
Srednje ime
Prezime
+ Nickname
Nema grupa
@@ -45,10 +48,13 @@
Prilikom dodira kontakta
Nazovi kontakt
Prikaži pojedinosti o kontaktu
- Prikaži karticu favorita
- Prikaži karticu grupa
Manage shown contact fields
Try filtering out duplicate contacts
+ Manage shown tabs
+ Contacts
+ Favorites
+ Recent calls
+ Show a call confirmation dialog before initiating a call
E-pošta
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
new file mode 100644
index 00000000..3235994a
--- /dev/null
+++ b/app/src/main/res/values-ja/strings.xml
@@ -0,0 +1,133 @@
+
+ Simple Contacts
+ 連絡先
+ 住所
+ 挿入中…
+ 更新中…
+ 内部ストレージ
+ 内部ストレージ (他のアプリからは表示されません)
+ 会社
+ 役職
+ ウェブサイト
+ 連絡先にSMSを送信
+ 連絡先にメールを送信
+ グループにSMSを送信
+ グループにメールを送信
+ Call %s
+ Request the required permissions
+
+ 新しい連絡先
+ 連絡先を編集
+ 連絡先を選択
+ 連絡先を選択
+ 名
+ ミドルネーム
+ 姓
+ Nickname
+
+
+ グループなし
+ 新しいグループを作成
+ グループから削除
+ このグループは空です
+ 連絡先を追加
+ 連絡先グループがありません
+ グループを作成
+ グループに追加
+ アカウントの下にグループを作成
+
+
+ 写真を撮影
+ 写真を選択
+ 写真を削除
+
+
+ 姓を先に表示
+ メイン画面に電話番号を表示
+ 連絡先のサムネイルを表示
+ 連絡先をタップ
+ 連絡先に発信
+ 連絡先の詳細を表示
+ 連絡先に表示するフィールドを管理
+ 重複した連絡先を除外する
+ 表示するタブを管理
+ 連絡先
+ お気に入り
+ Recent calls
+ 発信する前に確認ダイアログを表示する
+
+
+ メール
+ 自宅
+ 職場
+ その他
+
+
+ 番号
+ 携帯
+ Main
+ 職場FAX
+ 自宅FAX
+ ポケベル
+ 電話番号が見つかりません
+
+
+ 誕生日
+ 記念日
+
+
+ お気に入りの連絡先はまだありません
+ お気に入りを追加
+ お気に入りに追加
+ お気に入りから削除
+ 連絡先を編集するには編集画面に切り替えてください
+
+
+ 連絡先を検索
+ お気に入りを検索
+
+
+ 連絡先をインポート
+ 連絡先をエクスポート
+ .vcfファイルから連絡先をインポート
+ 連絡先を.vcfファイルにエクスポート
+ Target contact source
+ Include contact sources
+ ファイル名 (.vcfを含めない)
+
+
+ 表示する項目を選択
+ 敬称(名前の前)
+ 敬称(名前の後)
+ 電話番号
+ メール
+ 住所
+ 予定 (誕生日、記念日)
+ メモ
+ 所属
+ ウェブサイト
+ グループ
+ インポート元
+
+
+ 連絡先に表示される項目(フィールド)を変更することはできますか?
+ 可能です。[設定] -> [連絡先に表示するフィールドを管理] から、表示されるフィールドを選択することができます。これらの中にはデフォルトで無効になっているものもあるので、あなたは新しいものを見つけるかもしれません。
+
+
+
+ 連絡先を管理するシンプルなアプリ (広告表示なし)。
+
+ 連絡先を作成または管理するためのシンプルなアプリです。連絡先はお使いの端末上にのみ保存されますが、Googleや他のアカウントと同期することもできます。お気に入りの連絡先を別のリストとして表示することができます。
+
+ 友人のメールアドレスや予定の管理にも使用できます。これらは複数の項目で並べ替えやフィルタリングする機能があり、名前を\"姓 名\"の順に表示することもできます。
+
+ 広告や不要なアクセス許可は含まれていません。完全にオープンソースで、色のカスタマイズも可能です。
+
+ このアプリは大きな一連のアプリの一つです。 その他のアプリは http://www.simplemobiletools.com で見つけることができます。
+
+
+
+
diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml
index 99536a44..94819d86 100644
--- a/app/src/main/res/values-ko-rKR/strings.xml
+++ b/app/src/main/res/values-ko-rKR/strings.xml
@@ -13,6 +13,8 @@
Send email to contacts
Send SMS to group
Send email to group
+ Call %s
+ Request the required permissions
새로운 연락처
연락처 수정
@@ -21,6 +23,7 @@
이름
중간 이름
성
+ Nickname
No groups
@@ -45,10 +48,13 @@
On contact click
Call contact
View contact details
- Show favorites tab
- Show groups tab
Manage shown contact fields
Try filtering out duplicate contacts
+ Manage shown tabs
+ Contacts
+ Favorites
+ Recent calls
+ Show a call confirmation dialog before initiating a call
이메일
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index f5ee1255..4dbd270c 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -13,6 +13,8 @@
Send email to contacts
Send SMS to group
Send email to group
+ Call %s
+ Request the required permissions
Naujas kontaktas
Redaguoti kontaktą
@@ -21,6 +23,7 @@
Vardas
Antras vardas
Pavardė
+ Nickname
Nėra grupių
@@ -45,10 +48,13 @@
Ant kontakto paspaudimo
Skambinti kontaktui
Žiūrėti kontakto detales
- Rodyti mėgiamiausiųjų skirtuką
- Rodyti grupių skirtuką
Manage shown contact fields
Try filtering out duplicate contacts
+ Manage shown tabs
+ Contacts
+ Favorites
+ Recent calls
+ Show a call confirmation dialog before initiating a call
Elektroninis paštas
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 486feb5a..f2a6d47f 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -13,6 +13,8 @@
Enviar e-mail aos contactos
Enviar SMS para o grupo
Enviar e-mail para o grupo
+ Ligar a %s
+ Request the required permissions
Novo contacto
Editar contacto
@@ -21,6 +23,7 @@
Primeiro nome
Segundo nome
Apelido
+ Alcunha
Não há grupos
@@ -45,10 +48,13 @@
Ao tocar no contacto
Ligar
Ver detalhes
- Mostrar favoritos
- Mostrar grupos
Gerir campos a exibir
- Try filtering out duplicate contacts
+ Tentar filtrar contactos duplicados
+ Manage shown tabs
+ Contactos
+ Favoritos
+ Chamadas recentes
+ Mostrar diálogo para confirmar a chamada
E-mail
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index b107fb1e..86609349 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -13,6 +13,8 @@
Отправить письмо контактам
Отправить SMS группе
Отправить письмо группе
+ Call %s
+ Request the required permissions
Новый контакт
Редактировать контакт
@@ -21,6 +23,7 @@
Имя
Отчество
Фамилия
+ Nickname
Нет групп
@@ -45,10 +48,13 @@
При нажатии на контакт
Позвонить контакту
Просмотреть подробности о контакте
- Показывать вкладку избранного
- Показывать вкладку групп
Управление отображаемыми полями контактов
Отфильтровывать повторяющиеся контакты
+ Manage shown tabs
+ Contacts
+ Favorites
+ Recent calls
+ Show a call confirmation dialog before initiating a call
Эл. почта
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index c1f6d985..6e3f3d25 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -13,6 +13,8 @@
Poslať kontaktom email
Poslať skupine SMS
Poslať skupine email
+ Zavolať %s
+ Vyžiadať potrebné oprávnenia
Nový kontakt
Upraviť kontakt
@@ -21,6 +23,7 @@
Krstné meno
Stredné meno
Priezvisko
+ Prezývka
Žiadne skupiny
@@ -45,10 +48,13 @@
Po kliknutí na kontakt
Zavolať kontakt
Zobraziť údaje kontaktu
- Zobraziť okno s obľúbenými
- Zobraziť okno so skupinami
Spravovať zobrazené polia kontaktov
Pokúsiť sa vyfiltrovať duplicitné kontakty
+ Spravovať zobrazené karty
+ Kontakty
+ Obľúbené
+ Predošlé hovory
+ Zobraziť pred spustením hovoru okno na jeho potvrdenie
Email
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 42f606b5..c5f3e8a7 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -13,6 +13,8 @@
Skicka e-post till kontakter
Skicka sms till grupp
Skicka e-post till grupp
+ Ring %s
+ Begär de behörigheter som krävs
Ny kontakt
Redigera kontakt
@@ -21,6 +23,7 @@
Förnamn
Mellannamn
Efternamn
+ Smeknamn
Inga grupper
@@ -45,10 +48,13 @@
Vid kontakttryckning
Ring kontakt
Visa kontaktuppgifter
- Visa fliken Favoriter
- Visa fliken Grupper
Hantera visade kontaktfält
- Try filtering out duplicate contacts
+ Försök filtrera bort dubblettkontakter
+ Hantera visade flikar
+ Kontakter
+ Favoriter
+ Senaste samtal
+ Visa en bekräftelsedialogruta före uppringning
E-post
@@ -74,7 +80,7 @@
Lägg till favoriter
Lägg till i favoriter
Ta bort från favoriter
- You must be at the Edit screen to modify a contact
+ Kontakter kan bara redigeras i redigeringsvyn
Sök efter kontakter
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
new file mode 100644
index 00000000..f849e887
--- /dev/null
+++ b/app/src/main/res/values-tr/strings.xml
@@ -0,0 +1,133 @@
+
+ Basit Kişiler
+ Kişiler
+ Adres
+ Ekleniyor…
+ Güncelleniyor…
+ Telefon belleği
+ Telefon belleği (diğer uygulamalar tarafından görülmez)
+ Şirket
+ İş pozisyonu
+ Web sitesi
+ Kişilere SMS gönder
+ Kişilere e-posta gönder
+ Gruba SMS gönder
+ Gruba e-posta gönder
+ Call %s
+ Request the required permissions
+
+ Yeni kişi
+ Kişiyi düzenle
+ Kişi seç
+ Kişileri seç
+ Adı
+ Göbek adı
+ Soyadı
+ Nickname
+
+
+ Grup yok
+ Yeni grup oluştur
+ Gruptan kaldır
+ Bu grup boş
+ Kişi ekle
+ Cihazda kişi grubu yok
+ Grup oluştur
+ Gruba ekle
+ Hesap altında grup oluştur
+
+
+ Fotoğraf çek
+ Fotoğraf seç
+ Fotoğrafı kaldır
+
+
+ Soyadı ile başla
+ Ana ekranda telefon numaralarını göster
+ Kişi küçük resimlerini göster
+ Kişi tıklandığında
+ Kişiyi ara
+ Kişi bilgilerini göster
+ Görüntülenecek kişi alanlarını yönet
+ Çift kişileri filtrelemeyi dene
+ Manage shown tabs
+ Contacts
+ Favorites
+ Recent calls
+ Show a call confirmation dialog before initiating a call
+
+
+ E-posta
+ Ev
+ İş
+ Diğer
+
+
+ Numara
+ Cep
+ Ana
+ İş Faksı
+ Ev Faksı
+ Çağrı Cihazı
+ Telefon numarası bulunamadı
+
+
+ Doğum günü
+ Yıldönümü
+
+
+ Henüz hiç favori kişi eklemediniz gibi görünüyor.
+ Favorilerini ekle
+ Favorilere ekle
+ Favorilerden kaldır
+ Bir kişiyi değiştirmek için Düzen ekranında olmalısınız
+
+
+ Kişileri ara
+ Favorileri ara
+
+
+ Kişileri içe aktar
+ Kişileri dışa aktar
+ Kişileri bir .vcf dosyasından içe aktar
+ Kişileri bir .vcf dosyasına aktar
+ Hedef kişi kaynağı
+ Kişi kaynaklarını dahil et
+ Dosya adı (.vcf olmadan)
+
+
+ Görüntülenecek alanları seç
+ Önek
+ Sonek
+ Telefon numaraları
+ E-postalar
+ Adresler
+ Etkinlikler (doğum günleri, yıldönümleri)
+ Notlar
+ Organizasyon
+ Web siteleri
+ Gruplar
+ Kişi kaynağı
+
+
+ Rehberde görüntülenecek alanları değiştirmek istiyorum. Bunu yapabilir miyim?
+ Evet, tek yapmanız gereken Ayarlar -> Görüntülenecek kişi alanlarını yönet\'e gitmek. Orada hangi alanların görüntüleneceğini seçebilirsiniz. Bazıları varsayılan olarak devre dışı bile olsa, orada bazı yenilerini bulabilirsiniz.
+
+
+
+ Kişilerinizi reklamsız yönetmek için bir kişiler uygulaması.
+
+ Kişilerinizi herhangi bir kaynaktan oluşturmak veya yönetmek için basit bir uygulama. Kişiler yalnızca cihazınızda saklanabilir, aynı zamanda Google veya diğer hesaplarla senkronize edilebilir. Favori kişilerinizi ayrı bir listede görüntüleyebilirsiniz.
+
+ Kullanıcı e-postalarını ve etkinliklerini yönetmek için de kullanabilirsiniz. Birden çok parametreye göre sıralama/filtreleme, isteğe bağlı olarak soyadı ilk ad olarak görüntüleme yeteneğine sahiptir.
+
+ Reklam veya gereksiz izinler içermez. Tamamen açık kaynaktır, özelleştirilebilir renkler sağlar.
+
+ Bu uygulama, daha büyük bir uygulama serisinden sadece bir parça. Geri kalanı http://www.simplemobiletools.com adresinde bulabilirsiniz
+
+
+
+
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 79766c2a..c6f1b48b 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -8,11 +8,13 @@
手機空間 (其他程式不可見)
公司
職位
- Website
- Send SMS to contacts
- Send email to contacts
- Send SMS to group
- Send email to group
+ 網站
+ 發送簡訊給聯絡人
+ 發送電子郵件給聯絡人
+ 發送簡訊給群組
+ 發送電子郵件給群組
+ 打電話給 %s
+ 請求必要的權限
新聯絡人
編輯聯絡人
@@ -21,6 +23,7 @@
名字
中間名
姓氏
+ Nickname
沒有群組
@@ -45,10 +48,13 @@
點擊聯絡人
打電話給聯絡人
顯示聯絡人資料
- 顯示我的最愛頁面
- 顯示群組頁面
管理顯示的聯絡人欄位
- Try filtering out duplicate contacts
+ 試著過濾重複的聯絡人
+ 管理顯示的頁面
+ 聯絡人
+ 我的最愛
+ 通話紀錄
+ 開始通話前顯示通話確認框
電子信箱
@@ -74,7 +80,7 @@
添加我的最愛
加入我的最愛
從我的最愛移除
- You must be at the Edit screen to modify a contact
+ 你必須在編輯畫面去修改聯絡人
搜尋聯絡人
@@ -99,7 +105,7 @@
活動 (生日、紀念日)
筆記
組織
- Websites
+ 網站
群組
聯絡人來源
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index 613319f9..aa83af5e 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -2,6 +2,11 @@
+ Added an optional Nickname field
+
+ Allow customizing which tabs are visible\n
+ Added an optional call confirmation dialog
+
Added name prefix/suffix and contact organizations\n
Added a settings item \"Manage shown contact fields\" for customizing visible contact details, with some fields disabled by default
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a3c3eede..d0c4b5f5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,6 +13,8 @@
Send email to contacts
Send SMS to group
Send email to group
+ Call %s
+ Request the required permissions
New contact
Edit contact
@@ -21,6 +23,7 @@
First name
Middle name
Surname
+ Nickname
No groups
@@ -45,10 +48,13 @@
On contact click
Call contact
View contact details
- Show favorites tab
- Show groups tab
Manage shown contact fields
Try filtering out duplicate contacts
+ Manage shown tabs
+ Contacts
+ Favorites
+ Recent calls
+ Show a call confirmation dialog before initiating a call
Email
diff --git a/build.gradle b/build.gradle
index 8e5373c7..1b8fb3c3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.2.41'
+ ext.kotlin_version = '1.2.61'
repositories {
google()
@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.2'
+ classpath 'com.android.tools.build:gradle:3.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png
new file mode 100644
index 00000000..aeb53b51
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/tablet-7.png differ
diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/tablet-10.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/tablet-10.png
new file mode 100644
index 00000000..e4a8b675
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/tablet-10.png differ