custom sorting by drag and drop

This commit is contained in:
Pavel Poley
2022-05-16 13:51:26 +03:00
parent 0bcc9d5cb4
commit 76c7d4762a
8 changed files with 209 additions and 21 deletions

View File

@ -31,6 +31,7 @@ import com.simplemobiletools.dialer.R
import com.simplemobiletools.dialer.adapters.ViewPagerAdapter import com.simplemobiletools.dialer.adapters.ViewPagerAdapter
import com.simplemobiletools.dialer.dialogs.ChangeSortingDialog import com.simplemobiletools.dialer.dialogs.ChangeSortingDialog
import com.simplemobiletools.dialer.extensions.config import com.simplemobiletools.dialer.extensions.config
import com.simplemobiletools.dialer.fragments.FavoritesFragment
import com.simplemobiletools.dialer.fragments.MyViewPagerFragment import com.simplemobiletools.dialer.fragments.MyViewPagerFragment
import com.simplemobiletools.dialer.helpers.OPEN_DIAL_PAD_AT_LAUNCH import com.simplemobiletools.dialer.helpers.OPEN_DIAL_PAD_AT_LAUNCH
import com.simplemobiletools.dialer.helpers.RecentsHelper import com.simplemobiletools.dialer.helpers.RecentsHelper
@ -141,7 +142,7 @@ class MainActivity : SimpleActivity() {
R.id.clear_call_history -> clearCallHistory() R.id.clear_call_history -> clearCallHistory()
R.id.settings -> launchSettings() R.id.settings -> launchSettings()
R.id.about -> launchAbout() R.id.about -> launchAbout()
R.id.sort -> showSortingDialog() R.id.sort -> showSortingDialog(showCustomSorting = getCurrentFragment() is FavoritesFragment)
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }
return true return true
@ -476,15 +477,15 @@ class MainActivity : SimpleActivity() {
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
} }
private fun showSortingDialog() { private fun showSortingDialog(showCustomSorting: Boolean) {
ChangeSortingDialog(this) { ChangeSortingDialog(this, showCustomSorting) {
favorites_fragment?.refreshItems { favorites_fragment?.refreshItems {
if (isSearchOpen){ if (isSearchOpen) {
getCurrentFragment()?.onSearchQueryChanged(searchQuery) getCurrentFragment()?.onSearchQueryChanged(searchQuery)
} }
} }
contacts_fragment?.refreshItems { contacts_fragment?.refreshItems {
if (isSearchOpen){ if (isSearchOpen) {
getCurrentFragment()?.onSearchQueryChanged(searchQuery) getCurrentFragment()?.onSearchQueryChanged(searchQuery)
} }
} }

View File

@ -8,11 +8,14 @@ import android.net.Uri
import android.text.TextUtils import android.text.TextUtils
import android.util.TypedValue import android.util.TypedValue
import android.view.Menu import android.view.Menu
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.ConfirmationDialog 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.PERMISSION_WRITE_CONTACTS
import com.simplemobiletools.commons.helpers.SimpleContactsHelper import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.isOreoPlus 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.models.SimpleContact
import com.simplemobiletools.commons.views.MyRecyclerView import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.dialer.R 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.config
import com.simplemobiletools.dialer.extensions.startContactDetailsIntent import com.simplemobiletools.dialer.extensions.startContactDetailsIntent
import com.simplemobiletools.dialer.interfaces.RefreshItemsListener import com.simplemobiletools.dialer.interfaces.RefreshItemsListener
import java.util.*
class ContactsAdapter( class ContactsAdapter(
activity: SimpleActivity, var contacts: ArrayList<SimpleContact>, recyclerView: MyRecyclerView, val refreshItemsListener: RefreshItemsListener? = null, activity: SimpleActivity,
highlightText: String = "", val showDeleteButton: Boolean = true, itemClick: (Any) -> Unit var contacts: ArrayList<SimpleContact>,
) : recyclerView: MyRecyclerView,
MyRecyclerViewAdapter(activity, recyclerView, itemClick) { 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 textToHighlight = highlightText
private var fontSize = activity.getTextSize() private var fontSize = activity.getTextSize()
private var touchHelper: ItemTouchHelper? = null
private var startReorderDragListener: StartReorderDragListener? = null
var onDragEndListener: (() -> Unit)? = null
init { init {
setupDragListener(true) 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 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 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 onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.item_contact_without_number, parent)
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val contact = contacts[position] val contact = contacts[position]
holder.bindView(contact, true, true) { itemView, layoutPosition -> holder.bindView(contact, true, true) { itemView, layoutPosition ->
setupView(itemView, contact) setupView(itemView, contact, holder)
} }
bindViewHolder(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 { view.apply {
findViewById<FrameLayout>(R.id.item_contact_frame).isSelected = selectedKeys.contains(contact.rawId) findViewById<FrameLayout>(R.id.item_contact_frame).isSelected = selectedKeys.contains(contact.rawId)
findViewById<TextView>(R.id.item_contact_name).apply { findViewById<TextView>(R.id.item_contact_name).apply {
@ -230,7 +260,25 @@ class ContactsAdapter(
contact.name.highlightTextFromNumbers(textToHighlight, properPrimaryColor) contact.name.highlightTextFromNumbers(textToHighlight, properPrimaryColor)
} }
} }
}
val dragIcon = findViewById<ImageView>(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) { 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()
}
} }

View File

@ -2,14 +2,22 @@ package com.simplemobiletools.dialer.dialogs
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.activities.BaseSimpleActivity
import com.simplemobiletools.commons.extensions.beGoneIf
import com.simplemobiletools.commons.extensions.setupDialogStuff 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.commons.models.SimpleContact
import com.simplemobiletools.dialer.R import com.simplemobiletools.dialer.R
import com.simplemobiletools.dialer.extensions.config import com.simplemobiletools.dialer.extensions.config
import kotlinx.android.synthetic.main.dialog_change_sorting.view.* 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 currSorting = 0
private var config = activity.config private var config = activity.config
private var view = activity.layoutInflater.inflate(R.layout.dialog_change_sorting, null) 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) activity.setupDialogStuff(view, this, R.string.sort_by)
} }
currSorting = config.sorting currSorting = if (showCustomSorting && config.isCustomOrderSelected) {
SORT_BY_CUSTOM
} else {
config.sorting
}
setupSortRadio() setupSortRadio()
setupOrderRadio() setupOrderRadio()
} }
private fun setupSortRadio() { private fun setupSortRadio() {
val sortingRadio = view.sorting_dialog_radio_sorting 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 { val sortBtn = when {
currSorting and SORT_BY_FULL_NAME != 0 -> sortingRadio.sorting_dialog_radio_full_name 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 else -> sortingRadio.sorting_dialog_radio_date_created
} }
sortBtn.isChecked = true sortBtn.isChecked = true
if (showCustomSorting) {
sortingRadio.sorting_dialog_radio_custom.isChecked = config.isCustomOrderSelected
}
view.sorting_dialog_radio_custom.beGoneIf(!showCustomSorting)
} }
private fun setupOrderRadio() { private fun setupOrderRadio() {
@ -50,14 +78,25 @@ class ChangeSortingDialog(val activity: BaseSimpleActivity, private val callback
val sortingRadio = view.sorting_dialog_radio_sorting val sortingRadio = view.sorting_dialog_radio_sorting
var sorting = when (sortingRadio.checkedRadioButtonId) { var sorting = when (sortingRadio.checkedRadioButtonId) {
R.id.sorting_dialog_radio_full_name -> SORT_BY_FULL_NAME 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 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 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 SimpleContact.sorting = sorting
callback() callback()
} }

View File

@ -2,6 +2,7 @@ package com.simplemobiletools.dialer.fragments
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import com.google.gson.Gson
import com.reddit.indicatorfastscroll.FastScrollItemIndicator import com.reddit.indicatorfastscroll.FastScrollItemIndicator
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.CallConfirmationDialog 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.activities.SimpleActivity
import com.simplemobiletools.dialer.adapters.ContactsAdapter import com.simplemobiletools.dialer.adapters.ContactsAdapter
import com.simplemobiletools.dialer.extensions.config import com.simplemobiletools.dialer.extensions.config
import com.simplemobiletools.dialer.helpers.Converters
import com.simplemobiletools.dialer.interfaces.RefreshItemsListener import com.simplemobiletools.dialer.interfaces.RefreshItemsListener
import kotlinx.android.synthetic.main.fragment_letters_layout.view.* import kotlinx.android.synthetic.main.fragment_letters_layout.view.*
import java.util.* import java.util.*
@ -57,8 +59,14 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa
allContacts.sort() allContacts.sort()
} }
val sorted = if (activity!!.config.isCustomOrderSelected) {
sortByCustomOrder(contacts)
} else {
contacts
}
activity?.runOnUiThread { activity?.runOnUiThread {
gotContacts(contacts) gotContacts(sorted)
callback?.invoke() callback?.invoke()
} }
} }
@ -75,7 +83,14 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa
val currAdapter = fragment_list.adapter val currAdapter = fragment_list.adapter
if (currAdapter == null) { 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) { if (context.config.showCallConfirmation) {
CallConfirmationDialog(activity as SimpleActivity, (it as SimpleContact).name) { CallConfirmationDialog(activity as SimpleActivity, (it as SimpleContact).name) {
callContact(it) callContact(it)
@ -85,6 +100,15 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa
} }
}.apply { }.apply {
fragment_list.adapter = this 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) { if (context.areSystemAnimationsEnabled) {
@ -96,6 +120,28 @@ class FavoritesFragment(context: Context, attributeSet: AttributeSet) : MyViewPa
} }
} }
private fun sortByCustomOrder(favorites: List<SimpleContact>): ArrayList<SimpleContact> {
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<SimpleContact>) {
activity?.apply {
val orderIds = items.map { it.contactId }
val orderGsonString = Gson().toJson(orderIds)
config.favoritesContactsOrder = orderGsonString
}
}
private fun callContact(simpleContact: SimpleContact) { private fun callContact(simpleContact: SimpleContact) {
val phoneNumbers = simpleContact.phoneNumbers val phoneNumbers = simpleContact.phoneNumbers
if (phoneNumbers.size <= 1) { if (phoneNumbers.size <= 1) {

View File

@ -59,4 +59,12 @@ class Config(context: Context) : BaseConfig(context) {
var showTabs: Int var showTabs: Int
get() = prefs.getInt(SHOW_TABS, ALL_TABS_MASK) get() = prefs.getInt(SHOW_TABS, ALL_TABS_MASK)
set(showTabs) = prefs.edit().putInt(SHOW_TABS, showTabs).apply() 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()
} }

View File

@ -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_PROXIMITY_SENSOR = "disable_proximity_sensor"
const val DISABLE_SWIPE_TO_ANSWER = "disable_swipe_to_answer" const val DISABLE_SWIPE_TO_ANSWER = "disable_swipe_to_answer"
const val SHOW_TABS = "show_tabs" 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 const val ALL_TABS_MASK = TAB_CONTACTS or TAB_FAVORITES or TAB_CALL_HISTORY

View File

@ -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<List<String>>() {}.type
fun jsonToStringList(value: String) = gson.fromJson<ArrayList<String>>(value, stringType)
fun stringListToJson(list: ArrayList<String>) = gson.toJson(list)
}

View File

@ -35,9 +35,18 @@
android:paddingBottom="@dimen/medium_margin" android:paddingBottom="@dimen/medium_margin"
android:text="@string/date_created" /> android:text="@string/date_created" />
<com.simplemobiletools.commons.views.MyCompatRadioButton
android:id="@+id/sorting_dialog_radio_custom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/medium_margin"
android:paddingBottom="@dimen/medium_margin"
android:text="@string/custom" />
</RadioGroup> </RadioGroup>
<include layout="@layout/divider" /> <include
android:id="@+id/divider"
layout="@layout/divider" />
<RadioGroup <RadioGroup
android:id="@+id/sorting_dialog_radio_order" android:id="@+id/sorting_dialog_radio_order"