Merge pull request #19 from SimpleMobileTools/master

upd
This commit is contained in:
solokot 2018-12-05 08:41:33 +03:00 committed by GitHub
commit bba01c770a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 2149 additions and 1009 deletions

7
.gitignore vendored
View File

@ -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

View File

@ -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)*
----------------------------

View File

@ -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
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.contacts'><img src='http://simplemobiletools.github.io/assets/public/google-play.png' alt='Get it on Google Play' height='45' /></a>
<a href='https://f-droid.org/packages/com.simplemobiletools.contacts'><img src='http://simplemobiletools.github.io/assets/public/f-droid.png' alt='Get it on F-Droid' height='45' /></a>
<a href='https://play.google.com/store/apps/details?id=com.simplemobiletools.contacts.pro'><img src='http://simplemobiletools.github.io/assets/public/google-play.png' alt='Get it on Google Play' height='45' /></a>
<a href='https://f-droid.org/packages/com.simplemobiletools.contacts.pro'><img src='http://simplemobiletools.github.io/assets/public/f-droid.png' alt='Get it on F-Droid' height='45' /></a>
<div style="display:flex;">
<img alt="App image" src="fastlane/metadata/android/en-US/images/phoneScreenshots/app.png" width="30%">

View File

@ -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"
}

View File

@ -6,6 +6,7 @@
android:installLocation="auto">
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@ -13,6 +14,11 @@
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
<uses-feature
android:name="android.hardware.telephony"
@ -53,6 +59,15 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="vnd.android.cursor.dir/calls"/>
</intent-filter>
</activity>
<activity
@ -188,10 +203,42 @@
android:label="@string/frequently_asked_questions"
android:parentActivityName="com.simplemobiletools.commons.activities.AboutActivity"/>
<activity
android:name=".activities.ManageBlockedNumbersActivity"
android:label="@string/blocked_numbers"
android:parentActivityName=".activities.SettingsActivity"/>
<activity
android:name=".activities.DialpadActivity"
android:label="@string/dialpad"
android:parentActivityName=".activities.MainActivity"/>
android:parentActivityName=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="tel"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".activities.DialerActivity"
android:label="@string/dialer"
android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.CALL"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="tel"/>
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"

View File

@ -1,16 +1,11 @@
package com.simplemobiletools.contacts.pro
import android.app.Application
import com.facebook.stetho.Stetho
import com.simplemobiletools.commons.extensions.checkUseEnglish
class App : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this)
}
checkUseEnglish()
}
}

View File

@ -45,6 +45,10 @@ abstract class ContactActivity : SimpleActivity() {
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
if (isDestroyed) {
return
}
Glide.with(this)
.load(bitmap ?: path)
.transition(DrawableTransitionOptions.withCrossFade())

View File

@ -0,0 +1,66 @@
package com.simplemobiletools.contacts.pro.activities
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.telecom.PhoneAccount
import android.telecom.TelecomManager
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.extensions.isDefaultDialer
import com.simplemobiletools.contacts.pro.extensions.telecomManager
import com.simplemobiletools.contacts.pro.helpers.REQUEST_CODE_SET_DEFAULT_DIALER
@TargetApi(Build.VERSION_CODES.M)
class DialerActivity : SimpleActivity() {
private var callNumber: Uri? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action == Intent.ACTION_CALL && intent.data != null) {
callNumber = intent.data
// make sure Simple Contacts is the default Phone app before initiating an outgoing call
if (!isDefaultDialer()) {
launchSetDefaultDialerIntent()
} else {
initOutgoingCall()
}
} else {
toast(R.string.unknown_error_occurred)
finish()
}
}
@SuppressLint("MissingPermission")
private fun initOutgoingCall() {
try {
Bundle().apply {
putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL))
putBoolean(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, false)
putBoolean(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false)
telecomManager.placeCall(callNumber, this)
finish()
}
} catch (e: Exception) {
showErrorToast(e)
finish()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (requestCode == REQUEST_CODE_SET_DEFAULT_DIALER) {
if (!isDefaultDialer()) {
finish()
} else {
initOutgoingCall()
}
}
}
}

View File

@ -1,27 +1,36 @@
package com.simplemobiletools.contacts.pro.activities
import android.annotation.TargetApi
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Telephony.Sms.Intents.SECRET_CODE_ACTION
import android.telephony.TelephonyManager
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.View
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.contacts.pro.R
import com.simplemobiletools.contacts.pro.adapters.ContactsAdapter
import com.simplemobiletools.contacts.pro.dialogs.CallConfirmationDialog
import com.simplemobiletools.contacts.pro.extensions.callContact
import com.simplemobiletools.contacts.pro.extensions.config
import com.simplemobiletools.contacts.pro.extensions.isDefaultDialer
import com.simplemobiletools.contacts.pro.extensions.startCallIntent
import com.simplemobiletools.contacts.pro.helpers.ContactsHelper
import com.simplemobiletools.contacts.pro.helpers.KEY_PHONE
import com.simplemobiletools.contacts.pro.helpers.LOCATION_DIALPAD
import com.simplemobiletools.contacts.pro.helpers.REQUEST_CODE_SET_DEFAULT_DIALER
import com.simplemobiletools.contacts.pro.models.Contact
import kotlinx.android.synthetic.main.activity_dialpad.*
class DialpadActivity : SimpleActivity() {
var contacts = ArrayList<Contact>()
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<Contact>) {
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<Contact>
@ -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()) {

View File

@ -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<ContentValues>("data")
val data = intent.extras!!.getParcelableArrayList<ContentValues>("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

View File

@ -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<Contact>) {
Thread {
removeContactsFromGroup(contacts, group.id)
if (groupContacts.size == 0) {
removeContactsFromGroup(contacts, group.id!!)
if (groupContacts.size == contacts.size) {
refreshContacts()
}
}.start()

View File

@ -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>) {
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 {

View File

@ -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<Release>().apply {
add(Release(10, R.string.release_10))

View File

@ -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()
}
}
}

View File

@ -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>
Contact.sorting = config.sorting
Contact.startWithSurname = config.startNameWithSurname
contacts.sort()
runOnUiThread {
updatePlaceholderVisibility(contacts)
SelectContactsAdapter(this, contacts, ArrayList(), false, select_contact_list) {

View File

@ -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 {

View File

@ -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<Any>)?.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)
}
}

View File

@ -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
}
}
}

View File

@ -34,6 +34,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList<Cont
private val location: Int, private val removeListener: RemoveFromGroupListener?, recyclerView: MyRecyclerView,
fastScroller: FastScroller, highlightText: String = "", itemClick: (Any) -> 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<Cont
val positions = getSelectedItemPositions()
contactItems.removeAll(contactsToRemove)
ContactsHelper(activity).deleteContacts(contactsToRemove)
Thread {
ContactsHelper(activity).deleteContacts(contactsToRemove)
}.start()
if (contactItems.isEmpty()) {
refreshListener?.refreshContacts(ALL_TABS_MASK)
finishActMode()
@ -195,19 +199,23 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList<Cont
}
private fun addToGroup() {
val selectedContacts = getSelectedItems()
val NEW_GROUP_ID = -1
val items = ArrayList<RadioItem>()
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<RadioItem>) {
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()

View File

@ -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<Group>, 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<Group>, 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<Group>) {
groups = newItems
@ -92,7 +92,9 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList<Group>, val
private fun askConfirmDelete() {
ConfirmationDialog(activity) {
deleteGroups()
Thread {
deleteGroups()
}.start()
}
}
@ -101,28 +103,30 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList<Group>, val
return
}
val groupsToRemove = groups.filter { selectedKeys.contains(it.id.toInt()) } as ArrayList<Group>
val groupsToRemove = groups.filter { selectedKeys.contains(it.id!!.toInt()) } as ArrayList<Group>
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())

View File

@ -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<BlockedNumber>, 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<BlockedNumber>
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<BlockedNumber>(selectedKeys.size)
val positions = getSelectedItemPositions()
getSelectedItems().forEach {
removeBlockedNumbers.add(it)
activity.deleteBlockedNumber(it.number)
}
blockedNumbers.removeAll(removeBlockedNumbers)
removeSelectedItems(positions)
if (blockedNumbers.isEmpty()) {
listener?.refreshItems()
}
}
}

View File

