diff --git a/.gitignore b/.gitignore
index 32512494..660f31f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,11 @@
*.iml
+*.aab
.gradle
/local.properties
+/gradle.properties
/.idea/
.DS_Store
/build
/captures
-release.keystore
-signing.properties
-/library/build
+keystore.jks
+keystore.properties
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7902ed2..6ace45ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,33 @@
Changelog
==========
+Version 6.1.0 *(2018-11-30)*
+----------------------------
+
+ * Allow setting the app as the default for handling calls
+ * Allow blocking numbers on Android 7+
+ * Improved contact recognition at Recents
+ * Fixed some handling related to creating new contact with a number, or adding a number to an existing contact
+ * Add handling for secret codes like *#*#4636#*#*
+ * Allow copying all contact fields from the view screen by long pressing them
+ * Added an option for hiding the dialpad button on the main screen
+ * Fixed some issues related to importing contacts with birthdays
+
+Version 6.0.0 *(2018-11-06)*
+----------------------------
+
+ * Initial Pro version
+
+Version 5.1.2 *(2018-11-28)*
+----------------------------
+
+ * Had to remove the Recents tab due to the latest Googles' permissions policy. The Pro app will be updated with a Recents tab and a mandatory Dialer soon.
+ * This version of the app is no longer maintained, please upgrade to the Pro version. You can find the Upgrade button at the top of the app Settings.
+
Version 5.1.1 *(2018-11-05)*
----------------------------
- * This version of the app is no longer maintained. Please upgrade to the Pro version. It is free till Nov 13 2018. You can find the Upgrade button at the top of the app Settings.
+ * Added some stability and translation improvements
Version 5.1.0 *(2018-10-28)*
----------------------------
diff --git a/README.md b/README.md
index b7f75f1a..628bd7d1 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,8 @@ Contains no ads or unnecessary permissions. It is fully opensource, provides cus
This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com
-
-
+
+

