add support for local groups

This commit is contained in:
tibbi 2018-11-06 20:12:11 +01:00
parent 02a7d76196
commit 93cf31b2f2
16 changed files with 170 additions and 123 deletions

View File

@ -540,7 +540,7 @@ class EditContactActivity : ContactActivity() {
applyColorFilter(getAdjustedPrimaryColor())
background.applyColorFilter(config.textColor)
setOnClickListener {
removeGroup(group.id)
removeGroup(group.id!!)
}
}
}

View File

@ -70,8 +70,8 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh
private fun fabClicked() {
SelectContactsDialog(this, allContacts, groupContacts) { addedContacts, removedContacts ->
Thread {
addContactsToGroup(addedContacts, group.id)
removeContactsFromGroup(removedContacts, group.id)
addContactsToGroup(addedContacts, group.id!!)
removeContactsFromGroup(removedContacts, group.id!!)
refreshContacts()
}.start()
}
@ -129,7 +129,7 @@ class GroupContactsActivity : SimpleActivity(), RemoveFromGroupListener, Refresh
override fun removeFromGroup(contacts: ArrayList<Contact>) {
Thread {
removeContactsFromGroup(contacts, group.id)
removeContactsFromGroup(contacts, group.id!!)
if (groupContacts.size == 0) {
refreshContacts()
}

View File

@ -40,14 +40,18 @@ class ViewContactActivity : ContactActivity() {
if (isViewIntent) {
handlePermission(PERMISSION_READ_CONTACTS) {
if (it) {
initContact()
Thread {
initContact()
}.start()
} else {
toast(R.string.no_contacts_permission)
finish()
}
}
} else {
initContact()
Thread {
initContact()
}.start()
}
}
@ -99,24 +103,24 @@ class ViewContactActivity : ContactActivity() {
}
if (contactId != 0 && !wasLookupKeyUsed) {
Thread {
contact = ContactsHelper(this).getContactWithId(contactId, intent.getBooleanExtra(IS_PRIVATE, false))
if (contact == null) {
if (!wasEditLaunched) {
toast(R.string.unknown_error_occurred)
}
finish()
} else {
runOnUiThread {
gotContact()
}
contact = ContactsHelper(this).getContactWithId(contactId, intent.getBooleanExtra(IS_PRIVATE, false))
if (contact == null) {
if (!wasEditLaunched) {
toast(R.string.unknown_error_occurred)
}
}.start()
finish()
} else {
runOnUiThread {
gotContact()
}
}
} else {
if (contact == null) {
finish()
} else {
gotContact()
runOnUiThread {
gotContact()
}
}
}
}

View File

@ -34,6 +34,7 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList<Cont
private val location: Int, private val removeListener: RemoveFromGroupListener?, recyclerView: MyRecyclerView,
fastScroller: FastScroller, highlightText: String = "", itemClick: (Any) -> Unit) :
MyRecyclerViewAdapter(activity, recyclerView, fastScroller, itemClick) {
private val NEW_GROUP_ID = -1
private lateinit var contactDrawable: Drawable
private lateinit var businessContactDrawable: Drawable
@ -198,19 +199,23 @@ class ContactsAdapter(activity: SimpleActivity, var contactItems: ArrayList<Cont
}
private fun addToGroup() {
val selectedContacts = getSelectedItems()
val NEW_GROUP_ID = -1
val items = ArrayList<RadioItem>()
ContactsHelper(activity).getStoredGroups().forEach {
items.add(RadioItem(it.id.toInt(), it.title))
ContactsHelper(activity).getStoredGroups {
it.forEach {
items.add(RadioItem(it.id!!.toInt(), it.title))
items.add(RadioItem(NEW_GROUP_ID, activity.getString(R.string.create_new_group)))
}
showGroupsPicker(items)
}
items.add(RadioItem(NEW_GROUP_ID, activity.getString(R.string.create_new_group)))
}
RadioGroupDialog(activity, items, 0) {
private fun showGroupsPicker(radioItems: ArrayList<RadioItem>) {
val selectedContacts = getSelectedItems()
RadioGroupDialog(activity, radioItems, 0) {
if (it as Int == NEW_GROUP_ID) {
CreateNewGroupDialog(activity) {
Thread {
activity.addContactsToGroup(selectedContacts, it.id)
activity.addContactsToGroup(selectedContacts, it.id!!.toLong())
refreshListener?.refreshContacts(GROUPS_TAB_MASK)
}.start()
finishActMode()

View File

@ -59,7 +59,7 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList<Group>, val
override fun getItemSelectionKey(position: Int) = groups.getOrNull(position)?.id?.toInt()
override fun getItemKeyPosition(key: Int) = groups.indexOfFirst { it.id.toInt() == key }
override fun getItemKeyPosition(key: Int) = groups.indexOfFirst { it.id!!.toInt() == key }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_group, parent)
@ -73,7 +73,7 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList<Group>, val
override fun getItemCount() = groups.size
private fun getItemWithKey(key: Int): Group? = groups.firstOrNull { it.id.toInt() == key }
private fun getItemWithKey(key: Int): Group? = groups.firstOrNull { it.id!!.toInt() == key }
fun updateItems(newItems: ArrayList<Group>) {
groups = newItems
@ -101,13 +101,13 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList<Group>, val
return
}
val groupsToRemove = groups.filter { selectedKeys.contains(it.id.toInt()) } as ArrayList<Group>
val groupsToRemove = groups.filter { selectedKeys.contains(it.id!!.toInt()) } as ArrayList<Group>
val positions = getSelectedItemPositions()
groupsToRemove.forEach {
if (it.isPrivateSecretGroup()) {
activity.dbHelper.deleteGroup(it.id)
activity.dbHelper.deleteGroup(it.id!!)
} else {
ContactsHelper(activity).deleteGroup(it.id)
ContactsHelper(activity).deleteGroup(it.id!!)
}
}
groups.removeAll(groupsToRemove)
@ -122,7 +122,7 @@ class GroupsAdapter(activity: SimpleActivity, var groups: ArrayList<Group>, val
private fun setupView(view: View, group: Group) {
view.apply {
group_frame?.isSelected = selectedKeys.contains(group.id.toInt())
group_frame?.isSelected = selectedKeys.contains(group.id!!.toInt())
group_name.apply {
setTextColor(textColor)
text = String.format(activity.getString(R.string.groups_placeholder), group.title, group.contactsCount.toString())

View File

@ -7,18 +7,23 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase
import com.simplemobiletools.contacts.pro.helpers.Converters
import com.simplemobiletools.contacts.pro.helpers.FIRST_GROUP_ID
import com.simplemobiletools.contacts.pro.helpers.getEmptyLocalContact
import com.simplemobiletools.contacts.pro.interfaces.ContactsDao
import com.simplemobiletools.contacts.pro.interfaces.GroupsDao
import com.simplemobiletools.contacts.pro.models.Group
import com.simplemobiletools.contacts.pro.models.LocalContact
import com.simplemobiletools.contacts.pro.objects.MyExecutor
import java.util.concurrent.Executors
@Database(entities = [(LocalContact::class)], version = 1)
@Database(entities = [LocalContact::class, Group::class], version = 1)
@TypeConverters(Converters::class)
abstract class ContactsDatabase : RoomDatabase() {
abstract fun ContactsDao(): ContactsDao
abstract fun GroupsDao(): GroupsDao
companion object {
private const val FIRST_CONTACT_ID = 1000000
@ -33,7 +38,7 @@ abstract class ContactsDatabase : RoomDatabase() {
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
increaseAutoIncrementId()
increaseAutoIncrementIds()
}
})
.build()
@ -48,9 +53,9 @@ abstract class ContactsDatabase : RoomDatabase() {
db = null
}
// start autoincrement ID from FIRST_CONTACT_ID to avoid conflicts
// Room doesn't seem to have a built in way for it, so just create a contact and delete it
private fun increaseAutoIncrementId() {
// start autoincrement ID from FIRST_CONTACT_ID/FIRST_GROUP_ID to avoid conflicts
// Room doesn't seem to have a built in way for it, so just create a contact/group and delete it
private fun increaseAutoIncrementIds() {
Executors.newSingleThreadExecutor().execute {
val emptyContact = getEmptyLocalContact()
emptyContact.id = FIRST_CONTACT_ID
@ -58,6 +63,12 @@ abstract class ContactsDatabase : RoomDatabase() {
insertOrUpdate(emptyContact)
deleteContactId(FIRST_CONTACT_ID)
}
val emptyGroup = Group(FIRST_GROUP_ID, "")
db!!.GroupsDao().apply {
insertOrUpdate(emptyGroup)
deleteGroupId(FIRST_GROUP_ID)
}
}
}
}

View File

@ -65,10 +65,14 @@ class CreateNewGroupDialog(val activity: BaseSimpleActivity, val callback: (newG
}
private fun createGroupUnder(name: String, contactSource: ContactSource, dialog: AlertDialog) {
val newGroup = ContactsHelper(activity).createNewGroup(name, contactSource.name, contactSource.type)
if (newGroup != null) {
callback(newGroup)
}
dialog.dismiss()
Thread {
val newGroup = ContactsHelper(activity).createNewGroup(name, contactSource.name, contactSource.type)
activity.runOnUiThread {
if (newGroup != null) {
callback(newGroup)
}
dialog.dismiss()
}
}.start()
}
}

View File

@ -17,11 +17,20 @@ import java.util.*
class SelectGroupsDialog(val activity: SimpleActivity, val selectedGroups: ArrayList<Group>, val callback: (newGroups: ArrayList<Group>) -> Unit) {
private val view = activity.layoutInflater.inflate(R.layout.dialog_select_groups, null) as ViewGroup
private val checkboxes = ArrayList<MyAppCompatCheckbox>()
private val groups = ContactsHelper(activity).getStoredGroups()
private var groups = ArrayList<Group>()
private val config = activity.config
private val dialog: AlertDialog?
private var dialog: AlertDialog? = null
init {
ContactsHelper(activity).getStoredGroups {
groups = it
activity.runOnUiThread {
initDialog()
}
}
}
private fun initDialog() {
groups.sortedBy { it.title }.forEach {
addGroupCheckbox(it)
}

View File

@ -18,6 +18,7 @@ import com.simplemobiletools.contacts.pro.activities.ViewContactActivity
import com.simplemobiletools.contacts.pro.databases.ContactsDatabase
import com.simplemobiletools.contacts.pro.helpers.*
import com.simplemobiletools.contacts.pro.interfaces.ContactsDao
import com.simplemobiletools.contacts.pro.interfaces.GroupsDao
import com.simplemobiletools.contacts.pro.models.Contact
import com.simplemobiletools.contacts.pro.models.Organization
import java.io.File
@ -28,6 +29,8 @@ val Context.dbHelper: DBHelper get() = DBHelper.newInstance(applicationContext)
val Context.contactsDB: ContactsDao get() = ContactsDatabase.getInstance(applicationContext).ContactsDao()
val Context.groupsDB: GroupsDao get() = ContactsDatabase.getInstance(applicationContext).GroupsDao()
fun Context.getEmptyContact(): Contact {
val originalContactSource = if (hasContactPermissions()) config.lastUsedContactSource else SMT_PRIVATE
val organization = Organization("", "")

View File

@ -145,42 +145,44 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet)
}
private fun setupGroupsAdapter(contacts: ArrayList<Contact>) {
var storedGroups = ContactsHelper(activity!!).getStoredGroups()
contacts.forEach {
it.groups.forEach {
val group = it
val storedGroup = storedGroups.firstOrNull { it.id == group.id }
storedGroup?.addContact()
}
}
storedGroups = storedGroups.asSequence().sortedWith(compareBy { it.title.normalizeString() }).toMutableList() as ArrayList<Group>
fragment_placeholder_2.beVisibleIf(storedGroups.isEmpty())
fragment_placeholder.beVisibleIf(storedGroups.isEmpty())
fragment_list.beVisibleIf(storedGroups.isNotEmpty())
val currAdapter = fragment_list.adapter
if (currAdapter == null) {
GroupsAdapter(activity as SimpleActivity, storedGroups, activity, fragment_list, fragment_fastscroller) {
Intent(activity, GroupContactsActivity::class.java).apply {
putExtra(GROUP, it as Group)
activity!!.startActivity(this)
ContactsHelper(activity!!).getStoredGroups {
var storedGroups = it
contacts.forEach {
it.groups.forEach {
val group = it
val storedGroup = storedGroups.firstOrNull { it.id == group.id }
storedGroup?.addContact()
}
}.apply {
addVerticalDividers(true)
fragment_list.adapter = this
}
fragment_fastscroller.setScrollToY(0)
fragment_fastscroller.setViews(fragment_list) {
val item = (fragment_list.adapter as GroupsAdapter).groups.getOrNull(it)
fragment_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
}
} else {
(currAdapter as GroupsAdapter).apply {
showContactThumbnails = activity.config.showContactThumbnails
updateItems(storedGroups)
storedGroups = storedGroups.asSequence().sortedWith(compareBy { it.title.normalizeString() }).toMutableList() as ArrayList<Group>
fragment_placeholder_2.beVisibleIf(storedGroups.isEmpty())
fragment_placeholder.beVisibleIf(storedGroups.isEmpty())
fragment_list.beVisibleIf(storedGroups.isNotEmpty())
val currAdapter = fragment_list.adapter
if (currAdapter == null) {
GroupsAdapter(activity as SimpleActivity, storedGroups, activity, fragment_list, fragment_fastscroller) {
Intent(activity, GroupContactsActivity::class.java).apply {
putExtra(GROUP, it as Group)
activity!!.startActivity(this)
}
}.apply {
addVerticalDividers(true)
fragment_list.adapter = this
}
fragment_fastscroller.setScrollToY(0)
fragment_fastscroller.setViews(fragment_list) {
val item = (fragment_list.adapter as GroupsAdapter).groups.getOrNull(it)
fragment_fastscroller.updateBubbleText(item?.getBubbleText() ?: "")
}
} else {
(currAdapter as GroupsAdapter).apply {
showContactThumbnails = activity.config.showContactThumbnails
updateItems(storedGroups)
}
}
}
}

View File

@ -22,7 +22,7 @@ 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 FIRST_GROUP_ID = 10000L
const val PHONE_NUMBER_PATTERN = "[^0-9#*+]"
const val IS_FROM_SIMPLE_CONTACTS = "is_from_simple_contacts"
const val ADD_NEW_CONTACT_NUMBER = "add_new_contact_number"

View File

@ -75,7 +75,7 @@ class ContactsHelper(val activity: Activity) {
}
// groups are obtained with contactID, not rawID, so assign them to proper contacts like this
val groups = getContactGroups(getStoredGroups())
val groups = getContactGroups(getStoredGroupsSync())
val size = groups.size()
for (i in 0 until size) {
val key = groups.keyAt(i)
@ -648,9 +648,18 @@ class ContactsHelper(val activity: Activity) {
return args.toTypedArray()
}
fun getStoredGroups(): ArrayList<Group> {
fun getStoredGroups(callback: (ArrayList<Group>) -> Unit) {
Thread {
val groups = getStoredGroupsSync()
activity.runOnUiThread {
callback(groups)
}
}.start()
}
fun getStoredGroupsSync(): ArrayList<Group> {
val groups = getDeviceStoredGroups()
groups.addAll(activity.dbHelper.getGroups())
groups.addAll(activity.groupsDB.getGroups())
return groups
}
@ -696,7 +705,10 @@ class ContactsHelper(val activity: Activity) {
fun createNewGroup(title: String, accountName: String, accountType: String): Group? {
if (accountType == SMT_PRIVATE) {
return activity.dbHelper.insertGroup(Group(0, title))
val newGroup = Group(null, title)
val id = activity.groupsDB.insertOrUpdate(newGroup)
newGroup.id = id
return newGroup
}
val operations = ArrayList<ContentProviderOperation>()
@ -769,7 +781,7 @@ class ContactsHelper(val activity: Activity) {
}
private fun parseContactCursor(selection: String, selectionArgs: Array<String>): Contact? {
val storedGroups = getStoredGroups()
val storedGroups = getStoredGroupsSync()
val uri = ContactsContract.Data.CONTENT_URI
val projection = getContactProjection()
var cursor: Cursor? = null
@ -1089,7 +1101,7 @@ class ContactsHelper(val activity: Activity) {
}
// delete groups
val relevantGroupIDs = getStoredGroups().map { it.id }
val relevantGroupIDs = getStoredGroupsSync().map { it.id }
if (relevantGroupIDs.isNotEmpty()) {
val IDsString = TextUtils.join(",", relevantGroupIDs)
ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI).apply {

View File

@ -11,7 +11,6 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.extensions.getBlobValue
import com.simplemobiletools.commons.extensions.getIntValue
import com.simplemobiletools.commons.extensions.getLongValue
import com.simplemobiletools.commons.extensions.getStringValue
import com.simplemobiletools.contacts.pro.extensions.applyRegexFiltering
import com.simplemobiletools.contacts.pro.extensions.config
@ -81,16 +80,6 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
db.execSQL("REPLACE INTO sqlite_sequence (name, seq) VALUES ('$GROUPS_TABLE_NAME', $FIRST_GROUP_ID)")
}
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 = ?"
@ -106,21 +95,6 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
mDb.delete(GROUPS_TABLE_NAME, selection, null)
}
fun getGroups(): ArrayList<Group> {
val groups = ArrayList<Group>()
val projection = arrayOf(COL_ID, COL_TITLE)
val cursor = mDb.query(GROUPS_TABLE_NAME, projection, null, null, null, null, null)
cursor.use {
while (cursor.moveToNext()) {
val id = cursor.getLongValue(COL_ID)
val title = cursor.getStringValue(COL_TITLE)
val group = Group(id, title)
groups.add(group)
}
}
return groups
}
private fun fillGroupValues(group: Group): ContentValues {
return ContentValues().apply {
put(COL_TITLE, group.title)
@ -157,7 +131,7 @@ class DBHelper private constructor(val context: Context) : SQLiteOpenHelper(cont
}
fun getContacts(activity: Activity, selection: String? = null, selectionArgs: Array<String>? = null): ArrayList<Contact> {
val storedGroups = ContactsHelper(activity).getStoredGroups()
val storedGroups = ContactsHelper(activity).getStoredGroupsSync()
val filterDuplicates = activity.config.filterDuplicates
val contacts = ArrayList<Contact>()
val projection = arrayOf(COL_ID, COL_PREFIX, COL_FIRST_NAME, COL_MIDDLE_NAME, COL_SURNAME, COL_SUFFIX, COL_NICKNAME, COL_PHONE_NUMBERS,

View File

@ -6,9 +6,9 @@ import android.provider.ContactsContract.CommonDataKinds
import android.widget.Toast
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.contacts.pro.activities.SimpleActivity
import com.simplemobiletools.contacts.pro.extensions.dbHelper
import com.simplemobiletools.contacts.pro.extensions.getCachePhoto
import com.simplemobiletools.contacts.pro.extensions.getCachePhotoUri
import com.simplemobiletools.contacts.pro.extensions.groupsDB
import com.simplemobiletools.contacts.pro.helpers.VcfImporter.ImportResult.*
import com.simplemobiletools.contacts.pro.models.*
import ezvcard.Ezvcard
@ -170,7 +170,7 @@ class VcfImporter(val activity: SimpleActivity) {
val groupNames = ezContact.categories.values
if (groupNames != null) {
val storedGroups = ContactsHelper(activity).getStoredGroups()
val storedGroups = ContactsHelper(activity).getStoredGroupsSync()
groupNames.forEach {
val groupName = it
@ -179,11 +179,10 @@ class VcfImporter(val activity: SimpleActivity) {
if (storedGroup != null) {
groups.add(storedGroup)
} else {
val newContactGroup = activity.dbHelper.insertGroup(Group(0, groupName))
if (newContactGroup != null) {
groups.add(newContactGroup)
}
val newGroup = Group(null, groupName)
val id = activity.groupsDB.insertOrUpdate(newGroup)
newGroup.id = id
groups.add(newGroup)
}
}
}

View File

@ -0,0 +1,19 @@
package com.simplemobiletools.contacts.pro.interfaces
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.simplemobiletools.contacts.pro.models.Group
@Dao
interface GroupsDao {
@Query("SELECT * FROM groups")
fun getGroups(): List<Group>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrUpdate(group: Group): Long
@Query("DELETE FROM groups WHERE id = :id")
fun deleteGroupId(id: Long)
}

View File

@ -1,16 +1,21 @@
package com.simplemobiletools.contacts.pro.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.simplemobiletools.contacts.pro.helpers.FIRST_GROUP_ID
import java.io.Serializable
data class Group(var id: Long, var title: String, var contactsCount: Int = 0) : Serializable {
companion object {
private const val serialVersionUID = -1384515348451345L
}
@Entity(tableName = "groups", indices = [(Index(value = ["id"], unique = true))])
data class Group(
@PrimaryKey(autoGenerate = true) var id: Long?,
@ColumnInfo(name = "title") var title: String,
@ColumnInfo(name = "contacts_count") var contactsCount: Int = 0) : Serializable {
fun addContact() = contactsCount++
fun getBubbleText() = title
fun isPrivateSecretGroup() = id >= FIRST_GROUP_ID
fun isPrivateSecretGroup() = id ?: 0 >= FIRST_GROUP_ID
}