@ -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<Re
override fun getActionMenuId() = R.menu.cab_recent_calls
override fun prepareActionMode(menu: Menu) {}
override fun prepareActionMode(menu: Menu) {
menu.apply {
findItem(R.id.cab_block_number).isVisible = isNougatPlus()
findItem(R.id.cab_block_number).title = activity.getString(if (isOneItemSelected()) R.string.block_number else R.string.block_numbers)
}
}
override fun actionItemPressed(id: Int) {
if (selectedKeys.isEmpty()) {
@ -38,6 +45,7 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList<Re
when (id) {
R.id.cab_select_all -> selectAll()
R.id.cab_delete -> askConfirmDelete()
R.id.cab_block_number -> blockNumber()
}
}
@ -92,6 +100,19 @@ class RecentCallsAdapter(activity: SimpleActivity, var recentCalls: ArrayList<Re
}
}
private fun blockNumber() {
Thread {
getSelectedItems().forEach {
activity.addBlockedNumber(it.number)
}
refreshListener?.refreshContacts(RECENTS_TAB_MASK)
activity.runOnUiThread {
finishActMode()
}
}.start()
}
private fun getSelectedItems() = recentCalls.filter { selectedKeys.contains(it.id) } as ArrayList<RecentCall>
private fun setupView(view: View, recentCall: RecentCall) {

View File

@ -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)
}
}
}
}
}

View File

@ -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()
}
}
}
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}
}

View File

@ -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<Contact>, val selectContacts: ArrayList<Contact>? = null,
class SelectContactsDialog(val activity: SimpleActivity, initialContacts: ArrayList<Contact>, selectContacts: ArrayList<Contact>? = null,
val callback: (addedContacts: ArrayList<Contact>, removedContacts: ArrayList<Contact>) -> Unit) {
private var view = activity.layoutInflater.inflate(R.layout.layout_select_contact, null)
private var initiallySelectedContacts = ArrayList<Contact>()
@ -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)

View File

@ -17,11 +17,20 @@ import java.util.*
class SelectGroupsDialog(val activity: SimpleActivity, val selectedGroups: ArrayList<Group>, val callback: (newGroups: ArrayList<Group>) -> Unit) {
private val view = activity.layoutInflater.inflate(R.layout.dialog_select_groups, null) as ViewGroup
private val checkboxes = ArrayList<MyAppCompatCheckbox>()
private val groups = ContactsHelper(activity).getStoredGroups()
private var groups = ArrayList<Group>()
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)
}

View File

@ -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<Contact>) {
val file = getTempFile()
@ -117,96 +106,6 @@ fun BaseSimpleActivity.shareContacts(contacts: ArrayList<Contact>) {
}
}
fun BaseSimpleActivity.sendSMSToContacts(contacts: ArrayList<Contact>) {
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<Contact>) {
val emails = ArrayList<String>()
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<Contact>, 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<Contact>, 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<String> {
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<String>
sourceNames.removeAll(config.ignoredContactSources)
return sourceNames
}
fun SimpleActivity.contactClicked(contact: Contact) {
when (config.onContactClick) {
ON_CLICK_CALL_CONTACT -> callContact(contact)

View File

@ -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<Contact>) {
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<Contact>) {
val emails = ArrayList<String>()
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<Contact>, groupId: Long) {
val publicContacts = contacts.filter { !it.isPrivate() }.toMutableList() as ArrayList<Contact>
val privateContacts = contacts.filter { it.isPrivate() }.toMutableList() as ArrayList<Contact>
if (publicContacts.isNotEmpty()) {
ContactsHelper(this).addContactsToGroup(publicContacts, groupId)
}
if (privateContacts.isNotEmpty()) {
LocalContactsHelper(this).addContactsToGroup(privateContacts, groupId)
}
}
fun Context.removeContactsFromGroup(contacts: ArrayList<Contact>, groupId: Long) {
val publicContacts = contacts.filter { !it.isPrivate() }.toMutableList() as ArrayList<Contact>
val privateContacts = contacts.filter { it.isPrivate() }.toMutableList() as ArrayList<Contact>
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<String> {
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<String>
sourceNames.removeAll(config.ignoredContactSources)
return sourceNames
}
@TargetApi(Build.VERSION_CODES.N)
fun Context.getBlockedNumbers(): ArrayList<BlockedNumber> {
val blockedNumbers = ArrayList<BlockedNumber>()
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

View File

@ -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)

View File

@ -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<Contact>) {
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<Group>
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<Group>
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) {

View File

@ -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)

View File

@ -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()
}

View File

@ -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())

View File

@ -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<String>()
fun getContacts(callback: (ArrayList<Contact>) -> Unit) {
Thread {
val contacts = SparseArray<Contact>()
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<Contact>(contactsSize)
val resultContacts = ArrayList<Contact>(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<Contact>
@ -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<ContactSource>()
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<Contact>) {
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<Group>()
val organization = Organization("", "")
val websites = ArrayList<String>()
val cleanNumbers = ArrayList<PhoneNumber>()
val ims = ArrayList<IM>()
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<Group>, contactId: Int? = null): SparseArray<ArrayList<Group>> {
val groups = SparseArray<ArrayList<Group>>()
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<Group> {
fun getStoredGroups(callback: (ArrayList<Group>) -> Unit) {
Thread {
val groups = getStoredGroupsSync()
Handler(Looper.getMainLooper()).post {
callback(groups)
}
}.start()
}
fun getStoredGroupsSync(): ArrayList<Group> {
val groups = getDeviceStoredGroups()
groups.addAll(activity.dbHelper.getGroups())
groups.addAll(context.groupsDB.getGroups())
return groups
}
fun getDeviceStoredGroups(): ArrayList<Group> {
val groups = ArrayList<Group>()
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<ContentProviderOperation>()
@ -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<String>): 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<PhoneNumber>()
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<ContactSource> {
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<ContactSource> {
val sources = LinkedHashSet<ContactSource>()
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<ContentProviderOperation>): ArrayList<ContentProviderOperation> {
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<ContentProviderResult>
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<Contact>) {
toggleLocalFavorites(contacts, true)
if (activity.hasContactPermissions()) {
toggleFavorites(contacts, true)
}
Thread {
toggleLocalFavorites(contacts, true)
if (context.hasContactPermissions()) {
toggleFavorites(contacts, true)
}
}.start()
}
fun removeFavorites(contacts: ArrayList<Contact>) {
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<Contact>, addToFavorites: Boolean) {
try {
val operations = ArrayList<ContentProviderOperation>()
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<Contact>, 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<Contact>) {
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<ContentProviderOperation>()
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<ContentProviderOperation>()
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<RecentCall>) -> Unit) {
Thread {
val calls = ArrayList<RecentCall>()
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()
}

View File

@ -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<List<Long>>() {}.type
private val stringType = object : TypeToken<List<String>>() {}.type
private val numberType = object : TypeToken<List<PhoneNumber>>() {}.type
private val emailType = object : TypeToken<List<Email>>() {}.type
private val addressType = object : TypeToken<List<Address>>() {}.type
private val eventType = object : TypeToken<List<Event>>() {}.type
private val imType = object : TypeToken<List<IM>>() {}.type
@TypeConverter
fun jsonToStringList(value: String) = gson.fromJson<ArrayList<String>>(value, stringType)
@TypeConverter
fun stringListToJson(list: ArrayList<String>) = gson.toJson(list)
@TypeConverter
fun jsonToLongList(value: String) = gson.fromJson<ArrayList<Long>>(value, longType)
@TypeConverter
fun longListToJson(list: ArrayList<Long>) = gson.toJson(list)
@TypeConverter
fun jsonToPhoneNumberList(value: String) = gson.fromJson<ArrayList<PhoneNumber>>(value, numberType)
@TypeConverter
fun phoneNumberListToJson(list: ArrayList<PhoneNumber>) = gson.toJson(list)
@TypeConverter
fun jsonToEmailList(value: String) = gson.fromJson<ArrayList<Email>>(value, emailType)
@TypeConverter
fun emailListToJson(list: ArrayList<Email>) = gson.toJson(list)
@TypeConverter
fun jsonToAddressList(value: String) = gson.fromJson<ArrayList<Address>>(value, addressType)
@TypeConverter
fun addressListToJson(list: ArrayList<Address>) = gson.toJson(list)
@TypeConverter
fun jsonToEventList(value: String) = gson.fromJson<ArrayList<Event>>(value, eventType)
@TypeConverter
fun eventListToJson(list: ArrayList<Event>) = gson.toJson(list)
@TypeConverter
fun jsonToIMsList(value: String) = gson.fromJson<ArrayList<IM>>(value, imType)
@TypeConverter
fun IMsListToJson(list: ArrayList<IM>) = gson.toJson(list)
}

View File

@ -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<String>) {
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<String>, 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<String>) {
val args = TextUtils.join(", ", ids)
val selection = "$GROUPS_TABLE_NAME.$COL_ID IN ($args)"
mDb.delete(GROUPS_TABLE_NAME, selection, null)
}
fun getGroups(): ArrayList<Group> {
val groups = ArrayList<Group>()
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<Contact>, groupId: Long) {
contacts.forEach {
val currentGroupIds = it.groups.map { it.id } as ArrayList<Long>
currentGroupIds.add(groupId)
updateContactGroups(it, currentGroupIds)
}
}
fun removeContactsFromGroup(contacts: ArrayList<Contact>, groupId: Long) {
contacts.forEach {
val currentGroupIds = it.groups.map { it.id } as ArrayList<Long>
currentGroupIds.remove(groupId)
updateContactGroups(it, currentGroupIds)
}
}
private fun updateContactGroups(contact: Contact, groupIds: ArrayList<Long>) {
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<Long>): ContentValues {
return ContentValues().apply {
put(COL_GROUPS, gson.toJson(groupIds))
}
}
fun getContacts(activity: Activity, selection: String? = null, selectionArgs: Array<String>? = null): ArrayList<Contact> {
val storedGroups = ContactsHelper(activity).getStoredGroups()
val filterDuplicates = activity.config.filterDuplicates
val contacts = ArrayList<Contact>()
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<List<PhoneNumber>>() {}.type
val emailsToken = object : TypeToken<List<Email>>() {}.type
val addressesToken = object : TypeToken<List<Address>>() {}.type
val IMsToken = object : TypeToken<List<IM>>() {}.type
val eventsToken = object : TypeToken<List<Event>>() {}.type
val groupIdsToken = object : TypeToken<List<Long>>() {}.type
val websitesToken = object : TypeToken<List<String>>() {}.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<ArrayList<PhoneNumber>>(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<ArrayList<Email>>(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<ArrayList<Address>>(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<ArrayList<IM>>(IMsJson, IMsToken) ?: ArrayList(1)
val eventsJson = cursor.getStringValue(COL_EVENTS)
val events = if (eventsJson == "[]") ArrayList() else gson.fromJson<ArrayList<Event>>(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<ArrayList<Long>>(groupIdsJson, groupIdsToken)
?: ArrayList(1)
val groups = storedGroups.filter { groupIds.contains(it.id) } as ArrayList<Group>
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<ArrayList<String>>(websitesJson, websitesToken)
?: ArrayList(1)
val cleanPhoneNumbers = ArrayList<PhoneNumber>()
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()
}
}

View File

@ -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<Contact>
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<Contact>, 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<Contact>, 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<Int>) {
ids.forEach {
context.contactsDB.deleteContactId(it)
}
}
fun toggleFavorites(ids: Array<Int>, 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<Group>
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<Long>
company = contact.organization.company
jobPosition = contact.organization.jobPosition
websites = contact.websites
IMs = contact.IMs
}
}
}

View File

@ -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('%', '=')
}

View File

@ -152,7 +152,7 @@ class VcfExporter {
contactsExported++
}
Ezvcard.write(cards).go(file)
Ezvcard.write(cards).go(it)
} catch (e: Exception) {
activity.showErrorToast(e)
}

View File

@ -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<Email>()
@ -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<PhoneNumber>()
val IMs = ArrayList<IM>()
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<Group> {
@ -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)
}
}
}

