diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43d342aa..5cc45df2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
Changelog
==========
+Version 3.4.0 *(2018-03-21)*
+----------------------------
+
+ * Added groups
+ * Make phone numbers, emails and addresses clickable on the view screen
+ * Many smaller improvements and bugfixes
+
Version 3.3.3 *(2018-03-04)*
----------------------------
diff --git a/app/build.gradle b/app/build.gradle
index c6cb783b..abf62b29 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -10,8 +10,8 @@ android {
applicationId "com.simplemobiletools.contacts"
minSdkVersion 16
targetSdkVersion 27
- versionCode 14
- versionName "3.3.3"
+ versionCode 15
+ versionName "3.4.0"
setProperty("archivesBaseName", "contacts")
}
@@ -45,7 +45,7 @@ ext {
}
dependencies {
- implementation 'com.simplemobiletools:commons:3.16.11'
+ implementation 'com.simplemobiletools:commons:3.16.12'
implementation 'joda-time:joda-time:2.9.9'
implementation 'com.facebook.stetho:stetho:1.5.0'
implementation 'com.google.code.gson:gson:2.8.2'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c3692df2..cdb40add 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -74,6 +74,10 @@
android:label="@string/settings"
android:parentActivityName=".activities.MainActivity"/>
+
+
var groupHolder = contact_groups_holder.getChildAt(index)
if (groupHolder == null) {
- groupHolder = layoutInflater.inflate(R.layout.item_group, contact_groups_holder, false)
+ groupHolder = layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false)
contact_groups_holder.addView(groupHolder)
}
@@ -359,7 +359,7 @@ class EditContactActivity : ContactActivity() {
}
if (groups.isEmpty()) {
- layoutInflater.inflate(R.layout.item_group, contact_groups_holder, false).apply {
+ layoutInflater.inflate(R.layout.item_edit_group, contact_groups_holder, false).apply {
contact_group.apply {
alpha = 0.5f
text = getString(R.string.no_groups)
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt
new file mode 100644
index 00000000..03136965
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/GroupContactsActivity.kt
@@ -0,0 +1,118 @@
+package com.simplemobiletools.contacts.activities
+
+import android.os.Bundle
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.adapters.ContactsAdapter
+import com.simplemobiletools.contacts.dialogs.SelectContactsDialog
+import com.simplemobiletools.contacts.extensions.*
+import com.simplemobiletools.contacts.helpers.*
+import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
+import com.simplemobiletools.contacts.interfaces.RemoveFromGroupListener
+import com.simplemobiletools.contacts.models.Contact
+import com.simplemobiletools.contacts.models.Group
+import kotlinx.android.synthetic.main.activity_group_contacts.*
+
+class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, RefreshContactsListener {
+ private var allContacts = ArrayList()
+ private var groupContacts = ArrayList()
+ private var wasInit = false
+ lateinit var group: Group
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_group_contacts)
+ updateTextColors(group_contacts_coordinator)
+
+ group = intent.extras.getSerializable(GROUP) as Group
+ supportActionBar?.title = group.title
+
+ group_contacts_fab.setOnClickListener {
+ if (wasInit) {
+ fabClicked()
+ }
+ }
+
+ group_contacts_placeholder_2.setOnClickListener {
+ fabClicked()
+ }
+
+ group_contacts_placeholder_2.underlineText()
+ group_contacts_placeholder_2.setTextColor(getAdjustedPrimaryColor())
+ }
+
+ override fun onResume() {
+ super.onResume()
+ refreshContacts()
+ }
+
+ private fun fabClicked() {
+ SelectContactsDialog(this, allContacts, groupContacts) { addedContacts, removedContacts ->
+ Thread {
+ addContactsToGroup(addedContacts, group.id)
+ removeContactsFromGroup(removedContacts, group.id)
+ refreshContacts()
+ }.start()
+ }
+ }
+
+ private fun refreshContacts() {
+ ContactsHelper(this).getContacts {
+ wasInit = true
+ allContacts = it
+
+ groupContacts = it.filter { it.groups.map { it.id }.contains(group.id) } as ArrayList
+ group_contacts_placeholder_2.beVisibleIf(groupContacts.isEmpty())
+ group_contacts_placeholder.beVisibleIf(groupContacts.isEmpty())
+ group_contacts_list.beVisibleIf(groupContacts.isNotEmpty())
+
+ Contact.sorting = config.sorting
+ groupContacts.sort()
+
+ updateContacts(groupContacts)
+ }
+ }
+
+ private fun updateContacts(contacts: ArrayList) {
+ val currAdapter = group_contacts_list.adapter
+ if (currAdapter == null) {
+ ContactsAdapter(this, contacts, this, LOCATION_GROUP_CONTACTS, this, group_contacts_list, group_contacts_fastscroller) {
+ when (config.onContactClick) {
+ ON_CLICK_CALL_CONTACT -> {
+ val contact = it as Contact
+ if (contact.phoneNumbers.isNotEmpty()) {
+ tryStartCall(it)
+ } else {
+ toast(R.string.no_phone_number_found)
+ }
+ }
+ ON_CLICK_VIEW_CONTACT -> viewContact(it as Contact)
+ ON_CLICK_EDIT_CONTACT -> editContact(it as Contact)
+ }
+ }.apply {
+ setupDragListener(true)
+ addVerticalDividers(true)
+ group_contacts_list.adapter = this
+ }
+
+ group_contacts_fastscroller.setScrollTo(0)
+ group_contacts_fastscroller.setViews(group_contacts_list) {
+ val item = (group_contacts_list.adapter as ContactsAdapter).contactItems.getOrNull(it)
+ group_contacts_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
+ }
+ } else {
+ (currAdapter as ContactsAdapter).updateItems(contacts)
+ }
+ }
+
+ override fun refreshContacts(refreshTabsMask: Int) {
+ refreshContacts()
+ }
+
+ override fun removeFromGroup(contacts: ArrayList) {
+ ContactsHelper(this).removeContactsFromGroup(contacts, group.id)
+ if (groupContacts.size == 0) {
+ refreshContacts()
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt
index 27a0cadd..3997c5b0 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/MainActivity.kt
@@ -26,9 +26,7 @@ import com.simplemobiletools.contacts.dialogs.ImportContactsDialog
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.dbHelper
import com.simplemobiletools.contacts.extensions.getTempFile
-import com.simplemobiletools.contacts.fragments.MyViewPagerFragment
-import com.simplemobiletools.contacts.helpers.ContactsHelper
-import com.simplemobiletools.contacts.helpers.VcfExporter
+import com.simplemobiletools.contacts.helpers.*
import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
import com.simplemobiletools.contacts.models.Contact
import kotlinx.android.synthetic.main.activity_main.*
@@ -93,8 +91,9 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
val configShowContactThumbnails = config.showContactThumbnails
if (storedShowContactThumbnails != configShowContactThumbnails) {
- contacts_fragment?.showContactThumbnailsChanged(configShowContactThumbnails)
- favorites_fragment?.showContactThumbnailsChanged(configShowContactThumbnails)
+ getAllFragments().forEach {
+ it?.showContactThumbnailsChanged(configShowContactThumbnails)
+ }
}
val configTextColor = config.textColor
@@ -102,8 +101,9 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
getInactiveTabIndexes(viewpager.currentItem).forEach {
main_tabs_holder.getTabAt(it)?.icon?.applyColorFilter(configTextColor)
}
- contacts_fragment?.textColorChanged(configTextColor)
- favorites_fragment?.textColorChanged(configTextColor)
+ getAllFragments().forEach {
+ it?.textColorChanged(configTextColor)
+ }
}
val configBackgroundColor = config.backgroundColor
@@ -115,8 +115,9 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
if (storedPrimaryColor != configPrimaryColor) {
main_tabs_holder.setSelectedTabIndicatorColor(getAdjustedPrimaryColor())
main_tabs_holder.getTabAt(viewpager.currentItem)?.icon?.applyColorFilter(getAdjustedPrimaryColor())
- contacts_fragment?.primaryColorChanged(configPrimaryColor)
- favorites_fragment?.primaryColorChanged(configPrimaryColor)
+ getAllFragments().forEach {
+ it?.primaryColorChanged()
+ }
}
val configStartNameWithSurname = config.startNameWithSurname
@@ -130,9 +131,10 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
initFragments()
}
- contacts_fragment?.onActivityResume()
- favorites_fragment?.onActivityResume()
- refreshContacts(true, true)
+ getAllFragments().forEach {
+ it?.onActivityResume()
+ }
+ refreshContacts(ALL_TABS_MASK)
}
if (hasPermission(PERMISSION_WRITE_CONTACTS)) {
@@ -152,6 +154,12 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu, menu)
+ val currentPage = viewpager?.currentItem
+ menu.apply {
+ findItem(R.id.search).isVisible = currentPage != LOCATION_GROUPS_TAB
+ findItem(R.id.sort).isVisible = currentPage != LOCATION_GROUPS_TAB
+ findItem(R.id.filter).isVisible = currentPage != LOCATION_GROUPS_TAB
+ }
setupSearch(menu)
return true
}
@@ -193,7 +201,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
override fun onQueryTextChange(newText: String): Boolean {
if (isSearchOpen) {
- (getCurrentFragment() as? MyViewPagerFragment)?.onSearchQueryChanged(newText)
+ getCurrentFragment()?.onSearchQueryChanged(newText)
}
return true
}
@@ -202,13 +210,13 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
MenuItemCompat.setOnActionExpandListener(searchMenuItem, object : MenuItemCompat.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
- (getCurrentFragment() as? MyViewPagerFragment)?.onSearchOpened()
+ getCurrentFragment()?.onSearchOpened()
isSearchOpen = true
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
- (getCurrentFragment() as? MyViewPagerFragment)?.onSearchClosed()
+ getCurrentFragment()?.onSearchClosed()
isSearchOpen = false
return true
}
@@ -266,12 +274,12 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
private fun getInactiveTabIndexes(activeIndex: Int) = arrayListOf(0, 1, 2).filter { it != activeIndex }
private fun initFragments() {
- refreshContacts(true, true)
+ refreshContacts(ALL_TABS_MASK)
viewpager.offscreenPageLimit = 2
viewpager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
if (isSearchOpen) {
- (getCurrentFragment() as? MyViewPagerFragment)?.onSearchQueryChanged("")
+ getCurrentFragment()?.onSearchQueryChanged("")
searchMenuItem?.collapseActionView()
}
}
@@ -281,12 +289,12 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
override fun onPageSelected(position: Int) {
main_tabs_holder.getTabAt(position)?.select()
- contacts_fragment?.finishActMode()
- favorites_fragment?.finishActMode()
+ getAllFragments().forEach {
+ it?.finishActMode()
+ }
invalidateOptionsMenu()
}
})
- viewpager.currentItem = config.lastUsedViewPagerPage
main_tabs_holder.onTabSelectionChanged(
tabUnselectedAction = {
@@ -305,14 +313,14 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
private fun showSortingDialog() {
ChangeSortingDialog(this) {
- refreshContacts(true, true)
+ refreshContacts(CONTACTS_TAB_MASK or FAVORITES_TAB_MASK)
}
}
fun showFilterDialog() {
FilterContactSourcesDialog(this) {
contacts_fragment?.forceListRedraw = true
- refreshContacts(true, false)
+ refreshContacts(CONTACTS_TAB_MASK)
}
}
@@ -334,7 +342,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
ImportContactsDialog(this, path) {
if (it) {
runOnUiThread {
- refreshContacts(true, true)
+ refreshContacts(ALL_TABS_MASK)
}
}
}
@@ -397,7 +405,7 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
BuildConfig.VERSION_NAME, faqItems)
}
- override fun refreshContacts(refreshContactsTab: Boolean, refreshFavoritesTab: Boolean) {
+ override fun refreshContacts(refreshTabsMask: Int) {
if (isActivityDestroyed()) {
return
}
@@ -409,21 +417,27 @@ class MainActivity : SimpleActivity(), RefreshContactsListener {
if (viewpager.adapter == null) {
viewpager.adapter = ViewPagerAdapter(this, it)
+ viewpager.currentItem = config.lastUsedViewPagerPage
}
- if (refreshContactsTab) {
+ if (refreshTabsMask and CONTACTS_TAB_MASK != 0) {
contacts_fragment?.refreshContacts(it)
}
- if (refreshFavoritesTab) {
+ if (refreshTabsMask and FAVORITES_TAB_MASK != 0) {
favorites_fragment?.refreshContacts(it)
}
+
+ if (refreshTabsMask and GROUPS_TAB_MASK != 0) {
+ if (refreshTabsMask == GROUPS_TAB_MASK) {
+ groups_fragment.skipHashComparing = true
+ }
+ groups_fragment?.refreshContacts(it)
+ }
}
}
- override fun refreshFavorites() {
- refreshContacts(false, true)
- }
+ private fun getAllFragments() = arrayListOf(contacts_fragment, favorites_fragment, groups_fragment)
private fun checkWhatsNewDialog() {
arrayListOf().apply {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt
index b7a10501..601f6e85 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/SelectContactActivity.kt
@@ -23,7 +23,7 @@ import com.simplemobiletools.contacts.models.Contact
import kotlinx.android.synthetic.main.layout_select_contact.*
class SelectContactActivity : SimpleActivity() {
- private var isGetEmailIntent = false
+ private var specialMimeType: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -34,7 +34,11 @@ class SelectContactActivity : SimpleActivity() {
if (it) {
handlePermission(PERMISSION_WRITE_CONTACTS) {
if (it) {
- isGetEmailIntent = intent.data == ContactsContract.CommonDataKinds.Email.CONTENT_URI
+ specialMimeType = when (intent.data) {
+ ContactsContract.CommonDataKinds.Email.CONTENT_URI -> ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+ ContactsContract.CommonDataKinds.Phone.CONTENT_URI -> ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
+ else -> null
+ }
initContacts()
} else {
toast(R.string.no_contacts_permission)
@@ -81,8 +85,13 @@ class SelectContactActivity : SimpleActivity() {
}
var contacts = it.filter {
- if (isGetEmailIntent) {
- (it.source != SMT_PRIVATE && it.emails.isNotEmpty())
+ if (specialMimeType != null) {
+ val hasRequiredValues = when (specialMimeType) {
+ ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE -> it.emails.isNotEmpty()
+ ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> it.phoneNumbers.isNotEmpty()
+ else -> true
+ }
+ it.source != SMT_PRIVATE && hasRequiredValues
} else {
true
}
@@ -119,12 +128,15 @@ class SelectContactActivity : SimpleActivity() {
}
private fun getResultUri(contact: Contact): Uri {
- return if (isGetEmailIntent) {
- val emailID = ContactsHelper(this).getContactDataId(contact.id.toString())
- Uri.withAppendedPath(ContactsContract.Data.CONTENT_URI, emailID)
- } else {
- val lookupKey = ContactsHelper(this).getContactLookupKey(contact.id.toString())
- Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)
+ return when {
+ specialMimeType != null -> {
+ val contactId = ContactsHelper(this).getContactMimeTypeId(contact.id.toString(), specialMimeType!!)
+ Uri.withAppendedPath(ContactsContract.Data.CONTENT_URI, contactId)
+ }
+ else -> {
+ val lookupKey = ContactsHelper(this).getContactLookupKey(contact.id.toString())
+ Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey)
+ }
}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt
index 1bffbd05..d9c17a46 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/activities/ViewContactActivity.kt
@@ -60,6 +60,7 @@ class ViewContactActivity : ContactActivity() {
}
private fun initContact() {
+ var wasLookupKeyUsed = false
var contactId = intent.getIntExtra(CONTACT_ID, 0)
val action = intent.action
if (contactId == 0 && (action == ContactsContract.QuickContact.ACTION_QUICK_CONTACT || action == Intent.ACTION_VIEW)) {
@@ -69,6 +70,7 @@ class ViewContactActivity : ContactActivity() {
val lookupKey = getLookupKeyFromUri(data)
if (lookupKey != null) {
contact = ContactsHelper(this).getContactWithLookupKey(lookupKey)
+ wasLookupKeyUsed = true
}
getLookupUriRawId(data)
@@ -82,7 +84,7 @@ class ViewContactActivity : ContactActivity() {
}
}
- if (contactId != 0 && contact == null) {
+ if (contactId != 0 && !wasLookupKeyUsed) {
contact = ContactsHelper(this).getContactWithId(contactId, intent.getBooleanExtra(IS_PRIVATE, false))
if (contact == null) {
toast(R.string.unknown_error_occurred)
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt
index 95a04a55..7cc74c73 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ContactsAdapter.kt
@@ -11,28 +11,34 @@ import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.signature.ObjectKey
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
+import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.beVisibleIf
import com.simplemobiletools.commons.extensions.getColoredDrawableWithColor
import com.simplemobiletools.commons.extensions.isActivityDestroyed
+import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.commons.views.FastScroller
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.activities.SimpleActivity
+import com.simplemobiletools.contacts.dialogs.CreateNewGroupDialog
+import com.simplemobiletools.contacts.extensions.addContactsToGroup
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.editContact
import com.simplemobiletools.contacts.extensions.shareContacts
-import com.simplemobiletools.contacts.helpers.ContactsHelper
+import com.simplemobiletools.contacts.helpers.*
import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
+import com.simplemobiletools.contacts.interfaces.RemoveFromGroupListener
import com.simplemobiletools.contacts.models.Contact
import kotlinx.android.synthetic.main.item_contact_with_number.view.*
import java.util.*
-class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList, private val listener: RefreshContactsListener?,
- private val isFavoritesFragment: Boolean, recyclerView: MyRecyclerView, fastScroller: FastScroller, itemClick: (Any) -> Unit) :
+class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList, private val refreshListener: RefreshContactsListener?,
+ private val location: Int, private val removeListener: RemoveFromGroupListener?, recyclerView: MyRecyclerView,
+ fastScroller: FastScroller, itemClick: (Any) -> Unit) :
MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
private lateinit var contactDrawable: Drawable
- var config = activity.config
+ private var config = activity.config
var startNameWithSurname: Boolean
var showContactThumbnails: Boolean
var showPhoneNumbers: Boolean
@@ -52,10 +58,14 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList editContact()
R.id.cab_select_all -> selectAll()
R.id.cab_add_to_favorites -> addToFavorites()
+ R.id.cab_add_to_group -> addToGroup()
R.id.cab_share -> shareContacts()
- R.id.cab_remove -> removeFavorites()
+ R.id.cab_remove -> removeContacts()
R.id.cab_delete -> askConfirmDelete()
}
}
@@ -102,9 +113,11 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList) {
- contactItems = newItems
- notifyDataSetChanged()
- finishActMode()
+ if (newItems.hashCode() != contactItems.hashCode()) {
+ contactItems = newItems
+ notifyDataSetChanged()
+ finishActMode()
+ }
}
private fun editContact() {
@@ -130,38 +143,77 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList()
+ private fun removeContacts() {
+ val contactsToRemove = ArrayList()
selectedPositions.sortedDescending().forEach {
- favoritesToRemove.add(contactItems[it])
+ contactsToRemove.add(contactItems[it])
}
- contactItems.removeAll(favoritesToRemove)
+ contactItems.removeAll(contactsToRemove)
- ContactsHelper(activity).removeFavorites(favoritesToRemove)
- if (contactItems.isEmpty()) {
- listener?.refreshFavorites()
- finishActMode()
- } else {
+ if (location == LOCATION_FAVORITES_TAB) {
+ ContactsHelper(activity).removeFavorites(contactsToRemove)
+ if (contactItems.isEmpty()) {
+ refreshListener?.refreshContacts(FAVORITES_TAB_MASK)
+ finishActMode()
+ } else {
+ removeSelectedItems()
+ }
+ } else if (location == LOCATION_GROUP_CONTACTS) {
+ removeListener?.removeFromGroup(contactsToRemove)
removeSelectedItems()
}
}
private fun addToFavorites() {
val newFavorites = ArrayList()
- selectedPositions.forEach { newFavorites.add(contactItems[it]) }
+ selectedPositions.forEach {
+ newFavorites.add(contactItems[it])
+ }
ContactsHelper(activity).addFavorites(newFavorites)
- listener?.refreshFavorites()
+ refreshListener?.refreshContacts(FAVORITES_TAB_MASK)
finishActMode()
}
+ private fun addToGroup() {
+ val selectedContacts = ArrayList()
+ selectedPositions.forEach {
+ selectedContacts.add(contactItems[it])
+ }
+
+ val NEW_GROUP_ID = -1
+ val items = ArrayList()
+ ContactsHelper(activity).getStoredGroups().forEach {
+ items.add(RadioItem(it.id.toInt(), it.title))
+ }
+ items.add(RadioItem(NEW_GROUP_ID, activity.getString(R.string.create_new_group)))
+
+ RadioGroupDialog(activity, items, 0) {
+ if (it as Int == NEW_GROUP_ID) {
+ CreateNewGroupDialog(activity) {
+ Thread {
+ activity.addContactsToGroup(selectedContacts, it.id)
+ refreshListener?.refreshContacts(GROUPS_TAB_MASK)
+ }.start()
+ finishActMode()
+ }
+ } else {
+ Thread {
+ activity.addContactsToGroup(selectedContacts, it.toLong())
+ refreshListener?.refreshContacts(GROUPS_TAB_MASK)
+ }.start()
+ finishActMode()
+ }
+ }
+ }
+
private fun shareContacts() {
val contactsIDs = ArrayList()
selectedPositions.forEach {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/GroupsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/GroupsAdapter.kt
new file mode 100644
index 00000000..41cd4464
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/GroupsAdapter.kt
@@ -0,0 +1,131 @@
+package com.simplemobiletools.contacts.adapters
+
+import android.view.Menu
+import android.view.View
+import android.view.ViewGroup
+import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
+import com.simplemobiletools.commons.dialogs.ConfirmationDialog
+import com.simplemobiletools.commons.extensions.applyColorFilter
+import com.simplemobiletools.commons.extensions.beVisibleIf
+import com.simplemobiletools.commons.views.FastScroller
+import com.simplemobiletools.commons.views.MyRecyclerView
+import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.activities.SimpleActivity
+import com.simplemobiletools.contacts.dialogs.RenameGroupDialog
+import com.simplemobiletools.contacts.extensions.config
+import com.simplemobiletools.contacts.extensions.dbHelper
+import com.simplemobiletools.contacts.helpers.ContactsHelper
+import com.simplemobiletools.contacts.helpers.GROUPS_TAB_MASK
+import com.simplemobiletools.contacts.interfaces.RefreshContactsListener
+import com.simplemobiletools.contacts.models.Group
+import kotlinx.android.synthetic.main.item_group.view.*
+import java.util.*
+
+class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList, val refreshListener: RefreshContactsListener?, recyclerView: MyRecyclerView,
+ fastScroller: FastScroller, itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
+
+ private var config = activity.config
+ private var smallPadding = activity.resources.getDimension(R.dimen.small_margin).toInt()
+ private var bigPadding = activity.resources.getDimension(R.dimen.normal_margin).toInt()
+
+ var showContactThumbnails = config.showContactThumbnails
+
+ override fun getActionMenuId() = R.menu.cab_groups
+
+ override fun prepareActionMode(menu: Menu) {
+ menu.apply {
+ findItem(R.id.cab_edit).isVisible = isOneItemSelected()
+ }
+ }
+
+ override fun prepareItemSelection(view: View) {}
+
+ override fun markItemSelection(select: Boolean, view: View?) {
+ view?.group_frame?.isSelected = select
+ }
+
+ override fun actionItemPressed(id: Int) {
+ if (selectedPositions.isEmpty()) {
+ return
+ }
+
+ when (id) {
+ R.id.cab_edit -> editGroup()
+ R.id.cab_select_all -> selectAll()
+ R.id.cab_delete -> askConfirmDelete()
+ }
+ }
+
+ override fun getSelectableItemCount() = groups.size
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_group, parent)
+
+ override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
+ val group = groups[position]
+ val view = holder.bindView(group, true) { itemView, layoutPosition ->
+ setupView(itemView, group)
+ }
+ bindViewHolder(holder, position, view)
+ }
+
+ override fun getItemCount() = groups.size
+
+ fun updateItems(newItems: ArrayList) {
+ groups = newItems
+ notifyDataSetChanged()
+ finishActMode()
+ }
+
+ private fun editGroup() {
+ RenameGroupDialog(activity, groups[selectedPositions.first()]) {
+ finishActMode()
+ refreshListener?.refreshContacts(GROUPS_TAB_MASK)
+ }
+ }
+
+ private fun askConfirmDelete() {
+ ConfirmationDialog(activity) {
+ deleteContacts()
+ }
+ }
+
+ private fun deleteContacts() {
+ if (selectedPositions.isEmpty()) {
+ return
+ }
+
+ val groupsToRemove = ArrayList()
+ selectedPositions.sortedDescending().forEach {
+ val group = groups[it]
+ groupsToRemove.add(group)
+ if (group.isPrivateSecretGroup()) {
+ activity.dbHelper.deleteGroup(group.id)
+ } else {
+ ContactsHelper(activity).deleteGroup(group.id)
+ }
+ }
+ groups.removeAll(groupsToRemove)
+
+ if (groups.isEmpty()) {
+ refreshListener?.refreshContacts(GROUPS_TAB_MASK)
+ finishActMode()
+ } else {
+ removeSelectedItems()
+ }
+ }
+
+ private fun setupView(view: View, group: Group) {
+ view.apply {
+ group_name.apply {
+ setTextColor(textColor)
+ text = String.format(activity.getString(R.string.groups_placeholder), group.title, group.contactsCount.toString())
+ setPadding(if (showContactThumbnails) smallPadding else bigPadding, smallPadding, smallPadding, 0)
+ }
+
+ group_tmb.beVisibleIf(showContactThumbnails)
+ if (showContactThumbnails) {
+ group_tmb.applyColorFilter(textColor)
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/SelectContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/SelectContactsAdapter.kt
index b2bc35cb..4e118f24 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/SelectContactsAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/SelectContactsAdapter.kt
@@ -23,7 +23,7 @@ import com.simplemobiletools.contacts.models.Contact
import kotlinx.android.synthetic.main.item_add_favorite_with_number.view.*
import java.util.*
-class SelectContactsAdapter(val activity: SimpleActivity, val contacts: List, private val selectedContacts: ArrayList, private val allowPickMultiple: Boolean,
+class SelectContactsAdapter(val activity: SimpleActivity, val contacts: List, private val selectedContacts: ArrayList, private val allowPickMultiple: Boolean,
private val itemClick: ((Contact) -> Unit)? = null) : RecyclerView.Adapter() {
private val itemViews = SparseArray()
private val selectedPositions = HashSet()
@@ -39,7 +39,7 @@ class SelectContactsAdapter(val activity: SimpleActivity, val contacts: List
- if (selectedContacts.contains(contact.id.toString())) {
+ if (selectedContacts.map { it.id }.contains(contact.id)) {
selectedPositions.add(index)
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt
index 1adebd21..beb17f42 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/adapters/ViewPagerAdapter.kt
@@ -5,7 +5,7 @@ import android.view.View
import android.view.ViewGroup
import com.simplemobiletools.contacts.R
import com.simplemobiletools.contacts.activities.MainActivity
-import com.simplemobiletools.contacts.interfaces.FragmentInterface
+import com.simplemobiletools.contacts.fragments.MyViewPagerFragment
import com.simplemobiletools.contacts.models.Contact
class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList) : PagerAdapter() {
@@ -14,7 +14,7 @@ class ViewPagerAdapter(val activity: MainActivity, val contacts: ArrayList Unit) {
- private var view = activity.layoutInflater.inflate(R.layout.layout_select_contact, null)
- private val config = activity.config
- private var allContacts = ArrayList()
-
- init {
- ContactsHelper(activity).getContacts {
- allContacts = it
-
- val contactSources = config.displayContactSources
- if (!activity.config.showAllContacts()) {
- allContacts = allContacts.filter { contactSources.contains(it.source) } as ArrayList
- }
-
- val favorites = allContacts.filter { it.starred == 1 }.map { it.id.toString() } as ArrayList
-
- Contact.sorting = config.sorting
- allContacts.sort()
-
- activity.runOnUiThread {
- view.apply {
- select_contact_list.adapter = SelectContactsAdapter(activity, allContacts, favorites, true)
- select_contact_fastscroller.allowBubbleDisplay = activity.baseConfig.showInfoBubble
- select_contact_fastscroller.setViews(select_contact_list) {
- select_contact_fastscroller.updateBubbleText(allContacts[it].getBubbleText())
- }
- }
- }
- }
-
- AlertDialog.Builder(activity)
- .setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() })
- .setNegativeButton(R.string.cancel, null)
- .create().apply {
- activity.setupDialogStuff(view, this)
- }
- }
-
- private fun dialogConfirmed() {
- Thread {
- val contactsHelper = ContactsHelper(activity)
- val allDisplayedContacts = ArrayList()
- allContacts.mapTo(allDisplayedContacts, { it })
- val selectedContacts = (view?.select_contact_list?.adapter as? SelectContactsAdapter)?.getSelectedItemsSet() ?: LinkedHashSet()
- val contactsToAdd = selectedContacts.map { it } as ArrayList
- contactsHelper.addFavorites(contactsToAdd)
-
- allDisplayedContacts.removeAll(selectedContacts)
- contactsHelper.removeFavorites(allDisplayedContacts)
-
- callback()
- }.start()
- }
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CreateNewGroupDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CreateNewGroupDialog.kt
index 5897b671..33d9f348 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CreateNewGroupDialog.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/CreateNewGroupDialog.kt
@@ -3,12 +3,17 @@ package com.simplemobiletools.contacts.dialogs
import android.support.v7.app.AlertDialog
import android.view.View
import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.setupDialogStuff
import com.simplemobiletools.commons.extensions.showKeyboard
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.extensions.value
+import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.helpers.ContactsHelper
+import com.simplemobiletools.contacts.helpers.SMT_PRIVATE
+import com.simplemobiletools.contacts.models.ContactSource
import com.simplemobiletools.contacts.models.Group
import kotlinx.android.synthetic.main.dialog_create_new_group.view.*
@@ -29,13 +34,41 @@ class CreateNewGroupDialog(val activity: BaseSimpleActivity, val callback: (newG
return@OnClickListener
}
- val newGroup = ContactsHelper(activity).createNewGroup(name)
- if (newGroup != null) {
- callback(newGroup)
+ val contactSources = ArrayList()
+ if (activity.config.localAccountName.isNotEmpty()) {
+ contactSources.add(ContactSource(activity.config.localAccountName, activity.config.localAccountType))
+ }
+
+ ContactsHelper(activity).getContactSources {
+ it.filter { it.type.contains("google", true) }.mapTo(contactSources, { ContactSource(it.name, it.type) })
+ contactSources.add(ContactSource(activity.getString(R.string.phone_storage_hidden), SMT_PRIVATE))
+
+ val items = ArrayList()
+ contactSources.forEachIndexed { index, contactSource ->
+ items.add(RadioItem(index, contactSource.name))
+ }
+
+ activity.runOnUiThread {
+ if (items.size == 1) {
+ createGroupUnder(name, contactSources.first(), this)
+ } else {
+ RadioGroupDialog(activity, items, titleId = R.string.create_group_under_account) {
+ val contactSource = contactSources[it as Int]
+ createGroupUnder(name, contactSource, this)
+ }
+ }
+ }
}
- dismiss()
})
}
}
}
+
+ 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()
+ }
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/RenameGroupDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/RenameGroupDialog.kt
new file mode 100644
index 00000000..de1f654c
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/RenameGroupDialog.kt
@@ -0,0 +1,49 @@
+package com.simplemobiletools.contacts.dialogs
+
+import android.support.v7.app.AlertDialog
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.extensions.dbHelper
+import com.simplemobiletools.contacts.helpers.ContactsHelper
+import com.simplemobiletools.contacts.models.Group
+import kotlinx.android.synthetic.main.dialog_rename_group.view.*
+
+class RenameGroupDialog(val activity: BaseSimpleActivity, val group: Group, val callback: () -> Unit) {
+ init {
+
+ val view = activity.layoutInflater.inflate(R.layout.dialog_rename_group, null).apply {
+ rename_group_title.setText(group.title)
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this, R.string.rename) {
+ showKeyboard(view.rename_group_title)
+ getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
+ val newTitle = view.rename_group_title.value
+ if (newTitle.isEmpty()) {
+ activity.toast(R.string.empty_name)
+ return@setOnClickListener
+ }
+
+ if (!newTitle.isAValidFilename()) {
+ activity.toast(R.string.invalid_name)
+ return@setOnClickListener
+ }
+
+ group.title = newTitle
+ if (group.isPrivateSecretGroup()) {
+ activity.dbHelper.renameGroup(group)
+ } else {
+ ContactsHelper(activity).renameGroup(group)
+ }
+ callback()
+ dismiss()
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/SelectContactsDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/SelectContactsDialog.kt
new file mode 100644
index 00000000..7f5e259d
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/dialogs/SelectContactsDialog.kt
@@ -0,0 +1,62 @@
+package com.simplemobiletools.contacts.dialogs
+
+import android.support.v7.app.AlertDialog
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.activities.SimpleActivity
+import com.simplemobiletools.contacts.adapters.SelectContactsAdapter
+import com.simplemobiletools.contacts.extensions.config
+import com.simplemobiletools.contacts.models.Contact
+import kotlinx.android.synthetic.main.layout_select_contact.view.*
+
+class SelectContactsDialog(val activity: SimpleActivity, initialContacts: ArrayList, val selectContacts: ArrayList? = null,
+ val callback: (addedContacts: ArrayList, removedContacts: ArrayList) -> Unit) {
+ private var view = activity.layoutInflater.inflate(R.layout.layout_select_contact, null)
+ private var initiallySelectedContacts = ArrayList()
+
+ init {
+ var allContacts = initialContacts
+ if (selectContacts == null) {
+ val contactSources = activity.config.displayContactSources
+ if (!activity.config.showAllContacts()) {
+ allContacts = allContacts.filter { contactSources.contains(it.source) } as ArrayList
+ }
+
+ initiallySelectedContacts = allContacts.filter { it.starred == 1 } as ArrayList
+ } else {
+ initiallySelectedContacts = selectContacts
+ }
+
+ Contact.sorting = activity.config.sorting
+ allContacts.sort()
+
+ activity.runOnUiThread {
+ view.apply {
+ select_contact_list.adapter = SelectContactsAdapter(activity, allContacts, initiallySelectedContacts, true)
+ select_contact_fastscroller.allowBubbleDisplay = activity.baseConfig.showInfoBubble
+ select_contact_fastscroller.setViews(select_contact_list) {
+ select_contact_fastscroller.updateBubbleText(allContacts[it].getBubbleText())
+ }
+ }
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() })
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+
+ private fun dialogConfirmed() {
+ Thread {
+ val adapter = view?.select_contact_list?.adapter as? SelectContactsAdapter
+ val selectedContacts = adapter?.getSelectedItemsSet()?.toList() ?: ArrayList()
+
+ val newlySelectedContacts = selectedContacts.filter { !initiallySelectedContacts.contains(it) } as ArrayList
+ val unselectedContacts = initiallySelectedContacts.filter { !selectedContacts.contains(it) } as ArrayList
+ callback(newlySelectedContacts, unselectedContacts)
+ }.start()
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt
index 3aa0661c..5815f5a1 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/extensions/Activity.kt
@@ -5,6 +5,7 @@ import android.net.Uri
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.sharePathIntent
+import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.PERMISSION_CALL_PHONE
import com.simplemobiletools.commons.models.RadioItem
@@ -93,6 +94,8 @@ fun BaseSimpleActivity.shareContacts(contacts: ArrayList) {
VcfExporter().exportContacts(this, file, contacts) {
if (it == VcfExporter.ExportResult.EXPORT_OK) {
sharePathIntent(file.absolutePath, BuildConfig.APPLICATION_ID)
+ } else {
+ showErrorToast("$it")
}
}
}
@@ -108,3 +111,27 @@ fun BaseSimpleActivity.getTempFile(): File? {
return File(folder, "contacts.vcf")
}
+
+fun BaseSimpleActivity.addContactsToGroup(contacts: ArrayList, groupId: Long) {
+ val publicContacts = contacts.filter { it.source != SMT_PRIVATE }
+ val privateContacts = contacts.filter { it.source == SMT_PRIVATE }
+ if (publicContacts.isNotEmpty()) {
+ ContactsHelper(this).addContactsToGroup(contacts, groupId)
+ }
+
+ if (privateContacts.isNotEmpty()) {
+ dbHelper.addContactsToGroup(contacts, groupId)
+ }
+}
+
+fun BaseSimpleActivity.removeContactsFromGroup(contacts: ArrayList, groupId: Long) {
+ val publicContacts = contacts.filter { it.source != SMT_PRIVATE }
+ val privateContacts = contacts.filter { it.source == SMT_PRIVATE }
+ if (publicContacts.isNotEmpty()) {
+ ContactsHelper(this).removeContactsFromGroup(contacts, groupId)
+ }
+
+ if (privateContacts.isNotEmpty()) {
+ dbHelper.removeContactsFromGroup(contacts, groupId)
+ }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/FavoritesFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/FavoritesFragment.kt
index 104eaa41..a34fba56 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/FavoritesFragment.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/FavoritesFragment.kt
@@ -2,7 +2,10 @@ package com.simplemobiletools.contacts.fragments
import android.content.Context
import android.util.AttributeSet
-import com.simplemobiletools.contacts.dialogs.AddFavoritesDialog
+import com.simplemobiletools.contacts.activities.SimpleActivity
+import com.simplemobiletools.contacts.dialogs.SelectContactsDialog
+import com.simplemobiletools.contacts.helpers.ContactsHelper
+import com.simplemobiletools.contacts.helpers.FAVORITES_TAB_MASK
class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet) {
override fun fabClicked() {
@@ -15,8 +18,13 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa
}
private fun showAddFavoritesDialog() {
- AddFavoritesDialog(activity!!) {
- activity!!.refreshContacts(false, true)
+ SelectContactsDialog(activity!!, allContacts) { addedContacts, removedContacts ->
+ ContactsHelper(activity as SimpleActivity).apply {
+ addFavorites(addedContacts)
+ removeFavorites(removedContacts)
+ }
+
+ activity!!.refreshContacts(FAVORITES_TAB_MASK)
}
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/GroupsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/GroupsFragment.kt
index e52fe7e7..7b40710a 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/GroupsFragment.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/GroupsFragment.kt
@@ -1,22 +1,24 @@
package com.simplemobiletools.contacts.fragments
import android.content.Context
-import android.support.design.widget.CoordinatorLayout
import android.util.AttributeSet
-import com.simplemobiletools.contacts.activities.MainActivity
-import com.simplemobiletools.contacts.interfaces.FragmentInterface
-import com.simplemobiletools.contacts.models.Contact
+import com.simplemobiletools.contacts.activities.SimpleActivity
+import com.simplemobiletools.contacts.dialogs.CreateNewGroupDialog
+import com.simplemobiletools.contacts.helpers.GROUPS_TAB_MASK
-class GroupsFragment(context: Context, attributeSet: AttributeSet) : CoordinatorLayout(context, attributeSet), FragmentInterface {
- override fun setupFragment(activity: MainActivity) {
+class GroupsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet) {
+ override fun fabClicked() {
+ finishActMode()
+ showNewGroupsDialog()
}
- override fun textColorChanged(color: Int) {
+ override fun placeholderClicked() {
+ showNewGroupsDialog()
}
- override fun primaryColorChanged(color: Int) {
- }
-
- override fun refreshContacts(contacts: ArrayList) {
+ private fun showNewGroupsDialog() {
+ CreateNewGroupDialog(activity as SimpleActivity) {
+ activity!!.refreshContacts(GROUPS_TAB_MASK)
+ }
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt
index e273e94c..1adc2d25 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/fragments/MyViewPagerFragment.kt
@@ -1,37 +1,41 @@
package com.simplemobiletools.contacts.fragments
import android.content.Context
+import android.content.Intent
import android.support.design.widget.CoordinatorLayout
import android.util.AttributeSet
import android.view.ViewGroup
+import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.SORT_BY_FIRST_NAME
import com.simplemobiletools.commons.helpers.SORT_BY_SURNAME
import com.simplemobiletools.contacts.R
+import com.simplemobiletools.contacts.activities.GroupContactsActivity
import com.simplemobiletools.contacts.activities.MainActivity
import com.simplemobiletools.contacts.activities.SimpleActivity
import com.simplemobiletools.contacts.adapters.ContactsAdapter
+import com.simplemobiletools.contacts.adapters.GroupsAdapter
import com.simplemobiletools.contacts.extensions.config
import com.simplemobiletools.contacts.extensions.editContact
import com.simplemobiletools.contacts.extensions.tryStartCall
import com.simplemobiletools.contacts.extensions.viewContact
-import com.simplemobiletools.contacts.helpers.Config
-import com.simplemobiletools.contacts.helpers.ON_CLICK_CALL_CONTACT
-import com.simplemobiletools.contacts.helpers.ON_CLICK_EDIT_CONTACT
-import com.simplemobiletools.contacts.helpers.ON_CLICK_VIEW_CONTACT
-import com.simplemobiletools.contacts.interfaces.FragmentInterface
+import com.simplemobiletools.contacts.helpers.*
import com.simplemobiletools.contacts.models.Contact
+import com.simplemobiletools.contacts.models.Group
import kotlinx.android.synthetic.main.fragment_layout.view.*
-abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) : CoordinatorLayout(context, attributeSet), FragmentInterface {
+abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) : CoordinatorLayout(context, attributeSet) {
protected var activity: MainActivity? = null
+ protected var allContacts = ArrayList()
+
private var lastHashCode = 0
private var contactsIgnoringSearch = ArrayList()
private lateinit var config: Config
+ var skipHashComparing = false
var forceListRedraw = false
- override fun setupFragment(activity: MainActivity) {
+ fun setupFragment(activity: MainActivity) {
config = activity.config
if (this.activity == null) {
this.activity = activity
@@ -49,36 +53,51 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
if (this is FavoritesFragment) {
fragment_placeholder.text = activity.getString(R.string.no_favorites)
fragment_placeholder_2.text = activity.getString(R.string.add_favorites)
+ } else if (this is GroupsFragment) {
+ fragment_placeholder.text = activity.getString(R.string.no_group_created)
+ fragment_placeholder_2.text = activity.getString(R.string.create_group)
}
}
}
- override fun textColorChanged(color: Int) {
- (fragment_list.adapter as ContactsAdapter).apply {
- updateTextColor(color)
- initDrawables()
+ fun textColorChanged(color: Int) {
+ if (this is GroupsFragment) {
+ (fragment_list.adapter as GroupsAdapter).updateTextColor(color)
+ } else {
+ (fragment_list.adapter as ContactsAdapter).apply {
+ updateTextColor(color)
+ initDrawables()
+ }
}
}
- override fun primaryColorChanged(color: Int) {
+ fun primaryColorChanged() {
fragment_fastscroller.updatePrimaryColor()
fragment_fastscroller.updateBubblePrimaryColor()
}
fun startNameWithSurnameChanged(startNameWithSurname: Boolean) {
- (fragment_list.adapter as ContactsAdapter).apply {
- config.sorting = if (startNameWithSurname) SORT_BY_SURNAME else SORT_BY_FIRST_NAME
- this@MyViewPagerFragment.activity!!.refreshContacts(true, true)
+ if (this !is GroupsFragment) {
+ (fragment_list.adapter as ContactsAdapter).apply {
+ config.sorting = if (startNameWithSurname) SORT_BY_SURNAME else SORT_BY_FIRST_NAME
+ this@MyViewPagerFragment.activity!!.refreshContacts(CONTACTS_TAB_MASK or FAVORITES_TAB_MASK)
+ }
}
}
- override fun refreshContacts(contacts: ArrayList) {
+ fun refreshContacts(contacts: ArrayList) {
if (config.lastUsedContactSource.isEmpty()) {
val grouped = contacts.groupBy { it.source }.maxWith(compareBy { it.value.size })
config.lastUsedContactSource = grouped?.key ?: ""
}
- val filtered = if (this is FavoritesFragment) {
+ Contact.sorting = config.sorting
+ contacts.sort()
+ allContacts = contacts
+
+ val filtered = if (this is GroupsFragment) {
+ contacts
+ } else if (this is FavoritesFragment) {
contacts.filter { it.starred == 1 } as ArrayList
} else {
val contactSources = config.displayContactSources
@@ -89,10 +108,8 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
}
- Contact.sorting = config.sorting
- filtered.sort()
-
- if (filtered.hashCode() != lastHashCode) {
+ if (filtered.hashCode() != lastHashCode || skipHashComparing) {
+ skipHashComparing = false
lastHashCode = filtered.hashCode()
activity?.runOnUiThread {
setupContacts(filtered)
@@ -101,6 +118,56 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
private fun setupContacts(contacts: ArrayList) {
+ if (this is GroupsFragment) {
+ setupGroupsAdapter(contacts)
+ } else {
+ setupContactsFavoritesAdapter(contacts)
+ }
+ }
+
+ private fun setupGroupsAdapter(contacts: ArrayList) {
+ var storedGroups = ContactsHelper(activity!!).getStoredGroups()
+ contacts.forEach {
+ it.groups.forEach {
+ val group = it
+ val storedGroup = storedGroups.firstOrNull { it.id == group.id }
+ storedGroup?.addContact()
+ }
+ }
+
+ storedGroups = storedGroups.sortedWith(compareBy { it.title }).toMutableList() as ArrayList
+
+ fragment_placeholder_2.beVisibleIf(storedGroups.isEmpty())
+ fragment_placeholder.beVisibleIf(storedGroups.isEmpty())
+ fragment_list.beVisibleIf(storedGroups.isNotEmpty())
+
+ val currAdapter = fragment_list.adapter
+ if (currAdapter == null) {
+ GroupsAdapter(activity as SimpleActivity, storedGroups, activity, fragment_list, fragment_fastscroller) {
+ Intent(activity, GroupContactsActivity::class.java).apply {
+ putExtra(GROUP, it as Group)
+ activity!!.startActivity(this)
+ }
+ }.apply {
+ setupDragListener(true)
+ addVerticalDividers(true)
+ fragment_list.adapter = this
+ }
+
+ fragment_fastscroller.setScrollTo(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)
+ }
+ }
+ }
+
+ private fun setupContactsFavoritesAdapter(contacts: ArrayList) {
fragment_placeholder_2.beVisibleIf(contacts.isEmpty())
fragment_placeholder.beVisibleIf(contacts.isEmpty())
fragment_list.beVisibleIf(contacts.isNotEmpty())
@@ -108,7 +175,8 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
val currAdapter = fragment_list.adapter
if (currAdapter == null || forceListRedraw) {
forceListRedraw = false
- ContactsAdapter(activity as SimpleActivity, contacts, activity, this is FavoritesFragment, fragment_list, fragment_fastscroller) {
+ val location = if (this is FavoritesFragment) LOCATION_FAVORITES_TAB else LOCATION_CONTACTS_TAB
+ ContactsAdapter(activity as SimpleActivity, contacts, activity, location, null, fragment_list, fragment_fastscroller) {
when (config.onContactClick) {
ON_CLICK_CALL_CONTACT -> {
val contact = it as Contact
@@ -143,9 +211,16 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
fun showContactThumbnailsChanged(showThumbnails: Boolean) {
- (fragment_list.adapter as? ContactsAdapter)?.apply {
- showContactThumbnails = showThumbnails
- notifyDataSetChanged()
+ if (this is GroupsFragment) {
+ (fragment_list.adapter as? GroupsAdapter)?.apply {
+ showContactThumbnails = showThumbnails
+ notifyDataSetChanged()
+ }
+ } else {
+ (fragment_list.adapter as? ContactsAdapter)?.apply {
+ showContactThumbnails = showThumbnails
+ notifyDataSetChanged()
+ }
}
}
@@ -154,7 +229,7 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
fun finishActMode() {
- (fragment_list.adapter as? ContactsAdapter)?.finishActMode()
+ (fragment_list.adapter as? MyRecyclerViewAdapter)?.finishActMode()
}
fun onSearchQueryChanged(text: String) {
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt
index 6f104a57..6a7555f6 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/Constants.kt
@@ -13,6 +13,18 @@ const val ON_CONTACT_CLICK = "on_contact_click"
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 LOCATION_CONTACTS_TAB = 0
+const val LOCATION_FAVORITES_TAB = 1
+const val LOCATION_GROUPS_TAB = 2
+const val LOCATION_GROUP_CONTACTS = 3
+
+const val CONTACTS_TAB_MASK = 1
+const val FAVORITES_TAB_MASK = 2
+const val GROUPS_TAB_MASK = 4
+const val ALL_TABS_MASK = 7
// contact photo changes
const val PHOTO_ADDED = 1
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt
index ee8c0980..06c00a22 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/ContactsHelper.kt
@@ -11,6 +11,7 @@ import android.provider.ContactsContract
import android.provider.ContactsContract.CommonDataKinds
import android.provider.ContactsContract.CommonDataKinds.Note
import android.provider.MediaStore
+import android.text.TextUtils
import android.util.SparseArray
import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.*
@@ -101,14 +102,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
contacts[key]?.notes = notes.valueAt(i)
}
- val groups = getContactGroups(getStoredGroups())
- size = groups.size()
- for (i in 0 until size) {
- val key = groups.keyAt(i)
- contacts[key]?.groups = groups.valueAt(i)
- }
-
- activity.dbHelper.getContacts().forEach {
+ activity.dbHelper.getContacts(activity).forEach {
contacts.put(it.id, it)
}
@@ -116,6 +110,15 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
var resultContacts = ArrayList(contactsSize)
(0 until contactsSize).mapTo(resultContacts) { contacts.valueAt(it) }
resultContacts = resultContacts.distinctBy { it.contactId } as ArrayList
+
+ // groups are obtained with contactID, not rawID, so assign them to proper contacts like this
+ val groups = getContactGroups(getStoredGroups())
+ 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 {
callback(resultContacts)
}
@@ -336,12 +339,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val id = cursor.getIntValue(ContactsContract.Data.CONTACT_ID)
val newRowId = cursor.getLongValue(ContactsContract.Data.DATA1)
+ val groupTitle = storedGroups.firstOrNull { it.id == newRowId }?.title ?: continue
+ val group = Group(newRowId, groupTitle)
if (groups[id] == null) {
groups.put(id, ArrayList())
}
-
- val groupTitle = storedGroups.firstOrNull { it.id == newRowId }?.title ?: continue
- val group = Group(newRowId, groupTitle)
groups[id]!!.add(group)
} while (cursor.moveToNext())
}
@@ -359,10 +361,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
val uri = ContactsContract.Groups.CONTENT_URI
val projection = arrayOf(
ContactsContract.Groups._ID,
- ContactsContract.Groups.TITLE
+ ContactsContract.Groups.TITLE,
+ ContactsContract.Groups.SYSTEM_ID
)
- val selection = "${ContactsContract.Groups.AUTO_ADD} = ? AND ${ContactsContract.Groups.FAVORITES} = ? AND ${ContactsContract.Groups.SYSTEM_ID} IS NULL"
+ val selection = "${ContactsContract.Groups.AUTO_ADD} = ? AND ${ContactsContract.Groups.FAVORITES} = ?"
val selectionArgs = arrayOf("0", "0")
var cursor: Cursor? = null
@@ -372,6 +375,12 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
do {
val id = cursor.getLongValue(ContactsContract.Groups._ID)
val title = cursor.getStringValue(ContactsContract.Groups.TITLE)
+
+ val systemId = cursor.getStringValue(ContactsContract.Groups.SYSTEM_ID)
+ if (groups.map { it.title }.contains(title) && systemId != null) {
+ continue
+ }
+
groups.add(Group(id, title))
} while (cursor.moveToNext())
}
@@ -381,17 +390,25 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
cursor?.close()
}
+ groups.addAll(activity.dbHelper.getGroups())
return groups
}
- fun createNewGroup(title: String): Group? {
- try {
- val operations = ArrayList()
- ContentProviderOperation.newInsert(ContactsContract.Groups.CONTENT_URI).apply {
- withValue(ContactsContract.Groups.TITLE, title)
- operations.add(build())
- }
+ fun createNewGroup(title: String, accountName: String, accountType: String): Group? {
+ if (accountType == SMT_PRIVATE) {
+ return activity.dbHelper.insertGroup(Group(0, title))
+ }
+ val operations = ArrayList()
+ ContentProviderOperation.newInsert(ContactsContract.Groups.CONTENT_URI).apply {
+ withValue(ContactsContract.Groups.TITLE, title)
+ withValue(ContactsContract.Groups.GROUP_VISIBLE, 1)
+ withValue(ContactsContract.Groups.ACCOUNT_NAME, accountName)
+ withValue(ContactsContract.Groups.ACCOUNT_TYPE, accountType)
+ operations.add(build())
+ }
+
+ try {
val results = activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
val rawId = ContentUris.parseId(results[0].uri)
return Group(rawId, title)
@@ -401,14 +418,32 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
return null
}
- fun deleteGroup(id: Long) {
- try {
- val operations = ArrayList()
- val uri = ContentUris.withAppendedId(ContactsContract.Groups.CONTENT_URI, id).buildUpon()
- .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
- .build()
+ fun renameGroup(group: Group) {
+ val operations = ArrayList()
+ ContentProviderOperation.newUpdate(ContactsContract.Groups.CONTENT_URI).apply {
+ val selection = "${ContactsContract.Groups._ID} = ?"
+ val selectionArgs = arrayOf(group.id.toString())
+ withSelection(selection, selectionArgs)
+ withValue(ContactsContract.Groups.TITLE, group.title)
+ operations.add(build())
+ }
- operations.add(ContentProviderOperation.newDelete(uri).build())
+ try {
+ activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ }
+ }
+
+ fun deleteGroup(id: Long) {
+ val operations = ArrayList()
+ val uri = ContentUris.withAppendedId(ContactsContract.Groups.CONTENT_URI, id).buildUpon()
+ .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
+ .build()
+
+ operations.add(ContentProviderOperation.newDelete(uri).build())
+
+ try {
activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
} catch (e: Exception) {
activity.showErrorToast(e)
@@ -419,7 +454,7 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
if (id == 0) {
return null
} else if (isLocalPrivate) {
- return activity.dbHelper.getContactWithId(id)
+ return activity.dbHelper.getContactWithId(activity, id)
}
val selection = "${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?"
@@ -489,6 +524,10 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
cursor?.close()
}
+ if (sources.isEmpty() && activity.config.localAccountName.isEmpty() && activity.config.localAccountType.isEmpty()) {
+ sources.add(ContactSource("", ""))
+ }
+
sources.add(ContactSource(activity.getString(R.string.phone_storage_hidden), SMT_PRIVATE))
callback(ArrayList(sources))
}.start()
@@ -563,10 +602,12 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
}
fun updateContact(contact: Contact, photoUpdateStatus: Int): Boolean {
- return if (contact.source == SMT_PRIVATE) {
- activity.dbHelper.update(contact)
- } else try {
- activity.toast(R.string.updating)
+ activity.toast(R.string.updating)
+ if (contact.source == SMT_PRIVATE) {
+ return activity.dbHelper.updateContact(contact)
+ }
+
+ try {
val operations = ArrayList()
ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI).apply {
val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ?"
@@ -664,11 +705,15 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
}
// delete groups
- ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
- val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? "
- val selectionArgs = arrayOf(contact.id.toString(), CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE)
- withSelection(selection, selectionArgs)
- operations.add(build())
+ val relevantGroupIDs = getStoredGroups().map { it.id }
+ if (relevantGroupIDs.isNotEmpty()) {
+ val IDsString = TextUtils.join(",", relevantGroupIDs)
+ ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
+ val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.Data.DATA1} IN ($IDsString)"
+ val selectionArgs = arrayOf(contact.contactId.toString(), CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE)
+ withSelection(selection, selectionArgs)
+ operations.add(build())
+ }
}
// add groups
@@ -698,10 +743,10 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
}
activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
- true
+ return true
} catch (e: Exception) {
activity.showErrorToast(e)
- false
+ return false
}
}
@@ -741,144 +786,170 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
return operations
}
- fun insertContact(contact: Contact): Boolean {
- return if (contact.source == SMT_PRIVATE) {
- insertLocalContact(contact)
- } else {
- try {
- val operations = ArrayList()
- ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI).apply {
- withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.source)
- withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, getContactSourceType(contact.source))
- operations.add(build())
- }
-
- // names
- ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
- withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
- withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
- withValue(CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName)
- withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName)
- withValue(CommonDataKinds.StructuredName.FAMILY_NAME, contact.surname)
- operations.add(build())
- }
-
- // phone numbers
- contact.phoneNumbers.forEach {
- ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
- 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.TYPE, it.type)
- operations.add(build())
- }
- }
-
- // emails
- contact.emails.forEach {
- ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
- withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
- withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE)
- withValue(CommonDataKinds.Email.DATA, it.value)
- withValue(CommonDataKinds.Email.TYPE, it.type)
- operations.add(build())
- }
- }
-
- // addresses
- contact.addresses.forEach {
- ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
- withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
- withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)
- withValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, it.value)
- withValue(CommonDataKinds.StructuredPostal.TYPE, it.type)
- operations.add(build())
- }
- }
-
- // events
- contact.events.forEach {
- ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
- withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
- withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE)
- withValue(CommonDataKinds.Event.START_DATE, it.value)
- withValue(CommonDataKinds.Event.TYPE, it.type)
- operations.add(build())
- }
- }
-
- // notes
- ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
- withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
- withValue(ContactsContract.Data.MIMETYPE, Note.CONTENT_ITEM_TYPE)
- withValue(Note.NOTE, contact.notes)
- operations.add(build())
- }
-
- // groups
- contact.groups.forEach {
- ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
- withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
- withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE)
- withValue(CommonDataKinds.GroupMembership.GROUP_ROW_ID, it.id)
- operations.add(build())
- }
- }
-
- // photo (inspired by https://gist.github.com/slightfoot/5985900)
- var fullSizePhotoData: ByteArray? = null
- var scaledSizePhotoData: ByteArray?
- if (contact.photoUri.isNotEmpty()) {
- val photoUri = Uri.parse(contact.photoUri)
- val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri)
-
- val thumbnailSize = activity.getPhotoThumbnailSize()
- val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false)
- scaledSizePhotoData = scaledPhoto.getByteArray()
-
- fullSizePhotoData = bitmap.getByteArray()
- scaledPhoto.recycle()
- bitmap.recycle()
-
- ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
- withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
- withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
- withValue(CommonDataKinds.Photo.PHOTO, scaledSizePhotoData)
- operations.add(build())
- }
- }
-
- val results: Array
- try {
- results = activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
- } finally {
- scaledSizePhotoData = null
- }
-
- // fullsize photo
- val rawId = ContentUris.parseId(results[0].uri)
- if (contact.photoUri.isNotEmpty() && fullSizePhotoData != null) {
- addFullSizePhoto(rawId, fullSizePhotoData)
- }
-
- // favorite
- val userId = getRealContactId(rawId)
- if (userId != 0 && contact.starred == 1) {
- 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)
- }
-
- true
- } catch (e: Exception) {
- activity.showErrorToast(e)
- false
+ fun addContactsToGroup(contacts: ArrayList, groupId: Long) {
+ val operations = ArrayList()
+ contacts.forEach {
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValue(ContactsContract.Data.RAW_CONTACT_ID, it.id)
+ withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE)
+ withValue(CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId)
+ operations.add(build())
}
}
+ activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ }
+
+ fun removeContactsFromGroup(contacts: ArrayList, groupId: Long) {
+ val operations = ArrayList()
+ contacts.forEach {
+ ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
+ val selection = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.Data.DATA1} = ?"
+ val selectionArgs = arrayOf(it.contactId.toString(), CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE, groupId.toString())
+ withSelection(selection, selectionArgs)
+ operations.add(build())
+ }
+ }
+ activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ }
+
+ fun insertContact(contact: Contact): Boolean {
+ if (contact.source == SMT_PRIVATE) {
+ return insertLocalContact(contact)
+ }
+
+ try {
+ val operations = ArrayList()
+ ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI).apply {
+ withValue(ContactsContract.RawContacts.ACCOUNT_NAME, contact.source)
+ withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, getContactSourceType(contact.source))
+ operations.add(build())
+ }
+
+ // names
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
+ withValue(CommonDataKinds.StructuredName.GIVEN_NAME, contact.firstName)
+ withValue(CommonDataKinds.StructuredName.MIDDLE_NAME, contact.middleName)
+ withValue(CommonDataKinds.StructuredName.FAMILY_NAME, contact.surname)
+ operations.add(build())
+ }
+
+ // phone numbers
+ contact.phoneNumbers.forEach {
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ 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.TYPE, it.type)
+ operations.add(build())
+ }
+ }
+
+ // emails
+ contact.emails.forEach {
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Email.CONTENT_ITEM_TYPE)
+ withValue(CommonDataKinds.Email.DATA, it.value)
+ withValue(CommonDataKinds.Email.TYPE, it.type)
+ operations.add(build())
+ }
+ }
+
+ // addresses
+ contact.addresses.forEach {
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)
+ withValue(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS, it.value)
+ withValue(CommonDataKinds.StructuredPostal.TYPE, it.type)
+ operations.add(build())
+ }
+ }
+
+ // events
+ contact.events.forEach {
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE)
+ withValue(CommonDataKinds.Event.START_DATE, it.value)
+ withValue(CommonDataKinds.Event.TYPE, it.type)
+ operations.add(build())
+ }
+ }
+
+ // notes
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ withValue(ContactsContract.Data.MIMETYPE, Note.CONTENT_ITEM_TYPE)
+ withValue(Note.NOTE, contact.notes)
+ operations.add(build())
+ }
+
+ // groups
+ contact.groups.forEach {
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE)
+ withValue(CommonDataKinds.GroupMembership.GROUP_ROW_ID, it.id)
+ operations.add(build())
+ }
+ }
+
+ // photo (inspired by https://gist.github.com/slightfoot/5985900)
+ var fullSizePhotoData: ByteArray? = null
+ var scaledSizePhotoData: ByteArray?
+ if (contact.photoUri.isNotEmpty()) {
+ val photoUri = Uri.parse(contact.photoUri)
+ val bitmap = MediaStore.Images.Media.getBitmap(activity.contentResolver, photoUri)
+
+ val thumbnailSize = activity.getPhotoThumbnailSize()
+ val scaledPhoto = Bitmap.createScaledBitmap(bitmap, thumbnailSize, thumbnailSize, false)
+ scaledSizePhotoData = scaledPhoto.getByteArray()
+
+ fullSizePhotoData = bitmap.getByteArray()
+ scaledPhoto.recycle()
+ bitmap.recycle()
+
+ ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI).apply {
+ withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
+ withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Photo.CONTENT_ITEM_TYPE)
+ withValue(CommonDataKinds.Photo.PHOTO, scaledSizePhotoData)
+ operations.add(build())
+ }
+ }
+
+ val results: Array
+ try {
+ results = activity.contentResolver.applyBatch(ContactsContract.AUTHORITY, operations)
+ } finally {
+ scaledSizePhotoData = null
+ }
+
+ // fullsize photo
+ val rawId = ContentUris.parseId(results[0].uri)
+ if (contact.photoUri.isNotEmpty() && fullSizePhotoData != null) {
+ addFullSizePhoto(rawId, fullSizePhotoData)
+ }
+
+ // favorite
+ val userId = getRealContactId(rawId)
+ if (userId != 0 && contact.starred == 1) {
+ 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)
+ }
+
+ return true
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ return false
+ }
}
- private fun insertLocalContact(contact: Contact) = activity.dbHelper.insert(contact)
+ 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)
@@ -909,11 +980,11 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
return ""
}
- fun getContactDataId(contactId: String): String {
+ fun getContactMimeTypeId(contactId: String, mimeType: String): String {
val uri = ContactsContract.Data.CONTENT_URI
val projection = arrayOf(ContactsContract.Data._ID, ContactsContract.Data.RAW_CONTACT_ID, ContactsContract.Data.MIMETYPE)
val selection = "${ContactsContract.Data.MIMETYPE} = ? AND ${ContactsContract.Data.RAW_CONTACT_ID} = ?"
- val selectionArgs = arrayOf(CommonDataKinds.Email.CONTENT_ITEM_TYPE, contactId)
+ val selectionArgs = arrayOf(mimeType, contactId)
var cursor: Cursor? = null
try {
@@ -979,10 +1050,10 @@ class ContactsHelper(val activity: BaseSimpleActivity) {
try {
val contactIDs = HashSet()
val operations = ArrayList()
- val selection = "${ContactsContract.Data.RAW_CONTACT_ID} = ?"
+ val selection = "${ContactsContract.Data.CONTACT_ID} = ?"
contacts.filter { it.source != SMT_PRIVATE }.forEach {
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {
- val selectionArgs = arrayOf(it.id.toString())
+ val selectionArgs = arrayOf(it.contactId.toString())
withSelection(selection, selectionArgs)
operations.add(this.build())
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt
index 965c5256..0067b33e 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/helpers/DBHelper.kt
@@ -11,8 +11,10 @@ import android.provider.MediaStore
import android.text.TextUtils
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
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.extensions.getByteArray
import com.simplemobiletools.contacts.extensions.getPhotoThumbnailSize
@@ -31,13 +33,17 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
private val COL_STARRED = "starred"
private val COL_ADDRESSES = "addresses"
private val COL_NOTES = "notes"
+ private val COL_GROUPS = "groups"
+
+ private val GROUPS_TABLE_NAME = "groups"
+ private val COL_TITLE = "title"
private val FIRST_CONTACT_ID = 1000000
private val mDb = writableDatabase
companion object {
- private const val DB_VERSION = 2
+ private const val DB_VERSION = 3
const val DB_NAME = "contacts.db"
var dbInstance: DBHelper? = null
@@ -52,10 +58,12 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
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_ADDRESSES TEXT, $COL_NOTES TEXT, $COL_GROUPS 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) {
@@ -63,15 +71,27 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
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 ''")
+ }
}
- fun insert(contact: Contact): Boolean {
+ 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 update(contact: Contact): Boolean {
+ fun updateContact(contact: Contact): Boolean {
val contactValues = fillContactValues(contact)
val selection = "$COL_ID = ?"
val selectionArgs = arrayOf(contact.id.toString())
@@ -97,6 +117,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
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 }))
if (contact.photoUri.isNotEmpty()) {
put(COL_PHOTO, getPhotoByteArray(contact.photoUri))
@@ -126,10 +147,87 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
mDb.update(CONTACTS_TABLE_NAME, contactValues, selection, null)
}
- fun getContacts(selection: String? = null, selectionArgs: Array? = null): ArrayList {
+ 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()))
+
+ fun deleteGroups(ids: Array) {
+ val args = TextUtils.join(", ", ids)
+ val selection = "$GROUPS_TABLE_NAME.$COL_ID IN ($args)"
+ mDb.delete(GROUPS_TABLE_NAME, selection, null)
+ }
+
+ fun getGroups(): ArrayList {
+ val groups = ArrayList()
+ val projection = arrayOf(COL_ID, COL_TITLE)
+ val cursor = mDb.query(GROUPS_TABLE_NAME, projection, null, null, null, null, null)
+ cursor.use {
+ while (cursor.moveToNext()) {
+ val id = cursor.getLongValue(COL_ID)
+ val title = cursor.getStringValue(COL_TITLE)
+ val group = Group(id, title)
+ groups.add(group)
+ }
+ }
+ return groups
+ }
+
+ private fun fillGroupValues(group: Group): ContentValues {
+ return ContentValues().apply {
+ put(COL_TITLE, group.title)
+ }
+ }
+
+ fun addContactsToGroup(contacts: ArrayList, groupId: Long) {
+ contacts.forEach {
+ val currentGroupIds = it.groups.map { it.id } as ArrayList
+ currentGroupIds.add(groupId)
+ updateContactGroups(it, currentGroupIds)
+ }
+ }
+
+ fun removeContactsFromGroup(contacts: ArrayList, groupId: Long) {
+ contacts.forEach {
+ val currentGroupIds = it.groups.map { it.id } as ArrayList
+ currentGroupIds.remove(groupId)
+ updateContactGroups(it, currentGroupIds)
+ }
+ }
+
+ fun updateContactGroups(contact: Contact, groupIds: ArrayList) {
+ val contactValues = fillContactGroupValues(groupIds)
+ val selection = "$COL_ID = ?"
+ val selectionArgs = arrayOf(contact.id.toString())
+ mDb.update(CONTACTS_TABLE_NAME, contactValues, selection, selectionArgs)
+ }
+
+ private fun fillContactGroupValues(groupIds: ArrayList): ContentValues {
+ return ContentValues().apply {
+ put(COL_GROUPS, Gson().toJson(groupIds))
+ }
+ }
+
+
+ fun getContacts(activity: BaseSimpleActivity, selection: String? = null, selectionArgs: Array? = null): ArrayList {
+ val storedGroups = ContactsHelper(activity).getStoredGroups()
val contacts = ArrayList()
val projection = arrayOf(COL_ID, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_PHONE_NUMBERS, COL_EMAILS, COL_EVENTS, COL_STARRED,
- COL_PHOTO, COL_ADDRESSES, COL_NOTES)
+ COL_PHOTO, COL_ADDRESSES, COL_NOTES, COL_GROUPS)
val cursor = mDb.query(CONTACTS_TABLE_NAME, projection, selection, selectionArgs, null, null, null)
cursor.use {
while (cursor.moveToNext()) {
@@ -163,7 +261,11 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
val notes = cursor.getStringValue(COL_NOTES)
val starred = cursor.getIntValue(COL_STARRED)
- val groups = ArrayList()
+
+ val groupIdsJson = cursor.getStringValue(COL_GROUPS)
+ val groupIdsToken = object : TypeToken>() {}.type
+ val groupIds = Gson().fromJson>(groupIdsJson, groupIdsToken) ?: ArrayList(1)
+ val groups = storedGroups.filter { groupIds.contains(it.id) } as ArrayList
val contact = Contact(id, firstName, middleName, surname, "", phoneNumbers, emails, addresses, events, SMT_PRIVATE, starred, id, "", photo, notes, groups)
contacts.add(contact)
@@ -172,9 +274,9 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
return contacts
}
- fun getContactWithId(id: Int): Contact? {
+ fun getContactWithId(activity: BaseSimpleActivity, id: Int): Contact? {
val selection = "$COL_ID = ?"
val selectionArgs = arrayOf(id.toString())
- return getContacts(selection, selectionArgs).firstOrNull()
+ return getContacts(activity, selection, selectionArgs).firstOrNull()
}
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/FragmentInterface.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/FragmentInterface.kt
deleted file mode 100644
index 0f49e205..00000000
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/FragmentInterface.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.simplemobiletools.contacts.interfaces
-
-import com.simplemobiletools.contacts.activities.MainActivity
-import com.simplemobiletools.contacts.models.Contact
-
-interface FragmentInterface {
- fun setupFragment(activity: MainActivity)
-
- fun textColorChanged(color: Int)
-
- fun primaryColorChanged(color: Int)
-
- fun refreshContacts(contacts: ArrayList)
-}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/RefreshContactsListener.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/RefreshContactsListener.kt
index 7819aa1f..817e6074 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/RefreshContactsListener.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/RefreshContactsListener.kt
@@ -1,7 +1,5 @@
package com.simplemobiletools.contacts.interfaces
interface RefreshContactsListener {
- fun refreshContacts(refreshContactsTab: Boolean, refreshFavoritesTab: Boolean)
-
- fun refreshFavorites()
+ fun refreshContacts(refreshTabsMask: Int)
}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/RemoveFromGroupListener.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/RemoveFromGroupListener.kt
new file mode 100644
index 00000000..c435180a
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/interfaces/RemoveFromGroupListener.kt
@@ -0,0 +1,7 @@
+package com.simplemobiletools.contacts.interfaces
+
+import com.simplemobiletools.contacts.models.Contact
+
+interface RemoveFromGroupListener {
+ fun removeFromGroup(contacts: ArrayList)
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Group.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Group.kt
index dbd23558..1b53d894 100644
--- a/app/src/main/kotlin/com/simplemobiletools/contacts/models/Group.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/contacts/models/Group.kt
@@ -1,3 +1,16 @@
package com.simplemobiletools.contacts.models
-data class Group(var id: Long, var title: String)
+import com.simplemobiletools.contacts.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
+ }
+
+ fun addContact() = contactsCount++
+
+ fun getBubbleText() = title
+
+ fun isPrivateSecretGroup() = id >= FIRST_GROUP_ID
+}
diff --git a/app/src/main/res/drawable-hdpi/ic_group_add.png b/app/src/main/res/drawable-hdpi/ic_group_add.png
new file mode 100644
index 00000000..af01bae4
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_group_add.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_group_add.png b/app/src/main/res/drawable-xhdpi/ic_group_add.png
new file mode 100644
index 00000000..a769931c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_group_add.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_group_add.png b/app/src/main/res/drawable-xxhdpi/ic_group_add.png
new file mode 100644
index 00000000..f4acdf46
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_group_add.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_group_add.png b/app/src/main/res/drawable-xxxhdpi/ic_group_add.png
new file mode 100644
index 00000000..fadcc0f9
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_group_add.png differ
diff --git a/app/src/main/res/layout/activity_edit_contact.xml b/app/src/main/res/layout/activity_edit_contact.xml
index 66cbe244..f7a2fa11 100644
--- a/app/src/main/res/layout/activity_edit_contact.xml
+++ b/app/src/main/res/layout/activity_edit_contact.xml
@@ -333,7 +333,7 @@
android:layout_toRightOf="@+id/contact_name_image"
android:orientation="vertical">
-
+
diff --git a/app/src/main/res/layout/activity_group_contacts.xml b/app/src/main/res/layout/activity_group_contacts.xml
new file mode 100644
index 00000000..197db89c
--- /dev/null
+++ b/app/src/main/res/layout/activity_group_contacts.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_rename_group.xml b/app/src/main/res/layout/dialog_rename_group.xml
new file mode 100644
index 00000000..974d2b59
--- /dev/null
+++ b/app/src/main/res/layout/dialog_rename_group.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_groups.xml b/app/src/main/res/layout/fragment_groups.xml
index 02751c10..75390472 100644
--- a/app/src/main/res/layout/fragment_groups.xml
+++ b/app/src/main/res/layout/fragment_groups.xml
@@ -5,4 +5,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+
+
diff --git a/app/src/main/res/layout/item_add_favorite_with_number.xml b/app/src/main/res/layout/item_add_favorite_with_number.xml
index 7747f757..c8544807 100644
--- a/app/src/main/res/layout/item_add_favorite_with_number.xml
+++ b/app/src/main/res/layout/item_add_favorite_with_number.xml
@@ -19,6 +19,7 @@
android:id="@+id/contact_tmb"
android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size"
+ android:layout_centerVertical="true"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_person"/>
diff --git a/app/src/main/res/layout/item_add_favorite_without_number.xml b/app/src/main/res/layout/item_add_favorite_without_number.xml
index 30569bad..a9ecb39d 100644
--- a/app/src/main/res/layout/item_add_favorite_without_number.xml
+++ b/app/src/main/res/layout/item_add_favorite_without_number.xml
@@ -19,6 +19,7 @@
android:id="@+id/contact_tmb"
android:layout_width="@dimen/normal_icon_size"
android:layout_height="@dimen/normal_icon_size"
+ android:layout_centerVertical="true"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_person"/>
diff --git a/app/src/main/res/layout/item_edit_group.xml b/app/src/main/res/layout/item_edit_group.xml
new file mode 100644
index 00000000..5a5620e1
--- /dev/null
+++ b/app/src/main/res/layout/item_edit_group.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_group.xml b/app/src/main/res/layout/item_group.xml
index 5a5620e1..d8106772 100644
--- a/app/src/main/res/layout/item_group.xml
+++ b/app/src/main/res/layout/item_group.xml
@@ -1,41 +1,39 @@
-
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true"
+ android:foreground="@drawable/selector">
-
+ android:paddingRight="@dimen/activity_margin">
-
+
-
+
+
+
+
diff --git a/app/src/main/res/menu/cab.xml b/app/src/main/res/menu/cab.xml
index c727d9bd..1e6fe78b 100644
--- a/app/src/main/res/menu/cab.xml
+++ b/app/src/main/res/menu/cab.xml
@@ -16,6 +16,11 @@
android:icon="@drawable/ic_star_on"
android:title="@string/add_to_favorites"
app:showAsAction="ifRoom"/>
+
-
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index bda8c04a..28f5755a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -6,8 +6,6 @@
Aktualisiere…
Gerätespeicher
Gerätespeicher (nicht sichtbar für andere Apps)
- No groups
- Create a new group
Neuer Kontakt
Kontakt bearbeiten
@@ -17,6 +15,17 @@
Zweiter Vorname
Familienname
+
+ No groups
+ Create a new group
+ Remove from group
+ This group is empty
+ Add contacts
+ There are no contact groups on the device
+ Create group
+ Add to group
+ Create group under account
+
Foto machen
Foto auswählen
@@ -80,7 +89,7 @@
Beinhaltet keine Werbung oder unnötige Berechtigungen. Sie ist komplett Open Source, alle verwendeten Farben sind anpassbar.
- Diese App ist nur eine aus einer größeren Serie von schlichten Apps. Der Rest davon findet sich auf http://www.simplemobiletools.com
+ Diese App ist nur eine aus einer größeren Serie von schlichten Apps. Der Rest davon findet sich auf https://www.simplemobiletools.com
+ Δεν υπάρχουν ομάδες
+ Δημιουργία νέας ομάδας
+ Αφαίρεση από ομάδα
+ Η ομάδα είναι άδεια
+ Προσθήκη επαφών
+ Δεν υπάρχουν ομάδες επαφών στη συσκευή
+ Δημιουργία ομάδας
+ Προσθήκη σε ομάδα
+ Δημιουργία ομάδας κάτω από λογαριασμό
+
+
+ Λήψη φωτογραφίας
+ Επιλογή φωτογραφίας
+ Αφαίρεση φωτογραφίας
+
+
+ Το όνομα ξεκινά με το επώνυμο
+ Εμφάνιση τηλεφωνικών αριθμών στην κύρια οθόνη
+ Εμφάνιση μικρογραφιών επαφής
+ Στην επιλογή επαφής
+ Κλήση επαφής
+ Εμφάνιση λεπτομερειών επαφής
+ Εμφάνιση καρτέλας αγαπημένων
+ Εμφάνιση καρτέλας ομάδων
+
+
+ Email
+ Σπίτι
+ Εργασία
+ Άλλο
+
+
+ Αριθμός
+ Κινητό
+ Κύριο
+ Φαξ εργασίας
+ Φαξ σπιτιού
+ Βομβητής
+ Δεν βρέθηκε τηλεφωνικός αριθμός
+
+
+ Γενέθλια
+ Επέτειος
+
+
+ Φαίνεται ότι δεν έχεις προσθέσει αγαπημένες επαφές ακόμα.
+ Προσθήκη αγαπημένων
+ Προσθήκη στα αγαπημένα
+ Αφαίρεση από τα αγαπημένα
+
+
+ Αναζήτηση επαφών
+ Αναζήτηση αγαπημένων
+
+
+ Εισαγωγή επαφών
+ Εξαγωγή επαφών
+ Εισαγωγή επαφών από .vcf αρχείο
+ Εξαγωγή επαφών σε .vcf αρχείο
+ Πηγή επαφής προορισμού
+ Συμπερίληψη πηγών επαφών
+ Όνομα αρχείου (χωρίς .vcf)
+
+
+
+ Μια εφαρμογή επαφών για να διαχειρίζεσαι τις επαφές σου χωρίς διαφημίσεις.
+
+ Μια απλή εφαρμογή για δημιουργία και διαχείριση των επαφών σου από κάθε πηγή. Οι επαφές μπορεί να είναι αποθηκευμένες μόνο στη συσκευή σου, αλλά μπορούν να συγχρονίζονται στο Google, ή σε κάποιο άλλο λογαριασμό. Μπορείς να εμφανίσεις τις αγαπημένες σου επαφές σε ξεχωριστή λίστα.
+
+ Μπορείς να τη χρησιμοποιήσεις για τη διαχείριση των email των χρηστών και τα γεγονότα. Έχει τη δυνατότητα ταξινόμησης/φιλτραρίσματος με διάφορες παραμέτρους, προαιρετικά να εμφανίζεται το επώνυμο σαν όνομα.
+
+ Δεν περιέχει διαφημίσεις ή περιττές άδειες. Είναι πλήρως ανοικτού κώδικα, παρέχει δυνατότητα προσαρμογής των χρωμάτων.
+
+ Αυτή η εφαρμογή είναι ένα μικρό κομμάτι μιας μεγαλύτερης συλλογής εφαρμογών. Μπορείς να βρεις τις υπόλοιπες στο https://www.simplemobiletools.com
+
+
+
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index b6e38037..65a677e0 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -6,8 +6,6 @@
Mise à jour…
Stockage du téléphone
Stockage du téléphone (non visible par d\'autres applis)
- No groups
- Create a new group
Nouveau contact
Modifier contact
@@ -17,6 +15,17 @@
Nom
Surnom
+
+ No groups
+ Create a new group
+ Remove from group
+ This group is empty
+ Add contacts
+ There are no contact groups on the device
+ Create group
+ Add to group
+ Create group under account
+
Prendre une photo
Choisir une photo
@@ -80,7 +89,7 @@
Aucune publicité ni de permission inutile. Elle est entièrement open source et vous permet de personnaliser les couleurs.
- Cette application fait parti d\'un groupe d\'applications. Vous pouvez trouver le reste des applis sur http://www.simplemobiletools.com
+ Cette application fait parti d\'un groupe d\'applications. Vous pouvez trouver le reste des applis sur https://www.simplemobiletools.com
+ No groups
+ Create a new group
+ Remove from group
+ This group is empty
+ Add contacts
+ There are no contact groups on the device
+ Create group
+ Add to group
+ Create group under account
+
사진 촬영
사진 선택
@@ -80,7 +89,7 @@
광고가 포함되어 있거나, 불필요한 권한을 요청하지 않습니다. 이 앱의 모든 소스는 오픈소스이며, 사용자가 직접 애플리케이션의 컬러를 설정 할 수 있습니다.
- 이 앱은 다양한 시리즈의 모바일앱 중 하나입니다. 나머지는 http://www.simplemobiletools.com 에서 찾아 보실 수 있습니다.
+ 이 앱은 다양한 시리즈의 모바일앱 중 하나입니다. 나머지는 https://www.simplemobiletools.com 에서 찾아 보실 수 있습니다.
+ Nėra grupių
+ Sukurti naują grupę
+ Remove from group
+ This group is empty
+ Add contacts
+ There are no contact groups on the device
+ Create group
+ Add to group
+ Create group under account
+
+
+ Nufotografuoti
+ Pasirinkti nuotrauką
+ Pašalinti nuotrauką
+
+
+ Pavardė rodoma pirma
+ Rodyti telefono numerius pagrindiniame programos ekrane
+ Rodyti kontaktų miniatiūras
+ Ant kontakto paspaudimo
+ Skambinti kontaktui
+ Žiūrėti kontakto detales
+ Rodyti mėgiamiausiųjų skirtuką
+ Rodyti grupių skirtuką
+
+
+ Elektroninis paštas
+ Namų
+ Darbo
+ Kitas
+
+
+ Numeris
+ Mobilus
+ Pagrindinis
+ Darbo faksas
+ Namų faksas
+ Pranešimų gaviklis
+ Nerasta telefono numerio
+
+
+ Gimtadienis
+ Sukaktis
+
+
+ Atrodo jog Jūs dar neįvedėte nė vieno mėgiamiausiojo kontakto.
+ Pridėti mėgiamiausiuosius
+ Pridėti į mėgiamiausiuosius
+ Pašalinti iš mėgiamiausiųjų
+
+
+ Ieškoti kontaktų
+ Ieškoti mėgiamiausiųjų
+
+
+ Importuoti kontaktus
+ Eksportuoti kontaktus
+ Importuoti kontaktus iš .vcf bylos
+ Eksportuoti kontaktus į .vcf bylą
+ Tikslinis kontakto šaltinis
+ Įtraukti kontaktų šaltinius
+ Bylos vardas (be .vcf)
+
+
+
+ Kontaktų programėlė įrenginio kontaktų tvarkymui, be reklamų.
+
+ Paprasta programėlė įrenginio kontaktų kūrimui ir tvarkymui iš įvairių šaltinių. Kontaktai gali būti saugomi Jūsų įrenginyje, taip pat sinchronizuojami per Google, ar kitas paskyras. Jūs galite matyti mėgiamiausiuosius kontaktus atskirame sąraše.
+
+ Jūs taip pat galite naudoti programėlę elektroninių paštų adresų tvarkymui. Programėlė turi galimybę rikiuoti/filtruoti pagal įvairius parametrus, taip pat rodyti pavardę pirma vardo.
+
+ Neturi reklamų ar nereikalingų leidimų. Programėlė visiškai atviro kodo, yra galimybė keisti spalvas.
+
+ Ši programėle yra vienintelė iš keletos mūsų programėlių. Likusias Jūs galite rasti čia https://www.simplemobiletools.com
+
+
+
+
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 2d19395b..8c1521ee 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -6,8 +6,6 @@
A atualizar…
Armazenamento do telefone
Armazenamento do telefone (não visível por outras alicações)
- No groups
- Create a new group
Novo contacto
Editar contacto
@@ -17,6 +15,17 @@
Segundo nome
Apelido
+
+ No groups
+ Create a new group
+ Remove from group
+ This group is empty
+ Add contacts
+ There are no contact groups on the device
+ Create group
+ Add to group
+ Create group under account
+
Tirar foto
Escolher foto
@@ -80,7 +89,7 @@
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
- This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com
+ This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com
+ Нет групп
+ Создать новую группу
+ Remove from group
+ This group is empty
+ Add contacts
+ There are no contact groups on the device
+ Create group
+ Add to group
+ Create group under account
+
Снять фото
Выбрать фото
@@ -80,7 +89,7 @@
Не содержит рекламы или ненужных разрешений, полностью с открытым исходным кодом. Есть возможность настраивать цвета.
- Это приложение — всего лишь частица из большой серии приложений. Вы можете найти остальные на http://www.simplemobiletools.com
+ Это приложение — всего лишь частица из большой серии приложений. Вы можете найти остальные на https://www.simplemobiletools.com
+ Žiadne skupiny
+ Vytvoriť novú skupinu
+ Odstrániť zo skupiny
+ Skupina je prázdna
+ Pridať kontakty
+ Nemáte v zariadení vytvorené žiadne skupiny kontaktov
+ Vytvoriť skupinu
+ Pridať do skupiny
+ Vytvoriť skupinu pod účet
+
Vytvoriť foto
Zvoliť foto
@@ -80,7 +89,7 @@
Neobsahuje žiadne reklamy a nepotrebné oprávnenia. Je opensource, poskytuje možnosť zmeny farieb.
- Táto aplikácia je iba jednou zo skupiny aplikácií. Ostatné viete nájsť na http://www.simplemobiletools.com
+ Táto aplikácia je iba jednou zo skupiny aplikácií. Ostatné viete nájsť na https://www.simplemobiletools.com
+ No groups
+ Create a new group
+ Remove from group
+ This group is empty
+ Add contacts
+ There are no contact groups on the device
+ Create group
+ Add to group
+ Create group under account
+
Ta foto
Välj foto
@@ -80,7 +89,7 @@
Innehåller ingen reklam eller onödiga behörigheter. Den har helt öppen källkod och anpassningsbara färger.
- Denna app är bara en del av en större serie appar. Du hittar resten av dem på http://www.simplemobiletools.com
+ Denna app är bara en del av en större serie appar. Du hittar resten av dem på https://www.simplemobiletools.com
+ 沒有群組
+ 建立一個新群組
+ 從群組內移除
+ 這群組是空白的
+ 添加聯絡人
+ 裝置內沒有聯絡人群組
+ 建立群組
+ 添加到群組
+ Create group under account
+
拍照
選擇相片
@@ -80,7 +89,7 @@
不包含廣告及非必要的權限,而且完全開放原始碼,並提供自訂顏色。
- 這程式只是一系列眾多應用程式的其中一項,你可以在這發現更多 http://www.simplemobiletools.com
+ 這程式只是一系列眾多應用程式的其中一項,你可以在這發現更多 https://www.simplemobiletools.com
+ No groups
+ Create a new group
+ Remove from group
+ This group is empty
+ Add contacts
+ There are no contact groups on the device
+ Create group
+ Add to group
+ Create group under account
+
Take photo
Choose photo
@@ -80,7 +89,7 @@
Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
- This app is just one piece of a bigger series of apps. You can find the rest of them at http://www.simplemobiletools.com
+ This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com