diff --git a/app/build.gradle b/app/build.gradle index 8b4089a8..a62a215e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,7 +63,7 @@ android { } dependencies { - implementation 'com.github.SimpleMobileTools:Simple-Commons:b264da6cff' + implementation 'com.github.SimpleMobileTools:Simple-Commons:34fdfce71c' implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3' implementation 'com.github.tibbi:IndicatorFastScroll:4524cd0b61' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/MainActivity.kt index 8ad84e5c..be34c253 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/activities/MainActivity.kt @@ -35,6 +35,7 @@ import com.simplemobiletools.contacts.pro.dialogs.ImportContactsDialog import com.simplemobiletools.contacts.pro.extensions.config import com.simplemobiletools.contacts.pro.extensions.getTempFile import com.simplemobiletools.contacts.pro.extensions.handleGenericContactClick +import com.simplemobiletools.contacts.pro.fragments.FavoritesFragment import com.simplemobiletools.contacts.pro.fragments.MyViewPagerFragment import com.simplemobiletools.contacts.pro.helpers.ALL_TABS_MASK import com.simplemobiletools.contacts.pro.helpers.ContactsHelper @@ -185,8 +186,9 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { - R.id.sort -> showSortingDialog() + R.id.sort -> showSortingDialog(showCustomSorting = getCurrentFragment() is FavoritesFragment) R.id.filter -> showFilterDialog() R.id.dialpad -> launchDialpad() R.id.import_contacts -> tryImportContacts() @@ -410,8 +412,8 @@ class MainActivity : SimpleActivity(), RefreshContactsListener { } } - private fun showSortingDialog() { - ChangeSortingDialog(this) { + private fun showSortingDialog(showCustomSorting: Boolean) { + ChangeSortingDialog(this, showCustomSorting) { refreshContacts(TAB_CONTACTS or TAB_FAVORITES) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ContactsAdapter.kt index e918627a..e86e59f6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/adapters/ContactsAdapter.kt @@ -10,11 +10,14 @@ import android.graphics.drawable.Icon import android.graphics.drawable.LayerDrawable import android.util.TypedValue import android.view.Menu +import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions @@ -25,6 +28,9 @@ import com.simplemobiletools.commons.dialogs.ConfirmationDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* +import com.simplemobiletools.commons.interfaces.ItemMoveCallback +import com.simplemobiletools.commons.interfaces.ItemTouchHelperContract +import com.simplemobiletools.commons.interfaces.StartReorderDragListener import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.contacts.pro.R @@ -36,12 +42,19 @@ import com.simplemobiletools.contacts.pro.helpers.* import com.simplemobiletools.contacts.pro.interfaces.RefreshContactsListener import com.simplemobiletools.contacts.pro.interfaces.RemoveFromGroupListener import com.simplemobiletools.contacts.pro.models.Contact +import java.util.* class ContactsAdapter( - activity: SimpleActivity, var contactItems: ArrayList, private val refreshListener: RefreshContactsListener?, - private val location: Int, private val removeListener: RemoveFromGroupListener?, recyclerView: MyRecyclerView, - highlightText: String = "", itemClick: (Any) -> Unit -) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate { + activity: SimpleActivity, + var contactItems: ArrayList, + private val refreshListener: RefreshContactsListener?, + private val location: Int, + private val removeListener: RemoveFromGroupListener?, + recyclerView: MyRecyclerView, + highlightText: String = "", + private val enableDrag: Boolean = false, + itemClick: (Any) -> Unit +) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), RecyclerViewFastScroller.OnPopupTextUpdate, ItemTouchHelperContract { private val NEW_GROUP_ID = -1 private var config = activity.config @@ -54,8 +67,23 @@ class ContactsAdapter( private val itemLayout = if (showPhoneNumbers) R.layout.item_contact_with_number else R.layout.item_contact_without_number + private var touchHelper: ItemTouchHelper? = null + private var startReorderDragListener: StartReorderDragListener? = null + var onDragEndListener: (() -> Unit)? = null + init { setupDragListener(true) + + if (enableDrag) { + touchHelper = ItemTouchHelper(ItemMoveCallback(this)) + touchHelper!!.attachToRecyclerView(recyclerView) + + startReorderDragListener = object : StartReorderDragListener { + override fun requestDrag(viewHolder: RecyclerView.ViewHolder) { + touchHelper?.startDrag(viewHolder) + } + } + } } override fun getActionMenuId() = R.menu.cab @@ -107,9 +135,13 @@ class ContactsAdapter( override fun getItemKeyPosition(key: Int) = contactItems.indexOfFirst { it.id == key } - override fun onActionModeCreated() {} + override fun onActionModeCreated() { + notifyDataSetChanged() + } - override fun onActionModeDestroyed() {} + override fun onActionModeDestroyed() { + notifyDataSetChanged() + } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(itemLayout, parent) @@ -117,7 +149,7 @@ class ContactsAdapter( val contact = contactItems[position] val allowLongClick = location != LOCATION_INSERT_OR_EDIT holder.bindView(contact, true, allowLongClick) { itemView, layoutPosition -> - setupView(itemView, contact) + setupView(itemView, contact, holder) } bindViewHolder(holder) } @@ -335,7 +367,7 @@ class ContactsAdapter( } } - private fun setupView(view: View, contact: Contact) { + private fun setupView(view: View, contact: Contact, holder: ViewHolder) { view.apply { findViewById(R.id.item_contact_frame)?.isSelected = selectedKeys.contains(contact.id) val fullName = contact.getNameToDisplay() @@ -393,8 +425,49 @@ class ContactsAdapter( .into(findViewById(R.id.item_contact_image)) } } + + val dragIcon = findViewById(R.id.drag_handle_icon) + if (enableDrag && textToHighlight.isEmpty()) { + dragIcon.apply { + beVisibleIf(selectedKeys.isNotEmpty()) + applyColorFilter(textColor) + setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + startReorderDragListener?.requestDrag(holder) + } + false + } + } + } else { + dragIcon.apply { + beGone() + setOnTouchListener(null) + } + } } } override fun onChange(position: Int) = contactItems.getOrNull(position)?.getBubbleText() ?: "" + + override fun onRowMoved(fromPosition: Int, toPosition: Int) { + activity.config.isCustomOrderSelected = true + + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(contactItems, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(contactItems, i, i - 1) + } + } + + notifyItemMoved(fromPosition, toPosition) + } + + override fun onRowSelected(myViewHolder: ViewHolder?) { } + + override fun onRowClear(myViewHolder: ViewHolder?) { + onDragEndListener?.invoke() + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/ChangeSortingDialog.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/ChangeSortingDialog.kt index 480ed683..9240dc4a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/ChangeSortingDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/dialogs/ChangeSortingDialog.kt @@ -2,13 +2,14 @@ package com.simplemobiletools.contacts.pro.dialogs import androidx.appcompat.app.AlertDialog import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.extensions.beGoneIf import com.simplemobiletools.commons.extensions.setupDialogStuff import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.contacts.pro.R import com.simplemobiletools.contacts.pro.extensions.config import kotlinx.android.synthetic.main.dialog_change_sorting.view.* -class ChangeSortingDialog(val activity: BaseSimpleActivity, private val callback: () -> Unit) { +class ChangeSortingDialog(val activity: BaseSimpleActivity, private val showCustomSorting: Boolean = false, private val callback: () -> Unit) { private var currSorting = 0 private var config = activity.config private var view = activity.layoutInflater.inflate(R.layout.dialog_change_sorting, null) @@ -21,21 +22,39 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, private val callback activity.setupDialogStuff(view, this, R.string.sort_by) } - currSorting = config.sorting + currSorting = if (showCustomSorting && config.isCustomOrderSelected) { + SORT_BY_CUSTOM + } else { + config.sorting + } + setupSortRadio() setupOrderRadio() } private fun setupSortRadio() { val sortingRadio = view.sorting_dialog_radio_sorting + + sortingRadio.setOnCheckedChangeListener { group, checkedId -> + val isCustomSorting = checkedId == sortingRadio.sorting_dialog_radio_custom.id + view.sorting_dialog_radio_order.beGoneIf(isCustomSorting) + view.divider.beGoneIf(isCustomSorting) + } + val sortBtn = when { currSorting and SORT_BY_FIRST_NAME != 0 -> sortingRadio.sorting_dialog_radio_first_name currSorting and SORT_BY_MIDDLE_NAME != 0 -> sortingRadio.sorting_dialog_radio_middle_name currSorting and SORT_BY_SURNAME != 0 -> sortingRadio.sorting_dialog_radio_surname currSorting and SORT_BY_FULL_NAME != 0 -> sortingRadio.sorting_dialog_radio_full_name + currSorting and SORT_BY_CUSTOM != 0 -> sortingRadio.sorting_dialog_radio_custom else -> sortingRadio.sorting_dialog_radio_date_created } sortBtn.isChecked = true + + if (showCustomSorting) { + sortingRadio.sorting_dialog_radio_custom.isChecked = config.isCustomOrderSelected + } + view.sorting_dialog_radio_custom.beGoneIf(!showCustomSorting) } private fun setupOrderRadio() { @@ -55,14 +74,25 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, private val callback R.id.sorting_dialog_radio_middle_name -> SORT_BY_MIDDLE_NAME R.id.sorting_dialog_radio_surname -> SORT_BY_SURNAME R.id.sorting_dialog_radio_full_name -> SORT_BY_FULL_NAME + R.id.sorting_dialog_radio_custom -> SORT_BY_CUSTOM else -> SORT_BY_DATE_CREATED } - if (view.sorting_dialog_radio_order.checkedRadioButtonId == R.id.sorting_dialog_radio_descending) { + if (sorting != SORT_BY_CUSTOM && view.sorting_dialog_radio_order.checkedRadioButtonId == R.id.sorting_dialog_radio_descending) { sorting = sorting or SORT_DESCENDING } - config.sorting = sorting + if (showCustomSorting) { + if (sorting == SORT_BY_CUSTOM) { + config.isCustomOrderSelected = true + } else { + config.isCustomOrderSelected = false + config.sorting = sorting + } + } else { + config.sorting = sorting + } + callback() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/MyViewPagerFragment.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/MyViewPagerFragment.kt index ed425cdd..12703321 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/MyViewPagerFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/fragments/MyViewPagerFragment.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.util.AttributeSet import android.view.ViewGroup import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.gson.Gson import com.reddit.indicatorfastscroll.FastScrollItemIndicator import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.extensions.* @@ -30,7 +31,6 @@ import kotlinx.android.synthetic.main.fragment_layout.view.fragment_placeholder_ import kotlinx.android.synthetic.main.fragment_layout.view.fragment_wrapper import kotlinx.android.synthetic.main.fragment_letters_layout.view.* import java.util.* -import kotlin.collections.ArrayList abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) : CoordinatorLayout(context, attributeSet) { protected var activity: SimpleActivity? = null @@ -125,7 +125,15 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) val filtered = when { this is GroupsFragment -> contacts - this is FavoritesFragment -> contacts.filter { it.starred == 1 } as ArrayList + this is FavoritesFragment -> { + val favorites = contacts.filter { it.starred == 1 } as ArrayList + + if (activity!!.config.isCustomOrderSelected) { + sortByCustomOrder(favorites) + } else { + favorites + } + } else -> { val contactSources = activity!!.getVisibleContactSources() contacts.filter { contactSources.contains(it.source) } as ArrayList @@ -153,6 +161,20 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } } + private fun sortByCustomOrder(starred: List): ArrayList { + val favoritesOrder = activity!!.config.favoritesContactsOrder + + if (favoritesOrder.isEmpty()) { + return ArrayList(starred) + } + + val orderList = Converters().jsonToStringList(favoritesOrder) + val map = orderList.withIndex().associate { it.value to it.index } + val sorted = starred.sortedBy { map[it.id.toString()] } + + return ArrayList(sorted) + } + private fun setupContacts(contacts: ArrayList) { if (this is GroupsFragment) { setupGroupsAdapter(contacts) { @@ -219,10 +241,29 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) else -> LOCATION_CONTACTS_TAB } - ContactsAdapter(activity as SimpleActivity, contacts, activity as RefreshContactsListener, location, null, fragment_list) { + val enableDragReorder = this is FavoritesFragment + ContactsAdapter( + activity = activity as SimpleActivity, + contactItems = contacts, + refreshListener = activity as RefreshContactsListener, + location = location, + removeListener = null, + recyclerView = fragment_list, + enableDrag = enableDragReorder, + ) { (activity as RefreshContactsListener).contactClicked(it as Contact) }.apply { fragment_list.adapter = this + if (enableDragReorder) { + onDragEndListener = { + val adapter = fragment_list?.adapter + if (adapter is ContactsAdapter) { + val items = adapter.contactItems + saveCustomOrderToPrefs(items) + setupLetterFastscroller(items) + } + } + } } if (context.areSystemAnimationsEnabled) { @@ -238,6 +279,14 @@ abstract class MyViewPagerFragment(context: Context, attributeSet: AttributeSet) } } + private fun saveCustomOrderToPrefs(items: ArrayList) { + activity?.apply { + val orderIds = items.map { it.id } + val orderGsonString = Gson().toJson(orderIds) + config.favoritesContactsOrder = orderGsonString + } + } + fun showContactThumbnailsChanged(showThumbnails: Boolean) { if (this is GroupsFragment) { (fragment_list.adapter as? GroupsAdapter)?.apply { diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Config.kt index fe209b56..4c584f9c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Config.kt @@ -66,4 +66,12 @@ class Config(context: Context) : BaseConfig(context) { var mergeDuplicateContacts: Boolean get() = prefs.getBoolean(MERGE_DUPLICATE_CONTACTS, true) set(mergeDuplicateContacts) = prefs.edit().putBoolean(MERGE_DUPLICATE_CONTACTS, mergeDuplicateContacts).apply() + + var favoritesContactsOrder: String + get() = prefs.getString(FAVORITES_CONTACTS_ORDER, "")!! + set(order) = prefs.edit().putString(FAVORITES_CONTACTS_ORDER, order).apply() + + var isCustomOrderSelected: Boolean + get() = prefs.getBoolean(FAVORITES_CUSTOM_ORDER_SELECTED, false) + set(selected) = prefs.edit().putBoolean(FAVORITES_CUSTOM_ORDER_SELECTED, selected).apply() } diff --git a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt index 8a347061..ef821611 100644 --- a/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/contacts/pro/helpers/Constants.kt @@ -22,6 +22,8 @@ const val LAST_EXPORT_PATH = "last_export_path" const val WAS_LOCAL_ACCOUNT_INITIALIZED = "was_local_account_initialized" const val SHOW_PRIVATE_CONTACTS = "show_private_contacts" const val MERGE_DUPLICATE_CONTACTS = "merge_duplicate_contacts" +const val FAVORITES_CONTACTS_ORDER = "favorites_contacts_order" +const val FAVORITES_CUSTOM_ORDER_SELECTED = "favorites_custom_order_selected" const val SMT_PRIVATE = "smt_private" // used at the contact source of local contacts hidden from other apps const val GROUP = "group" diff --git a/app/src/main/res/layout/dialog_change_sorting.xml b/app/src/main/res/layout/dialog_change_sorting.xml index 29946081..2a073839 100644 --- a/app/src/main/res/layout/dialog_change_sorting.xml +++ b/app/src/main/res/layout/dialog_change_sorting.xml @@ -59,9 +59,19 @@ android:paddingBottom="@dimen/medium_margin" android:text="@string/date_created" /> + + - +