View File

@ -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<LocalContact>
@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)
}

View File

@ -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<Group>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(group: Group): Long
@Query("DELETE FROM groups WHERE id = :id")
fun deleteGroupId(id: Long)
}

View File

@ -0,0 +1,3 @@
package com.simplemobiletools.contacts.pro.models
data class BlockedNumber(val id: Long, val number: String, val normalizedNumber: String)

View File

@ -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<PhoneNumber>, var emails: ArrayList<Email>, var addresses: ArrayList<Address>,
var events: ArrayList<Event>, var source: String, var starred: Int, val contactId: Int, val thumbnailUri: String, var photo: Bitmap?, var notes: String,
var groups: ArrayList<Group>, var organization: Organization, var websites: ArrayList<String>, var cleanPhoneNumbers: ArrayList<PhoneNumber>,
var IMs: ArrayList<IM>) :
var events: ArrayList<Event>, var source: String, var starred: Int, var contactId: Int, var thumbnailUri: String, var photo: Bitmap?, var notes: String,
var groups: ArrayList<Group>, var organization: Organization, var websites: ArrayList<String>, var IMs: ArrayList<IM>) :
Comparable<Contact> {
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
}

View File

@ -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
}

View File

@ -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<PhoneNumber>,
@ColumnInfo(name = "emails") var emails: ArrayList<Email>,
@ColumnInfo(name = "events") var events: ArrayList<Event>,
@ColumnInfo(name = "starred") var starred: Int,
@ColumnInfo(name = "addresses") var addresses: ArrayList<Address>,
@ColumnInfo(name = "notes") var notes: String,
@ColumnInfo(name = "groups") var groups: ArrayList<Long>,
@ColumnInfo(name = "company") var company: String,
@ColumnInfo(name = "job_position") var jobPosition: String,
@ColumnInfo(name = "websites") var websites: ArrayList<String>,
@ColumnInfo(name = "ims") var IMs: ArrayList<IM>) {
override fun equals(other: Any?) = id == (other as? LocalContact?)?.id
override fun hashCode() = id ?: 0
}

View File

@ -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?)

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -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">
<RelativeLayout
android:id="@+id/contact_holder"

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/manage_blocked_numbers_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyRecyclerView
android:id="@+id/manage_blocked_numbers_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/manage_blocked_numbers_placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:paddingLeft="@dimen/big_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingRight="@dimen/big_margin"
android:text="@string/not_blocking_anyone"
android:textSize="@dimen/bigger_text_size"
android:visibility="gone"/>
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/manage_blocked_numbers_placeholder_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/manage_blocked_numbers_placeholder"
android:layout_centerHorizontal="true"
android:background="?attr/selectableItemBackground"
android:gravity="center"
android:padding="@dimen/activity_margin"
android:text="@string/add_a_blocked_number"
android:textSize="@dimen/bigger_text_size"
android:visibility="gone"/>
</RelativeLayout>

View File

@ -78,6 +78,28 @@
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_manage_blocked_numbers_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/medium_margin"
android:background="?attr/selectableItemBackground"
android:paddingLeft="@dimen/normal_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingRight="@dimen/normal_margin"
android:paddingBottom="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_manage_blocked_numbers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:paddingStart="@dimen/medium_margin"
android:paddingLeft="@dimen/medium_margin"
android:text="@string/manage_blocked_numbers"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_use_english_holder"
android:layout_width="match_parent"
@ -294,6 +316,30 @@
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_show_dialpad_button_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/medium_margin"
android:background="?attr/selectableItemBackground"
android:paddingLeft="@dimen/normal_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingRight="@dimen/normal_margin"
android:paddingBottom="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MySwitchCompat
android:id="@+id/settings_show_dialpad_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:paddingStart="@dimen/medium_margin"
android:paddingLeft="@dimen/medium_margin"
android:text="@string/show_dialpad_button"
app:switchPadding="@dimen/medium_margin"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_on_contact_click_holder"
android:layout_width="match_parent"

View File

@ -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">
<RelativeLayout
android:id="@+id/contact_holder"
@ -98,6 +99,7 @@
android:layout_below="@+id/contact_photo"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_name_image"
android:background="?attr/selectableItemBackground"
android:lines="1"
android:maxLines="1"
android:paddingLeft="@dimen/small_margin"
@ -113,6 +115,7 @@
android:layout_below="@+id/contact_prefix"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_name_image"
android:background="?attr/selectableItemBackground"
android:lines="1"
android:maxLines="1"
android:paddingLeft="@dimen/small_margin"
@ -128,6 +131,7 @@
android:layout_below="@+id/contact_first_name"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_name_image"
android:background="?attr/selectableItemBackground"
android:lines="1"
android:maxLines="1"
android:paddingLeft="@dimen/small_margin"
@ -143,6 +147,7 @@
android:layout_below="@+id/contact_middle_name"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_name_image"
android:background="?attr/selectableItemBackground"
android:lines="1"
android:maxLines="1"
android:paddingLeft="@dimen/small_margin"
@ -158,6 +163,7 @@
android:layout_below="@+id/contact_surname"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_name_image"
android:background="?attr/selectableItemBackground"
android:lines="1"
android:maxLines="1"
android:paddingLeft="@dimen/small_margin"
@ -173,6 +179,7 @@
android:layout_below="@+id/contact_suffix"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_name_image"
android:background="?attr/selectableItemBackground"
android:lines="1"
android:maxLines="1"
android:paddingLeft="@dimen/small_margin"
@ -298,6 +305,7 @@
android:layout_below="@+id/contact_events_holder"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_notes_image"
android:background="?attr/selectableItemBackground"
android:lineSpacingExtra="@dimen/medium_margin"
android:paddingLeft="@dimen/small_margin"
android:paddingTop="@dimen/normal_margin"
@ -322,6 +330,7 @@
android:layout_below="@+id/contact_notes"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_organization_image"
android:background="?attr/selectableItemBackground"
android:lineSpacingExtra="@dimen/medium_margin"
android:paddingLeft="@dimen/small_margin"
android:paddingTop="@dimen/normal_margin"
@ -335,6 +344,7 @@
android:layout_below="@+id/contact_organization_company"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_organization_image"
android:background="?attr/selectableItemBackground"
android:lineSpacingExtra="@dimen/medium_margin"
android:paddingLeft="@dimen/small_margin"
android:paddingTop="@dimen/normal_margin"
@ -398,6 +408,7 @@
android:layout_below="@+id/contact_groups_holder"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/contact_name_image"
android:background="?attr/selectableItemBackground"
android:lines="1"
android:maxLines="1"
android:paddingLeft="@dimen/small_margin"

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/dialog_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/add_blocked_number_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_margin"
android:layout_marginTop="@dimen/small_margin"
android:layout_marginRight="@dimen/activity_margin"
android:inputType="phone"
android:textCursorDrawable="@null"
android:textSize="@dimen/bigger_text_size"/>
</RelativeLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/manage_blocked_number_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/selector"
android:padding="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/manage_blocked_number_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginTop="@dimen/medium_margin"/>
</RelativeLayout>