diff --git a/app/build.gradle b/app/build.gradle
index 93de4f18..5d27b633 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,6 +1,11 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+
+def keystorePropertiesFile = rootProject.file("keystore.properties")
+def keystoreProperties = new Properties()
+keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
compileSdkVersion 28
@@ -10,13 +15,18 @@ android {
applicationId "com.simplemobiletools.contacts.pro"
minSdkVersion 21
targetSdkVersion 28
- versionCode 35
- versionName "5.1.1"
+ versionCode 37
+ versionName "6.1.0"
setProperty("archivesBaseName", "contacts")
}
signingConfigs {
- release
+ release {
+ keyAlias keystoreProperties['keyAlias']
+ keyPassword keystoreProperties['keyPassword']
+ storeFile file(keystoreProperties['storeFile'])
+ storePassword keystoreProperties['storePassword']
+ }
}
buildTypes {
@@ -41,28 +51,12 @@ android {
}
dependencies {
- implementation 'com.simplemobiletools:commons:5.3.11'
- implementation 'joda-time:joda-time:2.9.9'
- implementation 'com.facebook.stetho:stetho:1.5.0'
+ implementation 'com.simplemobiletools:commons:5.5.1'
+ implementation 'joda-time:joda-time:2.10.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.4'
-}
-Properties props = new Properties()
-def propFile = new File('signing.properties')
-if (propFile.canRead()) {
- props.load(new FileInputStream(propFile))
-
- if (props != null && props.containsKey('STORE_FILE') && props.containsKey('KEY_ALIAS') && props.containsKey('PASSWORD')) {
- android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
- android.signingConfigs.release.storePassword = props['PASSWORD']
- android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
- android.signingConfigs.release.keyPassword = props['PASSWORD']
- } else {
- println 'signing.properties found but some entries are missing'
- android.buildTypes.release.signingConfig = null
- }
-} else {
- println 'signing.properties not found'
- android.buildTypes.release.signingConfig = null
+ kapt "androidx.room:room-compiler:2.0.0"
+ implementation "androidx.room:room-runtime:2.0.0"
+ annotationProcessor "androidx.room:room-compiler:2.0.0"
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 81d50163..c965a109 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,7 @@
android:installLocation="auto">
+
@@ -13,6 +14,11 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:parentActivityName=".activities.MainActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dialpad)
@@ -70,6 +79,14 @@ class DialpadActivity : SimpleActivity() {
return true
}
+ private fun checkDialIntent() {
+ if (intent.action == Intent.ACTION_DIAL && intent.data != null && intent.dataString?.contains("tel:") == true) {
+ val number = Uri.decode(intent.dataString).substringAfter("tel:")
+ dialpad_input.setText(number)
+ dialpad_input.setSelection(number.length)
+ }
+ }
+
private fun addNumberToContact() {
Intent().apply {
action = Intent.ACTION_INSERT_OR_EDIT
@@ -121,12 +138,27 @@ class DialpadActivity : SimpleActivity() {
private fun gotContacts(newContacts: ArrayList) {
contacts = newContacts
- Contact.sorting = config.sorting
- Contact.startWithSurname = config.startNameWithSurname
- contacts.sort()
+ checkDialIntent()
}
+ @TargetApi(Build.VERSION_CODES.O)
private fun dialpadValueChanged(text: String) {
+ val len = text.length
+ if (len > 8 && text.startsWith("*#*#") && text.endsWith("#*#*")) {
+ val secretCode = text.substring(4, text.length - 4)
+ if (isOreoPlus()) {
+ if (isDefaultDialer()) {
+ getSystemService(TelephonyManager::class.java).sendDialerSpecialCode(secretCode)
+ } else {
+ launchSetDefaultDialerIntent()
+ }
+ } else {
+ val intent = Intent(SECRET_CODE_ACTION, Uri.parse("android_secret_code://$secretCode"))
+ sendBroadcast(intent)
+ }
+ return
+ }
+
(dialpad_list.adapter as? ContactsAdapter)?.finishActMode()
val filtered = contacts.filter { it.doesContainPhoneNumber(text) } as ArrayList
@@ -144,6 +176,13 @@ class DialpadActivity : SimpleActivity() {
}
}
+ override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+ super.onActivityResult(requestCode, resultCode, resultData)
+ if (requestCode == REQUEST_CODE_SET_DEFAULT_DIALER && isDefaultDialer()) {
+ dialpadValueChanged(dialpad_input.value)
+ }
+ }
+
private fun initCall() {
val number = dialpad_input.value
if (number.isNotEmpty()) {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/EditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/EditContactActivity.kt
index 51fc4760..f882d3c3 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/EditContactActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/EditContactActivity.kt
@@ -91,7 +91,7 @@ class EditContactActivity : ContactActivity() {
if (wasActivityInitialized) {
menu.findItem(R.id.delete).isVisible = contact?.id != 0
menu.findItem(R.id.share).isVisible = contact?.id != 0
- menu.findItem(R.id.open_with).isVisible = contact?.id != 0 && contact?.source != SMT_PRIVATE
+ menu.findItem(R.id.open_with).isVisible = contact?.id != 0 && contact?.isPrivate() == false
}
return true
}
@@ -128,7 +128,11 @@ class EditContactActivity : ContactActivity() {
val data = intent.data
if (data != null) {
val rawId = if (data.path.contains("lookup")) {
- getLookupUriRawId(data)
+ if (data.pathSegments.last().startsWith("local_")) {
+ data.path.substringAfter("local_").toInt()
+ } else {
+ getLookupUriRawId(data)
+ }
} else {
getContactUriRawId(data)
}
@@ -140,36 +144,46 @@ class EditContactActivity : ContactActivity() {
}
if (contactId != 0) {
- contact = ContactsHelper(this).getContactWithId(contactId, intent.getBooleanExtra(IS_PRIVATE, false))
- if (contact == null) {
- toast(R.string.unknown_error_occurred)
- finish()
- return
- }
+ Thread {
+ contact = ContactsHelper(this).getContactWithId(contactId, intent.getBooleanExtra(IS_PRIVATE, false))
+ if (contact == null) {
+ toast(R.string.unknown_error_occurred)
+ finish()
+ } else {
+ runOnUiThread {
+ gotContact()
+ }
+ }
+ }.start()
+ } else {
+ gotContact()
}
+ }
+ private fun gotContact() {
+ contact_scrollview.beVisible()
if (contact == null) {
setupNewContact()
} else {
setupEditContact()
}
- if ((contact!!.id == 0 && intent.extras != null && intent.extras.containsKey(KEY_PHONE) && action == Intent.ACTION_INSERT) || action == ADD_NEW_CONTACT_NUMBER) {
- val phone = intent.extras.get(KEY_PHONE)
- if (phone != null) {
- val phoneNumber = phone.toString()
- contact!!.phoneNumbers.add(PhoneNumber(phoneNumber, DEFAULT_PHONE_NUMBER_TYPE, ""))
+ val action = intent.action
+ if (((contact!!.id == 0 && action == Intent.ACTION_INSERT) || action == ADD_NEW_CONTACT_NUMBER) && intent.extras != null) {
+ val phoneNumber = getPhoneNumberFromIntent(intent)
+ if (phoneNumber != null) {
+ contact!!.phoneNumbers.add(PhoneNumber(phoneNumber, DEFAULT_PHONE_NUMBER_TYPE, "", phoneNumber.normalizeNumber()))
if (phoneNumber.isNotEmpty() && action == ADD_NEW_CONTACT_NUMBER) {
highlightLastPhoneNumber = true
}
}
- val firstName = intent.extras.get(KEY_NAME)
+ val firstName = intent.extras!!.get(KEY_NAME)
if (firstName != null) {
contact!!.firstName = firstName.toString()
}
- val data = intent.extras.getParcelableArrayList("data")
+ val data = intent.extras!!.getParcelableArrayList("data")
if (data != null) {
parseIntentData(data)
}
@@ -530,7 +544,7 @@ class EditContactActivity : ContactActivity() {
applyColorFilter(getAdjustedPrimaryColor())
background.applyColorFilter(config.textColor)
setOnClickListener {
- removeGroup(group.id)
+ removeGroup(group.id!!)
}
}
}
@@ -561,9 +575,7 @@ class EditContactActivity : ContactActivity() {
private fun setupNewContact() {
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(), ArrayList(), ArrayList())
+ contact = getEmptyContact()
contact_source.text = getPublicContactSource(contact!!.source)
}
@@ -875,7 +887,7 @@ class EditContactActivity : ContactActivity() {
val numberLabel = if (numberType == CommonDataKinds.Phone.TYPE_CUSTOM) numberHolder.contact_number_type.value else ""
if (number.isNotEmpty()) {
- phoneNumbers.add(PhoneNumber(number, numberType, numberLabel))
+ phoneNumbers.add(PhoneNumber(number, numberType, numberLabel, number.normalizeNumber()))
}
}
return phoneNumbers
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/GroupContactsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/GroupContactsActivity.kt
index 0e33c5c5..ab35f298 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/GroupContactsActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/GroupContactsActivity.kt
@@ -70,8 +70,8 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh
private fun fabClicked() {
SelectContactsDialog(this, allContacts, groupContacts) { addedContacts, removedContacts ->
Thread {
- addContactsToGroup(addedContacts, group.id)
- removeContactsFromGroup(removedContacts, group.id)
+ addContactsToGroup(addedContacts, group.id!!)
+ removeContactsFromGroup(removedContacts, group.id!!)
refreshContacts()
}.start()
}
@@ -86,11 +86,6 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh
group_contacts_placeholder_2.beVisibleIf(groupContacts.isEmpty())
group_contacts_placeholder.beVisibleIf(groupContacts.isEmpty())
group_contacts_list.beVisibleIf(groupContacts.isNotEmpty())
-
- Contact.sorting = config.sorting
- Contact.startWithSurname = config.startNameWithSurname
- groupContacts.sort()
-
updateContacts(groupContacts)
}
}
@@ -129,8 +124,8 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh
override fun removeFromGroup(contacts: ArrayList) {
Thread {
- removeContactsFromGroup(contacts, group.id)
- if (groupContacts.size == 0) {
+ removeContactsFromGroup(contacts, group.id!!)
+ if (groupContacts.size == contacts.size) {
refreshContacts()
}
}.start()
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/InsertOrEditContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/InsertOrEditContactActivity.kt
index b2303819..36934f94 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/InsertOrEditContactActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/InsertOrEditContactActivity.kt
@@ -13,10 +13,7 @@ import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.adapters.ContactsAdapter
import com.simplemobiletools.contacts.pro.extensions.config
import com.simplemobiletools.contacts.pro.extensions.getContactPublicUri
-import com.simplemobiletools.contacts.pro.helpers.ADD_NEW_CONTACT_NUMBER
-import com.simplemobiletools.contacts.pro.helpers.ContactsHelper
-import com.simplemobiletools.contacts.pro.helpers.KEY_PHONE
-import com.simplemobiletools.contacts.pro.helpers.LOCATION_INSERT_OR_EDIT
+import com.simplemobiletools.contacts.pro.helpers.*
import com.simplemobiletools.contacts.pro.models.Contact
import kotlinx.android.synthetic.main.activity_insert_edit_contact.*
@@ -45,7 +42,7 @@ class InsertOrEditContactActivity : SimpleActivity() {
Intent().apply {
action = Intent.ACTION_INSERT
data = ContactsContract.Contacts.CONTENT_URI
- putExtra(KEY_PHONE, intent.getStringExtra(KEY_PHONE))
+ putExtra(KEY_PHONE, getPhoneNumberFromIntent(intent))
if (resolveActivity(packageManager) != null) {
startActivityForResult(this, START_INSERT_ACTIVITY)
} else {
@@ -58,15 +55,13 @@ class InsertOrEditContactActivity : SimpleActivity() {
}
private fun gotContacts(contacts: ArrayList) {
- Contact.sorting = config.sorting
- Contact.startWithSurname = config.startNameWithSurname
- contacts.sort()
-
ContactsAdapter(this, contacts, null, LOCATION_INSERT_OR_EDIT, null, existing_contact_list, existing_contact_fastscroller) {
+ val contact = it as Contact
Intent(applicationContext, EditContactActivity::class.java).apply {
- data = getContactPublicUri(it as Contact)
+ data = getContactPublicUri(contact)
action = ADD_NEW_CONTACT_NUMBER
- putExtra(KEY_PHONE, intent.getStringExtra(KEY_PHONE))
+ putExtra(KEY_PHONE, getPhoneNumberFromIntent(intent))
+ putExtra(IS_PRIVATE, contact.isPrivate())
startActivityForResult(this, START_EDIT_ACTIVITY)
}
}.apply {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/MainActivity.kt
index f8eefe7d..ec08ce56 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/MainActivity.kt
@@ -22,6 +22,7 @@ import com.simplemobiletools.commons.models.Release
import com.simplemobiletools.contacts.pro.BuildConfig
import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.adapters.ViewPagerAdapter
+import com.simplemobiletools.contacts.pro.databases.ContactsDatabase
import com.simplemobiletools.contacts.pro.dialogs.ChangeSortingDialog
import com.simplemobiletools.contacts.pro.dialogs.ExportContactsDialog
import com.simplemobiletools.contacts.pro.dialogs.FilterContactSourcesDialog
@@ -39,6 +40,7 @@ import kotlinx.android.synthetic.main.fragment_groups.*
import kotlinx.android.synthetic.main.fragment_recents.*
import java.io.FileOutputStream
+
class MainActivity : SimpleActivity(), RefreshContactsListener {
private var isSearchOpen = false
private var searchMenuItem: MenuItem? = null
@@ -158,11 +160,15 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
}
}
}
- isFirstResume = false
val dialpadIcon = resources.getColoredDrawableWithColor(R.drawable.ic_dialpad, if (isBlackAndWhiteTheme()) Color.BLACK else Color.WHITE)
- main_dialpad_button.setImageDrawable(dialpadIcon)
- main_dialpad_button.background.applyColorFilter(getAdjustedPrimaryColor())
+ main_dialpad_button.apply {
+ setImageDrawable(dialpadIcon)
+ background.applyColorFilter(getAdjustedPrimaryColor())
+ beVisibleIf(config.showDialpadButton)
+ }
+
+ isFirstResume = false
}
override fun onPause() {
@@ -173,6 +179,9 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
override fun onDestroy() {
super.onDestroy()
config.lastUsedViewPagerPage = viewpager.currentItem
+ if (!isChangingConfigurations) {
+ ContactsDatabase.destroyInstance()
+ }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -362,7 +371,11 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
// 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()
+ if (intent?.action == Intent.ACTION_VIEW && intent.type == "vnd.android.cursor.dir/calls") {
+ main_tabs_holder.getTabAt(getRecentsTabIndex())?.select()
+ } else {
+ main_tabs_holder.getTabAt(config.lastUsedViewPagerPage)?.select()
+ }
invalidateOptionsMenu()
}, 100L)
}
@@ -478,7 +491,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
}
private fun launchAbout() {
- val licenses = LICENSE_JODA or LICENSE_GLIDE or LICENSE_GSON or LICENSE_STETHO
+ val licenses = LICENSE_JODA or LICENSE_GLIDE or LICENSE_GSON
val faqItems = arrayListOf(
FAQItem(R.string.faq_1_title, R.string.faq_1_text),
@@ -506,30 +519,39 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
return@getContacts
}
+ val contacts = it
if (refreshTabsMask and CONTACTS_TAB_MASK != 0) {
- contacts_fragment?.refreshContacts(it)
+ contacts_fragment?.refreshContacts(contacts)
}
if (refreshTabsMask and FAVORITES_TAB_MASK != 0) {
- favorites_fragment?.refreshContacts(it)
+ favorites_fragment?.refreshContacts(contacts)
}
if (refreshTabsMask and RECENTS_TAB_MASK != 0) {
- recents_fragment?.refreshContacts(it)
+ recents_fragment?.refreshContacts(contacts)
}
if (refreshTabsMask and GROUPS_TAB_MASK != 0) {
if (refreshTabsMask == GROUPS_TAB_MASK) {
groups_fragment.skipHashComparing = true
}
- groups_fragment?.refreshContacts(it)
+ groups_fragment?.refreshContacts(contacts)
}
- }
- if (refreshTabsMask and RECENTS_TAB_MASK != 0) {
- ContactsHelper(this).getRecents {
- runOnUiThread {
- recents_fragment?.updateRecentCalls(it)
+ if (refreshTabsMask and RECENTS_TAB_MASK != 0) {
+ ContactsHelper(this).getRecents {
+ it.filter { it.name == null }.forEach {
+ val namelessCall = it
+ val contact = contacts.firstOrNull { it.doesContainPhoneNumber(namelessCall.number) }
+ if (contact != null) {
+ it.name = contact.getNameToDisplay()
+ }
+ }
+
+ runOnUiThread {
+ recents_fragment?.updateRecentCalls(it)
+ }
}
}
}
@@ -537,6 +559,22 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
private fun getAllFragments() = arrayListOf(contacts_fragment, favorites_fragment, recents_fragment, groups_fragment)
+ private fun getRecentsTabIndex(): Int {
+ var index = 0
+ if (config.showTabs and RECENTS_TAB_MASK == 0) {
+ return index
+ }
+
+ if (config.showTabs and CONTACTS_TAB_MASK != 0) {
+ index++
+ }
+
+ if (config.showTabs and FAVORITES_TAB_MASK != 0) {
+ index++
+ }
+ return index
+ }
+
private fun checkWhatsNewDialog() {
arrayListOf().apply {
add(Release(10, R.string.release_10))
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/ManageBlockedNumbersActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/ManageBlockedNumbersActivity.kt
new file mode 100644
index 00000000..ae1ed642
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/ManageBlockedNumbersActivity.kt
@@ -0,0 +1,93 @@
+package com.simplemobiletools.contacts.pro.activities
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+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.commons.interfaces.RefreshRecyclerViewListener
+import com.simplemobiletools.contacts.pro.R
+import com.simplemobiletools.contacts.pro.adapters.ManageBlockedNumbersAdapter
+import com.simplemobiletools.contacts.pro.dialogs.AddBlockedNumberDialog
+import com.simplemobiletools.contacts.pro.extensions.getBlockedNumbers
+import com.simplemobiletools.contacts.pro.extensions.isDefaultDialer
+import com.simplemobiletools.contacts.pro.helpers.REQUEST_CODE_SET_DEFAULT_DIALER
+import com.simplemobiletools.contacts.pro.models.BlockedNumber
+import kotlinx.android.synthetic.main.activity_manage_blocked_numbers.*
+
+class ManageBlockedNumbersActivity : SimpleActivity(), RefreshRecyclerViewListener {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_manage_blocked_numbers)
+ updateBlockedNumbers()
+ updateTextColors(manage_blocked_numbers_wrapper)
+ updatePlaceholderTexts()
+
+ manage_blocked_numbers_placeholder_2.apply {
+ underlineText()
+ setTextColor(getAdjustedPrimaryColor())
+ setOnClickListener {
+ if (isDefaultDialer()) {
+ addOrEditBlockedNumber()
+ } else {
+ launchSetDefaultDialerIntent()
+ }
+ }
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.menu_add_blocked_number, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.add_blocked_number -> addOrEditBlockedNumber()
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ override fun refreshItems() {
+ updateBlockedNumbers()
+ }
+
+ private fun updatePlaceholderTexts() {
+ manage_blocked_numbers_placeholder.text = getString(if (isDefaultDialer()) R.string.not_blocking_anyone else R.string.must_make_default_dialer)
+ manage_blocked_numbers_placeholder_2.text = getString(if (isDefaultDialer()) R.string.add_a_blocked_number else R.string.set_to_default)
+ }
+
+ private fun updateBlockedNumbers() {
+ Thread {
+ val blockedNumbers = getBlockedNumbers()
+ runOnUiThread {
+ ManageBlockedNumbersAdapter(this, blockedNumbers, this, manage_blocked_numbers_list) {
+ addOrEditBlockedNumber(it as BlockedNumber)
+ }.apply {
+ manage_blocked_numbers_list.adapter = this
+ }
+
+ manage_blocked_numbers_placeholder.beVisibleIf(blockedNumbers.isEmpty())
+ manage_blocked_numbers_placeholder_2.beVisibleIf(blockedNumbers.isEmpty())
+ }
+ }.start()
+ }
+
+ private fun addOrEditBlockedNumber(currentNumber: BlockedNumber? = null) {
+ AddBlockedNumberDialog(this, currentNumber) {
+ updateBlockedNumbers()
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+ super.onActivityResult(requestCode, resultCode, resultData)
+ if (requestCode == REQUEST_CODE_SET_DEFAULT_DIALER && isDefaultDialer()) {
+ updatePlaceholderTexts()
+ updateBlockedNumbers()
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SelectContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SelectContactActivity.kt
index d31c6462..ffeb168c 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SelectContactActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SelectContactActivity.kt
@@ -17,7 +17,6 @@ import com.simplemobiletools.contacts.pro.extensions.config
import com.simplemobiletools.contacts.pro.extensions.getContactPublicUri
import com.simplemobiletools.contacts.pro.extensions.getVisibleContactSources
import com.simplemobiletools.contacts.pro.helpers.ContactsHelper
-import com.simplemobiletools.contacts.pro.helpers.SMT_PRIVATE
import com.simplemobiletools.contacts.pro.models.Contact
import kotlinx.android.synthetic.main.activity_select_contact.*
@@ -90,7 +89,7 @@ class SelectContactActivity : SimpleActivity() {
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> it.phoneNumbers.isNotEmpty()
else -> true
}
- it.source != SMT_PRIVATE && hasRequiredValues
+ !it.isPrivate() && hasRequiredValues
} else {
true
}
@@ -99,10 +98,6 @@ class SelectContactActivity : SimpleActivity() {
val contactSources = getVisibleContactSources()
contacts = contacts.filter { contactSources.contains(it.source) } as ArrayList
- Contact.sorting = config.sorting
- Contact.startWithSurname = config.startNameWithSurname
- contacts.sort()
-
runOnUiThread {
updatePlaceholderVisibility(contacts)
SelectContactsAdapter(this, contacts, ArrayList(), false, select_contact_list) {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SettingsActivity.kt
index 0950612b..db351714 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SettingsActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SettingsActivity.kt
@@ -1,8 +1,13 @@
package com.simplemobiletools.contacts.pro.activities
+import android.annotation.TargetApi
+import android.content.Intent
+import android.os.Build
import android.os.Bundle
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
-import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.extensions.beVisibleIf
+import com.simplemobiletools.commons.extensions.updateTextColors
+import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.dialogs.ManageVisibleFieldsDialog
@@ -26,6 +31,7 @@ class SettingsActivity : SimpleActivity() {
setupCustomizeColors()
setupManageShownContactFields()
setupManageShownTabs()
+ setupManageBlockedNumbers()
setupUseEnglish()
setupShowInfoBubble()
setupShowContactThumbnails()
@@ -35,6 +41,7 @@ class SettingsActivity : SimpleActivity() {
setupUse24HourTimeFormat()
setupFilterDuplicates()
setupShowCallConfirmation()
+ setupShowDialpadButton()
setupOnContactClick()
updateTextColors(settings_holder)
}
@@ -57,6 +64,15 @@ class SettingsActivity : SimpleActivity() {
}
}
+ // support for device-wise blocking came on Android 7, rely only on that
+ @TargetApi(Build.VERSION_CODES.N)
+ private fun setupManageBlockedNumbers() {
+ settings_manage_blocked_numbers_holder.beVisibleIf(isNougatPlus())
+ settings_manage_blocked_numbers_holder.setOnClickListener {
+ startActivity(Intent(this, ManageBlockedNumbersActivity::class.java))
+ }
+ }
+
private fun setupUseEnglish() {
settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en")
settings_use_english.isChecked = config.useEnglish
@@ -123,6 +139,14 @@ class SettingsActivity : SimpleActivity() {
}
}
+ private fun setupShowDialpadButton() {
+ settings_show_dialpad_button.isChecked = config.showDialpadButton
+ settings_show_dialpad_button_holder.setOnClickListener {
+ settings_show_dialpad_button.toggle()
+ config.showDialpadButton = settings_show_dialpad_button.isChecked
+ }
+ }
+
private fun setupOnContactClick() {
settings_on_contact_click.text = getOnContactClickText()
settings_on_contact_click_holder.setOnClickListener {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SimpleActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SimpleActivity.kt
index cff6e0a3..63ac285a 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SimpleActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/SimpleActivity.kt
@@ -1,7 +1,14 @@
package com.simplemobiletools.contacts.pro.activities
+import android.annotation.TargetApi
+import android.content.ContentValues
+import android.content.Intent
+import android.os.Build
+import android.telecom.TelecomManager
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.contacts.pro.R
+import com.simplemobiletools.contacts.pro.helpers.KEY_PHONE
+import com.simplemobiletools.contacts.pro.helpers.REQUEST_CODE_SET_DEFAULT_DIALER
open class SimpleActivity : BaseSimpleActivity() {
override fun getAppIconIDs() = arrayListOf(
@@ -27,4 +34,27 @@ open class SimpleActivity : BaseSimpleActivity() {
)
override fun getAppLauncherName() = getString(R.string.app_launcher_name)
+
+ protected fun getPhoneNumberFromIntent(intent: Intent): String? {
+ if (intent.extras?.containsKey(KEY_PHONE) == true) {
+ return intent.getStringExtra(KEY_PHONE)
+ } else if (intent.extras?.containsKey("data") == true) {
+ // sample contact number from Google Contacts:
+ // data: [data1=+123 456 789 mimetype=vnd.android.cursor.item/phone_v2 _id=-1 data2=0]
+ val data = intent.extras!!.get("data")
+ if (data != null) {
+ val contentValues = (data as? ArrayList)?.firstOrNull() as? ContentValues
+ if (contentValues != null && contentValues.containsKey("data1")) {
+ return contentValues.getAsString("data1")
+ }
+ }
+ }
+ return null
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ protected fun launchSetDefaultDialerIntent() {
+ val intent = Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER).putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName)
+ startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER)
+ }
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/ViewContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/ViewContactActivity.kt
index 386aaa2d..b1ef1744 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/ViewContactActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/ViewContactActivity.kt
@@ -6,6 +6,7 @@ import android.os.Bundle
import android.provider.ContactsContract
import android.view.Menu
import android.view.MenuItem
+import android.view.View
import android.view.WindowManager
import android.widget.RelativeLayout
import com.simplemobiletools.commons.extensions.*
@@ -25,6 +26,7 @@ import kotlinx.android.synthetic.main.item_website.view.*
class ViewContactActivity : ContactActivity() {
private var isViewIntent = false
+ private var wasEditLaunched = false
private var showFields = 0
override fun onCreate(savedInstanceState: Bundle?) {
@@ -39,21 +41,25 @@ class ViewContactActivity : ContactActivity() {
if (isViewIntent) {
handlePermission(PERMISSION_READ_CONTACTS) {
if (it) {
- initContact()
+ Thread {
+ initContact()
+ }.start()
} else {
toast(R.string.no_contacts_permission)
finish()
}
}
} else {
- initContact()
+ Thread {
+ initContact()
+ }.start()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_view_contact, menu)
menu.apply {
- findItem(R.id.open_with).isVisible = contact?.source != SMT_PRIVATE
+ findItem(R.id.open_with).isVisible = contact?.isPrivate() == false
}
return true
}
@@ -64,7 +70,7 @@ class ViewContactActivity : ContactActivity() {
}
when (item.itemId) {
- R.id.edit -> editContact(contact!!)
+ R.id.edit -> editContact()
R.id.share -> shareContact()
R.id.open_with -> openWith()
R.id.delete -> deleteContact()
@@ -100,19 +106,29 @@ class ViewContactActivity : ContactActivity() {
if (contactId != 0 && !wasLookupKeyUsed) {
contact = ContactsHelper(this).getContactWithId(contactId, intent.getBooleanExtra(IS_PRIVATE, false))
if (contact == null) {
- toast(R.string.unknown_error_occurred)
+ if (!wasEditLaunched) {
+ toast(R.string.unknown_error_occurred)
+ }
finish()
- return
+ } else {
+ runOnUiThread {
+ gotContact()
+ }
+ }
+ } else {
+ if (contact == null) {
+ finish()
+ } else {
+ runOnUiThread {
+ gotContact()
+ }
}
}
+ }
- if (contact == null) {
- finish()
- return
- }
-
+ private fun gotContact() {
+ contact_scrollview.beVisible()
setupViewContact()
-
contact_send_sms.beVisibleIf(contact!!.phoneNumbers.isNotEmpty())
contact_start_call.beVisibleIf(contact!!.phoneNumbers.isNotEmpty())
contact_send_email.beVisibleIf(contact!!.emails.isNotEmpty())
@@ -163,6 +179,11 @@ class ViewContactActivity : ContactActivity() {
setupContactSource()
}
+ private fun editContact() {
+ wasEditLaunched = true
+ editContact(contact!!)
+ }
+
private fun openWith() {
Intent().apply {
action = ContactsContract.QuickContact.ACTION_QUICK_CONTACT
@@ -191,21 +212,27 @@ class ViewContactActivity : ContactActivity() {
contact!!.apply {
contact_prefix.text = prefix
contact_prefix.beVisibleIf(prefix.isNotEmpty() && showFields and SHOW_PREFIX_FIELD != 0)
+ contact_prefix.copyOnLongClick(prefix)
contact_first_name.text = firstName
contact_first_name.beVisibleIf(firstName.isNotEmpty() && showFields and SHOW_FIRST_NAME_FIELD != 0)
+ contact_first_name.copyOnLongClick(firstName)
contact_middle_name.text = middleName
contact_middle_name.beVisibleIf(middleName.isNotEmpty() && showFields and SHOW_MIDDLE_NAME_FIELD != 0)
+ contact_middle_name.copyOnLongClick(middleName)
contact_surname.text = surname
contact_surname.beVisibleIf(surname.isNotEmpty() && showFields and SHOW_SURNAME_FIELD != 0)
+ contact_surname.copyOnLongClick(surname)
contact_suffix.text = suffix
contact_suffix.beVisibleIf(suffix.isNotEmpty() && showFields and SHOW_SUFFIX_FIELD != 0)
+ contact_suffix.copyOnLongClick(suffix)
contact_nickname.text = nickname
contact_nickname.beVisibleIf(nickname.isNotEmpty() && showFields and SHOW_NICKNAME_FIELD != 0)
+ contact_nickname.copyOnLongClick(nickname)
if (contact_prefix.isGone() && contact_first_name.isGone() && contact_middle_name.isGone() && contact_surname.isGone() && contact_suffix.isGone()
&& contact_nickname.isGone()) {
@@ -225,6 +252,7 @@ class ViewContactActivity : ContactActivity() {
contact_numbers_holder.addView(this)
contact_number.text = phoneNumber.value
contact_number_type.text = getPhoneNumberTypeText(phoneNumber.type, phoneNumber.label)
+ copyOnLongClick(phoneNumber.value)
setOnClickListener {
if (config.showCallConfirmation) {
@@ -235,12 +263,6 @@ class ViewContactActivity : ContactActivity() {
startCallIntent(phoneNumber.value)
}
}
-
- setOnLongClickListener {
- copyToClipboard(phoneNumber.value)
- toast(R.string.value_copied_to_clipboard)
- true
- }
}
}
contact_numbers_image.beVisible()
@@ -261,6 +283,7 @@ class ViewContactActivity : ContactActivity() {
contact_emails_holder.addView(this)
contact_email.text = email.value
contact_email_type.text = getEmailTypeText(email.type, email.label)
+ copyOnLongClick(email.value)
setOnClickListener {
sendEmailIntent(email.value)
@@ -285,6 +308,7 @@ class ViewContactActivity : ContactActivity() {
contact_addresses_holder.addView(this)
contact_address.text = address.value
contact_address_type.text = getAddressTypeText(address.type, address.label)
+ copyOnLongClick(address.value)
setOnClickListener {
sendAddressIntent(address.value)
@@ -309,6 +333,7 @@ class ViewContactActivity : ContactActivity() {
contact_ims_holder.addView(this)
contact_im.text = IM.value
contact_im_type.text = getIMTypeText(IM.type, IM.label)
+ copyOnLongClick(IM.value)
}
}
contact_ims_image.beVisible()
@@ -330,6 +355,7 @@ class ViewContactActivity : ContactActivity() {
it.value.getDateTimeFromDateString(contact_event)
contact_event_type.setText(getEventTextId(it.type))
contact_event_remove.beGone()
+ copyOnLongClick(it.value)
}
}
contact_events_image.beVisible()
@@ -346,6 +372,7 @@ class ViewContactActivity : ContactActivity() {
contact_notes.text = notes
contact_notes_image.beVisible()
contact_notes.beVisible()
+ contact_notes.copyOnLongClick(notes)
} else {
contact_notes_image.beGone()
contact_notes.beGone()
@@ -360,6 +387,8 @@ class ViewContactActivity : ContactActivity() {
contact_organization_image.beGoneIf(organization.isEmpty())
contact_organization_company.beGoneIf(organization.company.isEmpty())
contact_organization_job_position.beGoneIf(organization.jobPosition.isEmpty())
+ contact_organization_company.copyOnLongClick(contact_organization_company.value)
+ contact_organization_job_position.copyOnLongClick(contact_organization_job_position.value)
if (organization.company.isEmpty() && organization.jobPosition.isNotEmpty()) {
(contact_organization_image.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_TOP, contact_organization_job_position.id)
@@ -380,6 +409,7 @@ class ViewContactActivity : ContactActivity() {
layoutInflater.inflate(R.layout.item_website, contact_websites_holder, false).apply {
contact_websites_holder.addView(this)
contact_website.text = url
+ copyOnLongClick(url)
setOnClickListener {
openWebsiteIntent(url)
@@ -403,6 +433,7 @@ class ViewContactActivity : ContactActivity() {
val group = it
contact_groups_holder.addView(this)
contact_group.text = group.title
+ copyOnLongClick(group.title)
}
}
contact_groups_image.beVisible()
@@ -415,9 +446,11 @@ class ViewContactActivity : ContactActivity() {
private fun setupContactSource() {
if (showFields and SHOW_CONTACT_SOURCE_FIELD != 0) {
- contact_source.text = getPublicContactSource(contact!!.source)
+ val contactSourceValue = getPublicContactSource(contact!!.source)
+ contact_source.text = contactSourceValue
contact_source_image.beVisible()
contact_source.beVisible()
+ contact_source.copyOnLongClick(contactSourceValue)
} else {
contact_source_image.beGone()
contact_source.beGone()
@@ -425,4 +458,12 @@ class ViewContactActivity : ContactActivity() {
}
private fun getStarDrawable(on: Boolean) = resources.getDrawable(if (on) R.drawable.ic_star_on_big else R.drawable.ic_star_off_big)
+
+ private fun View.copyOnLongClick(value: String) {
+ setOnLongClickListener {
+ copyToClipboard(value)
+ toast(R.string.value_copied_to_clipboard)
+ true
+ }
+ }
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ContactsAdapter.kt
index 5a4baf46..3ab8b592 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ContactsAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ContactsAdapter.kt
@@ -34,6 +34,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList Unit) :
MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
+ private val NEW_GROUP_ID = -1
private lateinit var contactDrawable: Drawable
private lateinit var businessContactDrawable: Drawable
@@ -158,7 +159,10 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList()
- ContactsHelper(activity).getStoredGroups().forEach {
- items.add(RadioItem(it.id.toInt(), it.title))
+ ContactsHelper(activity).getStoredGroups {
+ it.forEach {
+ items.add(RadioItem(it.id!!.toInt(), it.title))
+ }
+ items.add(RadioItem(NEW_GROUP_ID, activity.getString(R.string.create_new_group)))
+ showGroupsPicker(items)
}
- items.add(RadioItem(NEW_GROUP_ID, activity.getString(R.string.create_new_group)))
+ }
- RadioGroupDialog(activity, items, 0) {
+ private fun showGroupsPicker(radioItems: ArrayList) {
+ val selectedContacts = getSelectedItems()
+ RadioGroupDialog(activity, radioItems, 0) {
if (it as Int == NEW_GROUP_ID) {
CreateNewGroupDialog(activity) {
Thread {
- activity.addContactsToGroup(selectedContacts, it.id)
+ activity.addContactsToGroup(selectedContacts, it.id!!.toLong())
refreshListener?.refreshContacts(GROUPS_TAB_MASK)
}.start()
finishActMode()
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/GroupsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/GroupsAdapter.kt
index f700168c..1d6f76d6 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/GroupsAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/GroupsAdapter.kt
@@ -13,7 +13,7 @@ import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.activities.SimpleActivity
import com.simplemobiletools.contacts.pro.dialogs.RenameGroupDialog
import com.simplemobiletools.contacts.pro.extensions.config
-import com.simplemobiletools.contacts.pro.extensions.dbHelper
+import com.simplemobiletools.contacts.pro.extensions.groupsDB
import com.simplemobiletools.contacts.pro.helpers.ContactsHelper
import com.simplemobiletools.contacts.pro.helpers.GROUPS_TAB_MASK
import com.simplemobiletools.contacts.pro.interfaces.RefreshContactsListener
@@ -59,7 +59,7 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val
override fun getItemSelectionKey(position: Int) = groups.getOrNull(position)?.id?.toInt()
- override fun getItemKeyPosition(key: Int) = groups.indexOfFirst { it.id.toInt() == key }
+ override fun getItemKeyPosition(key: Int) = groups.indexOfFirst { it.id!!.toInt() == key }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_group, parent)
@@ -73,7 +73,7 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val
override fun getItemCount() = groups.size
- private fun getItemWithKey(key: Int): Group? = groups.firstOrNull { it.id.toInt() == key }
+ private fun getItemWithKey(key: Int): Group? = groups.firstOrNull { it.id!!.toInt() == key }
fun updateItems(newItems: ArrayList) {
groups = newItems
@@ -92,7 +92,9 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val
private fun askConfirmDelete() {
ConfirmationDialog(activity) {
- deleteGroups()
+ Thread {
+ deleteGroups()
+ }.start()
}
}
@@ -101,28 +103,30 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val
return
}
- val groupsToRemove = groups.filter { selectedKeys.contains(it.id.toInt()) } as ArrayList
+ val groupsToRemove = groups.filter { selectedKeys.contains(it.id!!.toInt()) } as ArrayList
val positions = getSelectedItemPositions()
groupsToRemove.forEach {
if (it.isPrivateSecretGroup()) {
- activity.dbHelper.deleteGroup(it.id)
+ activity.groupsDB.deleteGroupId(it.id!!)
} else {
- ContactsHelper(activity).deleteGroup(it.id)
+ ContactsHelper(activity).deleteGroup(it.id!!)
}
}
groups.removeAll(groupsToRemove)
- if (groups.isEmpty()) {
- refreshListener?.refreshContacts(GROUPS_TAB_MASK)
- finishActMode()
- } else {
- removeSelectedItems(positions)
+ activity.runOnUiThread {
+ if (groups.isEmpty()) {
+ refreshListener?.refreshContacts(GROUPS_TAB_MASK)
+ finishActMode()
+ } else {
+ removeSelectedItems(positions)
+ }
}
}
private fun setupView(view: View, group: Group) {
view.apply {
- group_frame?.isSelected = selectedKeys.contains(group.id.toInt())
+ group_frame?.isSelected = selectedKeys.contains(group.id!!.toInt())
group_name.apply {
setTextColor(textColor)
text = String.format(activity.getString(R.string.groups_placeholder), group.title, group.contactsCount.toString())
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ManageBlockedNumbersAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ManageBlockedNumbersAdapter.kt
new file mode 100644
index 00000000..5dee5305
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ManageBlockedNumbersAdapter.kt
@@ -0,0 +1,83 @@
+package com.simplemobiletools.contacts.pro.adapters
+
+import android.view.Menu
+import android.view.View
+import android.view.ViewGroup
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
+import com.simplemobiletools.commons.interfaces.RefreshRecyclerViewListener
+import com.simplemobiletools.commons.views.MyRecyclerView
+import com.simplemobiletools.contacts.pro.R
+import com.simplemobiletools.contacts.pro.extensions.config
+import com.simplemobiletools.contacts.pro.extensions.deleteBlockedNumber
+import com.simplemobiletools.contacts.pro.models.BlockedNumber
+import kotlinx.android.synthetic.main.item_manage_blocked_number.view.*
+import java.util.*
+
+class ManageBlockedNumbersAdapter(activity: BaseSimpleActivity, var blockedNumbers: ArrayList, val listener: RefreshRecyclerViewListener?,
+ recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick) {
+
+ private val config = activity.config
+
+ init {
+ setupDragListener(true)
+ }
+
+ override fun getActionMenuId() = R.menu.cab_remove_only
+
+ override fun prepareActionMode(menu: Menu) {}
+
+ override fun actionItemPressed(id: Int) {
+ when (id) {
+ R.id.cab_remove -> removeSelection()
+ }
+ }
+
+ override fun getSelectableItemCount() = blockedNumbers.size
+
+ override fun getIsItemSelectable(position: Int) = true
+
+ override fun getItemSelectionKey(position: Int) = blockedNumbers.getOrNull(position)?.id?.toInt()
+
+ override fun getItemKeyPosition(key: Int) = blockedNumbers.indexOfFirst { it.id.toInt() == key }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_manage_blocked_number, parent)
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val blockedNumber = blockedNumbers[position]
+ holder.bindView(blockedNumber, true, true) { itemView, adapterPosition ->
+ setupView(itemView, blockedNumber)
+ }
+ bindViewHolder(holder)
+ }
+
+ override fun getItemCount() = blockedNumbers.size
+
+ private fun getSelectedItems() = blockedNumbers.filter { selectedKeys.contains(it.id.toInt()) } as ArrayList
+
+ private fun setupView(view: View, blockedNumber: BlockedNumber) {
+ view.apply {
+ manage_blocked_number_holder?.isSelected = selectedKeys.contains(blockedNumber.id.toInt())
+ manage_blocked_number_title.apply {
+ text = blockedNumber.number
+ setTextColor(config.textColor)
+ }
+ }
+ }
+
+ private fun removeSelection() {
+ val removeBlockedNumbers = ArrayList(selectedKeys.size)
+ val positions = getSelectedItemPositions()
+
+ getSelectedItems().forEach {
+ removeBlockedNumbers.add(it)
+ activity.deleteBlockedNumber(it.number)
+ }
+
+ blockedNumbers.removeAll(removeBlockedNumbers)
+ removeSelectedItems(positions)
+ if (blockedNumbers.isEmpty()) {
+ listener?.refreshItems()
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/RecentCallsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/RecentCallsAdapter.kt
index 5a871563..41b24af4 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/RecentCallsAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/RecentCallsAdapter.kt
@@ -6,10 +6,12 @@ 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.helpers.isNougatPlus
import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.activities.SimpleActivity
+import com.simplemobiletools.contacts.pro.extensions.addBlockedNumber
import com.simplemobiletools.contacts.pro.extensions.config
import com.simplemobiletools.contacts.pro.helpers.ContactsHelper
import com.simplemobiletools.contacts.pro.helpers.RECENTS_TAB_MASK
@@ -28,7 +30,12 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList selectAll()
R.id.cab_delete -> askConfirmDelete()
+ R.id.cab_block_number -> blockNumber()
}
}
@@ -92,6 +100,19 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList
private fun setupView(view: View, recentCall: RecentCall) {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/databases/ContactsDatabase.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/databases/ContactsDatabase.kt
new file mode 100644
index 00000000..530b1cde
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/databases/ContactsDatabase.kt
@@ -0,0 +1,71 @@
+package com.simplemobiletools.contacts.pro.databases
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import androidx.sqlite.db.SupportSQLiteDatabase
+import com.simplemobiletools.contacts.pro.helpers.Converters
+import com.simplemobiletools.contacts.pro.helpers.FIRST_CONTACT_ID
+import com.simplemobiletools.contacts.pro.helpers.FIRST_GROUP_ID
+import com.simplemobiletools.contacts.pro.helpers.getEmptyLocalContact
+import com.simplemobiletools.contacts.pro.interfaces.ContactsDao
+import com.simplemobiletools.contacts.pro.interfaces.GroupsDao
+import com.simplemobiletools.contacts.pro.models.Group
+import com.simplemobiletools.contacts.pro.models.LocalContact
+import java.util.concurrent.Executors
+
+@Database(entities = [LocalContact::class, Group::class], version = 1)
+@TypeConverters(Converters::class)
+abstract class ContactsDatabase : RoomDatabase() {
+
+ abstract fun ContactsDao(): ContactsDao
+
+ abstract fun GroupsDao(): GroupsDao
+
+ companion object {
+ private var db: ContactsDatabase? = null
+
+ fun getInstance(context: Context): ContactsDatabase {
+ if (db == null) {
+ synchronized(ContactsDatabase::class) {
+ if (db == null) {
+ db = Room.databaseBuilder(context.applicationContext, ContactsDatabase::class.java, "local_contacts.db")
+ .addCallback(object : Callback() {
+ override fun onCreate(db: SupportSQLiteDatabase) {
+ super.onCreate(db)
+ increaseAutoIncrementIds()
+ }
+ })
+ .build()
+ }
+ }
+ }
+ return db!!
+ }
+
+ fun destroyInstance() {
+ db = null
+ }
+
+ // start autoincrement ID from FIRST_CONTACT_ID/FIRST_GROUP_ID to avoid conflicts
+ // Room doesn't seem to have a built in way for it, so just create a contact/group and delete it
+ private fun increaseAutoIncrementIds() {
+ Executors.newSingleThreadExecutor().execute {
+ val emptyContact = getEmptyLocalContact()
+ emptyContact.id = FIRST_CONTACT_ID
+ db!!.ContactsDao().apply {
+ insertOrUpdate(emptyContact)
+ deleteContactId(FIRST_CONTACT_ID)
+ }
+
+ val emptyGroup = Group(FIRST_GROUP_ID, "")
+ db!!.GroupsDao().apply {
+ insertOrUpdate(emptyGroup)
+ deleteGroupId(FIRST_GROUP_ID)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/AddBlockedNumberDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/AddBlockedNumberDialog.kt
new file mode 100644
index 00000000..32a9497f
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/AddBlockedNumberDialog.kt
@@ -0,0 +1,44 @@
+package com.simplemobiletools.contacts.pro.dialogs
+
+import androidx.appcompat.app.AlertDialog
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import com.simplemobiletools.commons.extensions.showKeyboard
+import com.simplemobiletools.commons.extensions.value
+import com.simplemobiletools.contacts.pro.R
+import com.simplemobiletools.contacts.pro.extensions.addBlockedNumber
+import com.simplemobiletools.contacts.pro.extensions.deleteBlockedNumber
+import com.simplemobiletools.contacts.pro.models.BlockedNumber
+import kotlinx.android.synthetic.main.dialog_add_blocked_number.view.*
+
+class AddBlockedNumberDialog(val activity: BaseSimpleActivity, val originalNumber: BlockedNumber? = null, val callback: () -> Unit) {
+ init {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_add_blocked_number, null).apply {
+ if (originalNumber != null) {
+ add_blocked_number_edittext.setText(originalNumber.number)
+ }
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this) {
+ showKeyboard(view.add_blocked_number_edittext)
+ getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
+ val newBlockedNumber = view.add_blocked_number_edittext.value
+ if (originalNumber != null && newBlockedNumber != originalNumber.number) {
+ activity.deleteBlockedNumber(originalNumber.number)
+ }
+
+ if (newBlockedNumber.isNotEmpty()) {
+ activity.addBlockedNumber(newBlockedNumber)
+ }
+
+ callback()
+ dismiss()
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/CreateNewGroupDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/CreateNewGroupDialog.kt
index f4eb3dbb..a0e0e9e8 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/CreateNewGroupDialog.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/CreateNewGroupDialog.kt
@@ -65,10 +65,14 @@ class CreateNewGroupDialog(val activity: BaseSimpleActivity, val callback: (newG
}
private fun createGroupUnder(name: String, contactSource: ContactSource, dialog: AlertDialog) {
- val newGroup = ContactsHelper(activity).createNewGroup(name, contactSource.name, contactSource.type)
- if (newGroup != null) {
- callback(newGroup)
- }
- dialog.dismiss()
+ Thread {
+ val newGroup = ContactsHelper(activity).createNewGroup(name, contactSource.name, contactSource.type)
+ activity.runOnUiThread {
+ if (newGroup != null) {
+ callback(newGroup)
+ }
+ dialog.dismiss()
+ }
+ }.start()
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/RenameGroupDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/RenameGroupDialog.kt
index ea9180fb..835bc5e5 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/RenameGroupDialog.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/RenameGroupDialog.kt
@@ -4,7 +4,7 @@ import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.contacts.pro.R
-import com.simplemobiletools.contacts.pro.extensions.dbHelper
+import com.simplemobiletools.contacts.pro.extensions.groupsDB
import com.simplemobiletools.contacts.pro.helpers.ContactsHelper
import com.simplemobiletools.contacts.pro.models.Group
import kotlinx.android.synthetic.main.dialog_rename_group.view.*
@@ -35,13 +35,17 @@ class RenameGroupDialog(val activity: BaseSimpleActivity, val group: Group, val
}
group.title = newTitle
- if (group.isPrivateSecretGroup()) {
- activity.dbHelper.renameGroup(group)
- } else {
- ContactsHelper(activity).renameGroup(group)
- }
- callback()
- dismiss()
+ Thread {
+ if (group.isPrivateSecretGroup()) {
+ activity.groupsDB.insertOrUpdate(group)
+ } else {
+ ContactsHelper(activity).renameGroup(group)
+ }
+ activity.runOnUiThread {
+ callback()
+ dismiss()
+ }
+ }.start()
}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/SelectContactsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/SelectContactsDialog.kt
index b01bfacd..16872932 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/SelectContactsDialog.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/SelectContactsDialog.kt
@@ -6,12 +6,11 @@ import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.activities.SimpleActivity
import com.simplemobiletools.contacts.pro.adapters.SelectContactsAdapter
-import com.simplemobiletools.contacts.pro.extensions.config
import com.simplemobiletools.contacts.pro.extensions.getVisibleContactSources
import com.simplemobiletools.contacts.pro.models.Contact
import kotlinx.android.synthetic.main.layout_select_contact.view.*
-class SelectContactsDialog(val activity: SimpleActivity, initialContacts: ArrayList, val selectContacts: ArrayList? = null,
+class SelectContactsDialog(val activity: SimpleActivity, initialContacts: ArrayList, selectContacts: ArrayList? = null,
val callback: (addedContacts: ArrayList, removedContacts: ArrayList) -> Unit) {
private var view = activity.layoutInflater.inflate(R.layout.layout_select_contact, null)
private var initiallySelectedContacts = ArrayList()
@@ -27,10 +26,6 @@ class SelectContactsDialog(val activity: SimpleActivity, initialContacts: ArrayL
initiallySelectedContacts = selectContacts
}
- Contact.sorting = activity.config.sorting
- Contact.startWithSurname = activity.config.startNameWithSurname
- allContacts.sort()
-
activity.runOnUiThread {
view.apply {
select_contact_list.adapter = SelectContactsAdapter(activity, allContacts, initiallySelectedContacts, true, select_contact_list)
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/SelectGroupsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/SelectGroupsDialog.kt
index 78a35542..25e97f5b 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/SelectGroupsDialog.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/SelectGroupsDialog.kt
@@ -17,11 +17,20 @@ import java.util.*
class SelectGroupsDialog(val activity: SimpleActivity, val selectedGroups: ArrayList, val callback: (newGroups: ArrayList) -> Unit) {
private val view = activity.layoutInflater.inflate(R.layout.dialog_select_groups, null) as ViewGroup
private val checkboxes = ArrayList()
- private val groups = ContactsHelper(activity).getStoredGroups()
+ private var groups = ArrayList()
private val config = activity.config
- private val dialog: AlertDialog?
+ private var dialog: AlertDialog? = null
init {
+ ContactsHelper(activity).getStoredGroups {
+ groups = it
+ activity.runOnUiThread {
+ initDialog()
+ }
+ }
+ }
+
+ private fun initDialog() {
groups.sortedBy { it.title }.forEach {
addGroupCheckbox(it)
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Activity.kt
index 8fddb344..cda4b77a 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Activity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Activity.kt
@@ -1,9 +1,7 @@
package com.simplemobiletools.contacts.pro.extensions
-import android.app.Activity
import android.content.Intent
import android.net.Uri
-import android.provider.ContactsContract
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.sharePathIntent
@@ -17,8 +15,6 @@ import com.simplemobiletools.contacts.pro.activities.SimpleActivity
import com.simplemobiletools.contacts.pro.dialogs.CallConfirmationDialog
import com.simplemobiletools.contacts.pro.helpers.*
import com.simplemobiletools.contacts.pro.models.Contact
-import com.simplemobiletools.contacts.pro.models.ContactSource
-import java.io.File
fun SimpleActivity.startCallIntent(recipient: String) {
handlePermission(PERMISSION_CALL_PHONE) {
@@ -93,13 +89,6 @@ fun SimpleActivity.showContactSourcePicker(currentSource: String, callback: (new
}
}
-fun SimpleActivity.getPublicContactSource(source: String): String {
- return when (source) {
- config.localAccountName -> getString(R.string.phone_storage)
- SMT_PRIVATE -> getString(R.string.phone_storage_hidden)
- else -> source
- }
-}
fun BaseSimpleActivity.shareContacts(contacts: ArrayList) {
val file = getTempFile()
@@ -117,96 +106,6 @@ fun BaseSimpleActivity.shareContacts(contacts: ArrayList) {
}
}
-fun BaseSimpleActivity.sendSMSToContacts(contacts: ArrayList) {
- val numbers = StringBuilder()
- contacts.forEach {
- 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(';')}"
- Intent(Intent.ACTION_SENDTO, Uri.parse(uriString)).apply {
- if (resolveActivity(packageManager) != null) {
- startActivity(this)
- } else {
- toast(R.string.no_app_found)
- }
- }
- }
-}
-
-fun BaseSimpleActivity.sendEmailToContacts(contacts: ArrayList) {
- val emails = ArrayList()
- contacts.forEach {
- it.emails.forEach {
- if (it.value.isNotEmpty()) {
- emails.add(it.value)
- }
- }
- }
-
- Intent(Intent.ACTION_SEND_MULTIPLE).apply {
- type = "message/rfc822"
- putExtra(Intent.EXTRA_EMAIL, emails.toTypedArray())
- if (resolveActivity(packageManager) != null) {
- startActivity(this)
- } else {
- toast(R.string.no_app_found)
- }
- }
-}
-
-fun BaseSimpleActivity.getTempFile(): File? {
- val folder = File(cacheDir, "contacts")
- if (!folder.exists()) {
- if (!folder.mkdir()) {
- toast(R.string.unknown_error_occurred)
- return null
- }
- }
-
- return File(folder, "contacts.vcf")
-}
-
-fun BaseSimpleActivity.addContactsToGroup(contacts: ArrayList, groupId: Long) {
- val publicContacts = contacts.filter { it.source != SMT_PRIVATE }
- val privateContacts = contacts.filter { it.source == SMT_PRIVATE }
- if (publicContacts.isNotEmpty()) {
- ContactsHelper(this).addContactsToGroup(contacts, groupId)
- }
-
- if (privateContacts.isNotEmpty()) {
- dbHelper.addContactsToGroup(contacts, groupId)
- }
-}
-
-fun BaseSimpleActivity.removeContactsFromGroup(contacts: ArrayList, groupId: Long) {
- val publicContacts = contacts.filter { it.source != SMT_PRIVATE }
- val privateContacts = contacts.filter { it.source == SMT_PRIVATE }
- if (publicContacts.isNotEmpty() && hasContactPermissions()) {
- ContactsHelper(this).removeContactsFromGroup(contacts, groupId)
- }
-
- if (privateContacts.isNotEmpty()) {
- dbHelper.removeContactsFromGroup(contacts, groupId)
- }
-}
-
-fun BaseSimpleActivity.getContactPublicUri(contact: Contact): Uri {
- val lookupKey = ContactsHelper(this).getContactLookupKey(contact.id.toString())
- return Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)
-}
-
-fun Activity.getVisibleContactSources(): ArrayList {
- val sources = ContactsHelper(this).getDeviceContactSources()
- sources.add(ContactSource(getString(R.string.phone_storage_hidden), SMT_PRIVATE))
- val sourceNames = ArrayList(sources).map { if (it.type == SMT_PRIVATE) SMT_PRIVATE else it.name }.toMutableList() as ArrayList
- sourceNames.removeAll(config.ignoredContactSources)
- return sourceNames
-}
-
fun SimpleActivity.contactClicked(contact: Contact) {
when (config.onContactClick) {
ON_CLICK_CALL_CONTACT -> callContact(contact)
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Context.kt
index 0726550a..0b42a0aa 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Context.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/Context.kt
@@ -1,32 +1,55 @@
package com.simplemobiletools.contacts.pro.extensions
+import android.annotation.TargetApi
+import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.net.Uri
+import android.os.Build
+import android.provider.BlockedNumberContract
+import android.provider.BlockedNumberContract.BlockedNumbers
import android.provider.ContactsContract
+import android.telecom.TelecomManager
import androidx.core.content.FileProvider
-import com.simplemobiletools.commons.extensions.getIntValue
-import com.simplemobiletools.commons.extensions.hasPermission
-import com.simplemobiletools.commons.extensions.toast
+import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_READ_CONTACTS
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CONTACTS
+import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.contacts.pro.BuildConfig
import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.activities.EditContactActivity
import com.simplemobiletools.contacts.pro.activities.ViewContactActivity
+import com.simplemobiletools.contacts.pro.databases.ContactsDatabase
import com.simplemobiletools.contacts.pro.helpers.*
+import com.simplemobiletools.contacts.pro.interfaces.ContactsDao
+import com.simplemobiletools.contacts.pro.interfaces.GroupsDao
+import com.simplemobiletools.contacts.pro.models.BlockedNumber
import com.simplemobiletools.contacts.pro.models.Contact
+import com.simplemobiletools.contacts.pro.models.ContactSource
+import com.simplemobiletools.contacts.pro.models.Organization
import java.io.File
+
val Context.config: Config get() = Config.newInstance(applicationContext)
-val Context.dbHelper: DBHelper get() = DBHelper.newInstance(applicationContext)
+val Context.contactsDB: ContactsDao get() = ContactsDatabase.getInstance(applicationContext).ContactsDao()
+
+val Context.groupsDB: GroupsDao get() = ContactsDatabase.getInstance(applicationContext).GroupsDao()
+
+val Context.telecomManager: TelecomManager get() = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
+
+fun Context.getEmptyContact(): Contact {
+ val originalContactSource = if (hasContactPermissions()) config.lastUsedContactSource else SMT_PRIVATE
+ val organization = Organization("", "")
+ return Contact(0, "", "", "", "", "", "", "", ArrayList(), ArrayList(), ArrayList(), ArrayList(), originalContactSource, 0, 0, "",
+ null, "", ArrayList(), organization, ArrayList(), ArrayList())
+}
fun Context.viewContact(contact: Contact) {
Intent(applicationContext, ViewContactActivity::class.java).apply {
putExtra(CONTACT_ID, contact.id)
- putExtra(IS_PRIVATE, contact.source == SMT_PRIVATE)
+ putExtra(IS_PRIVATE, contact.isPrivate())
startActivity(this)
}
}
@@ -34,7 +57,7 @@ fun Context.viewContact(contact: Contact) {
fun Context.editContact(contact: Contact) {
Intent(applicationContext, EditContactActivity::class.java).apply {
putExtra(CONTACT_ID, contact.id)
- putExtra(IS_PRIVATE, contact.source == SMT_PRIVATE)
+ putExtra(IS_PRIVATE, contact.isPrivate())
startActivity(this)
}
}
@@ -168,3 +191,157 @@ fun Context.getPhotoThumbnailSize(): Int {
}
fun Context.hasContactPermissions() = hasPermission(PERMISSION_READ_CONTACTS) && hasPermission(PERMISSION_WRITE_CONTACTS)
+
+fun Context.getPublicContactSource(source: String): String {
+ return when (source) {
+ config.localAccountName -> getString(R.string.phone_storage)
+ SMT_PRIVATE -> getString(R.string.phone_storage_hidden)
+ else -> source
+ }
+}
+
+fun Context.sendSMSToContacts(contacts: ArrayList) {
+ val numbers = StringBuilder()
+ contacts.forEach {
+ 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(';')}"
+ Intent(Intent.ACTION_SENDTO, Uri.parse(uriString)).apply {
+ if (resolveActivity(packageManager) != null) {
+ startActivity(this)
+ } else {
+ toast(R.string.no_app_found)
+ }
+ }
+ }
+}
+
+fun Context.sendEmailToContacts(contacts: ArrayList) {
+ val emails = ArrayList()
+ contacts.forEach {
+ it.emails.forEach {
+ if (it.value.isNotEmpty()) {
+ emails.add(it.value)
+ }
+ }
+ }
+
+ Intent(Intent.ACTION_SEND_MULTIPLE).apply {
+ type = "message/rfc822"
+ putExtra(Intent.EXTRA_EMAIL, emails.toTypedArray())
+ if (resolveActivity(packageManager) != null) {
+ startActivity(this)
+ } else {
+ toast(R.string.no_app_found)
+ }
+ }
+}
+
+fun Context.getTempFile(): File? {
+ val folder = File(cacheDir, "contacts")
+ if (!folder.exists()) {
+ if (!folder.mkdir()) {
+ toast(R.string.unknown_error_occurred)
+ return null
+ }
+ }
+
+ return File(folder, "contacts.vcf")
+}
+
+fun Context.addContactsToGroup(contacts: ArrayList, groupId: Long) {
+ val publicContacts = contacts.filter { !it.isPrivate() }.toMutableList() as ArrayList
+ val privateContacts = contacts.filter { it.isPrivate() }.toMutableList() as ArrayList
+ if (publicContacts.isNotEmpty()) {
+ ContactsHelper(this).addContactsToGroup(publicContacts, groupId)
+ }
+
+ if (privateContacts.isNotEmpty()) {
+ LocalContactsHelper(this).addContactsToGroup(privateContacts, groupId)
+ }
+}
+
+fun Context.removeContactsFromGroup(contacts: ArrayList, groupId: Long) {
+ val publicContacts = contacts.filter { !it.isPrivate() }.toMutableList() as ArrayList
+ val privateContacts = contacts.filter { it.isPrivate() }.toMutableList() as ArrayList
+ if (publicContacts.isNotEmpty() && hasContactPermissions()) {
+ ContactsHelper(this).removeContactsFromGroup(publicContacts, groupId)
+ }
+
+ if (privateContacts.isNotEmpty()) {
+ LocalContactsHelper(this).removeContactsFromGroup(privateContacts, groupId)
+ }
+}
+
+fun Context.getContactPublicUri(contact: Contact): Uri {
+ val lookupKey = if (contact.isPrivate()) {
+ "local_${contact.id}"
+ } else {
+ ContactsHelper(this).getContactLookupKey(contact.id.toString())
+ }
+ return Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)
+}
+
+fun Context.getVisibleContactSources(): ArrayList {
+ val sources = ContactsHelper(this).getDeviceContactSources()
+ sources.add(ContactSource(getString(R.string.phone_storage_hidden), SMT_PRIVATE))
+ val sourceNames = ArrayList(sources).map { if (it.type == SMT_PRIVATE) SMT_PRIVATE else it.name }.toMutableList() as ArrayList
+ sourceNames.removeAll(config.ignoredContactSources)
+ return sourceNames
+}
+
+@TargetApi(Build.VERSION_CODES.N)
+fun Context.getBlockedNumbers(): ArrayList {
+ val blockedNumbers = ArrayList()
+ if (!isNougatPlus() || !isDefaultDialer()) {
+ return blockedNumbers
+ }
+
+ val uri = BlockedNumberContract.BlockedNumbers.CONTENT_URI
+ val projection = arrayOf(
+ BlockedNumberContract.BlockedNumbers.COLUMN_ID,
+ BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
+ BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER
+ )
+
+ var cursor: Cursor? = null
+ try {
+ cursor = contentResolver.query(uri, projection, null, null, null)
+ if (cursor?.moveToFirst() == true) {
+ do {
+ val id = cursor.getLongValue(BlockedNumberContract.BlockedNumbers.COLUMN_ID)
+ val number = cursor.getStringValue(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER) ?: ""
+ val normalizedNumber = cursor.getStringValue(BlockedNumberContract.BlockedNumbers.COLUMN_E164_NUMBER) ?: ""
+ val blockedNumber = BlockedNumber(id, number, normalizedNumber)
+ blockedNumbers.add(blockedNumber)
+ } while (cursor.moveToNext())
+ }
+ } finally {
+ cursor?.close()
+ }
+
+ return blockedNumbers
+}
+
+@TargetApi(Build.VERSION_CODES.N)
+fun Context.addBlockedNumber(number: String) {
+ ContentValues().apply {
+ put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number)
+ contentResolver.insert(BlockedNumbers.CONTENT_URI, this)
+ }
+}
+
+@TargetApi(Build.VERSION_CODES.N)
+fun Context.deleteBlockedNumber(number: String) {
+ val values = ContentValues()
+ values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number)
+ val uri = contentResolver.insert(BlockedNumbers.CONTENT_URI, values)
+ contentResolver.delete(uri, null, null)
+}
+
+@TargetApi(Build.VERSION_CODES.M)
+fun Context.isDefaultDialer() = telecomManager.defaultDialerPackage == packageName
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/String.kt
index b35d3b9a..beda894f 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/String.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/extensions/String.kt
@@ -1,8 +1,8 @@
package com.simplemobiletools.contacts.pro.extensions
+import android.telephony.PhoneNumberUtils
import android.widget.TextView
import com.simplemobiletools.commons.helpers.getDateFormats
-import com.simplemobiletools.contacts.pro.helpers.PHONE_NUMBER_PATTERN
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import java.text.DateFormat
@@ -34,4 +34,4 @@ fun String.getDateTimeFromDateString(viewToUpdate: TextView? = null): DateTime {
return date
}
-fun String.applyRegexFiltering() = replace(PHONE_NUMBER_PATTERN.toRegex(), "")
+fun String.normalizeNumber() = PhoneNumberUtils.normalizeNumber(this)
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/MyViewPagerFragment.kt
index c35c676d..6421f519 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/MyViewPagerFragment.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/MyViewPagerFragment.kt
@@ -61,6 +61,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
this is RecentsFragment -> {
fragment_fab.beGone()
+ fragment_placeholder.text = activity.getString(R.string.no_recent_calls_found)
fragment_placeholder_2.text = activity.getString(R.string.request_the_required_permissions)
}
}
@@ -108,9 +109,6 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
config.lastUsedContactSource = grouped?.key ?: ""
}
- Contact.sorting = config.sorting
- Contact.startWithSurname = config.startNameWithSurname
- contacts.sort()
allContacts = contacts
val filtered = when {
@@ -145,42 +143,44 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
private fun setupGroupsAdapter(contacts: ArrayList) {
- var storedGroups = ContactsHelper(activity!!).getStoredGroups()
- contacts.forEach {
- it.groups.forEach {
- val group = it
- val storedGroup = storedGroups.firstOrNull { it.id == group.id }
- storedGroup?.addContact()
- }
- }
-
- storedGroups = storedGroups.asSequence().sortedWith(compareBy { it.title.normalizeString() }).toMutableList() as ArrayList
-
- fragment_placeholder_2.beVisibleIf(storedGroups.isEmpty())
- fragment_placeholder.beVisibleIf(storedGroups.isEmpty())
- fragment_list.beVisibleIf(storedGroups.isNotEmpty())
-
- val currAdapter = fragment_list.adapter
- if (currAdapter == null) {
- GroupsAdapter(activity as SimpleActivity, storedGroups, activity, fragment_list, fragment_fastscroller) {
- Intent(activity, GroupContactsActivity::class.java).apply {
- putExtra(GROUP, it as Group)
- activity!!.startActivity(this)
+ ContactsHelper(activity!!).getStoredGroups {
+ var storedGroups = it
+ contacts.forEach {
+ it.groups.forEach {
+ val group = it
+ val storedGroup = storedGroups.firstOrNull { it.id == group.id }
+ storedGroup?.addContact()
}
- }.apply {
- addVerticalDividers(true)
- fragment_list.adapter = this
}
- fragment_fastscroller.setScrollToY(0)
- fragment_fastscroller.setViews(fragment_list) {
- val item = (fragment_list.adapter as GroupsAdapter).groups.getOrNull(it)
- fragment_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
- }
- } else {
- (currAdapter as GroupsAdapter).apply {
- showContactThumbnails = activity.config.showContactThumbnails
- updateItems(storedGroups)
+ storedGroups = storedGroups.asSequence().sortedWith(compareBy { it.title.normalizeString() }).toMutableList() as ArrayList
+
+ fragment_placeholder_2.beVisibleIf(storedGroups.isEmpty())
+ fragment_placeholder.beVisibleIf(storedGroups.isEmpty())
+ fragment_list.beVisibleIf(storedGroups.isNotEmpty())
+
+ val currAdapter = fragment_list.adapter
+ if (currAdapter == null) {
+ GroupsAdapter(activity as SimpleActivity, storedGroups, activity, fragment_list, fragment_fastscroller) {
+ Intent(activity, GroupContactsActivity::class.java).apply {
+ putExtra(GROUP, it as Group)
+ activity!!.startActivity(this)
+ }
+ }.apply {
+ addVerticalDividers(true)
+ fragment_list.adapter = this
+ }
+
+ fragment_fastscroller.setScrollToY(0)
+ fragment_fastscroller.setViews(fragment_list) {
+ val item = (fragment_list.adapter as GroupsAdapter).groups.getOrNull(it)
+ fragment_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
+ }
+ } else {
+ (currAdapter as GroupsAdapter).apply {
+ showContactThumbnails = activity.config.showContactThumbnails
+ updateItems(storedGroups)
+ }
}
}
}
@@ -251,9 +251,6 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
it.websites.any { it.contains(text, true) }
} as ArrayList
- Contact.sorting = config.sorting
- Contact.startWithSurname = config.startNameWithSurname
- filtered.sort()
filtered.sortBy { !getProperText(it.getNameToDisplay(), shouldNormalize).startsWith(text, true) }
if (filtered.isEmpty() && this@MyViewPagerFragment is FavoritesFragment) {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/RecentsFragment.kt
index 12678e9f..a227c7e9 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/RecentsFragment.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/RecentsFragment.kt
@@ -1,16 +1,21 @@
package com.simplemobiletools.contacts.pro.fragments
+import android.annotation.TargetApi
import android.content.Context
import android.content.Intent
+import android.os.Build
+import android.telecom.TelecomManager
import android.util.AttributeSet
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.hasPermission
import com.simplemobiletools.commons.helpers.PERMISSION_READ_CALL_LOG
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CALL_LOG
-import com.simplemobiletools.contacts.pro.activities.EditContactActivity
+import com.simplemobiletools.commons.helpers.isMarshmallowPlus
+import com.simplemobiletools.contacts.pro.activities.InsertOrEditContactActivity
import com.simplemobiletools.contacts.pro.adapters.RecentCallsAdapter
-import com.simplemobiletools.contacts.pro.extensions.applyRegexFiltering
import com.simplemobiletools.contacts.pro.extensions.contactClicked
+import com.simplemobiletools.contacts.pro.extensions.isDefaultDialer
+import com.simplemobiletools.contacts.pro.extensions.normalizeNumber
import com.simplemobiletools.contacts.pro.helpers.IS_FROM_SIMPLE_CONTACTS
import com.simplemobiletools.contacts.pro.helpers.KEY_PHONE
import com.simplemobiletools.contacts.pro.helpers.RECENTS_TAB_MASK
@@ -21,13 +26,19 @@ import kotlinx.android.synthetic.main.fragment_layout.view.*
class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet) {
override fun fabClicked() {}
+ @TargetApi(Build.VERSION_CODES.M)
override fun placeholderClicked() {
- activity!!.handlePermission(PERMISSION_WRITE_CALL_LOG) {
- if (it) {
- activity!!.handlePermission(PERMISSION_READ_CALL_LOG) {
- activity?.refreshContacts(RECENTS_TAB_MASK)
+ if (!isMarshmallowPlus() || (isMarshmallowPlus() && context.isDefaultDialer())) {
+ activity!!.handlePermission(PERMISSION_WRITE_CALL_LOG) {
+ if (it) {
+ activity!!.handlePermission(PERMISSION_READ_CALL_LOG) {
+ activity?.refreshContacts(RECENTS_TAB_MASK)
+ }
}
}
+ } else {
+ val intent = Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER).putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, context.packageName)
+ context.startActivity(intent)
}
}
@@ -43,10 +54,10 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage
val currAdapter = fragment_list.adapter
if (currAdapter == null) {
RecentCallsAdapter(activity!!, recentCalls, activity, fragment_list, fragment_fastscroller) {
- val recentCall = (it as RecentCall).number.applyRegexFiltering()
+ val recentCall = (it as RecentCall).number.normalizeNumber()
var selectedContact: Contact? = null
for (contact in allContacts) {
- if (contact.phoneNumbers.any { it.value.applyRegexFiltering() == recentCall }) {
+ if (contact.doesContainPhoneNumber(recentCall)) {
selectedContact = contact
break
}
@@ -55,8 +66,8 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage
if (selectedContact != null) {
activity?.contactClicked(selectedContact)
} else {
- Intent(context, EditContactActivity::class.java).apply {
- action = Intent.ACTION_INSERT
+ Intent(context, InsertOrEditContactActivity::class.java).apply {
+ action = Intent.ACTION_INSERT_OR_EDIT
putExtra(KEY_PHONE, recentCall)
putExtra(IS_FROM_SIMPLE_CONTACTS, true)
context.startActivity(this)
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Config.kt
index bd1fbcd3..483ac685 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Config.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Config.kt
@@ -60,4 +60,8 @@ class Config(context: Context) : BaseConfig(context) {
var showCallConfirmation: Boolean
get() = prefs.getBoolean(SHOW_CALL_CONFIRMATION, false)
set(showCallConfirmation) = prefs.edit().putBoolean(SHOW_CALL_CONFIRMATION, showCallConfirmation).apply()
+
+ var showDialpadButton: Boolean
+ get() = prefs.getBoolean(SHOW_DIALPAD_BUTTON, true)
+ set(showDialpadButton) = prefs.edit().putBoolean(SHOW_DIALPAD_BUTTON, showDialpadButton).apply()
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt
index 9073d347..8826a543 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt
@@ -1,6 +1,7 @@
package com.simplemobiletools.contacts.pro.helpers
import android.provider.ContactsContract.CommonDataKinds
+import com.simplemobiletools.contacts.pro.models.LocalContact
// shared prefs
const val SHOW_CONTACT_THUMBNAILS = "show_contact_thumbnails"
@@ -16,15 +17,17 @@ 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 SHOW_DIALPAD_BUTTON = "show_dialpad_button"
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 = "[^0-9#*+]"
const val IS_FROM_SIMPLE_CONTACTS = "is_from_simple_contacts"
const val ADD_NEW_CONTACT_NUMBER = "add_new_contact_number"
+const val FIRST_CONTACT_ID = 1000000
+const val FIRST_GROUP_ID = 10000L
+const val REQUEST_CODE_SET_DEFAULT_DIALER = 1
// extras used at third party intents
const val KEY_PHONE = "phone"
@@ -119,3 +122,5 @@ val localAccountTypes = arrayListOf("vnd.sec.contact.phone",
const val TELEGRAM_PACKAGE = "org.telegram.messenger"
const val SIGNAL_PACKAGE = "org.thoughtcrime.securesms"
const val WHATSAPP_PACKAGE = "com.whatsapp"
+
+fun getEmptyLocalContact() = LocalContact(0, "", "", "", "", "", "", null, ArrayList(), ArrayList(), ArrayList(), 0, ArrayList(), "", ArrayList(), "", "", ArrayList(), ArrayList())
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/ContactsHelper.kt
index 57447ffc..8a14f930 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/ContactsHelper.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/ContactsHelper.kt
@@ -3,11 +3,12 @@ package com.simplemobiletools.contacts.pro.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.os.Handler
+import android.os.Looper
import android.provider.CallLog
import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
@@ -26,24 +27,24 @@ import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
-class ContactsHelper(val activity: Activity) {
+class ContactsHelper(val context: Context) {
private val BATCH_SIZE = 100
private var displayContactSources = ArrayList()
fun getContacts(callback: (ArrayList) -> Unit) {
Thread {
val contacts = SparseArray()
- displayContactSources = activity.getVisibleContactSources()
+ displayContactSources = context.getVisibleContactSources()
getDeviceContacts(contacts)
if (displayContactSources.contains(SMT_PRIVATE)) {
- activity.dbHelper.getContacts(activity).forEach {
+ LocalContactsHelper(context).getAllContacts().forEach {
contacts.put(it.id, it)
}
}
val contactsSize = contacts.size()
- val showOnlyContactsWithNumbers = activity.config.showOnlyContactsWithNumbers
+ val showOnlyContactsWithNumbers = context.config.showOnlyContactsWithNumbers
var tempContacts = ArrayList(contactsSize)
val resultContacts = ArrayList(contactsSize)
@@ -57,7 +58,7 @@ class ContactsHelper(val activity: Activity) {
contacts.valueAt(it)
}
- if (activity.config.filterDuplicates) {
+ if (context.config.filterDuplicates) {
tempContacts = tempContacts.distinctBy {
it.getHashToCompare()
} as ArrayList
@@ -75,14 +76,18 @@ class ContactsHelper(val activity: Activity) {
}
// groups are obtained with contactID, not rawID, so assign them to proper contacts like this
- val groups = getContactGroups(getStoredGroups())
+ val groups = getContactGroups(getStoredGroupsSync())
val size = groups.size()
for (i in 0 until size) {
val key = groups.keyAt(i)
resultContacts.firstOrNull { it.contactId == key }?.groups = groups.valueAt(i)
}
- activity.runOnUiThread {
+ Contact.sorting = context.config.sorting
+ Contact.startWithSurname = context.config.startNameWithSurname
+ resultContacts.sort()
+
+ Handler(Looper.getMainLooper()).post {
callback(resultContacts)
}
}.start()
@@ -98,7 +103,7 @@ class ContactsHelper(val activity: Activity) {
val sources = HashSet()
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, null, null, null)
+ cursor = context.contentResolver.query(uri, projection, null, null, null)
if (cursor?.moveToFirst() == true) {
do {
val name = cursor.getStringValue(ContactsContract.RawContacts.ACCOUNT_NAME) ?: ""
@@ -116,7 +121,7 @@ class ContactsHelper(val activity: Activity) {
}
private fun getDeviceContacts(contacts: SparseArray) {
- if (!activity.hasContactPermissions()) {
+ if (!context.hasContactPermissions()) {
return
}
@@ -128,7 +133,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -151,21 +156,19 @@ class ContactsHelper(val activity: Activity) {
val groups = ArrayList()
val organization = Organization("", "")
val websites = ArrayList()
- val cleanNumbers = ArrayList()
val ims = ArrayList()
val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, numbers, emails, addresses,
- events, accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, cleanNumbers, ims)
+ events, accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, ims)
contacts.put(id, contact)
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
- val filterDuplicates = activity.config.filterDuplicates
val phoneNumbers = getPhoneNumbers(null)
var size = phoneNumbers.size()
for (i in 0 until size) {
@@ -173,13 +176,6 @@ class ContactsHelper(val activity: Activity) {
if (contacts[key] != null) {
val numbers = phoneNumbers.valueAt(i)
contacts[key].phoneNumbers = numbers
-
- if (filterDuplicates) {
- // remove all spaces, dashes etc from numbers for easier comparing, used only at list views
- numbers.forEach {
- numbers.mapTo(contacts[key].cleanPhoneNumbers) { PhoneNumber(it.value.applyRegexFiltering(), 0, "") }
- }
- }
}
}
@@ -246,6 +242,7 @@ class ContactsHelper(val activity: Activity) {
val projection = arrayOf(
ContactsContract.Data.RAW_CONTACT_ID,
CommonDataKinds.Phone.NUMBER,
+ CommonDataKinds.Phone.NORMALIZED_NUMBER,
CommonDataKinds.Phone.TYPE,
CommonDataKinds.Phone.LABEL
)
@@ -255,11 +252,12 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
val number = cursor.getStringValue(CommonDataKinds.Phone.NUMBER) ?: continue
+ val normalizedNumber = cursor.getStringValue(CommonDataKinds.Phone.NORMALIZED_NUMBER) ?: number.normalizeNumber()
val type = cursor.getIntValue(CommonDataKinds.Phone.TYPE)
val label = cursor.getStringValue(CommonDataKinds.Phone.LABEL) ?: ""
@@ -267,12 +265,12 @@ class ContactsHelper(val activity: Activity) {
phoneNumbers.put(id, ArrayList())
}
- val phoneNumber = PhoneNumber(number, type, label)
+ val phoneNumber = PhoneNumber(number, type, label, normalizedNumber)
phoneNumbers[id].add(phoneNumber)
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -293,7 +291,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -302,7 +300,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -325,7 +323,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -341,7 +339,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -364,7 +362,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -380,7 +378,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -403,7 +401,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -419,7 +417,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -441,7 +439,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -456,7 +454,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -477,7 +475,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -486,7 +484,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -508,7 +506,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -523,7 +521,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -544,7 +542,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
@@ -558,7 +556,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -568,7 +566,7 @@ class ContactsHelper(val activity: Activity) {
private fun getContactGroups(storedGroups: ArrayList, contactId: Int? = null): SparseArray> {
val groups = SparseArray>()
- if (!activity.hasContactPermissions()) {
+ if (!context.hasContactPermissions()) {
return groups
}
@@ -583,7 +581,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(ContactsContract.Data.CONTACT_ID)
@@ -598,7 +596,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -648,15 +646,24 @@ class ContactsHelper(val activity: Activity) {
return args.toTypedArray()
}
- fun getStoredGroups(): ArrayList {
+ fun getStoredGroups(callback: (ArrayList) -> Unit) {
+ Thread {
+ val groups = getStoredGroupsSync()
+ Handler(Looper.getMainLooper()).post {
+ callback(groups)
+ }
+ }.start()
+ }
+
+ fun getStoredGroupsSync(): ArrayList {
val groups = getDeviceStoredGroups()
- groups.addAll(activity.dbHelper.getGroups())
+ groups.addAll(context.groupsDB.getGroups())
return groups
}
fun getDeviceStoredGroups(): ArrayList {
val groups = ArrayList()
- if (!activity.hasContactPermissions()) {
+ if (!context.hasContactPermissions()) {
return groups
}
@@ -672,7 +679,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getLongValue(ContactsContract.Groups._ID)
@@ -687,7 +694,7 @@ class ContactsHelper(val activity: Activity) {
} while (cursor.moveToNext())
}
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
} finally {
cursor?.close()
}
@@ -696,7 +703,10 @@ class ContactsHelper(val activity: Activity) {
fun createNewGroup(title: String, accountName: String, accountType: String): Group? {
if (accountType == SMT_PRIVATE) {
- return activity.dbHelper.insertGroup(Group(0, title))
+ val newGroup = Group(null, title)
+ val id = context.groupsDB.insertOrUpdate(newGroup)
+ newGroup.id = id
+ return newGroup
}
val operations = ArrayList()
@@ -709,11 +719,11 @@ class ContactsHelper(val activity: Activity) {
}
try {
- val results = activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ val results = context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
val rawId = ContentUris.parseId(results[0].uri)
return Group(rawId, title)
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
}
return null
}
@@ -729,9 +739,9 @@ class ContactsHelper(val activity: Activity) {
}
try {
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
}
}
@@ -744,9 +754,9 @@ class ContactsHelper(val activity: Activity) {
operations.add(ContentProviderOperation.newDelete(uri).build())
try {
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
}
}
@@ -754,7 +764,7 @@ class ContactsHelper(val activity: Activity) {
if (id == 0) {
return null
} else if (isLocalPrivate) {
- return activity.dbHelper.getContactWithId(activity, id)
+ return LocalContactsHelper(context).getContactWithId(id)
}
val selection = "${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?"
@@ -769,12 +779,12 @@ class ContactsHelper(val activity: Activity) {
}
private fun parseContactCursor(selection: String, selectionArgs: Array): Contact? {
- val storedGroups = getStoredGroups()
+ val storedGroups = getStoredGroupsSync()
val uri = ContactsContract.Data.CONTENT_URI
val projection = getContactProjection()
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
val id = cursor.getIntValue(ContactsContract.Data.RAW_CONTACT_ID)
val prefix = cursor.getStringValue(CommonDataKinds.StructuredName.PREFIX) ?: ""
@@ -796,10 +806,9 @@ class ContactsHelper(val activity: Activity) {
val thumbnailUri = cursor.getStringValue(CommonDataKinds.StructuredName.PHOTO_THUMBNAIL_URI) ?: ""
val organization = getOrganizations(id)[id] ?: Organization("", "")
val websites = getWebsites(id)[id] ?: ArrayList()
- val cleanNumbers = ArrayList()
val ims = getIMs(id)[id] ?: ArrayList()
return Contact(id, prefix, firstName, middleName, surname, suffix, nickname, photoUri, number, emails, addresses, events,
- accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, cleanNumbers, ims)
+ accountName, starred, contactId, thumbnailUri, null, notes, groups, organization, websites, ims)
}
} finally {
cursor?.close()
@@ -816,22 +825,22 @@ class ContactsHelper(val activity: Activity) {
private fun getContactSourcesSync(): ArrayList {
val sources = getDeviceContactSources()
- sources.add(ContactSource(activity.getString(R.string.phone_storage_hidden), SMT_PRIVATE))
+ sources.add(ContactSource(context.getString(R.string.phone_storage_hidden), SMT_PRIVATE))
return ArrayList(sources)
}
fun getDeviceContactSources(): LinkedHashSet {
val sources = LinkedHashSet()
- if (!activity.hasContactPermissions()) {
+ if (!context.hasContactPermissions()) {
return sources
}
- val accounts = AccountManager.get(activity).accounts
+ val accounts = AccountManager.get(context).accounts
accounts.forEach {
if (ContentResolver.getIsSyncable(it, ContactsContract.AUTHORITY) == 1) {
val contactSource = ContactSource(it.name, it.type)
if (it.type == TELEGRAM_PACKAGE) {
- contactSource.name += " (${activity.getString(R.string.telegram)})"
+ contactSource.name += " (${context.getString(R.string.telegram)})"
}
sources.add(contactSource)
}
@@ -842,7 +851,7 @@ class ContactsHelper(val activity: Activity) {
}
sources.addAll(contentResolverAccounts)
- if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) {
+ if (sources.isEmpty() && context.config.localAccountName.isEmpty() && context.config.localAccountType.isEmpty()) {
sources.add(ContactSource("", ""))
}
@@ -866,7 +875,7 @@ class ContactsHelper(val activity: Activity) {
)
private fun getSortString(): String {
- val sorting = activity.config.sorting
+ val sorting = context.config.sorting
var sort = when {
sorting and SORT_BY_FIRST_NAME != 0 -> "${CommonDataKinds.StructuredName.GIVEN_NAME} COLLATE NOCASE"
sorting and SORT_BY_MIDDLE_NAME != 0 -> "${CommonDataKinds.StructuredName.MIDDLE_NAME} COLLATE NOCASE"
@@ -888,7 +897,7 @@ class ContactsHelper(val activity: Activity) {
val selectionArgs = arrayOf(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, id.toString())
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
return cursor.getIntValue(ContactsContract.Data.CONTACT_ID)
}
@@ -900,9 +909,9 @@ class ContactsHelper(val activity: Activity) {
}
fun updateContact(contact: Contact, photoUpdateStatus: Int): Boolean {
- activity.toast(R.string.updating)
- if (contact.source == SMT_PRIVATE) {
- return activity.dbHelper.updateContact(contact)
+ context.toast(R.string.updating)
+ if (contact.isPrivate()) {
+ return LocalContactsHelper(context).insertOrUpdateContact(contact)
}
try {
@@ -949,6 +958,7 @@ class ContactsHelper(val activity: Activity) {
withValue(ContactsContract.Data.RAW_CONTACT_ID, contact.id)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Phone.NUMBER, it.value)
+ withValue(CommonDataKinds.Phone.NORMALIZED_NUMBER, it.normalizedNumber)
withValue(CommonDataKinds.Phone.TYPE, it.type)
withValue(CommonDataKinds.Phone.LABEL, it.label)
operations.add(build())
@@ -1089,7 +1099,7 @@ class ContactsHelper(val activity: Activity) {
}
// delete groups
- val relevantGroupIDs = getStoredGroups().map { it.id }
+ val relevantGroupIDs = getStoredGroupsSync().map { it.id }
if (relevantGroupIDs.isNotEmpty()) {
val IDsString = TextUtils.join(",", relevantGroupIDs)
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
@@ -1115,9 +1125,9 @@ class ContactsHelper(val activity: Activity) {
val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, contact.contactId.toString())
val contentValues = ContentValues(1)
contentValues.put(ContactsContract.Contacts.STARRED, contact.starred)
- activity.contentResolver.update(uri, contentValues, null, null)
+ context.contentResolver.update(uri, contentValues, null, null)
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
}
// photo
@@ -1126,10 +1136,10 @@ class ContactsHelper(val activity: Activity) {
PHOTO_REMOVED -> removePhoto(contact, operations)
}
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
return true
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
return false
}
}
@@ -1137,9 +1147,9 @@ class ContactsHelper(val activity: Activity) {
private fun addPhoto(contact: Contact, operations: ArrayList): ArrayList {
if (contact.photoUri.isNotEmpty()) {
val photoUri = Uri.parse(contact.photoUri)
- val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri)
+ val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, photoUri)
- val thumbnailSize = activity.getPhotoThumbnailSize()
+ val thumbnailSize = context.getPhotoThumbnailSize()
val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false)
val scaledSizePhotoData = scaledPhoto.getByteArray()
scaledPhoto.recycle()
@@ -1181,15 +1191,15 @@ class ContactsHelper(val activity: Activity) {
}
if (operations.size % BATCH_SIZE == 0) {
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
operations.clear()
}
}
try {
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
}
}
@@ -1204,16 +1214,16 @@ class ContactsHelper(val activity: Activity) {
}
if (operations.size % BATCH_SIZE == 0) {
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
operations.clear()
}
}
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
}
fun insertContact(contact: Contact): Boolean {
- if (contact.source == SMT_PRIVATE) {
- return insertLocalContact(contact)
+ if (contact.isPrivate()) {
+ return LocalContactsHelper(context).insertOrUpdateContact(contact)
}
try {
@@ -1251,6 +1261,7 @@ class ContactsHelper(val activity: Activity) {
withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
withValue(CommonDataKinds.Phone.NUMBER, it.value)
+ withValue(CommonDataKinds.Phone.NORMALIZED_NUMBER, it.normalizedNumber)
withValue(CommonDataKinds.Phone.TYPE, it.type)
withValue(CommonDataKinds.Phone.LABEL, it.label)
operations.add(build())
@@ -1351,9 +1362,9 @@ class ContactsHelper(val activity: Activity) {
var scaledSizePhotoData: ByteArray?
if (contact.photoUri.isNotEmpty()) {
val photoUri = Uri.parse(contact.photoUri)
- val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri)
+ val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, photoUri)
- val thumbnailSize = activity.getPhotoThumbnailSize()
+ val thumbnailSize = context.getPhotoThumbnailSize()
val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false)
scaledSizePhotoData = scaledPhoto.getByteArray()
@@ -1371,7 +1382,7 @@ class ContactsHelper(val activity: Activity) {
val results: Array
try {
- results = activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ results = context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
} finally {
scaledSizePhotoData = null
}
@@ -1388,22 +1399,20 @@ class ContactsHelper(val activity: Activity) {
val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, userId.toString())
val contentValues = ContentValues(1)
contentValues.put(ContactsContract.Contacts.STARRED, contact.starred)
- activity.contentResolver.update(uri, contentValues, null, null)
+ context.contentResolver.update(uri, contentValues, null, null)
}
return true
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
return false
}
}
- private fun insertLocalContact(contact: Contact) = activity.dbHelper.insertContact(contact)
-
private fun addFullSizePhoto(contactId: Long, fullSizePhotoData: ByteArray) {
val baseUri = ContentUris.withAppendedId(ContactsContract.RawContacts.CONTENT_URI, contactId)
val displayPhotoUri = Uri.withAppendedPath(baseUri, ContactsContract.RawContacts.DisplayPhoto.CONTENT_DIRECTORY)
- val fileDescriptor = activity.contentResolver.openAssetFileDescriptor(displayPhotoUri, "rw")
+ val fileDescriptor = context.contentResolver.openAssetFileDescriptor(displayPhotoUri, "rw")
val photoStream = fileDescriptor.createOutputStream()
photoStream.write(fullSizePhotoData)
photoStream.close()
@@ -1417,7 +1426,7 @@ class ContactsHelper(val activity: Activity) {
val selectionArgs = arrayOf(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE, contactId)
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
val id = cursor.getIntValue(ContactsContract.Data.CONTACT_ID)
val lookupKey = cursor.getStringValue(ContactsContract.Data.LOOKUP_KEY)
@@ -1437,7 +1446,7 @@ class ContactsHelper(val activity: Activity) {
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor?.moveToFirst() == true) {
return cursor.getStringValue(ContactsContract.Data._ID)
}
@@ -1448,23 +1457,27 @@ class ContactsHelper(val activity: Activity) {
}
fun addFavorites(contacts: ArrayList) {
- toggleLocalFavorites(contacts, true)
- if (activity.hasContactPermissions()) {
- toggleFavorites(contacts, true)
- }
+ Thread {
+ toggleLocalFavorites(contacts, true)
+ if (context.hasContactPermissions()) {
+ toggleFavorites(contacts, true)
+ }
+ }.start()
}
fun removeFavorites(contacts: ArrayList) {
- toggleLocalFavorites(contacts, false)
- if (activity.hasContactPermissions()) {
- toggleFavorites(contacts, false)
- }
+ Thread {
+ toggleLocalFavorites(contacts, false)
+ if (context.hasContactPermissions()) {
+ toggleFavorites(contacts, false)
+ }
+ }.start()
}
private fun toggleFavorites(contacts: ArrayList, addToFavorites: Boolean) {
try {
val operations = ArrayList()
- contacts.filter { it.source != SMT_PRIVATE }.map { it.contactId.toString() }.forEach {
+ contacts.filter { !it.isPrivate() }.map { it.contactId.toString() }.forEach {
val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, it)
ContentProviderOperation.newUpdate(uri).apply {
withValue(ContactsContract.Contacts.STARRED, if (addToFavorites) 1 else 0)
@@ -1472,68 +1485,69 @@ class ContactsHelper(val activity: Activity) {
}
if (operations.size % BATCH_SIZE == 0) {
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
operations.clear()
}
}
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
}
}
private fun toggleLocalFavorites(contacts: ArrayList, addToFavorites: Boolean) {
- val localContacts = contacts.filter { it.source == SMT_PRIVATE }.map { it.id.toString() }.toTypedArray()
- activity.dbHelper.toggleFavorites(localContacts, addToFavorites)
+ val localContacts = contacts.filter { it.isPrivate() }.map { it.id }.toTypedArray()
+ LocalContactsHelper(context).toggleFavorites(localContacts, addToFavorites)
}
fun deleteContact(contact: Contact) {
- if (contact.source == SMT_PRIVATE) {
- activity.dbHelper.deleteContact(contact.id)
- } else {
- deleteContacts(arrayListOf(contact))
- }
+ Thread {
+ if (contact.isPrivate()) {
+ context.contactsDB.deleteContactId(contact.id)
+ } else {
+ deleteContacts(arrayListOf(contact))
+ }
+ }.start()
}
fun deleteContacts(contacts: ArrayList) {
- Thread {
- val localContacts = contacts.filter { it.source == SMT_PRIVATE }.map { it.id.toString() }.toTypedArray()
- activity.dbHelper.deleteContacts(localContacts)
+ val localContacts = contacts.filter { it.isPrivate() }.map { it.id }.toTypedArray()
+ LocalContactsHelper(context).deleteContactIds(localContacts)
- try {
- val operations = ArrayList()
- val selection = "${ContactsContract.RawContacts._ID} = ?"
- contacts.filter { it.source != SMT_PRIVATE }.forEach {
- ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI).apply {
- val selectionArgs = arrayOf(it.id.toString())
- withSelection(selection, selectionArgs)
- operations.add(build())
- }
-
- if (operations.size % BATCH_SIZE == 0) {
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
- operations.clear()
- }
+ try {
+ val operations = ArrayList()
+ val selection = "${ContactsContract.RawContacts._ID} = ?"
+ contacts.filter { !it.isPrivate() }.forEach {
+ ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI).apply {
+ val selectionArgs = arrayOf(it.id.toString())
+ withSelection(selection, selectionArgs)
+ operations.add(build())
}
- if (activity.hasPermission(PERMISSION_WRITE_CONTACTS)) {
- activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ if (operations.size % BATCH_SIZE == 0) {
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ operations.clear()
}
- } catch (e: Exception) {
- activity.showErrorToast(e)
}
- }.start()
+
+ if (context.hasPermission(PERMISSION_WRITE_CONTACTS)) {
+ context.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ }
+ } catch (e: Exception) {
+ context.showErrorToast(e)
+ }
}
@SuppressLint("MissingPermission")
fun getRecents(callback: (ArrayList) -> Unit) {
Thread {
val calls = ArrayList()
- if (!activity.hasPermission(PERMISSION_WRITE_CALL_LOG) || !activity.hasPermission(PERMISSION_READ_CALL_LOG)) {
+ if (!context.hasPermission(PERMISSION_WRITE_CALL_LOG) || !context.hasPermission(PERMISSION_READ_CALL_LOG)) {
callback(calls)
return@Thread
}
+ val blockedNumbers = context.getBlockedNumbers()
val uri = CallLog.Calls.CONTENT_URI
val projection = arrayOf(
CallLog.Calls._ID,
@@ -1547,13 +1561,13 @@ class ContactsHelper(val activity: Activity) {
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)
- val timeFormat = if (activity.config.use24HourFormat) "HH:mm" else "h:mm a"
+ val yesterday = context.getString(R.string.yesterday)
+ val timeFormat = if (context.config.use24HourFormat) "HH:mm" else "h:mm a"
var prevNumber = ""
var cursor: Cursor? = null
try {
- cursor = activity.contentResolver.query(uri, projection, null, null, sorting)
+ cursor = context.contentResolver.query(uri, projection, null, null, sorting)
if (cursor?.moveToFirst() == true) {
do {
val id = cursor.getIntValue(CallLog.Calls._ID)
@@ -1564,6 +1578,10 @@ class ContactsHelper(val activity: Activity) {
continue
}
+ if (blockedNumbers.any { it.number == number || it.normalizedNumber == number }) {
+ continue
+ }
+
var formattedDate = SimpleDateFormat("dd MMM yyyy, $timeFormat", Locale.getDefault()).format(Date(date))
val datePart = formattedDate.substring(0, 11)
when {
@@ -1597,14 +1615,14 @@ class ContactsHelper(val activity: Activity) {
}
if (operations.size % BATCH_SIZE == 0) {
- activity.contentResolver.applyBatch(CallLog.AUTHORITY, operations)
+ context.contentResolver.applyBatch(CallLog.AUTHORITY, operations)
operations.clear()
}
}
- activity.contentResolver.applyBatch(CallLog.AUTHORITY, operations)
+ context.contentResolver.applyBatch(CallLog.AUTHORITY, operations)
} catch (e: Exception) {
- activity.showErrorToast(e)
+ context.showErrorToast(e)
}
}.start()
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Converters.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Converters.kt
new file mode 100644
index 00000000..63205ad6
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Converters.kt
@@ -0,0 +1,59 @@
+package com.simplemobiletools.contacts.pro.helpers
+
+import androidx.room.TypeConverter
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.simplemobiletools.contacts.pro.models.*
+
+class Converters {
+ private val gson = Gson()
+ private val longType = object : TypeToken>() {}.type
+ private val stringType = object : TypeToken>() {}.type
+ private val numberType = object : TypeToken>() {}.type
+ private val emailType = object : TypeToken>() {}.type
+ private val addressType = object : TypeToken>() {}.type
+ private val eventType = object : TypeToken>() {}.type
+ private val imType = object : TypeToken>() {}.type
+
+ @TypeConverter
+ fun jsonToStringList(value: String) = gson.fromJson>(value, stringType)
+
+ @TypeConverter
+ fun stringListToJson(list: ArrayList) = gson.toJson(list)
+
+ @TypeConverter
+ fun jsonToLongList(value: String) = gson.fromJson>(value, longType)
+
+ @TypeConverter
+ fun longListToJson(list: ArrayList) = gson.toJson(list)
+
+ @TypeConverter
+ fun jsonToPhoneNumberList(value: String) = gson.fromJson>(value, numberType)
+
+ @TypeConverter
+ fun phoneNumberListToJson(list: ArrayList) = gson.toJson(list)
+
+ @TypeConverter
+ fun jsonToEmailList(value: String) = gson.fromJson>(value, emailType)
+
+ @TypeConverter
+ fun emailListToJson(list: ArrayList) = gson.toJson(list)
+
+ @TypeConverter
+ fun jsonToAddressList(value: String) = gson.fromJson>(value, addressType)
+
+ @TypeConverter
+ fun addressListToJson(list: ArrayList) = gson.toJson(list)
+
+ @TypeConverter
+ fun jsonToEventList(value: String) = gson.fromJson>(value, eventType)
+
+ @TypeConverter
+ fun eventListToJson(list: ArrayList) = gson.toJson(list)
+
+ @TypeConverter
+ fun jsonToIMsList(value: String) = gson.fromJson>(value, imType)
+
+ @TypeConverter
+ fun IMsListToJson(list: ArrayList) = gson.toJson(list)
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/DBHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/DBHelper.kt
deleted file mode 100644
index 645c1a61..00000000
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/DBHelper.kt
+++ /dev/null
@@ -1,369 +0,0 @@
-package com.simplemobiletools.contacts.pro.helpers
-
-import android.app.Activity
-import android.content.ContentValues
-import android.content.Context
-import android.database.sqlite.SQLiteDatabase
-import android.database.sqlite.SQLiteOpenHelper
-import android.graphics.Bitmap
-import android.graphics.BitmapFactory
-import android.net.Uri
-import android.provider.MediaStore
-import android.text.TextUtils
-import com.google.gson.Gson
-import com.google.gson.reflect.TypeToken
-import com.simplemobiletools.commons.extensions.getBlobValue
-import com.simplemobiletools.commons.extensions.getIntValue
-import com.simplemobiletools.commons.extensions.getLongValue
-import com.simplemobiletools.commons.extensions.getStringValue
-import com.simplemobiletools.contacts.pro.extensions.applyRegexFiltering
-import com.simplemobiletools.contacts.pro.extensions.config
-import com.simplemobiletools.contacts.pro.extensions.getByteArray
-import com.simplemobiletools.contacts.pro.extensions.getPhotoThumbnailSize
-import com.simplemobiletools.contacts.pro.models.*
-
-class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
- private val CONTACTS_TABLE_NAME = "contacts"
- private val COL_ID = "id"
- private val COL_PREFIX = "prefix"
- private val COL_FIRST_NAME = "first_name"
- private val COL_MIDDLE_NAME = "middle_name"
- private val COL_SURNAME = "surname"
- private val COL_SUFFIX = "suffix"
- private val COL_NICKNAME = "nickname"
- private val COL_PHOTO = "photo"
- private val COL_PHONE_NUMBERS = "phone_numbers"
- private val COL_EMAILS = "emails"
- private val COL_EVENTS = "events"
- private val COL_STARRED = "starred"
- private val COL_ADDRESSES = "addresses"
- private val COL_IMS = "ims"
- private val COL_NOTES = "notes"
- private val COL_COMPANY = "company"
- private val COL_JOB_POSITION = "job_position"
- private val COL_GROUPS = "groups"
- private val COL_WEBSITES = "websites"
-
- private val GROUPS_TABLE_NAME = "groups"
- private val COL_TITLE = "title"
-
- private val FIRST_CONTACT_ID = 1000000
-
- private val mDb = writableDatabase
-
- companion object {
- const val DB_NAME = "contacts.db"
- private const val DB_VERSION = 7
- private var dbInstance: DBHelper? = null
- var gson = Gson()
-
- fun newInstance(context: Context): DBHelper {
- if (dbInstance == null)
- dbInstance = DBHelper(context)
-
- return dbInstance!!
- }
- }
-
- override fun onCreate(db: SQLiteDatabase) {
- db.execSQL("CREATE TABLE $CONTACTS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_FIRST_NAME TEXT, $COL_MIDDLE_NAME TEXT, " +
- "$COL_SURNAME TEXT, $COL_PHOTO BLOB, $COL_PHONE_NUMBERS TEXT, $COL_EMAILS TEXT, $COL_EVENTS TEXT, $COL_STARRED INTEGER, " +
- "$COL_ADDRESSES TEXT, $COL_NOTES TEXT, $COL_GROUPS TEXT, $COL_PREFIX TEXT, $COL_SUFFIX TEXT, $COL_COMPANY TEXT, $COL_JOB_POSITION TEXT," +
- "$COL_WEBSITES TEXT, $COL_NICKNAME TEXT, $COL_IMS TEXT)")
-
- // start autoincrement ID from FIRST_CONTACT_ID to avoid conflicts
- db.execSQL("REPLACE INTO sqlite_sequence (name, seq) VALUES ('$CONTACTS_TABLE_NAME', $FIRST_CONTACT_ID)")
-
- createGroupsTable(db)
- }
-
- override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
- if (oldVersion == 1) {
- db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_ADDRESSES TEXT DEFAULT ''")
- db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_NOTES TEXT DEFAULT ''")
- }
-
- if (oldVersion < 3) {
- createGroupsTable(db)
- db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_GROUPS TEXT DEFAULT ''")
- }
-
- if (oldVersion < 4) {
- db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_PREFIX TEXT DEFAULT ''")
- db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_SUFFIX TEXT DEFAULT ''")
- db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_COMPANY TEXT DEFAULT ''")
- db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_JOB_POSITION TEXT DEFAULT ''")
- }
-
- 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 ''")
- }
-
- if (oldVersion < 7) {
- db.execSQL("ALTER TABLE $CONTACTS_TABLE_NAME ADD COLUMN $COL_IMS TEXT DEFAULT ''")
- }
- }
-
- private fun createGroupsTable(db: SQLiteDatabase) {
- db.execSQL("CREATE TABLE $GROUPS_TABLE_NAME ($COL_ID INTEGER PRIMARY KEY AUTOINCREMENT, $COL_TITLE TEXT)")
-
- // start autoincrement ID from FIRST_GROUP_ID to avoid conflicts
- db.execSQL("REPLACE INTO sqlite_sequence (name, seq) VALUES ('$GROUPS_TABLE_NAME', $FIRST_GROUP_ID)")
- }
-
- fun insertContact(contact: Contact): Boolean {
- val contactValues = fillContactValues(contact)
- val id = mDb.insert(CONTACTS_TABLE_NAME, null, contactValues).toInt()
- return id != -1
- }
-
- fun updateContact(contact: Contact): Boolean {
- val contactValues = fillContactValues(contact)
- val selection = "$COL_ID = ?"
- val selectionArgs = arrayOf(contact.id.toString())
- return mDb.update(CONTACTS_TABLE_NAME, contactValues, selection, selectionArgs) == 1
- }
-
- fun deleteContact(id: Int) = deleteContacts(arrayOf(id.toString()))
-
- fun deleteContacts(ids: Array) {
- if (ids.isEmpty()) {
- return
- }
-
- val args = TextUtils.join(", ", ids)
- val selection = "$CONTACTS_TABLE_NAME.$COL_ID IN ($args)"
- mDb.delete(CONTACTS_TABLE_NAME, selection, null)
- }
-
- private fun fillContactValues(contact: Contact): ContentValues {
- return ContentValues().apply {
- put(COL_PREFIX, contact.prefix)
- put(COL_FIRST_NAME, contact.firstName)
- 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))
- put(COL_IMS, gson.toJson(contact.IMs))
- put(COL_EVENTS, gson.toJson(contact.events))
- put(COL_STARRED, contact.starred)
- put(COL_NOTES, contact.notes)
- put(COL_GROUPS, gson.toJson(contact.groups.map { it.id }))
- put(COL_COMPANY, contact.organization.company)
- put(COL_JOB_POSITION, contact.organization.jobPosition)
- put(COL_WEBSITES, gson.toJson(contact.websites))
-
- if (contact.photoUri.isNotEmpty()) {
- put(COL_PHOTO, getPhotoByteArray(contact.photoUri))
- } else if (contact.photo == null) {
- putNull(COL_PHOTO)
- }
- }
- }
-
- private fun getPhotoByteArray(uri: String): ByteArray {
- val photoUri = Uri.parse(uri)
- val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, photoUri)
-
- val thumbnailSize = context.getPhotoThumbnailSize()
- val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize * 2, thumbnailSize * 2, false)
- val scaledSizePhotoData = scaledPhoto.getByteArray()
- scaledPhoto.recycle()
- return scaledSizePhotoData
- }
-
- fun toggleFavorites(ids: Array, addToFavorites: Boolean) {
- val contactValues = ContentValues()
- contactValues.put(COL_STARRED, if (addToFavorites) 1 else 0)
-
- val args = TextUtils.join(", ", ids)
- val selection = "$COL_ID IN ($args)"
- mDb.update(CONTACTS_TABLE_NAME, contactValues, selection, null)
- }
-
- fun insertGroup(group: Group): Group? {
- val contactValues = fillGroupValues(group)
- val id = mDb.insert(GROUPS_TABLE_NAME, null, contactValues)
- return if (id == -1L) {
- null
- } else {
- Group(id, group.title)
- }
- }
-
- fun renameGroup(group: Group): Boolean {
- val contactValues = fillGroupValues(group)
- val selection = "$COL_ID = ?"
- val selectionArgs = arrayOf(group.id.toString())
- return mDb.update(GROUPS_TABLE_NAME, contactValues, selection, selectionArgs) == 1
- }
-
- fun deleteGroup(id: Long) = deleteGroups(arrayOf(id.toString()))
-
- private fun deleteGroups(ids: Array) {
- val args = TextUtils.join(", ", ids)
- val selection = "$GROUPS_TABLE_NAME.$COL_ID IN ($args)"
- mDb.delete(GROUPS_TABLE_NAME, selection, null)
- }
-
- fun getGroups(): ArrayList {
- val groups = ArrayList()
- val projection = arrayOf(COL_ID, COL_TITLE)
- val cursor = mDb.query(GROUPS_TABLE_NAME, projection, null, null, null, null, null)
- cursor.use {
- while (cursor.moveToNext()) {
- val id = cursor.getLongValue(COL_ID)
- val title = cursor.getStringValue(COL_TITLE)
- val group = Group(id, title)
- groups.add(group)
- }
- }
- return groups
- }
-
- private fun fillGroupValues(group: Group): ContentValues {
- return ContentValues().apply {
- put(COL_TITLE, group.title)
- }
- }
-
- fun addContactsToGroup(contacts: ArrayList, groupId: Long) {
- contacts.forEach {
- val currentGroupIds = it.groups.map { it.id } as ArrayList
- currentGroupIds.add(groupId)
- updateContactGroups(it, currentGroupIds)
- }
- }
-
- fun removeContactsFromGroup(contacts: ArrayList, groupId: Long) {
- contacts.forEach {
- val currentGroupIds = it.groups.map { it.id } as ArrayList
- currentGroupIds.remove(groupId)
- updateContactGroups(it, currentGroupIds)
- }
- }
-
- private fun updateContactGroups(contact: Contact, groupIds: ArrayList) {
- val contactValues = fillContactGroupValues(groupIds)
- val selection = "$COL_ID = ?"
- val selectionArgs = arrayOf(contact.id.toString())
- mDb.update(CONTACTS_TABLE_NAME, contactValues, selection, selectionArgs)
- }
-
- private fun fillContactGroupValues(groupIds: ArrayList): ContentValues {
- return ContentValues().apply {
- put(COL_GROUPS, gson.toJson(groupIds))
- }
- }
-
- fun getContacts(activity: Activity, selection: String? = null, selectionArgs: Array? = null): ArrayList {
- val storedGroups = ContactsHelper(activity).getStoredGroups()
- val filterDuplicates = activity.config.filterDuplicates
- val contacts = ArrayList()
- val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_NICKNAME, COL_PHONE_NUMBERS,
- COL_EMAILS, COL_EVENTS, COL_STARRED, COL_PHOTO, COL_ADDRESSES, COL_IMS, COL_NOTES, COL_GROUPS, COL_COMPANY, COL_JOB_POSITION, COL_WEBSITES)
-
- val phoneNumbersToken = object : TypeToken>() {}.type
- val emailsToken = object : TypeToken>() {}.type
- val addressesToken = object : TypeToken>() {}.type
- val IMsToken = object : TypeToken>() {}.type
- val eventsToken = object : TypeToken>() {}.type
- val groupIdsToken = object : TypeToken>() {}.type
- val websitesToken = object : TypeToken>() {}.type
-
- val cursor = mDb.query(CONTACTS_TABLE_NAME, projection, selection, selectionArgs, null, null, null)
- cursor.use {
- while (cursor.moveToNext()) {
- val id = cursor.getIntValue(COL_ID)
- val prefix = cursor.getStringValue(COL_PREFIX)
- val firstName = cursor.getStringValue(COL_FIRST_NAME)
- val middleName = cursor.getStringValue(COL_MIDDLE_NAME)
- val surname = cursor.getStringValue(COL_SURNAME)
- val suffix = cursor.getStringValue(COL_SUFFIX)
- val nickname = cursor.getStringValue(COL_NICKNAME)
-
- val phoneNumbersJson = cursor.getStringValue(COL_PHONE_NUMBERS)
- val phoneNumbers = if (phoneNumbersJson == "[]") ArrayList() else gson.fromJson>(phoneNumbersJson, phoneNumbersToken)
- ?: ArrayList(1)
-
- // labels can be null at upgrading from older app versions, when the label wasn't available at all yet
- phoneNumbers.filter { it.label == null }.forEach {
- it.label = ""
- }
-
- val emailsJson = cursor.getStringValue(COL_EMAILS)
- val emails = if (emailsJson == "[]") ArrayList() else gson.fromJson>(emailsJson, emailsToken)
- ?: ArrayList(1)
-
- emails.filter { it.label == null }.forEach {
- it.label = ""
- }
-
- val addressesJson = cursor.getStringValue(COL_ADDRESSES)
- val addresses = if (addressesJson == "[]") ArrayList() else gson.fromJson>(addressesJson, addressesToken)
- ?: ArrayList(1)
-
- addresses.filter { it.label == null }.forEach {
- it.label = ""
- }
-
- val IMsJson = cursor.getStringValue(COL_IMS)
- val IMs = if (IMsJson == "[]") ArrayList() else gson.fromJson>(IMsJson, IMsToken) ?: ArrayList(1)
-
- val eventsJson = cursor.getStringValue(COL_EVENTS)
- val events = if (eventsJson == "[]") ArrayList() else gson.fromJson>(eventsJson, eventsToken)
- ?: ArrayList(1)
-
- val photoByteArray = cursor.getBlobValue(COL_PHOTO) ?: null
- val photo = if (photoByteArray?.isNotEmpty() == true) {
- try {
- BitmapFactory.decodeByteArray(photoByteArray, 0, photoByteArray.size)
- } catch (e: OutOfMemoryError) {
- null
- }
- } else {
- null
- }
-
- val notes = cursor.getStringValue(COL_NOTES)
- val starred = cursor.getIntValue(COL_STARRED)
-
- val groupIdsJson = cursor.getStringValue(COL_GROUPS)
- val groupIds = if (groupIdsJson == "[]") ArrayList() else gson.fromJson>(groupIdsJson, groupIdsToken)
- ?: ArrayList(1)
- val groups = storedGroups.filter { groupIds.contains(it.id) } as ArrayList
-
- val company = cursor.getStringValue(COL_COMPANY)
- val jobPosition = cursor.getStringValue(COL_JOB_POSITION)
- val organization = Organization(company, jobPosition)
-
- val websitesJson = cursor.getStringValue(COL_WEBSITES)
- val websites = if (websitesJson == "[]") ArrayList() else gson.fromJson>(websitesJson, websitesToken)
- ?: ArrayList(1)
-
- val cleanPhoneNumbers = ArrayList()
- if (filterDuplicates) {
- phoneNumbers.mapTo(cleanPhoneNumbers) { PhoneNumber(it.value.applyRegexFiltering(), 0, "") }
- }
-
- val contact = Contact(id, prefix, firstName, middleName, surname, suffix, nickname, "", phoneNumbers, emails, addresses,
- events, SMT_PRIVATE, starred, id, "", photo, notes, groups, organization, websites, cleanPhoneNumbers, IMs)
- contacts.add(contact)
- }
- }
- return contacts
- }
-
- fun getContactWithId(activity: Activity, id: Int): Contact? {
- val selection = "$COL_ID = ?"
- val selectionArgs = arrayOf(id.toString())
- return getContacts(activity, selection, selectionArgs).firstOrNull()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/LocalContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/LocalContactsHelper.kt
new file mode 100644
index 00000000..54045fa3
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/LocalContactsHelper.kt
@@ -0,0 +1,142 @@
+package com.simplemobiletools.contacts.pro.helpers
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.provider.MediaStore
+import com.simplemobiletools.contacts.pro.extensions.contactsDB
+import com.simplemobiletools.contacts.pro.extensions.getByteArray
+import com.simplemobiletools.contacts.pro.extensions.getEmptyContact
+import com.simplemobiletools.contacts.pro.extensions.getPhotoThumbnailSize
+import com.simplemobiletools.contacts.pro.models.Contact
+import com.simplemobiletools.contacts.pro.models.Group
+import com.simplemobiletools.contacts.pro.models.LocalContact
+import com.simplemobiletools.contacts.pro.models.Organization
+
+class LocalContactsHelper(val context: Context) {
+ fun getAllContacts() = context.contactsDB.getContacts().map { convertLocalContactToContact(it) }.toMutableList() as ArrayList
+
+ fun getContactWithId(id: Int) = convertLocalContactToContact(context.contactsDB.getContactWithId(id))
+
+ fun insertOrUpdateContact(contact: Contact): Boolean {
+ val localContact = convertContactToLocalContact(contact)
+ return context.contactsDB.insertOrUpdate(localContact) > 0
+ }
+
+ fun addContactsToGroup(contacts: ArrayList, groupId: Long) {
+ contacts.forEach {
+ val localContact = convertContactToLocalContact(it)
+ val newGroups = localContact.groups
+ newGroups.add(groupId)
+ newGroups.distinct()
+ localContact.groups = newGroups
+ context.contactsDB.insertOrUpdate(localContact)
+ }
+ }
+
+ fun removeContactsFromGroup(contacts: ArrayList, groupId: Long) {
+ contacts.forEach {
+ val localContact = convertContactToLocalContact(it)
+ val newGroups = localContact.groups
+ newGroups.remove(groupId)
+ localContact.groups = newGroups
+ context.contactsDB.insertOrUpdate(localContact)
+ }
+ }
+
+ fun deleteContactIds(ids: Array) {
+ ids.forEach {
+ context.contactsDB.deleteContactId(it)
+ }
+ }
+
+ fun toggleFavorites(ids: Array, addToFavorites: Boolean) {
+ val isStarred = if (addToFavorites) 1 else 0
+ ids.forEach {
+ context.contactsDB.updateStarred(isStarred, it)
+ }
+ }
+
+ private fun getPhotoByteArray(uri: String): ByteArray {
+ if (uri.isEmpty()) {
+ return ByteArray(0)
+ }
+
+ val photoUri = Uri.parse(uri)
+ val bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, photoUri)
+
+ val thumbnailSize = context.getPhotoThumbnailSize()
+ val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize * 2, thumbnailSize * 2, false)
+ val scaledSizePhotoData = scaledPhoto.getByteArray()
+ scaledPhoto.recycle()
+ return scaledSizePhotoData
+ }
+
+ private fun convertLocalContactToContact(localContact: LocalContact?): Contact? {
+ if (localContact == null) {
+ return null
+ }
+
+ val contactPhoto = if (localContact.photo == null) {
+ null
+ } else {
+ try {
+ BitmapFactory.decodeByteArray(localContact.photo, 0, localContact.photo!!.size)
+ } catch (e: OutOfMemoryError) {
+ null
+ }
+ }
+
+ val storedGroups = ContactsHelper(context).getStoredGroupsSync()
+
+ return context.getEmptyContact().apply {
+ id = localContact.id!!
+ prefix = localContact.prefix
+ firstName = localContact.firstName
+ middleName = localContact.middleName
+ surname = localContact.surname
+ suffix = localContact.suffix
+ nickname = localContact.nickname
+ photoUri = ""
+ phoneNumbers = localContact.phoneNumbers
+ emails = localContact.emails
+ addresses = localContact.addresses
+ events = localContact.events
+ source = SMT_PRIVATE
+ starred = localContact.starred
+ contactId = localContact.id!!
+ thumbnailUri = ""
+ photo = contactPhoto
+ notes = localContact.notes
+ groups = storedGroups.filter { localContact.groups.contains(it.id) } as ArrayList
+ organization = Organization(localContact.company, localContact.jobPosition)
+ websites = localContact.websites
+ IMs = localContact.IMs
+ }
+ }
+
+ private fun convertContactToLocalContact(contact: Contact): LocalContact {
+ return getEmptyLocalContact().apply {
+ id = if (contact.id == 0) null else contact.id
+ prefix = contact.prefix
+ firstName = contact.firstName
+ middleName = contact.middleName
+ surname = contact.surname
+ suffix = contact.suffix
+ nickname = contact.nickname
+ photo = getPhotoByteArray(contact.photoUri)
+ phoneNumbers = contact.phoneNumbers
+ emails = contact.emails
+ events = contact.events
+ starred = contact.starred
+ addresses = contact.addresses
+ notes = contact.notes
+ groups = contact.groups.map { it.id }.toMutableList() as ArrayList
+ company = contact.organization.company
+ jobPosition = contact.organization.jobPosition
+ websites = contact.websites
+ IMs = contact.IMs
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/QuotedPrintable.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/QuotedPrintable.kt
deleted file mode 100644
index e39bf43b..00000000
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/QuotedPrintable.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.simplemobiletools.contacts.pro.helpers
-
-import java.io.ByteArrayOutputStream
-import java.net.URLEncoder
-
-// https://alvinalexander.com/java/jwarehouse/android/core/java/com/google/android/mms/pdu/QuotedPrintable.java.shtml
-object QuotedPrintable {
- private const val ESCAPE_CHAR: Byte = '='.toByte()
- fun decode(value: String?): String {
- val bytes = value?.toByteArray()
- if (bytes == null || bytes.isEmpty()) {
- return ""
- }
-
- val buffer = ByteArrayOutputStream()
- var i = 0
- while (i < bytes.size) {
- val b = bytes[i].toInt()
- if (b == ESCAPE_CHAR.toInt()) {
- try {
- if ('\r' == bytes[i + 1].toChar() && '\n' == bytes[i + 2].toChar()) {
- i += 3
- continue
- }
-
- val u = Character.digit(bytes[++i].toChar(), 16)
- val l = Character.digit(bytes[++i].toChar(), 16)
- if (u == -1 || l == -1) {
- return ""
- }
-
- buffer.write(((u shl 4) + l).toChar().toInt())
- } catch (e: ArrayIndexOutOfBoundsException) {
- return ""
- }
-
- } else {
- buffer.write(b)
- }
- i++
- }
- return String(buffer.toByteArray())
- }
-
- fun encode(value: String): String {
- val result = StringBuilder()
- value.forEach {
- if (it == ' ') {
- result.append(' ')
- } else {
- val urlEncoded = urlEncode(it.toString())
- if (urlEncoded == it.toString()) {
- val hex = String.format("%04x", it.toInt()).trimStart('0').toUpperCase()
- result.append("=$hex")
- } else {
- result.append(urlEncoded)
- }
- }
- }
- return result.toString()
- }
-
- fun urlEncode(value: String) = URLEncoder.encode(value, "UTF-8").replace("+", " ").replace('%', '=')
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/VcfExporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/VcfExporter.kt
index 485010a8..0fc41e3b 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/VcfExporter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/VcfExporter.kt
@@ -152,7 +152,7 @@ class VcfExporter {
contactsExported++
}
- Ezvcard.write(cards).go(file)
+ Ezvcard.write(cards).go(it)
} catch (e: Exception) {
activity.showErrorToast(e)
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/VcfImporter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/VcfImporter.kt
index 9102abdb..290327c1 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/VcfImporter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/VcfImporter.kt
@@ -6,15 +6,14 @@ import android.provider.ContactsContract.CommonDataKinds
import android.widget.Toast
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.contacts.pro.activities.SimpleActivity
-import com.simplemobiletools.contacts.pro.extensions.dbHelper
import com.simplemobiletools.contacts.pro.extensions.getCachePhoto
import com.simplemobiletools.contacts.pro.extensions.getCachePhotoUri
+import com.simplemobiletools.contacts.pro.extensions.groupsDB
+import com.simplemobiletools.contacts.pro.extensions.normalizeNumber
import com.simplemobiletools.contacts.pro.helpers.VcfImporter.ImportResult.*
import com.simplemobiletools.contacts.pro.models.*
import ezvcard.Ezvcard
import ezvcard.VCard
-import org.joda.time.DateTime
-import org.joda.time.format.DateTimeFormat
import java.io.File
import java.io.FileOutputStream
import java.net.URLDecoder
@@ -25,8 +24,6 @@ class VcfImporter(val activity: SimpleActivity) {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL
}
- private val PATTERN = "EEE MMM dd HH:mm:ss 'GMT'ZZ YYYY"
-
private var contactsImported = 0
private var contactsFailed = 0
@@ -59,7 +56,7 @@ class VcfImporter(val activity: SimpleActivity) {
""
}
- phoneNumbers.add(PhoneNumber(number, type, label))
+ phoneNumbers.add(PhoneNumber(number, type, label, number.normalizeNumber()))
}
val emails = ArrayList()
@@ -112,7 +109,6 @@ class VcfImporter(val activity: SimpleActivity) {
val photoData = ezContact.photos.firstOrNull()?.data
val photo = null
val thumbnailUri = savePhoto(photoData)
- val cleanPhoneNumbers = ArrayList()
val IMs = ArrayList()
ezContact.impps.forEach {
@@ -136,7 +132,7 @@ class VcfImporter(val activity: SimpleActivity) {
}
val contact = Contact(0, prefix, firstName, middleName, surname, suffix, nickname, photoUri, phoneNumbers, emails, addresses, events,
- targetContactSource, starred, contactId, thumbnailUri, photo, notes, groups, organization, websites, cleanPhoneNumbers, IMs)
+ targetContactSource, starred, contactId, thumbnailUri, photo, notes, groups, organization, websites, IMs)
// if there is no N and ORG fields at the given contact, only FN, treat it as an organization
if (contact.getNameToDisplay().isEmpty() && contact.organization.isEmpty() && ezContact.formattedName.value.isNotEmpty()) {
@@ -160,8 +156,10 @@ class VcfImporter(val activity: SimpleActivity) {
}
private fun formatDateToDayCode(date: Date): String {
- val dateTime = DateTime.parse(date.toString(), DateTimeFormat.forPattern(PATTERN))
- return dateTime.toString("yyyy-MM-dd")
+ val year = 1900 + date.year
+ val month = String.format("%02d", date.month + 1)
+ val day = String.format("%02d", date.date)
+ return "$year-$month-$day"
}
private fun getContactGroups(ezContact: VCard): ArrayList {
@@ -170,7 +168,7 @@ class VcfImporter(val activity: SimpleActivity) {
val groupNames = ezContact.categories.values
if (groupNames != null) {
- val storedGroups = ContactsHelper(activity).getStoredGroups()
+ val storedGroups = ContactsHelper(activity).getStoredGroupsSync()
groupNames.forEach {
val groupName = it
@@ -179,11 +177,10 @@ class VcfImporter(val activity: SimpleActivity) {
if (storedGroup != null) {
groups.add(storedGroup)
} else {
- val newContactGroup = activity.dbHelper.insertGroup(Group(0, groupName))
-
- if (newContactGroup != null) {
- groups.add(newContactGroup)
- }
+ val newGroup = Group(null, groupName)
+ val id = activity.groupsDB.insertOrUpdate(newGroup)
+ newGroup.id = id
+ groups.add(newGroup)
}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/interfaces/ContactsDao.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/interfaces/ContactsDao.kt
new file mode 100644
index 00000000..373df2d3
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/interfaces/ContactsDao.kt
@@ -0,0 +1,25 @@
+package com.simplemobiletools.contacts.pro.interfaces
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.simplemobiletools.contacts.pro.models.LocalContact
+
+@Dao
+interface ContactsDao {
+ @Query("SELECT * FROM contacts")
+ fun getContacts(): List
+
+ @Query("SELECT * FROM contacts WHERE id = :id")
+ fun getContactWithId(id: Int): LocalContact?
+
+ @Query("UPDATE contacts SET starred = :isStarred WHERE id = :id")
+ fun updateStarred(isStarred: Int, id: Int)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertOrUpdate(contact: LocalContact): Long
+
+ @Query("DELETE FROM contacts WHERE id = :id")
+ fun deleteContactId(id: Int)
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/interfaces/GroupsDao.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/interfaces/GroupsDao.kt
new file mode 100644
index 00000000..8991a6f0
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/interfaces/GroupsDao.kt
@@ -0,0 +1,19 @@
+package com.simplemobiletools.contacts.pro.interfaces
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.simplemobiletools.contacts.pro.models.Group
+
+@Dao
+interface GroupsDao {
+ @Query("SELECT * FROM groups")
+ fun getGroups(): List
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertOrUpdate(group: Group): Long
+
+ @Query("DELETE FROM groups WHERE id = :id")
+ fun deleteGroupId(id: Long)
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/BlockedNumber.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/BlockedNumber.kt
new file mode 100644
index 00000000..13fe44fd
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/BlockedNumber.kt
@@ -0,0 +1,3 @@
+package com.simplemobiletools.contacts.pro.models
+
+data class BlockedNumber(val id: Long, val number: String, val normalizedNumber: String)
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/Contact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/Contact.kt
index adad262d..7eb0d57e 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/Contact.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/Contact.kt
@@ -1,17 +1,18 @@
package com.simplemobiletools.contacts.pro.models
import android.graphics.Bitmap
+import android.telephony.PhoneNumberUtils
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.pro.extensions.applyRegexFiltering
+import com.simplemobiletools.contacts.pro.extensions.normalizeNumber
+import com.simplemobiletools.contacts.pro.helpers.SMT_PRIVATE
-data class Contact(val id: Int, var prefix: String, var firstName: String, var middleName: String, var surname: String, var suffix: String, var nickname: String,
+data class Contact(var 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, var cleanPhoneNumbers: ArrayList,
- var IMs: ArrayList) :
+ var events: ArrayList, var source: String, var starred: Int, var contactId: Int, var thumbnailUri: String, var photo: Bitmap?, var notes: String,
+ var groups: ArrayList, var organization: Organization, var websites: ArrayList, var IMs: ArrayList) :
Comparable {
companion object {
var sorting = 0
@@ -126,21 +127,19 @@ data class Contact(val id: Int, var prefix: String, var firstName: String, var m
fun isABusinessContact() = prefix.isEmpty() && firstName.isEmpty() && middleName.isEmpty() && surname.isEmpty() && suffix.isEmpty() && organization.isNotEmpty()
- // do a more advanced phone number check here, compare numbers and and search query with dashes, spaces and everything but numbers removed
fun doesContainPhoneNumber(text: String): Boolean {
- if (text.isNotEmpty()) {
- if (phoneNumbers.any { it.value.contains(text) } || cleanPhoneNumbers.any { it.value.contains(text) }) {
- return true
+ return if (text.isNotEmpty()) {
+ val normalizedText = text.normalizeNumber()
+ phoneNumbers.any {
+ PhoneNumberUtils.compare(it.normalizedNumber, normalizedText) ||
+ it.value.contains(text) ||
+ it.normalizedNumber?.contains(normalizedText) == true ||
+ it.value.normalizeNumber().contains(normalizedText)
}
+ } else {
+ false
}
-
- val filteredNumber = text.applyRegexFiltering()
- if (filteredNumber.isNotEmpty()) {
- if (phoneNumbers.any { it.value.contains(filteredNumber) } || cleanPhoneNumbers.any { it.value.contains(filteredNumber) }) {
- return true
- }
- }
-
- return false
}
+
+ fun isPrivate() = source == SMT_PRIVATE
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/Group.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/Group.kt
index c36635bf..9628e664 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/Group.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/Group.kt
@@ -1,16 +1,21 @@
package com.simplemobiletools.contacts.pro.models
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.PrimaryKey
import com.simplemobiletools.contacts.pro.helpers.FIRST_GROUP_ID
import java.io.Serializable
-data class Group(var id: Long, var title: String, var contactsCount: Int = 0) : Serializable {
- companion object {
- private const val serialVersionUID = -1384515348451345L
- }
+@Entity(tableName = "groups", indices = [(Index(value = ["id"], unique = true))])
+data class Group(
+ @PrimaryKey(autoGenerate = true) var id: Long?,
+ @ColumnInfo(name = "title") var title: String,
+ @ColumnInfo(name = "contacts_count") var contactsCount: Int = 0) : Serializable {
fun addContact() = contactsCount++
fun getBubbleText() = title
- fun isPrivateSecretGroup() = id >= FIRST_GROUP_ID
+ fun isPrivateSecretGroup() = id ?: 0 >= FIRST_GROUP_ID
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/LocalContact.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/LocalContact.kt
new file mode 100644
index 00000000..a2947246
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/LocalContact.kt
@@ -0,0 +1,33 @@
+package com.simplemobiletools.contacts.pro.models
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "contacts", indices = [(Index(value = ["id"], unique = true))])
+data class LocalContact(
+ @PrimaryKey(autoGenerate = true) var id: Int?,
+ @ColumnInfo(name = "prefix") var prefix: String,
+ @ColumnInfo(name = "first_name") var firstName: String,
+ @ColumnInfo(name = "middle_name") var middleName: String,
+ @ColumnInfo(name = "surname") var surname: String,
+ @ColumnInfo(name = "suffix") var suffix: String,
+ @ColumnInfo(name = "nickname") var nickname: String,
+ @ColumnInfo(name = "photo", typeAffinity = ColumnInfo.BLOB) var photo: ByteArray?,
+ @ColumnInfo(name = "phone_numbers") var phoneNumbers: ArrayList,
+ @ColumnInfo(name = "emails") var emails: ArrayList,
+ @ColumnInfo(name = "events") var events: ArrayList,
+ @ColumnInfo(name = "starred") var starred: Int,
+ @ColumnInfo(name = "addresses") var addresses: ArrayList,
+ @ColumnInfo(name = "notes") var notes: String,
+ @ColumnInfo(name = "groups") var groups: ArrayList,
+ @ColumnInfo(name = "company") var company: String,
+ @ColumnInfo(name = "job_position") var jobPosition: String,
+ @ColumnInfo(name = "websites") var websites: ArrayList,
+ @ColumnInfo(name = "ims") var IMs: ArrayList) {
+
+ override fun equals(other: Any?) = id == (other as? LocalContact?)?.id
+
+ override fun hashCode() = id ?: 0
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/PhoneNumber.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/PhoneNumber.kt
index b95127aa..3c0850c3 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/PhoneNumber.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/models/PhoneNumber.kt
@@ -1,3 +1,3 @@
package com.simplemobiletools.contacts.pro.models
-data class PhoneNumber(var value: String, var type: Int, var label: String)
+data class PhoneNumber(var value: String, var type: Int, var label: String, var normalizedNumber: String?)
diff --git a/app/src/main/res/drawable-hdpi/ic_block.png b/app/src/main/res/drawable-hdpi/ic_block.png
new file mode 100644
index 00000000..1c541fe5
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_block.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_block.png b/app/src/main/res/drawable-xhdpi/ic_block.png
new file mode 100644
index 00000000..ae3856fe
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_block.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_block.png b/app/src/main/res/drawable-xxhdpi/ic_block.png
new file mode 100644
index 00000000..d6dc2222
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_block.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_block.png b/app/src/main/res/drawable-xxxhdpi/ic_block.png
new file mode 100644
index 00000000..9f1191c9
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_block.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 ecf79006..304a7a52 100644
--- a/app/src/main/res/layout/activity_edit_contact.xml
+++ b/app/src/main/res/layout/activity_edit_contact.xml
@@ -4,7 +4,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/contact_scrollview"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
index 167eb122..9950dfd3 100644
--- a/app/src/main/res/layout/activity_settings.xml
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -78,6 +78,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_manage_blocked_number.xml b/app/src/main/res/layout/item_manage_blocked_number.xml
new file mode 100644
index 00000000..e78f8724
--- /dev/null
+++ b/app/src/main/res/layout/item_manage_blocked_number.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_view_group.xml b/app/src/main/res/layout/item_view_group.xml
index 8aed14d6..12959127 100644
--- a/app/src/main/res/layout/item_view_group.xml
+++ b/app/src/main/res/layout/item_view_group.xml
@@ -5,10 +5,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
+ android:background="?attr/selectableItemBackground"
android:ellipsize="end"
android:lines="1"
android:maxLines="1"
- android:paddingBottom="@dimen/normal_margin"
android:paddingTop="@dimen/normal_margin"
+ android:paddingBottom="@dimen/normal_margin"
android:singleLine="true"
android:textSize="@dimen/bigger_text_size"/>
diff --git a/app/src/main/res/menu/cab_recent_calls.xml b/app/src/main/res/menu/cab_recent_calls.xml
index 128e6362..f5e7c4ba 100644
--- a/app/src/main/res/menu/cab_recent_calls.xml
+++ b/app/src/main/res/menu/cab_recent_calls.xml
@@ -11,4 +11,9 @@
android:icon="@drawable/ic_select_all"
android:title="@string/select_all"
app:showAsAction="ifRoom"/>
+
diff --git a/app/src/main/res/menu/menu_add_blocked_number.xml b/app/src/main/res/menu/menu_add_blocked_number.xml
new file mode 100644
index 00000000..fb290efd
--- /dev/null
+++ b/app/src/main/res/menu/menu_add_blocked_number.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml
index 8056c20e..fd4f43b3 100644
--- a/app/src/main/res/values-az/strings.xml
+++ b/app/src/main/res/values-az/strings.xml
@@ -17,11 +17,14 @@
Lazım olan icazələri istə
Create new contact
Add to an existing contact
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
No contacts found
No contacts with emails have been found
No contacts with phone numbers have been found
+ No recent calls found
Yeni kontakt
Redaktə et
@@ -52,6 +55,7 @@
Ada soyaddan başla
Telefon nömrələrini əsas ekranda göstər
Kontakt görüntülərini göstər
+ Show a dialpad button on the main screen
Kontakta toxunduqda
Kontakta zəng et
Kontakt detallarına bax
@@ -107,6 +111,16 @@
Dialpad
Add number to contact
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Göstərmək üçün sahəni seç
Ön şəkilçi
@@ -122,6 +136,14 @@
Kontakt kökü
Instant messaging (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
I want to change what fields are visible at contacts. Can I do it?
Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there.
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index aa9e30e1..d031d5b3 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -17,11 +17,14 @@
Benötigte Berechtigungen anfordern
Neuen Kontakt erstellen
Zu einem existierenden Kontakt hinzufügen
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
- No contacts found
- No contacts with emails have been found
- No contacts with phone numbers have been found
+ Keine Kontakte gefunden
+ Keine Kontakte mit E-Mailadressen gefunden
+ Keine Kontakte mit Telefonnummern gefunden
+ No recent calls found
Neuer Kontakt
Kontakt bearbeiten
@@ -52,6 +55,7 @@
Namen mit Nachnamen beginnen
Telefonnummern im Hauptmenü zeigen
Vorschaubilder der Kontakte zeigen
+ Show a dialpad button on the main screen
Beim Klicken auf den Kontakt
Kontakt anrufen
Kontaktdetails anzeigen
@@ -62,7 +66,7 @@
Favoriten
Anrufliste
Bestätigungsdialog zeigen, bevor ein Anruf durchgeführt wird
- Show only contacts with phone numbers
+ Nur Kontakte mit Telefonnummern anzeigen
E-Mail
@@ -107,6 +111,16 @@
Wählfeld
Nummer zu Kontakt hinzufügen
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Sichtbare Felder auswählen
Titel
@@ -122,6 +136,14 @@
Kontaktquelle
Instant messaging (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
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.
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 618c88fb..b18b448e 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -17,11 +17,14 @@
Ζητούνται τα απαιτούμενα δικαιώματα
Δημιουργία νέας Επαφής
Προσθήκη σε μια υπάρχουσα Επαφή
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
No contacts found
No contacts with emails have been found
No contacts with phone numbers have been found
+ No recent calls found
Νέα επαφή
Επεξεργασία επαφής
@@ -52,6 +55,7 @@
Εμφάνιση πρώτα το επώνυμο
Εμφάνιση τηλεφωνικών αριθμών στην κύρια οθόνη
Εμφάνιση μικρογραφιών επαφής
+ Show a dialpad button on the main screen
Στην επιλογή επαφής
Κλήση επαφής
Εμφάνιση λεπτομερειών επαφής
@@ -107,6 +111,16 @@
Πληκτρολόγιο
Προσθήκη αριθμού σε επαφή
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Επιλογή εμφάνισης πεδίων
Πρόθεμα
@@ -122,6 +136,14 @@
Προέλευση επαφής
Αμεσο μήνυμα (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
Θέλω να αλλάξω τα πεδία που θα είναι ορατά στις επαφές. Μπορώ να το κάνω?
Ναι, το μόνο που έχετε να κάνετε είναι να μεταβείτε στις Ρυθμίσεις -> Διαχείριση εμφανιζόμενων πεδίων επαφής. Εκεί μπορείτε να επιλέξετε ποια πεδία θα πρέπει να είναι ορατά. Κάποια από αυτά είναι ακόμη και απενεργοποιημένα από προεπιλογή, επομένως ίσως βρείτε κάποια νέα εκεί.
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index fab37fca..da6af990 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -2,8 +2,8 @@
Kontaktu sinpleak
Kontaktuak
Helbidea
- Txertatzen...
- Eguneratzen...
+ Txertatzen…
+ Eguneratzen…
Telefono memoria
Telefono memoria (beste aplikazioentzat ikustezina)
Enpresa
@@ -15,6 +15,16 @@
Bidali emaila taldeari
%s deitu
Eskatu beharrezko baimenak
+ Create new contact
+ Add to an existing contact
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
+
+
+ No contacts found
+ No contacts with emails have been found
+ No contacts with phone numbers have been found
+ No recent calls found
Kontaktu berria
Editatu taldea
@@ -23,6 +33,7 @@
Izena
Erdiko izena
Abizena
+ Nickname
Talderik ez
@@ -43,6 +54,8 @@
Abizenaren arabera sailkatu
Erakutsi telefono zenbakiak pantaila nagusian
+ Show contact thumbnails
+ Show a dialpad button on the main screen
Kontaktu sakatzean
Kontaktua deitu
Ikusi kontaktu detaileak
@@ -53,6 +66,7 @@
Gogokoak
Azken deiak
Erakutsi egiaztatze mezua dei bat hasi baino lehen
+ Show only contacts with phone numbers
Emaila
@@ -66,6 +80,7 @@
Nagusia
Laneko faxa
Etxeko faxa
+ Pager
Ez da telefono zenbakirik aurkitu
@@ -88,8 +103,24 @@
Esportatu kontaktuak
Inportatu .vcf fitxategiko kontaktuak
Esportatu kontaktua .vcf fitxategi batera
+ Target contact source
+ Include contact sources
Fitxategi izena (.vcf gabe)
+
+ Dialpad
+ Add number to contact
+
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Hautatu erakusteko eremuak
Aurrizkia
@@ -103,6 +134,15 @@
Webguneak
Taldeak
Kontaktu jatorria
+ Instant messaging (IM)
+
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
Aldatu ditzaket kontaktuetan ikusgarri dauden eremuak?
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index d51d9d93..093f665a 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -2,26 +2,29 @@
Simple Contacts
Contacts
Adresse
- Ajout…
- Mise à jour…
+ Ajout en cours
+ Actualisation en cours
Stockage du téléphone
- Stockage du téléphone (non visible par d\'autres applis)
- Société
+ Stockage du téléphone (non visible par d\'autres applications)
+ Entreprise
Poste
- Site web
+ Site Internet
Envoyer un SMS aux contacts
- Envoyer un e-mail aux contacts
+ Envoyer un courriel aux contacts
Envoyer un SMS au groupe
- Envoyer un e-mail au groupe
- Call %s
- Demander les autorisations requises
+ Envoyer un courriel au groupe
+ Appeler %s
+ Demander les autorisations nécessaires
Créer un nouveau contact
Ajouter à un contact existant
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
- No contacts found
- No contacts with emails have been found
- No contacts with phone numbers have been found
+ Aucun contact n\'a été trouvé
+ Aucun contact avec une adresse de courriel n\'a été trouvé
+ Aucun contact avec un numéro de téléphone n\'a été trouvé
+ Aucun appel récent n\'a été trouvé
Nouveau contact
Modifier contact
@@ -33,15 +36,15 @@
Surnom
- Pas de groupe
+ Aucun groupe
Créer un nouveau groupe
- Enlever du groupe
+ Supprimer du groupe
Ce groupe est vide
- Ajout contacts
- Il n\'y a pas de groupes de contacts sur l\'appareil
+ Ajouter des contacts
+ Aucun groupe de contacts sur l\'appareil
Créer un groupe
Ajouter à un groupe
- Créer un groupe pris en compte
+ Créer un groupe dans le compte
Prendre une photo
@@ -52,20 +55,21 @@
Trier les contacts par nom de famille
Afficher les numéros de téléphone
Afficher les vignettes des contacts
- Sur appui du contact
+ Afficher un bouton \"Clavier téléphonique\" sur l\'écran principal
+ Lors d\'un appui sur un contact
Appeler le contact
- Voir les détails du contact
+ Afficher les détails du contact
Configurer l\'affichage des champs des contacts
Essayez de filtrer les contacts en double
Gérer les onglets affichés
Contacts
- Favorites
+ Favoris
Appels récents
- Afficher une boîte de dialogue de confirmation d\'appel avant de lancer un appel
- Show only contacts with phone numbers
+ Afficher une demande de confirmation avant de démarrer un appel
+ Afficher uniquement les contacts avec un numéro de téléphone
- E-mail
+ Adresse de courriel
Maison
Travail
Autre
@@ -84,11 +88,11 @@
Anniversaire
- Vous n\'ayez pas encore ajouté de contact favori.
+ Aucun contact favori n\'a été trouvé
Ajouter des favoris
Ajouter aux favoris
- Retirer des favoris
- Vous devez être sur l\'écran Modifier pour modifier un contact
+ Supprimer des favoris
+ Vous devez être sur l\'écran \"Modifier\" pour modifier un contact
Rechercher des contacts
@@ -99,44 +103,62 @@
Exporter des contacts
Importer des contacts depuis un fichier .vcf
Exporter des contacts vers un fichier .vcf
- Source du contact cible
- Inclure les sources du contact
+ Compte pour le du contact destinataire
+ Ajouter le compte pour le contact
Nom du fichier (sans .vcf)
- Dialpad
+ Clavier téléphonique
Ajouter un numéro au contact
+
+ Numéroteur
+ Appel en cours
+ Appel entrant
+ Appel entrant de…
+ Appel en cours
+ Interrompu
+ Refuser
+ Répondre
+
Sélectionner les champs à afficher
Préfixe
Suffixe
Numéros de téléphone
- E-mails
+ Adresse de courriels
Adresses
Évènements (naissances, anniversaires)
Notes
Organisation
- Sites web
+ Sites Internet
Groupe
- Source du contact
+ Compte pour le contact
Messagerie instantanée (MI)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
- Je veux changer quelles champs sont visibles. Est-ce que je peux ?
- Oui, tout ce que vous avez à faire c\'est d\'aller dans Paramètres -> Configurer l\'affichage des champs de contact. Ici vous pouvez sélectionner quelles champs vous voulez afficher. Certains sont désactivés par défaut, ainsi vous pourrez y trouver des nouveaux champs.
+ Je veux modifier les champs affichés sur les fiches de mes contacts. Puis-je le faire ?
+ Oui, tout ce que vous avez à faire c\'est d\'aller dans \"Paramètres\" -> \"Configurer l\'affichage des champs de contact\". Sélectionnez les champs à afficher. Certains champs sont désactivés par défaut.
- Une appli de contacts pour gérer vos contacts sans pubs.
+ Une application répertoire pour gérer vos contacts sans publicité.
- Une appli simple pour créer et gérer vos contacts depuis n\'importe quelle source. Les contacts peuvent être stockés sur votre appareil mais aussi synchronisés via Google ou d\'autres comptes. Vous pouvez afficher vos contacts favoris dans une liste séparée.
+ Un outil simple pour créer et gérer vos contacts depuis n\'importe quelle source. Les contacts peuvent être stockés sur votre appareil mais aussi synchronisés via votre compte Google ou d\'autres comptes. Vous pouvez afficher vos contacts favoris dans une liste séparée.
- Vous pouvez l\'utiliser pour gérer les e-mail et événements de vos contacts. Elle permet de trier/filter via de multiples paramètres, et même afficher le surnom en premier.
+ Vous pouvez l\'utiliser pour gérer les adresses de courriels et les événements de vos contacts. Cet outil permet de trier/filtrer à l\'aide de multiples paramètres, par exemple : afficher le surnom en premier.
- Aucune publicité ni de permission inutile. Elle est entièrement open source et vous permet de personnaliser les couleurs.
+ L\'application ne contient ni publicité, ni autorisation inutile. Elle est totalement opensource et est également fournie avec des couleurs personnalisables.
- Cette application fait parti d\'un groupe d\'applications. Vous pouvez trouver le reste des applis sur https://www.simplemobiletools.com
+ Cette application fait partie d\'une plus grande suite. Vous pouvez trouver les autres applications sur https://www.simplemobiletools.com
No contacts found
No contacts with emails have been found
No contacts with phone numbers have been found
+ No recent calls found
Novi kontakt
Uredi kontakt
@@ -52,6 +55,7 @@
Započnite imena s prezimenima
Prikaži telefonske brojeve na glavnom zaslonu
Prikaži sličice kontakata
+ Show a dialpad button on the main screen
Prilikom dodira kontakta
Nazovi kontakt
Prikaži pojedinosti o kontaktu
@@ -107,6 +111,16 @@
Dialpad
Add number to contact
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Odaberi polja za prikaz
Prefiks
@@ -122,6 +136,14 @@
Izvori kontakata
Brzo slanje poruka (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
Želim promijeniti polja koja su vidljiva na kontaktima. Mogu li to napraviti?
Da, sve što morate učiniti je otići u Postavke -> Upravljanje poljima za prikaz. Tamo možete odabrati polja koja bi trebala biti vidljiva. Neka od njih su čak i onemogućena prema zadanim postavkama, tako da možete pronaći neke nove.
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index dd4efaa4..49dde94e 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -5,7 +5,7 @@
Inserimento in corso…
Aggiornamento in corso…
Memoria del telefono
- Memoria del telfono (non visibile alle altre applicazioni)
+ Memoria del telefono (non visibile alle altre applicazioni)
Compagnia
Posizione lavorativa
Sito web
@@ -17,11 +17,14 @@
Richiedi le permissioni necessarie
Crea un nuovo contatto
Aggiungi ad un contatto esistente
+ È necessario impostare quest\'app come predefinita per utilizzare i numeri bloccati.
+ Imposta come predefinita
Nessun contatto trovato
Nessun contatto trovato con un\'email
Nessun contatto trovato con un numero di telefono
+ Nessuna chiamata recente trovata
Nuovo contatto
Modifica contatto
@@ -52,11 +55,12 @@
Prima il nome poi il cognome
Mostra i numeri di telefono nella schermata principale
Mostra le anteprime dei contatti
+ Mostra il pulante per la tastiera nello schermo principale
Al click sul contatto
Chiama contatto
Visualizza i dettagli del contatto
Gestisci i campi mostrati
- Prova a filetrare i contatti duplicati
+ Prova a filtrare i contatti duplicati
Gestisci le schede mostrate
Contatti
Preferiti
@@ -107,6 +111,16 @@
Tastiera
Aggiungi numero ai contatti
+
+ Compositore
+ Chiamata in corso
+ Chiamata in arrivo
+ Chiamata in arrivo da…
+ Chiamata in corso
+ Disconnesso
+ Rifiuta
+ Rispondi
+
Seleziona i campi da mostrare
Prefisso
@@ -122,6 +136,14 @@
Provenienza del contatto
Messaggistica istantanea (IM)
+
+ Gestisci i numeri bloccati
+ Non si sta blocccando alcun numero.
+ Aggiungi un numero da bloccare
+ Blocca numero
+ Blocca numeri
+ Numeri bloccati
+
Voglio cambiare i campi visibili ai contatti. Come posso fare?
Puoi farlo andando in Impostazioni -> Gestisci i campi mostrati. Qui puoi selezionare i campi che saranno visibili. Alcuni sono anche disabilitati in maniera predefinita, quindi potresti trovare qualche nuovo campo.
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index d22b12f6..fec96b80 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -17,11 +17,14 @@
Request the required permissions
新しい連絡先を作成
既存の連絡先に追加
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
連絡先が見つかりません
メールアドレスが登録された連絡先が見つかりません
電話番号が登録された連絡先が見つかりません
+ 通話履歴はありません
新しい連絡先
連絡先を編集
@@ -52,6 +55,7 @@
姓を先に表示
メイン画面に電話番号を表示
連絡先のサムネイルを表示
+ メイン画面にダイヤルパッドを表示
連絡先をタップ
連絡先に発信
連絡先の詳細を表示
@@ -60,7 +64,7 @@
表示するタブを管理
連絡先
お気に入り
- Recent calls
+ 通話履歴
発信する前に確認ダイアログを表示する
電話番号が登録された連絡先のみ表示する
@@ -107,6 +111,16 @@
ダイヤルパッド
連絡先に番号を追加
+
+ 電話
+ 発信中
+ 着信中
+ 着信中…
+ 通話中
+ 切断されました
+ 拒否
+ 応答
+
表示する項目を選択
敬称(名前の前)
@@ -122,6 +136,14 @@
インポート元
Instant messaging (IM)
+
+ ブロックした番号を管理
+ まだ誰もブロックしていません.
+ ブロックする番号を追加
+ Block number
+ Block numbers
+ Blocked numbers
+
連絡先に表示される項目(フィールド)を変更することはできますか?
可能です。[設定] -> [連絡先に表示するフィールドを管理] から、表示されるフィールドを選択することができます。これらの中にはデフォルトで無効になっているものもあるので、あなたは新しいものを見つけるかもしれません。
diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml
index 12228256..38dc1ab2 100644
--- a/app/src/main/res/values-ko-rKR/strings.xml
+++ b/app/src/main/res/values-ko-rKR/strings.xml
@@ -17,11 +17,14 @@
Request the required permissions
Create new contact
Add to an existing contact
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
No contacts found
No contacts with emails have been found
No contacts with phone numbers have been found
+ No recent calls found
새로운 연락처
연락처 수정
@@ -52,6 +55,7 @@
성을 먼저 표시
메인 스크린에 전화번호 표시
Show contact thumbnails
+ Show a dialpad button on the main screen
On contact click
Call contact
View contact details
@@ -107,6 +111,16 @@
Dialpad
Add number to contact
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Select fields to show
Prefix
@@ -122,6 +136,14 @@
Contact source
Instant messaging (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
I want to change what fields are visible at contacts. Can I do it?
Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there.
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index e29dad0f..471adfb6 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -17,11 +17,14 @@
Request the required permissions
Create new contact
Add to an existing contact
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
No contacts found
No contacts with emails have been found
No contacts with phone numbers have been found
+ No recent calls found
Naujas kontaktas
Redaguoti kontaktą
@@ -52,6 +55,7 @@
Pavardė rodoma pirma
Rodyti telefono numerius pagrindiniame programos ekrane
Rodyti kontaktų miniatiūras
+ Show a dialpad button on the main screen
Ant kontakto paspaudimo
Skambinti kontaktui
Žiūrėti kontakto detales
@@ -107,6 +111,16 @@
Dialpad
Add number to contact
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Pasirinkti rodomus laukelius
Priešdėlis
@@ -122,6 +136,14 @@
Kontakto šaltinis
Instant messaging (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
Noriu pakeisti, kokie laukai yra matomi kontaktuose. Ar galiu tai padaryti?
Taip, viskas, ką jums reikia padaryti, tai eiti į Nustatymai -> Tvarkyti rodomus kontaktų laukus. Čia galite pasirinkti, kurie laukai turėtų būti matomi. Kai kurie iš jų netgi yra išjungiami pagal numatytuosius nustatymus, todėl ten galite rasti naujų.
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 8d3ee2c4..d185d0fd 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -17,11 +17,14 @@
Pedir a permissão necessária
Criar novo contacto
Adicionar a contacto existente
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
- No contacts found
- No contacts with emails have been found
- No contacts with phone numbers have been found
+ Não existem contactos
+ Não existem contactos com endereço de e-mail
+ Não existem contactos com número de telefone
+ Não existem chamadas recentes
Novo contacto
Editar contacto
@@ -33,7 +36,7 @@
Alcunha
- Não há grupos
+ Não existem grupos
Criar um novo grupo
Remover do grupo
Este grupo está vazio
@@ -52,17 +55,18 @@
Ordenar por apelido
Mostrar número de telefone no ecrã principal
Mostrar miniatura do contacto
+ Mostrar botão Marcador no ecrã principal
Ao tocar no contacto
Ligar
Ver detalhes
- Gerir campos a exibir
+ Gerir campos a mostrar
Tentar filtrar contactos duplicados
Gerir separadores a exibir
Contactos
Favoritos
Chamadas recentes
Mostrar diálogo para confirmar a chamada
- Show only contacts with phone numbers
+ Mostrar apenas contactos com número de telefone
E-mail
@@ -104,9 +108,19 @@
Nome do ficheiro (sem .vcf)
- Teclado
+ Marcador
Adicionar número a um contacto
+
+ Marcador
+ A chamar
+ Chamada recebida
+ Chamada recebida de…
+ Chamada efetuada
+ Desligada
+ Recusar
+ Atender
+
Selecione os campos a mostrar
Prefixo
@@ -122,6 +136,14 @@
Origem do contacto
Mensagem instantânea (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
I want to change what fields are visible at contacts. Can I do it?
Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there.
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 6d5c8629..7bab0c68 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -17,11 +17,14 @@
Запрос необходимых разрешений
Создать новый контакт
Добавить к существующему контакту
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
Контакты не найдены
Контакты с адресами электронной почты не найдены
Контакты с номерами телефонов не найдены
+ No recent calls found
Новый контакт
Редактировать контакт
@@ -52,6 +55,7 @@
Показывать сначала фамилию
Показывать номера телефонов на главном экране
Показывать фото контакта
+ Show a dialpad button on the main screen
При нажатии на контакт
Позвонить контакту
Просмотреть подробности о контакте
@@ -107,6 +111,16 @@
Набор номера
Добавить номер к контакту
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Выберите отображаемые поля
Префикс
@@ -122,6 +136,14 @@
Источник контакта
Мессенджеры (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
Я хочу изменить поля, отображаемые у контактов. Могу ли я это сделать?
Да, всё, что вам нужно сделать, это зайти в \"Настройки\" -> \"Управление отображаемыми полями контактов\". Там вы сможете выбрать, какие поля должны быть видимы. Некоторые из них даже отключены по умолчанию, так что вы можете найти некоторые новые.
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 77d6cfe2..614febb0 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -17,11 +17,14 @@
Vyžiadať potrebné oprávnenia
Vytvoriť nový kontakt
Pridať k existujúcemu kontaktu
+ Pre použitie blokovania čísel musíte nastaviť aplikáciu ako predvolenú pre správu hovorov.
+ Nastaviť ako predvolenú
Nenašli sa žiadne kontakty
Nenašli sa žiadne kontakty s emailami
Nenašli sa žiadne kontakty s telefónnymi číslami
+ Nenašli sa žiadne posledné hovory
Nový kontakt
Upraviť kontakt
@@ -52,6 +55,7 @@
Začať meno priezviskom
Zobraziť telefónne čísla na hlavnej obrazovke
Zobraziť obrázky kontaktov
+ Zobraziť tlačidlo pre číselník na hlavnej obrazovke
Po kliknutí na kontakt
Zavolať kontakt
Zobraziť údaje kontaktu
@@ -107,6 +111,16 @@
Číselník
Pridať číslo kontaktu
+
+ Telefón
+ Vytáča sa
+ Prichádzajúci hovor
+ Prichádzajúci hovor od…
+ Prebiehajúci hovor
+ Ukončený hovor
+ Odmietnuť
+ Prijať
+
Zvoľte polia na zobrazenie
Titul pred menom
@@ -122,6 +136,14 @@
Zdroje kontaktov
Rýchle správy (IM)
+
+ Spravovať blokované čísla
+ Neblokujete nikoho.
+ Pridať blokované číslo
+ Blokovať číslo
+ Blokovať čísla
+ Blokované čísla
+
Chcem upraviť viditeľné polia kontaktov. Dá sa to?
Áno, stačí ísť do Nastavenia -> Spravovať zobrazené polia kontaktov. Tam si viete zvoliť, ktoré polia majú byť viditeľné. Niektoré sú v predvolenom stave vypnuté, čiže tam môžete objaviť aj nové.
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 53652727..43dba3f3 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -17,11 +17,14 @@
Begär de behörigheter som krävs
Skapa ny kontakt
Lägg till i en befintlig kontakt
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
Inga kontakter hittades
Inga kontakter med e-postadresser hittades
Inga kontakter med telefonnummer hittades
+ No recent calls found
Ny kontakt
Redigera kontakt
@@ -52,6 +55,7 @@
Visa efternamn först
Visa telefonnummer i huvudvyn
Visa kontaktminiatyrer
+ Show a dialpad button on the main screen
Vid kontakttryckning
Ring kontakt
Visa kontaktuppgifter
@@ -107,6 +111,16 @@
Knappsats
Lägg till nummer i kontakt
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Välj vilka fält som ska visas
Prefix
@@ -122,6 +136,14 @@
Kontaktkälla
Snabbmeddelanden (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
I want to change what fields are visible at contacts. Can I do it?
Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there.
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index f94da90e..15d9bf3d 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -17,11 +17,14 @@
Gerekli izinleri iste
Yeni kişi oluştur
Mevcut bir kişiye ekle
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
Kişi bulunamadı
E-posta ile hiç bağlantı bulunamadı
Telefon numaralarını içeren kişi bulunamadı
+ No recent calls found
Yeni kişi
Kişiyi düzenle
@@ -52,6 +55,7 @@
Soyadı ile başla
Ana ekranda telefon numaralarını göster
Kişi küçük resimlerini göster
+ Show a dialpad button on the main screen
Kişi tıklandığında
Kişiyi ara
Kişi bilgilerini göster
@@ -107,6 +111,16 @@
Tuş takımı
Kişiye numara ekle
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Görüntülenecek alanları seç
Önek
@@ -122,6 +136,14 @@
Kişi kaynağı
Anlık mesajlaşma (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
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.
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 3f730034..9be3d686 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -17,11 +17,14 @@
請求必要的權限
建立新聯絡人
添加至已存在的聯絡人
+ 你必須將這應用程式設為預設的撥號程式來使用黑名單。
+ 設為預設
- No contacts found
- No contacts with emails have been found
- No contacts with phone numbers have been found
+ 未發現聯絡人
+ 未發現含有電子信箱的聯絡人
+ 未發現含有電話號碼的聯絡人
+ 未發現通話紀錄
新聯絡人
編輯聯絡人
@@ -52,6 +55,7 @@
姓氏在前
主畫面顯示電話號碼
顯示聯絡人縮圖
+ 在主畫面顯示撥號按鈕
點擊聯絡人
打電話給聯絡人
顯示聯絡人資料
@@ -62,7 +66,7 @@
我的最愛
通話紀錄
開始通話前顯示通話確認框
- Show only contacts with phone numbers
+ 只顯示含有電話話碼的聯絡人
電子信箱
@@ -107,6 +111,16 @@
撥號畫面
添加號碼至通訊錄
+
+ 撥號器
+ 撥號中
+ 來電
+ 通話來自於…
+ 持續通話
+ 未接電話
+ 掛斷電話
+ 回撥
+
選擇要顯示的欄位
前缀
@@ -122,6 +136,14 @@
聯絡人來源
即時通訊 (IM)
+
+ 管理黑名單
+ 你沒有封鎖任何人
+ 添加封鎖的號碼
+ 封鎖號碼
+ 封鎖號碼
+ 黑名單
+
我想要更改在通訊錄會看到哪些欄位。我能這麼做嗎?
可以,你要做的是到[設定] -> [管理顯示的聯絡人欄位]。在那裡,你可以選擇應該看到什麼欄位。其中有些甚至預設是關閉的,所以你可能會在那裡發現一些新的。
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index d781ec5f..00000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index b0496177..e5a33be5 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -7,5 +7,6 @@
56dp
68dp
60dp
+
34sp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8bf9c12c..af52d0b9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,11 +17,14 @@
Request the required permissions
Create new contact
Add to an existing contact
+ You have to make this app the default dialer app to make use of blocked numbers.
+ Set to default
No contacts found
No contacts with emails have been found
No contacts with phone numbers have been found
+ No recent calls found
New contact
Edit contact
@@ -52,6 +55,7 @@
Start name with surname
Show phone numbers on the main screen
Show contact thumbnails
+ Show a dialpad button on the main screen
On contact click
Call contact
View contact details
@@ -107,6 +111,16 @@
Dialpad
Add number to contact
+
+ Dialer
+ Calling
+ Incoming call
+ Incoming call from…
+ Ongoing call
+ Disconnected
+ Decline
+ Answer
+
Select fields to show
Prefix
@@ -122,6 +136,14 @@
Contact source
Instant messaging (IM)
+
+ Manage blocked numbers
+ You are not blocking anyone.
+ Add a blocked number
+ Block number
+ Block numbers
+ Blocked numbers
+
I want to change what fields are visible at contacts. Can I do it?
Yes, all you have to do is go in Settings -> Manage shown contact fields. There you can select what fields should be visible. Some of them are even disabled by default, so you might find some new ones there.
diff --git a/build.gradle b/build.gradle
index 4ddb226d..85c5b5af 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.3.0'
+ ext.kotlin_version = '1.3.10'
repositories {
google()
diff --git a/keystore.properties_sample b/keystore.properties_sample
new file mode 100644
index 00000000..569edd73
--- /dev/null
+++ b/keystore.properties_sample
@@ -0,0 +1,4 @@
+storePassword=123456
+keyPassword=abcdef
+keyAlias=myAlias
+storeFile=../keystore.jks
diff --git a/signing.properties_sample b/signing.properties_sample
deleted file mode 100644
index cf8e2396..00000000
--- a/signing.properties_sample
+++ /dev/null
@@ -1,3 +0,0 @@
-STORE_FILE=/path/to/your.keystore
-KEY_ALIAS=projectkeyalias
-PASSWORD=yourpass