diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/activities/MainActivity.kt index b2459c50..c53e3a9b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/activities/MainActivity.kt @@ -31,6 +31,7 @@ import com.simplemobiletools.dialer.R import com.simplemobiletools.dialer.adapters.ViewPagerAdapter import com.simplemobiletools.dialer.dialogs.ChangeSortingDialog import com.simplemobiletools.dialer.extensions.config +import com.simplemobiletools.dialer.fragments.FavoritesFragment import com.simplemobiletools.dialer.fragments.MyViewPagerFragment import com.simplemobiletools.dialer.helpers.OPEN_DIAL_PAD_AT_LAUNCH import com.simplemobiletools.dialer.helpers.RecentsHelper @@ -141,7 +142,7 @@ class MainActivity : SimpleActivity() { R.id.clear_call_history -> clearCallHistory() R.id.settings -> launchSettings() R.id.about -> launchAbout() - R.id.sort -> showSortingDialog() + R.id.sort -> showSortingDialog(showCustomSorting = getCurrentFragment() is FavoritesFragment) else -> return super.onOptionsItemSelected(item) } return true @@ -476,15 +477,15 @@ class MainActivity : SimpleActivity() { startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) } - private fun showSortingDialog() { - ChangeSortingDialog(this) { + private fun showSortingDialog(showCustomSorting: Boolean) { + ChangeSortingDialog(this, showCustomSorting) { favorites_fragment?.refreshItems { - if (isSearchOpen){ + if (isSearchOpen) { getCurrentFragment()?.onSearchQueryChanged(searchQuery) } } contacts_fragment?.refreshItems { - if (isSearchOpen){ + if (isSearchOpen) { getCurrentFragment()?.onSearchQueryChanged(searchQuery) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/adapters/ContactsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/adapters/ContactsAdapter.kt index 15cf85d3..c8f76208 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/adapters/ContactsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/adapters/ContactsAdapter.kt @@ -8,11 +8,14 @@ import android.net.Uri import android.text.TextUtils 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.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.dialogs.ConfirmationDialog @@ -21,6 +24,9 @@ import com.simplemobiletools.commons.helpers.PERMISSION_CALL_PHONE import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_CONTACTS import com.simplemobiletools.commons.helpers.SimpleContactsHelper import com.simplemobiletools.commons.helpers.isOreoPlus +import com.simplemobiletools.commons.interfaces.ItemMoveCallback +import com.simplemobiletools.commons.interfaces.ItemTouchHelperContract +import com.simplemobiletools.commons.interfaces.StartReorderDragListener import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.dialer.R @@ -30,18 +36,38 @@ import com.simplemobiletools.dialer.extensions.callContactWithSim import com.simplemobiletools.dialer.extensions.config import com.simplemobiletools.dialer.extensions.startContactDetailsIntent import com.simplemobiletools.dialer.interfaces.RefreshItemsListener +import java.util.* class ContactsAdapter( - activity: SimpleActivity, var contacts: ArrayList, recyclerView: MyRecyclerView, val refreshItemsListener: RefreshItemsListener? = null, - highlightText: String = "", val showDeleteButton: Boolean = true, itemClick: (Any) -> Unit -) : - MyRecyclerViewAdapter(activity, recyclerView, itemClick) { + activity: SimpleActivity, + var contacts: ArrayList, + recyclerView: MyRecyclerView, + val refreshItemsListener: RefreshItemsListener? = null, + highlightText: String = "", + val showDeleteButton: Boolean = true, + private val enableDrag: Boolean = false, + itemClick: (Any) -> Unit +) : MyRecyclerViewAdapter(activity, recyclerView, itemClick), ItemTouchHelperContract { private var textToHighlight = highlightText private var fontSize = activity.getTextSize() + 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_contacts @@ -87,16 +113,20 @@ class ContactsAdapter( override fun getItemKeyPosition(key: Int) = contacts.indexOfFirst { it.rawId == key } - override fun onActionModeCreated() {} + override fun onActionModeCreated() { + notifyDataSetChanged() + } - override fun onActionModeDestroyed() {} + override fun onActionModeDestroyed() { + notifyDataSetChanged() + } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_contact_without_number, parent) override fun onBindViewHolder(holder: ViewHolder, position: Int) { val contact = contacts[position] holder.bindView(contact, true, true) { itemView, layoutPosition -> - setupView(itemView, contact) + setupView(itemView, contact, holder) } bindViewHolder(holder) } @@ -216,7 +246,7 @@ class ContactsAdapter( } } - private fun setupView(view: View, contact: SimpleContact) { + private fun setupView(view: View, contact: SimpleContact, holder: ViewHolder) { view.apply { findViewById(R.id.item_contact_frame).isSelected = selectedKeys.contains(contact.rawId) findViewById(R.id.item_contact_name).apply { @@ -230,7 +260,25 @@ class ContactsAdapter( contact.name.highlightTextFromNumbers(textToHighlight, properPrimaryColor) } } + } + 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) + } } if (!activity.isDestroyed) { @@ -238,4 +286,26 @@ class ContactsAdapter( } } } + + override fun onRowMoved(fromPosition: Int, toPosition: Int) { + activity.config.isCustomOrderSelected = true + + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(contacts, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(contacts, 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/dialer/dialogs/ChangeSortingDialog.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/dialogs/ChangeSortingDialog.kt index de6b3b12..628d97d7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/dialogs/ChangeSortingDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/dialogs/ChangeSortingDialog.kt @@ -2,14 +2,22 @@ package com.simplemobiletools.dialer.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.commons.helpers.SORT_BY_CUSTOM +import com.simplemobiletools.commons.helpers.SORT_BY_DATE_CREATED +import com.simplemobiletools.commons.helpers.SORT_BY_FULL_NAME +import com.simplemobiletools.commons.helpers.SORT_DESCENDING import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.dialer.R import com.simplemobiletools.dialer.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) @@ -22,18 +30,38 @@ 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_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() { @@ -50,14 +78,25 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, private val callback val sortingRadio = view.sorting_dialog_radio_sorting var sorting = when (sortingRadio.checkedRadioButtonId) { 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 + } + SimpleContact.sorting = sorting callback() } diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/fragments/FavoritesFragment.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/fragments/FavoritesFragment.kt index d354f2e8..0c8c166f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/fragments/FavoritesFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/fragments/FavoritesFragment.kt @@ -2,6 +2,7 @@ package com.simplemobiletools.dialer.fragments import android.content.Context import android.util.AttributeSet +import com.google.gson.Gson import com.reddit.indicatorfastscroll.FastScrollItemIndicator import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.dialogs.CallConfirmationDialog @@ -16,6 +17,7 @@ import com.simplemobiletools.dialer.R import com.simplemobiletools.dialer.activities.SimpleActivity import com.simplemobiletools.dialer.adapters.ContactsAdapter import com.simplemobiletools.dialer.extensions.config +import com.simplemobiletools.dialer.helpers.Converters import com.simplemobiletools.dialer.interfaces.RefreshItemsListener import kotlinx.android.synthetic.main.fragment_letters_layout.view.* import java.util.* @@ -57,8 +59,14 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa allContacts.sort() } + val sorted = if (activity!!.config.isCustomOrderSelected) { + sortByCustomOrder(contacts) + } else { + contacts + } + activity?.runOnUiThread { - gotContacts(contacts) + gotContacts(sorted) callback?.invoke() } } @@ -75,7 +83,14 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa val currAdapter = fragment_list.adapter if (currAdapter == null) { - ContactsAdapter(activity as SimpleActivity, contacts, fragment_list, this, showDeleteButton = false) { + ContactsAdapter( + activity = activity as SimpleActivity, + contacts = contacts, + recyclerView = fragment_list, + refreshItemsListener = this, + showDeleteButton = false, + enableDrag = true, + ) { if (context.config.showCallConfirmation) { CallConfirmationDialog(activity as SimpleActivity, (it as SimpleContact).name) { callContact(it) @@ -85,6 +100,15 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa } }.apply { fragment_list.adapter = this + + onDragEndListener = { + val adapter = fragment_list?.adapter + if (adapter is ContactsAdapter) { + val items = adapter.contacts + saveCustomOrderToPrefs(items) + setupLetterFastscroller(items) + } + } } if (context.areSystemAnimationsEnabled) { @@ -96,6 +120,28 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa } } + private fun sortByCustomOrder(favorites: List): ArrayList { + val favoritesOrder = activity!!.config.favoritesContactsOrder + + if (favoritesOrder.isEmpty()) { + return ArrayList(favorites) + } + + val orderList = Converters().jsonToStringList(favoritesOrder) + val map = orderList.withIndex().associate { it.value to it.index } + val sorted = favorites.sortedBy { map[it.contactId.toString()] } + + return ArrayList(sorted) + } + + private fun saveCustomOrderToPrefs(items: ArrayList) { + activity?.apply { + val orderIds = items.map { it.contactId } + val orderGsonString = Gson().toJson(orderIds) + config.favoritesContactsOrder = orderGsonString + } + } + private fun callContact(simpleContact: SimpleContact) { val phoneNumbers = simpleContact.phoneNumbers if (phoneNumbers.size <= 1) { diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Config.kt index 62c7b25e..1cbc15b5 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Config.kt @@ -59,4 +59,12 @@ class Config(context: Context) : BaseConfig(context) { var showTabs: Int get() = prefs.getInt(SHOW_TABS, ALL_TABS_MASK) set(showTabs) = prefs.edit().putInt(SHOW_TABS, showTabs).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/dialer/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Constants.kt index b4bfad3f..b478c957 100644 --- a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Constants.kt @@ -12,6 +12,8 @@ const val OPEN_DIAL_PAD_AT_LAUNCH = "open_dial_pad_at_launch" const val DISABLE_PROXIMITY_SENSOR = "disable_proximity_sensor" const val DISABLE_SWIPE_TO_ANSWER = "disable_swipe_to_answer" const val SHOW_TABS = "show_tabs" +const val FAVORITES_CONTACTS_ORDER = "favorites_contacts_order" +const val FAVORITES_CUSTOM_ORDER_SELECTED = "favorites_custom_order_selected" const val ALL_TABS_MASK = TAB_CONTACTS or TAB_FAVORITES or TAB_CALL_HISTORY diff --git a/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Converters.kt b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Converters.kt new file mode 100644 index 00000000..3949c2bd --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/dialer/helpers/Converters.kt @@ -0,0 +1,13 @@ +package com.simplemobiletools.dialer.helpers + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +class Converters { + private val gson = Gson() + private val stringType = object : TypeToken>() {}.type + + fun jsonToStringList(value: String) = gson.fromJson>(value, stringType) + + fun stringListToJson(list: ArrayList) = gson.toJson(list) +} diff --git a/app/src/main/res/layout/dialog_change_sorting.xml b/app/src/main/res/layout/dialog_change_sorting.xml index fc2f957a..df9c02a8 100644 --- a/app/src/main/res/layout/dialog_change_sorting.xml +++ b/app/src/main/res/layout/dialog_change_sorting.xml @@ -35,9 +35,18 @@ android:paddingBottom="@dimen/medium_margin" android:text="@string/date_created" /> + - +