View File

@ -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"/>

View File

@ -11,4 +11,9 @@
android:icon="@drawable/ic_select_all"
android:title="@string/select_all"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/cab_block_number"
android:icon="@drawable/ic_block"
android:title="@string/block_number"
app:showAsAction="ifRoom"/>
</menu>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add_blocked_number"
android:icon="@drawable/ic_plus"
android:title="@string/add_a_blocked_number"
app:showAsAction="ifRoom"/>
</menu>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Lazım olan icazələri istə</string>
<string name="create_new_contact">Create new contact</string>
<string name="add_to_existing_contact">Add to an existing contact</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Yeni kontakt</string>
<string name="edit_contact">Redaktə et</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Ada soyaddan başla</string>
<string name="show_phone_numbers">Telefon nömrələrini əsas ekranda göstər</string>
<string name="show_contact_thumbnails">Kontakt görüntülərini göstər</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">Kontakta toxunduqda</string>
<string name="call_contact">Kontakta zəng et</string>
<string name="view_contact">Kontakt detallarına bax</string>
@ -107,6 +111,16 @@
<string name="dialpad">Dialpad</string>
<string name="add_number_to_contact">Add number to contact</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Göstərmək üçün sahəni seç</string>
<string name="prefix">Ön şəkilçi</string>
@ -122,6 +136,14 @@
<string name="contact_source">Kontakt kökü</string>
<string name="instant_messaging">Instant messaging (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">I want to change what fields are visible at contacts. Can I do it?</string>
<string name="faq_1_text">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.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Benötigte Berechtigungen anfordern</string>
<string name="create_new_contact">Neuen Kontakt erstellen</string>
<string name="add_to_existing_contact">Zu einem existierenden Kontakt hinzufügen</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_contacts_found">Keine Kontakte gefunden</string>
<string name="no_contacts_with_emails">Keine Kontakte mit E-Mailadressen gefunden</string>
<string name="no_contacts_with_phone_numbers">Keine Kontakte mit Telefonnummern gefunden</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Neuer Kontakt</string>
<string name="edit_contact">Kontakt bearbeiten</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Namen mit Nachnamen beginnen</string>
<string name="show_phone_numbers">Telefonnummern im Hauptmenü zeigen</string>
<string name="show_contact_thumbnails">Vorschaubilder der Kontakte zeigen</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">Beim Klicken auf den Kontakt</string>
<string name="call_contact">Kontakt anrufen</string>
<string name="view_contact">Kontaktdetails anzeigen</string>
@ -62,7 +66,7 @@
<string name="favorites">Favoriten</string>
<string name="recent_calls">Anrufliste</string>
<string name="show_call_confirmation_dialog">Bestätigungsdialog zeigen, bevor ein Anruf durchgeführt wird</string>
<string name="show_only_contacts_with_numbers">Show only contacts with phone numbers</string>
<string name="show_only_contacts_with_numbers">Nur Kontakte mit Telefonnummern anzeigen</string>
<!-- Emails -->
<string name="email">E-Mail</string>
@ -107,6 +111,16 @@
<string name="dialpad">Wählfeld</string>
<string name="add_number_to_contact">Nummer zu Kontakt hinzufügen</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Sichtbare Felder auswählen</string>
<string name="prefix">Titel</string>
@ -122,6 +136,14 @@
<string name="contact_source">Kontaktquelle</string>
<string name="instant_messaging">Instant messaging (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">Ich möchte die sichtbaren Kontaktfelder ändern. Kann ich das machen?</string>
<string name="faq_1_text">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.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Ζητούνται τα απαιτούμενα δικαιώματα</string>
<string name="create_new_contact">Δημιουργία νέας Επαφής</string>
<string name="add_to_existing_contact">Προσθήκη σε μια υπάρχουσα Επαφή</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Νέα επαφή</string>
<string name="edit_contact">Επεξεργασία επαφής</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Εμφάνιση πρώτα το επώνυμο</string>
<string name="show_phone_numbers">Εμφάνιση τηλεφωνικών αριθμών στην κύρια οθόνη</string>
<string name="show_contact_thumbnails">Εμφάνιση μικρογραφιών επαφής</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">Στην επιλογή επαφής</string>
<string name="call_contact">Κλήση επαφής</string>
<string name="view_contact">Εμφάνιση λεπτομερειών επαφής</string>
@ -107,6 +111,16 @@
<string name="dialpad">Πληκτρολόγιο</string>
<string name="add_number_to_contact">Προσθήκη αριθμού σε επαφή</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Επιλογή εμφάνισης πεδίων</string>
<string name="prefix">Πρόθεμα</string>
@ -122,6 +136,14 @@
<string name="contact_source">Προέλευση επαφής</string>
<string name="instant_messaging">Αμεσο μήνυμα (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">Θέλω να αλλάξω τα πεδία που θα είναι ορατά στις επαφές. Μπορώ να το κάνω?</string>
<string name="faq_1_text">Ναι, το μόνο που έχετε να κάνετε είναι να μεταβείτε στις Ρυθμίσεις -> Διαχείριση εμφανιζόμενων πεδίων επαφής. Εκεί μπορείτε να επιλέξετε ποια πεδία θα πρέπει να είναι ορατά. Κάποια από αυτά είναι ακόμη και απενεργοποιημένα από προεπιλογή, επομένως ίσως βρείτε κάποια νέα εκεί.</string>

View File

@ -2,8 +2,8 @@
<string name="app_name">Kontaktu sinpleak</string>
<string name="app_launcher_name">Kontaktuak</string>
<string name="address">Helbidea</string>
<string name="inserting">Txertatzen...</string>
<string name="updating">Eguneratzen...</string>
<string name="inserting">Txertatzen</string>
<string name="updating">Eguneratzen</string>
<string name="phone_storage">Telefono memoria</string>
<string name="phone_storage_hidden">Telefono memoria (beste aplikazioentzat ikustezina)</string>
<string name="company">Enpresa</string>
@ -15,6 +15,16 @@
<string name="send_email_to_group">Bidali emaila taldeari</string>
<string name="call_person">%s deitu</string>
<string name="request_the_required_permissions">Eskatu beharrezko baimenak</string>
<string name="create_new_contact">Create new contact</string>
<string name="add_to_existing_contact">Add to an existing contact</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Kontaktu berria</string>
<string name="edit_contact">Editatu taldea</string>
@ -23,6 +33,7 @@
<string name="first_name">Izena</string>
<string name="middle_name">Erdiko izena</string>
<string name="surname">Abizena</string>
<string name="nickname">Nickname</string>
<!-- Groups -->
<string name="no_groups">Talderik ez</string>
@ -43,6 +54,8 @@
<!-- Settings -->
<string name="start_name_with_surname">Abizenaren arabera sailkatu</string>
<string name="show_phone_numbers">Erakutsi telefono zenbakiak pantaila nagusian</string>
<string name="show_contact_thumbnails">Show contact thumbnails</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">Kontaktu sakatzean</string>
<string name="call_contact">Kontaktua deitu</string>
<string name="view_contact">Ikusi kontaktu detaileak</string>
@ -53,6 +66,7 @@
<string name="favorites">Gogokoak</string>
<string name="recent_calls">Azken deiak</string>
<string name="show_call_confirmation_dialog">Erakutsi egiaztatze mezua dei bat hasi baino lehen</string>
<string name="show_only_contacts_with_numbers">Show only contacts with phone numbers</string>
<!-- Emails -->
<string name="email">Emaila</string>
@ -66,6 +80,7 @@
<string name="main_number">Nagusia</string>
<string name="work_fax">Laneko faxa</string>
<string name="home_fax">Etxeko faxa</string>
<string name="pager">Pager</string>
<string name="no_phone_number_found">Ez da telefono zenbakirik aurkitu</string>
<!-- Events -->
@ -88,8 +103,24 @@
<string name="export_contacts">Esportatu kontaktuak</string>
<string name="import_contacts_from_vcf">Inportatu .vcf fitxategiko kontaktuak</string>
<string name="export_contacts_to_vcf">Esportatu kontaktua .vcf fitxategi batera</string>
<string name="target_contact_source">Target contact source</string>
<string name="include_contact_sources">Include contact sources</string>
<string name="filename_without_vcf">Fitxategi izena (.vcf gabe)</string>
<!-- Dialpad -->
<string name="dialpad">Dialpad</string>
<string name="add_number_to_contact">Add number to contact</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Hautatu erakusteko eremuak</string>
<string name="prefix">Aurrizkia</string>
@ -103,6 +134,15 @@
<string name="websites">Webguneak</string>
<string name="groups">Taldeak</string>
<string name="contact_source">Kontaktu jatorria</string>
<string name="instant_messaging">Instant messaging (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">Aldatu ditzaket kontaktuetan ikusgarri dauden eremuak?</string>

View File

@ -2,26 +2,29 @@
<string name="app_name">Simple Contacts</string>
<string name="app_launcher_name">Contacts</string>
<string name="address">Adresse</string>
<string name="inserting">Ajout</string>
<string name="updating">Mise à jour…</string>
<string name="inserting">Ajout en cours</string>
<string name="updating">Actualisation en cours</string>
<string name="phone_storage">Stockage du téléphone</string>
<string name="phone_storage_hidden">Stockage du téléphone (non visible par d\'autres applis)</string>
<string name="company">Société</string>
<string name="phone_storage_hidden">Stockage du téléphone (non visible par d\'autres applications)</string>
<string name="company">Entreprise</string>
<string name="job_position">Poste</string>
<string name="website">Site web</string>
<string name="website">Site Internet</string>
<string name="send_sms_to_contacts">Envoyer un SMS aux contacts</string>
<string name="send_email_to_contacts">Envoyer un e-mail aux contacts</string>
<string name="send_email_to_contacts">Envoyer un courriel aux contacts</string>
<string name="send_sms_to_group">Envoyer un SMS au groupe</string>
<string name="send_email_to_group">Envoyer un e-mail au groupe</string>
<string name="call_person">Call %s</string>
<string name="request_the_required_permissions">Demander les autorisations requises</string>
<string name="send_email_to_group">Envoyer un courriel au groupe</string>
<string name="call_person">Appeler %s</string>
<string name="request_the_required_permissions">Demander les autorisations nécessaires</string>
<string name="create_new_contact">Créer un nouveau contact</string>
<string name="add_to_existing_contact">Ajouter à un contact existant</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_contacts_found">Aucun contact n\'a été trouvé</string>
<string name="no_contacts_with_emails">Aucun contact avec une adresse de courriel n\'a été trouvé</string>
<string name="no_contacts_with_phone_numbers">Aucun contact avec un numéro de téléphone n\'a été trouvé</string>
<string name="no_recent_calls_found">Aucun appel récent n\'a été trouvé</string>
<string name="new_contact">Nouveau contact</string>
<string name="edit_contact">Modifier contact</string>
@ -33,15 +36,15 @@
<string name="nickname">Surnom</string>
<!-- Groups -->
<string name="no_groups">Pas de groupe</string>
<string name="no_groups">Aucun groupe</string>
<string name="create_new_group">Créer un nouveau groupe</string>
<string name="remove_from_group">Enlever du groupe</string>
<string name="remove_from_group">Supprimer du groupe</string>
<string name="no_group_participants">Ce groupe est vide</string>
<string name="add_contacts">Ajout contacts</string>
<string name="no_group_created">Il n\'y a pas de groupes de contacts sur l\'appareil</string>
<string name="add_contacts">Ajouter des contacts</string>
<string name="no_group_created">Aucun groupe de contacts sur l\'appareil</string>
<string name="create_group">Créer un groupe</string>
<string name="add_to_group">Ajouter à un groupe</string>
<string name="create_group_under_account">Créer un groupe pris en compte</string>
<string name="create_group_under_account">Créer un groupe dans le compte</string>
<!-- Photo -->
<string name="take_photo">Prendre une photo</string>
@ -52,20 +55,21 @@
<string name="start_name_with_surname">Trier les contacts par nom de famille</string>
<string name="show_phone_numbers">Afficher les numéros de téléphone</string>
<string name="show_contact_thumbnails">Afficher les vignettes des contacts</string>
<string name="on_contact_click">Sur appui du contact</string>
<string name="show_dialpad_button">Afficher un bouton \"Clavier téléphonique\" sur l\'écran principal</string>
<string name="on_contact_click">Lors d\'un appui sur un contact</string>
<string name="call_contact">Appeler le contact</string>
<string name="view_contact">Voir les détails du contact</string>
<string name="view_contact">Afficher les détails du contact</string>
<string name="manage_shown_contact_fields">Configurer l\'affichage des champs des contacts</string>
<string name="filter_duplicates">Essayez de filtrer les contacts en double</string>
<string name="manage_shown_tabs">Gérer les onglets affichés</string>
<string name="contacts">Contacts</string>
<string name="favorites">Favorites</string>
<string name="favorites">Favoris</string>
<string name="recent_calls">Appels récents</string>
<string name="show_call_confirmation_dialog">Afficher une boîte de dialogue de confirmation d\'appel avant de lancer un appel</string>
<string name="show_only_contacts_with_numbers">Show only contacts with phone numbers</string>
<string name="show_call_confirmation_dialog">Afficher une demande de confirmation avant de démarrer un appel</string>
<string name="show_only_contacts_with_numbers">Afficher uniquement les contacts avec un numéro de téléphone</string>
<!-- Emails -->
<string name="email">E-mail</string>
<string name="email">Adresse de courriel</string>
<string name="home">Maison</string>
<string name="work">Travail</string>
<string name="other">Autre</string>
@ -84,11 +88,11 @@
<string name="anniversary">Anniversaire</string>
<!-- Favorites -->
<string name="no_favorites">Vous n\'ayez pas encore ajouté de contact favori.</string>
<string name="no_favorites">Aucun contact favori n\'a été trouvé</string>
<string name="add_favorites">Ajouter des favoris</string>
<string name="add_to_favorites">Ajouter aux favoris</string>
<string name="remove_from_favorites">Retirer des favoris</string>
<string name="must_be_at_edit">Vous devez être sur l\'écran Modifier pour modifier un contact</string>
<string name="remove_from_favorites">Supprimer des favoris</string>
<string name="must_be_at_edit">Vous devez être sur l\'écran \"Modifier\" pour modifier un contact</string>
<!-- Search -->
<string name="search_contacts">Rechercher des contacts</string>
@ -99,44 +103,62 @@
<string name="export_contacts">Exporter des contacts</string>
<string name="import_contacts_from_vcf">Importer des contacts depuis un fichier .vcf</string>
<string name="export_contacts_to_vcf">Exporter des contacts vers un fichier .vcf</string>
<string name="target_contact_source">Source du contact cible</string>
<string name="include_contact_sources">Inclure les sources du contact</string>
<string name="target_contact_source">Compte pour le du contact destinataire</string>
<string name="include_contact_sources">Ajouter le compte pour le contact</string>
<string name="filename_without_vcf">Nom du fichier (sans .vcf)</string>
<!-- Dialpad -->
<string name="dialpad">Dialpad</string>
<string name="dialpad">Clavier téléphonique</string>
<string name="add_number_to_contact">Ajouter un numéro au contact</string>
<!-- Dialer -->
<string name="dialer">Numéroteur</string>
<string name="calling">Appel en cours</string>
<string name="incoming_call">Appel entrant</string>
<string name="incoming_call_from">Appel entrant de…</string>
<string name="ongoing_call">Appel en cours</string>
<string name="disconnected">Interrompu</string>
<string name="decline_call">Refuser</string>
<string name="answer_call">Répondre</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Sélectionner les champs à afficher</string>
<string name="prefix">Préfixe</string>
<string name="suffix">Suffixe</string>
<string name="phone_numbers">Numéros de téléphone</string>
<string name="emails">E-mails</string>
<string name="emails">Adresse de courriels</string>
<string name="addresses">Adresses</string>
<string name="events">Évènements (naissances, anniversaires)</string>
<string name="notes">Notes</string>
<string name="organization">Organisation</string>
<string name="websites">Sites web</string>
<string name="websites">Sites Internet</string>
<string name="groups">Groupe</string>
<string name="contact_source">Source du contact</string>
<string name="contact_source">Compte pour le contact</string>
<string name="instant_messaging">Messagerie instantanée (MI)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">Je veux changer quelles champs sont visibles. Est-ce que je peux ?</string>
<string name="faq_1_text">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.</string>
<string name="faq_1_title">Je veux modifier les champs affichés sur les fiches de mes contacts. Puis-je le faire ?</string>
<string name="faq_1_text">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.</string>
<!-- Strings displayed only on Google Playstore. Optional, but good to have -->
<!-- Short description has to have less than 80 chars -->
<string name="app_short_description">Une appli de contacts pour gérer vos contacts sans pubs.</string>
<string name="app_short_description">Une application répertoire pour gérer vos contacts sans publicité.</string>
<string name="app_long_description">
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
</string>
<!--

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Zatraži potrebna dopuštenja</string>
<string name="create_new_contact">Create new contact</string>
<string name="add_to_existing_contact">Add to an existing contact</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Novi kontakt</string>
<string name="edit_contact">Uredi kontakt</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Započnite imena s prezimenima</string>
<string name="show_phone_numbers">Prikaži telefonske brojeve na glavnom zaslonu</string>
<string name="show_contact_thumbnails">Prikaži sličice kontakata</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">Prilikom dodira kontakta</string>
<string name="call_contact">Nazovi kontakt</string>
<string name="view_contact">Prikaži pojedinosti o kontaktu</string>
@ -107,6 +111,16 @@
<string name="dialpad">Dialpad</string>
<string name="add_number_to_contact">Add number to contact</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Odaberi polja za prikaz</string>
<string name="prefix">Prefiks</string>
@ -122,6 +136,14 @@
<string name="contact_source">Izvori kontakata</string>
<string name="instant_messaging">Brzo slanje poruka (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">Želim promijeniti polja koja su vidljiva na kontaktima. Mogu li to napraviti?</string>
<string name="faq_1_text">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.</string>

View File

@ -5,7 +5,7 @@
<string name="inserting">Inserimento in corso…</string>
<string name="updating">Aggiornamento in corso…</string>
<string name="phone_storage">Memoria del telefono</string>
<string name="phone_storage_hidden">Memoria del telfono (non visibile alle altre applicazioni)</string>
<string name="phone_storage_hidden">Memoria del telefono (non visibile alle altre applicazioni)</string>
<string name="company">Compagnia</string>
<string name="job_position">Posizione lavorativa</string>
<string name="website">Sito web</string>
@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Richiedi le permissioni necessarie</string>
<string name="create_new_contact">Crea un nuovo contatto</string>
<string name="add_to_existing_contact">Aggiungi ad un contatto esistente</string>
<string name="must_make_default_dialer">È necessario impostare quest\'app come predefinita per utilizzare i numeri bloccati.</string>
<string name="set_to_default">Imposta come predefinita</string>
<!-- Placeholders -->
<string name="no_contacts_found">Nessun contatto trovato</string>
<string name="no_contacts_with_emails">Nessun contatto trovato con un\'email</string>
<string name="no_contacts_with_phone_numbers">Nessun contatto trovato con un numero di telefono</string>
<string name="no_recent_calls_found">Nessuna chiamata recente trovata</string>
<string name="new_contact">Nuovo contatto</string>
<string name="edit_contact">Modifica contatto</string>
@ -52,11 +55,12 @@
<string name="start_name_with_surname">Prima il nome poi il cognome</string>
<string name="show_phone_numbers">Mostra i numeri di telefono nella schermata principale</string>
<string name="show_contact_thumbnails">Mostra le anteprime dei contatti</string>
<string name="show_dialpad_button">Mostra il pulante per la tastiera nello schermo principale</string>
<string name="on_contact_click">Al click sul contatto</string>
<string name="call_contact">Chiama contatto</string>
<string name="view_contact">Visualizza i dettagli del contatto</string>
<string name="manage_shown_contact_fields">Gestisci i campi mostrati</string>
<string name="filter_duplicates">Prova a filetrare i contatti duplicati</string>
<string name="filter_duplicates">Prova a filtrare i contatti duplicati</string>
<string name="manage_shown_tabs">Gestisci le schede mostrate</string>
<string name="contacts">Contatti</string>
<string name="favorites">Preferiti</string>
@ -107,6 +111,16 @@
<string name="dialpad">Tastiera</string>
<string name="add_number_to_contact">Aggiungi numero ai contatti</string>
<!-- Dialer -->
<string name="dialer">Compositore</string>
<string name="calling">Chiamata in corso</string>
<string name="incoming_call">Chiamata in arrivo</string>
<string name="incoming_call_from">Chiamata in arrivo da…</string>
<string name="ongoing_call">Chiamata in corso</string>
<string name="disconnected">Disconnesso</string>
<string name="decline_call">Rifiuta</string>
<string name="answer_call">Rispondi</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Seleziona i campi da mostrare</string>
<string name="prefix">Prefisso</string>
@ -122,6 +136,14 @@
<string name="contact_source">Provenienza del contatto</string>
<string name="instant_messaging">Messaggistica istantanea (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Gestisci i numeri bloccati</string>
<string name="not_blocking_anyone">Non si sta blocccando alcun numero.</string>
<string name="add_a_blocked_number">Aggiungi un numero da bloccare</string>
<string name="block_number">Blocca numero</string>
<string name="block_numbers">Blocca numeri</string>
<string name="blocked_numbers">Numeri bloccati</string>
<!-- FAQ -->
<string name="faq_1_title">Voglio cambiare i campi visibili ai contatti. Come posso fare?</string>
<string name="faq_1_text">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.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Request the required permissions</string>
<string name="create_new_contact">新しい連絡先を作成</string>
<string name="add_to_existing_contact">既存の連絡先に追加</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">連絡先が見つかりません</string>
<string name="no_contacts_with_emails">メールアドレスが登録された連絡先が見つかりません</string>
<string name="no_contacts_with_phone_numbers">電話番号が登録された連絡先が見つかりません</string>
<string name="no_recent_calls_found">通話履歴はありません</string>
<string name="new_contact">新しい連絡先</string>
<string name="edit_contact">連絡先を編集</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">姓を先に表示</string>
<string name="show_phone_numbers">メイン画面に電話番号を表示</string>
<string name="show_contact_thumbnails">連絡先のサムネイルを表示</string>
<string name="show_dialpad_button">メイン画面にダイヤルパッドを表示</string>
<string name="on_contact_click">連絡先をタップ</string>
<string name="call_contact">連絡先に発信</string>
<string name="view_contact">連絡先の詳細を表示</string>
@ -60,7 +64,7 @@
<string name="manage_shown_tabs">表示するタブを管理</string>
<string name="contacts">連絡先</string>
<string name="favorites">お気に入り</string>
<string name="recent_calls">Recent calls</string>
<string name="recent_calls">通話履歴</string>
<string name="show_call_confirmation_dialog">発信する前に確認ダイアログを表示する</string>
<string name="show_only_contacts_with_numbers">電話番号が登録された連絡先のみ表示する</string>
@ -107,6 +111,16 @@
<string name="dialpad">ダイヤルパッド</string>
<string name="add_number_to_contact">連絡先に番号を追加</string>
<!-- Dialer -->
<string name="dialer">電話</string>
<string name="calling">発信中</string>
<string name="incoming_call">着信中</string>
<string name="incoming_call_from">着信中…</string>
<string name="ongoing_call">通話中</string>
<string name="disconnected">切断されました</string>
<string name="decline_call">拒否</string>
<string name="answer_call">応答</string>
<!-- Visible fields -->
<string name="select_fields_to_show">表示する項目を選択</string>
<string name="prefix">敬称(名前の前)</string>
@ -122,6 +136,14 @@
<string name="contact_source">インポート元</string>
<string name="instant_messaging">Instant messaging (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">ブロックした番号を管理</string>
<string name="not_blocking_anyone">まだ誰もブロックしていません.</string>
<string name="add_a_blocked_number">ブロックする番号を追加</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">連絡先に表示される項目(フィールド)を変更することはできますか?</string>
<string name="faq_1_text">可能です。[設定] -> [連絡先に表示するフィールドを管理] から、表示されるフィールドを選択することができます。これらの中にはデフォルトで無効になっているものもあるので、あなたは新しいものを見つけるかもしれません。</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Request the required permissions</string>
<string name="create_new_contact">Create new contact</string>
<string name="add_to_existing_contact">Add to an existing contact</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">새로운 연락처</string>
<string name="edit_contact">연락처 수정</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">성을 먼저 표시</string>
<string name="show_phone_numbers">메인 스크린에 전화번호 표시</string>
<string name="show_contact_thumbnails">Show contact thumbnails</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">On contact click</string>
<string name="call_contact">Call contact</string>
<string name="view_contact">View contact details</string>
@ -107,6 +111,16 @@
<string name="dialpad">Dialpad</string>
<string name="add_number_to_contact">Add number to contact</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Select fields to show</string>
<string name="prefix">Prefix</string>
@ -122,6 +136,14 @@
<string name="contact_source">Contact source</string>
<string name="instant_messaging">Instant messaging (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">I want to change what fields are visible at contacts. Can I do it?</string>
<string name="faq_1_text">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.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Request the required permissions</string>
<string name="create_new_contact">Create new contact</string>
<string name="add_to_existing_contact">Add to an existing contact</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Naujas kontaktas</string>
<string name="edit_contact">Redaguoti kontaktą</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Pavardė rodoma pirma</string>
<string name="show_phone_numbers">Rodyti telefono numerius pagrindiniame programos ekrane</string>
<string name="show_contact_thumbnails">Rodyti kontaktų miniatiūras</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">Ant kontakto paspaudimo</string>
<string name="call_contact">Skambinti kontaktui</string>
<string name="view_contact">Žiūrėti kontakto detales</string>
@ -107,6 +111,16 @@
<string name="dialpad">Dialpad</string>
<string name="add_number_to_contact">Add number to contact</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Pasirinkti rodomus laukelius</string>
<string name="prefix">Priešdėlis</string>
@ -122,6 +136,14 @@
<string name="contact_source">Kontakto šaltinis</string>
<string name="instant_messaging">Instant messaging (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">Noriu pakeisti, kokie laukai yra matomi kontaktuose. Ar galiu tai padaryti?</string>
<string name="faq_1_text">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ų.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Pedir a permissão necessária</string>
<string name="create_new_contact">Criar novo contacto</string>
<string name="add_to_existing_contact">Adicionar a contacto existente</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_contacts_found">Não existem contactos</string>
<string name="no_contacts_with_emails">Não existem contactos com endereço de e-mail</string>
<string name="no_contacts_with_phone_numbers">Não existem contactos com número de telefone</string>
<string name="no_recent_calls_found">Não existem chamadas recentes</string>
<string name="new_contact">Novo contacto</string>
<string name="edit_contact">Editar contacto</string>
@ -33,7 +36,7 @@
<string name="nickname">Alcunha</string>
<!-- Groups -->
<string name="no_groups">Não grupos</string>
<string name="no_groups">Não existem grupos</string>
<string name="create_new_group">Criar um novo grupo</string>
<string name="remove_from_group">Remover do grupo</string>
<string name="no_group_participants">Este grupo está vazio</string>
@ -52,17 +55,18 @@
<string name="start_name_with_surname">Ordenar por apelido</string>
<string name="show_phone_numbers">Mostrar número de telefone no ecrã principal</string>
<string name="show_contact_thumbnails">Mostrar miniatura do contacto</string>
<string name="show_dialpad_button">Mostrar botão Marcador no ecrã principal</string>
<string name="on_contact_click">Ao tocar no contacto</string>
<string name="call_contact">Ligar</string>
<string name="view_contact">Ver detalhes</string>
<string name="manage_shown_contact_fields">Gerir campos a exibir</string>
<string name="manage_shown_contact_fields">Gerir campos a mostrar</string>
<string name="filter_duplicates">Tentar filtrar contactos duplicados</string>
<string name="manage_shown_tabs">Gerir separadores a exibir</string>
<string name="contacts">Contactos</string>
<string name="favorites">Favoritos</string>
<string name="recent_calls">Chamadas recentes</string>
<string name="show_call_confirmation_dialog">Mostrar diálogo para confirmar a chamada</string>
<string name="show_only_contacts_with_numbers">Show only contacts with phone numbers</string>
<string name="show_only_contacts_with_numbers">Mostrar apenas contactos com número de telefone</string>
<!-- Emails -->
<string name="email">E-mail</string>
@ -104,9 +108,19 @@
<string name="filename_without_vcf">Nome do ficheiro (sem .vcf)</string>
<!-- Dialpad -->
<string name="dialpad">Teclado</string>
<string name="dialpad">Marcador</string>
<string name="add_number_to_contact">Adicionar número a um contacto</string>
<!-- Dialer -->
<string name="dialer">Marcador</string>
<string name="calling">A chamar</string>
<string name="incoming_call">Chamada recebida</string>
<string name="incoming_call_from">Chamada recebida de…</string>
<string name="ongoing_call">Chamada efetuada</string>
<string name="disconnected">Desligada</string>
<string name="decline_call">Recusar</string>
<string name="answer_call">Atender</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Selecione os campos a mostrar</string>
<string name="prefix">Prefixo</string>
@ -122,6 +136,14 @@
<string name="contact_source">Origem do contacto</string>
<string name="instant_messaging">Mensagem instantânea (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">I want to change what fields are visible at contacts. Can I do it?</string>
<string name="faq_1_text">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.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Запрос необходимых разрешений</string>
<string name="create_new_contact">Создать новый контакт</string>
<string name="add_to_existing_contact">Добавить к существующему контакту</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">Контакты не найдены</string>
<string name="no_contacts_with_emails">Контакты с адресами электронной почты не найдены</string>
<string name="no_contacts_with_phone_numbers">Контакты с номерами телефонов не найдены</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Новый контакт</string>
<string name="edit_contact">Редактировать контакт</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Показывать сначала фамилию</string>
<string name="show_phone_numbers">Показывать номера телефонов на главном экране</string>
<string name="show_contact_thumbnails">Показывать фото контакта</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">При нажатии на контакт</string>
<string name="call_contact">Позвонить контакту</string>
<string name="view_contact">Просмотреть подробности о контакте</string>
@ -107,6 +111,16 @@
<string name="dialpad">Набор номера</string>
<string name="add_number_to_contact">Добавить номер к контакту</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Выберите отображаемые поля</string>
<string name="prefix">Префикс</string>
@ -122,6 +136,14 @@
<string name="contact_source">Источник контакта</string>
<string name="instant_messaging">Мессенджеры (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">Я хочу изменить поля, отображаемые у контактов. Могу ли я это сделать?</string>
<string name="faq_1_text">Да, всё, что вам нужно сделать, это зайти в \"Настройки\" -> \"Управление отображаемыми полями контактов\". Там вы сможете выбрать, какие поля должны быть видимы. Некоторые из них даже отключены по умолчанию, так что вы можете найти некоторые новые.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Vyžiadať potrebné oprávnenia</string>
<string name="create_new_contact">Vytvoriť nový kontakt</string>
<string name="add_to_existing_contact">Pridať k existujúcemu kontaktu</string>
<string name="must_make_default_dialer">Pre použitie blokovania čísel musíte nastaviť aplikáciu ako predvolenú pre správu hovorov.</string>
<string name="set_to_default">Nastaviť ako predvolenú</string>
<!-- Placeholders -->
<string name="no_contacts_found">Nenašli sa žiadne kontakty</string>
<string name="no_contacts_with_emails">Nenašli sa žiadne kontakty s emailami</string>
<string name="no_contacts_with_phone_numbers">Nenašli sa žiadne kontakty s telefónnymi číslami</string>
<string name="no_recent_calls_found">Nenašli sa žiadne posledné hovory</string>
<string name="new_contact">Nový kontakt</string>
<string name="edit_contact">Upraviť kontakt</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Začať meno priezviskom</string>
<string name="show_phone_numbers">Zobraziť telefónne čísla na hlavnej obrazovke</string>
<string name="show_contact_thumbnails">Zobraziť obrázky kontaktov</string>
<string name="show_dialpad_button">Zobraziť tlačidlo pre číselník na hlavnej obrazovke</string>
<string name="on_contact_click">Po kliknutí na kontakt</string>
<string name="call_contact">Zavolať kontakt</string>
<string name="view_contact">Zobraziť údaje kontaktu</string>
@ -107,6 +111,16 @@
<string name="dialpad">Číselník</string>
<string name="add_number_to_contact">Pridať číslo kontaktu</string>
<!-- Dialer -->
<string name="dialer">Telefón</string>
<string name="calling">Vytáča sa</string>
<string name="incoming_call">Prichádzajúci hovor</string>
<string name="incoming_call_from">Prichádzajúci hovor od…</string>
<string name="ongoing_call">Prebiehajúci hovor</string>
<string name="disconnected">Ukončený hovor</string>
<string name="decline_call">Odmietnuť</string>
<string name="answer_call">Prijať</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Zvoľte polia na zobrazenie</string>
<string name="prefix">Titul pred menom</string>
@ -122,6 +136,14 @@
<string name="contact_source">Zdroje kontaktov</string>
<string name="instant_messaging">Rýchle správy (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Spravovať blokované čísla</string>
<string name="not_blocking_anyone">Neblokujete nikoho.</string>
<string name="add_a_blocked_number">Pridať blokované číslo</string>
<string name="block_number">Blokovať číslo</string>
<string name="block_numbers">Blokovať čísla</string>
<string name="blocked_numbers">Blokované čísla</string>
<!-- FAQ -->
<string name="faq_1_title">Chcem upraviť viditeľné polia kontaktov. Dá sa to?</string>
<string name="faq_1_text">Á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é.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Begär de behörigheter som krävs</string>
<string name="create_new_contact">Skapa ny kontakt</string>
<string name="add_to_existing_contact">Lägg till i en befintlig kontakt</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">Inga kontakter hittades</string>
<string name="no_contacts_with_emails">Inga kontakter med e-postadresser hittades</string>
<string name="no_contacts_with_phone_numbers">Inga kontakter med telefonnummer hittades</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Ny kontakt</string>
<string name="edit_contact">Redigera kontakt</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Visa efternamn först</string>
<string name="show_phone_numbers">Visa telefonnummer i huvudvyn</string>
<string name="show_contact_thumbnails">Visa kontaktminiatyrer</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">Vid kontakttryckning</string>
<string name="call_contact">Ring kontakt</string>
<string name="view_contact">Visa kontaktuppgifter</string>
@ -107,6 +111,16 @@
<string name="dialpad">Knappsats</string>
<string name="add_number_to_contact">Lägg till nummer i kontakt</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Välj vilka fält som ska visas</string>
<string name="prefix">Prefix</string>
@ -122,6 +136,14 @@
<string name="contact_source">Kontaktkälla</string>
<string name="instant_messaging">Snabbmeddelanden (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">I want to change what fields are visible at contacts. Can I do it?</string>
<string name="faq_1_text">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.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Gerekli izinleri iste</string>
<string name="create_new_contact">Yeni kişi oluştur</string>
<string name="add_to_existing_contact">Mevcut bir kişiye ekle</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">Kişi bulunamadı</string>
<string name="no_contacts_with_emails">E-posta ile hiç bağlantı bulunamadı</string>
<string name="no_contacts_with_phone_numbers">Telefon numaralarını içeren kişi bulunamadı</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">Yeni kişi</string>
<string name="edit_contact">Kişiyi düzenle</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Soyadı ile başla</string>
<string name="show_phone_numbers">Ana ekranda telefon numaralarını göster</string>
<string name="show_contact_thumbnails">Kişi küçük resimlerini göster</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">Kişi tıklandığında</string>
<string name="call_contact">Kişiyi ara</string>
<string name="view_contact">Kişi bilgilerini göster</string>
@ -107,6 +111,16 @@
<string name="dialpad">Tuş takımı</string>
<string name="add_number_to_contact">Kişiye numara ekle</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Görüntülenecek alanları seç</string>
<string name="prefix">Önek</string>
@ -122,6 +136,14 @@
<string name="contact_source">Kişi kaynağı</string>
<string name="instant_messaging">Anlık mesajlaşma (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">Rehberde görüntülenecek alanları değiştirmek istiyorum. Bunu yapabilir miyim?</string>
<string name="faq_1_text">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.</string>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">請求必要的權限</string>
<string name="create_new_contact">建立新聯絡人</string>
<string name="add_to_existing_contact">添加至已存在的聯絡人</string>
<string name="must_make_default_dialer">你必須將這應用程式設為預設的撥號程式來使用黑名單。</string>
<string name="set_to_default">設為預設</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_contacts_found">未發現聯絡人</string>
<string name="no_contacts_with_emails">未發現含有電子信箱的聯絡人</string>
<string name="no_contacts_with_phone_numbers">未發現含有電話號碼的聯絡人</string>
<string name="no_recent_calls_found">未發現通話紀錄</string>
<string name="new_contact">新聯絡人</string>
<string name="edit_contact">編輯聯絡人</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">姓氏在前</string>
<string name="show_phone_numbers">主畫面顯示電話號碼</string>
<string name="show_contact_thumbnails">顯示聯絡人縮圖</string>
<string name="show_dialpad_button">在主畫面顯示撥號按鈕</string>
<string name="on_contact_click">點擊聯絡人</string>
<string name="call_contact">打電話給聯絡人</string>
<string name="view_contact">顯示聯絡人資料</string>
@ -62,7 +66,7 @@
<string name="favorites">我的最愛</string>
<string name="recent_calls">通話紀錄</string>
<string name="show_call_confirmation_dialog">開始通話前顯示通話確認框</string>
<string name="show_only_contacts_with_numbers">Show only contacts with phone numbers</string>
<string name="show_only_contacts_with_numbers">只顯示含有電話話碼的聯絡人</string>
<!-- Emails -->
<string name="email">電子信箱</string>
@ -107,6 +111,16 @@
<string name="dialpad">撥號畫面</string>
<string name="add_number_to_contact">添加號碼至通訊錄</string>
<!-- Dialer -->
<string name="dialer">撥號器</string>
<string name="calling">撥號中</string>
<string name="incoming_call">來電</string>
<string name="incoming_call_from">通話來自於…</string>
<string name="ongoing_call">持續通話</string>
<string name="disconnected">未接電話</string>
<string name="decline_call">掛斷電話</string>
<string name="answer_call">回撥</string>
<!-- Visible fields -->
<string name="select_fields_to_show">選擇要顯示的欄位</string>
<string name="prefix">前缀</string>
@ -122,6 +136,14 @@
<string name="contact_source">聯絡人來源</string>
<string name="instant_messaging">即時通訊 (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">管理黑名單</string>
<string name="not_blocking_anyone">你沒有封鎖任何人</string>
<string name="add_a_blocked_number">添加封鎖的號碼</string>
<string name="block_number">封鎖號碼</string>
<string name="block_numbers">封鎖號碼</string>
<string name="blocked_numbers">黑名單</string>
<!-- FAQ -->
<string name="faq_1_title">我想要更改在通訊錄會看到哪些欄位。我能這麼做嗎?</string>
<string name="faq_1_text">可以,你要做的是到[設定] -> [管理顯示的聯絡人欄位]。在那裡,你可以選擇應該看到什麼欄位。其中有些甚至預設是關閉的,所以你可能會在那裡發現一些新的。</string>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -7,5 +7,6 @@
<dimen name="contact_item_with_number_height">56dp</dimen>
<dimen name="create_new_contact_height">68dp</dimen>
<dimen name="dialpad_button_size">60dp</dimen>
<dimen name="dialpad_text_size">34sp</dimen>
</resources>

View File

@ -17,11 +17,14 @@
<string name="request_the_required_permissions">Request the required permissions</string>
<string name="create_new_contact">Create new contact</string>
<string name="add_to_existing_contact">Add to an existing contact</string>
<string name="must_make_default_dialer">You have to make this app the default dialer app to make use of blocked numbers.</string>
<string name="set_to_default">Set to default</string>
<!-- Placeholders -->
<string name="no_contacts_found">No contacts found</string>
<string name="no_contacts_with_emails">No contacts with emails have been found</string>
<string name="no_contacts_with_phone_numbers">No contacts with phone numbers have been found</string>
<string name="no_recent_calls_found">No recent calls found</string>
<string name="new_contact">New contact</string>
<string name="edit_contact">Edit contact</string>
@ -52,6 +55,7 @@
<string name="start_name_with_surname">Start name with surname</string>
<string name="show_phone_numbers">Show phone numbers on the main screen</string>
<string name="show_contact_thumbnails">Show contact thumbnails</string>
<string name="show_dialpad_button">Show a dialpad button on the main screen</string>
<string name="on_contact_click">On contact click</string>
<string name="call_contact">Call contact</string>
<string name="view_contact">View contact details</string>
@ -107,6 +111,16 @@
<string name="dialpad">Dialpad</string>
<string name="add_number_to_contact">Add number to contact</string>
<!-- Dialer -->
<string name="dialer">Dialer</string>
<string name="calling">Calling</string>
<string name="incoming_call">Incoming call</string>
<string name="incoming_call_from">Incoming call from…</string>
<string name="ongoing_call">Ongoing call</string>
<string name="disconnected">Disconnected</string>
<string name="decline_call">Decline</string>
<string name="answer_call">Answer</string>
<!-- Visible fields -->
<string name="select_fields_to_show">Select fields to show</string>
<string name="prefix">Prefix</string>
@ -122,6 +136,14 @@
<string name="contact_source">Contact source</string>
<string name="instant_messaging">Instant messaging (IM)</string>
<!-- Blocked numbers -->
<string name="manage_blocked_numbers">Manage blocked numbers</string>
<string name="not_blocking_anyone">You are not blocking anyone.</string>
<string name="add_a_blocked_number">Add a blocked number</string>
<string name="block_number">Block number</string>
<string name="block_numbers">Block numbers</string>
<string name="blocked_numbers">Blocked numbers</string>
<!-- FAQ -->
<string name="faq_1_title">I want to change what fields are visible at contacts. Can I do it?</string>
<string name="faq_1_text">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.</string>

View File

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

View File

@ -0,0 +1,4 @@
storePassword=123456
keyPassword=abcdef
keyAlias=myAlias
storeFile=../keystore.jks

View File

@ -1,3 +0,0 @@
STORE_FILE=/path/to/your.keystore
KEY_ALIAS=projectkeyalias
PASSWORD=yourpass