Simple-Dialer/app/src/main/kotlin/com/simplemobiletools/dialer/adapters/RecentCallsAdapter.kt

462 lines
18 KiB
Kotlin

package com.simplemobiletools.dialer.adapters
import android.content.Intent
import android.graphics.drawable.Drawable
import android.provider.CallLog.Calls
import android.text.SpannableString
import android.text.TextUtils
import android.util.TypedValue
import android.view.*
import android.widget.PopupMenu
import com.bumptech.glide.Glide
import com.simplemobiletools.commons.adapters.MyRecyclerViewAdapter
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.dialogs.FeatureLockedDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.contacts.Contact
import com.simplemobiletools.commons.views.MyRecyclerView
import com.simplemobiletools.dialer.R
import com.simplemobiletools.dialer.activities.MainActivity
import com.simplemobiletools.dialer.activities.SimpleActivity
import com.simplemobiletools.dialer.databinding.ItemRecentCallBinding
import com.simplemobiletools.dialer.dialogs.ShowGroupedCallsDialog
import com.simplemobiletools.dialer.extensions.*
import com.simplemobiletools.dialer.helpers.RecentsHelper
import com.simplemobiletools.dialer.interfaces.RefreshItemsListener
import com.simplemobiletools.dialer.models.RecentCall
class RecentCallsAdapter(
activity: SimpleActivity,
private var recentCalls: MutableList<RecentCall>,
recyclerView: MyRecyclerView,
private val refreshItemsListener: RefreshItemsListener?,
private val showOverflowMenu: Boolean,
itemClick: (Any) -> Unit
) : MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
private lateinit var outgoingCallIcon: Drawable
private lateinit var incomingCallIcon: Drawable
private lateinit var incomingMissedCallIcon: Drawable
var fontSize: Float = activity.getTextSize()
private val areMultipleSIMsAvailable = activity.areMultipleSIMsAvailable()
private val redColor = resources.getColor(R.color.md_red_700)
private var textToHighlight = ""
private var durationPadding = resources.getDimension(R.dimen.normal_margin).toInt()
init {
initDrawables()
setupDragListener(true)
}
override fun getActionMenuId() = R.menu.cab_recent_calls
override fun prepareActionMode(menu: Menu) {
val hasMultipleSIMs = activity.areMultipleSIMsAvailable()
val selectedItems = getSelectedItems()
val isOneItemSelected = selectedItems.size == 1
val selectedNumber = "tel:${getSelectedPhoneNumber()}"
menu.apply {
findItem(R.id.cab_call_sim_1).isVisible = hasMultipleSIMs && isOneItemSelected
findItem(R.id.cab_call_sim_2).isVisible = hasMultipleSIMs && isOneItemSelected
findItem(R.id.cab_remove_default_sim).isVisible = isOneItemSelected && (activity.config.getCustomSIM(selectedNumber) ?: "") != ""
findItem(R.id.cab_block_number).title = activity.addLockedLabelIfNeeded(R.string.block_number)
findItem(R.id.cab_block_number).isVisible = isNougatPlus()
findItem(R.id.cab_add_number).isVisible = isOneItemSelected
findItem(R.id.cab_copy_number).isVisible = isOneItemSelected
findItem(R.id.cab_show_call_details).isVisible = isOneItemSelected
findItem(R.id.cab_view_details).isVisible = isOneItemSelected && findContactByCall(selectedItems.first()) != null
}
}
override fun actionItemPressed(id: Int) {
if (selectedKeys.isEmpty()) {
return
}
when (id) {
R.id.cab_call_sim_1 -> callContact(true)
R.id.cab_call_sim_2 -> callContact(false)
R.id.cab_remove_default_sim -> removeDefaultSIM()
R.id.cab_block_number -> tryBlocking()
R.id.cab_add_number -> addNumberToContact()
R.id.cab_send_sms -> sendSMS()
R.id.cab_show_call_details -> showCallDetails()
R.id.cab_copy_number -> copyNumber()
R.id.cab_remove -> askConfirmRemove()
R.id.cab_select_all -> selectAll()
R.id.cab_view_details -> launchContactDetailsIntent(findContactByCall(getSelectedItems().first()))
}
}
override fun getSelectableItemCount() = recentCalls.size
override fun getIsItemSelectable(position: Int) = true
override fun getItemSelectionKey(position: Int) = recentCalls.getOrNull(position)?.id
override fun getItemKeyPosition(key: Int) = recentCalls.indexOfFirst { it.id == key }
override fun onActionModeCreated() {}
override fun onActionModeDestroyed() {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return createViewHolder(ItemRecentCallBinding.inflate(layoutInflater, parent, false).root)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val recentCall = recentCalls[position]
holder.bindView(
any = recentCall,
allowSingleClick = refreshItemsListener != null && !recentCall.isUnknownNumber,
allowLongClick = refreshItemsListener != null && !recentCall.isUnknownNumber
) { itemView, _ ->
val binding = ItemRecentCallBinding.bind(itemView)
setupView(binding, recentCall)
}
bindViewHolder(holder)
}
override fun getItemCount() = recentCalls.size
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
if (!activity.isDestroyed && !activity.isFinishing) {
ItemRecentCallBinding.bind(holder.itemView).apply {
Glide.with(activity).clear(itemRecentsImage)
}
}
}
fun initDrawables() {
outgoingCallIcon = resources.getColoredDrawableWithColor(R.drawable.ic_outgoing_call_vector, activity.getProperTextColor())
incomingCallIcon = resources.getColoredDrawableWithColor(R.drawable.ic_incoming_call_vector, activity.getProperTextColor())
incomingMissedCallIcon = resources.getColoredDrawableWithColor(R.drawable.ic_incoming_call_vector, redColor)
}
private fun callContact(useSimOne: Boolean) {
val phoneNumber = getSelectedPhoneNumber() ?: return
activity.callContactWithSim(phoneNumber, useSimOne)
}
private fun callContact() {
val phoneNumber = getSelectedPhoneNumber() ?: return
(activity as SimpleActivity).startCallIntent(phoneNumber)
}
private fun removeDefaultSIM() {
val phoneNumber = getSelectedPhoneNumber() ?: return
activity.config.removeCustomSIM("tel:$phoneNumber")
finishActMode()
}
private fun tryBlocking() {
if (activity.isOrWasThankYouInstalled()) {
askConfirmBlock()
} else {
FeatureLockedDialog(activity) { }
}
}
private fun askConfirmBlock() {
val numbers = TextUtils.join(", ", getSelectedItems().distinctBy { it.phoneNumber }.map { it.phoneNumber })
val baseString = R.string.block_confirmation
val question = String.format(resources.getString(baseString), numbers)
ConfirmationDialog(activity, question) {
blockNumbers()
}
}
private fun blockNumbers() {
if (selectedKeys.isEmpty()) {
return
}
val callsToBlock = getSelectedItems()
val positions = getSelectedItemPositions()
recentCalls.removeAll(callsToBlock)
ensureBackgroundThread {
callsToBlock.map { it.phoneNumber }.forEach { number ->
activity.addBlockedNumber(number)
}
activity.runOnUiThread {
removeSelectedItems(positions)
finishActMode()
}
}
}
private fun addNumberToContact() {
val phoneNumber = getSelectedPhoneNumber() ?: return
Intent().apply {
action = Intent.ACTION_INSERT_OR_EDIT
type = "vnd.android.cursor.item/contact"
putExtra(KEY_PHONE, phoneNumber)
activity.launchActivityIntent(this)
}
}
private fun sendSMS() {
val numbers = getSelectedItems().map { it.phoneNumber }
val recipient = TextUtils.join(";", numbers)
activity.launchSendSMSIntent(recipient)
}
private fun showCallDetails() {
val recentCall = getSelectedItems().firstOrNull() ?: return
val callIds = recentCall.neighbourIDs.map { it }.toMutableList() as ArrayList<Int>
callIds.add(recentCall.id)
ShowGroupedCallsDialog(activity, callIds)
}
private fun copyNumber() {
val recentCall = getSelectedItems().firstOrNull() ?: return
activity.copyToClipboard(recentCall.phoneNumber)
finishActMode()
}
private fun askConfirmRemove() {
ConfirmationDialog(activity, activity.getString(R.string.remove_confirmation)) {
activity.handlePermission(PERMISSION_WRITE_CALL_LOG) {
removeRecents()
}
}
}
private fun removeRecents() {
if (selectedKeys.isEmpty()) {
return
}
val callsToRemove = getSelectedItems()
val positions = getSelectedItemPositions()
val idsToRemove = ArrayList<Int>()
callsToRemove.forEach {
idsToRemove.add(it.id)
it.neighbourIDs.mapTo(idsToRemove, { it })
}
RecentsHelper(activity).removeRecentCalls(idsToRemove) {
recentCalls.removeAll(callsToRemove)
activity.runOnUiThread {
if (recentCalls.isEmpty()) {
refreshItemsListener?.refreshItems()
finishActMode()
} else {
removeSelectedItems(positions)
}
}
}
}
private fun findContactByCall(recentCall: RecentCall): Contact? {
return (activity as MainActivity).cachedContacts.find { it.name == recentCall.name && it.doesHavePhoneNumber(recentCall.phoneNumber) }
}
private fun launchContactDetailsIntent(contact: Contact?) {
if (contact != null) {
activity.startContactDetailsIntent(contact)
}
}
fun updateItems(newItems: List<RecentCall>, highlightText: String = "") {
if (newItems.hashCode() != recentCalls.hashCode()) {
recentCalls = newItems.toMutableList()
textToHighlight = highlightText
recyclerView.resetItemCount()
notifyDataSetChanged()
finishActMode()
} else if (textToHighlight != highlightText) {
textToHighlight = highlightText
notifyDataSetChanged()
}
}
private fun getSelectedItems() = recentCalls.filter { selectedKeys.contains(it.id) } as ArrayList<RecentCall>
private fun getSelectedPhoneNumber() = getSelectedItems().firstOrNull()?.phoneNumber
private fun setupView(binding: ItemRecentCallBinding, call: RecentCall) {
binding.apply {
val currentFontSize = fontSize
itemRecentsHolder.isSelected = selectedKeys.contains(call.id)
val name = findContactByCall(call)?.getNameToDisplay() ?: call.name
var nameToShow = SpannableString(name)
if (call.specificType.isNotEmpty()) {
nameToShow = SpannableString("${name} - ${call.specificType}")
// show specific number at "Show call details" dialog too
if (refreshItemsListener == null) {
nameToShow = SpannableString("${name} - ${call.specificType}, ${call.specificNumber}")
}
}
if (call.neighbourIDs.isNotEmpty()) {
nameToShow = SpannableString("$nameToShow (${call.neighbourIDs.size + 1})")
}
if (textToHighlight.isNotEmpty() && nameToShow.contains(textToHighlight, true)) {
nameToShow = SpannableString(nameToShow.toString().highlightTextPart(textToHighlight, properPrimaryColor))
}
itemRecentsName.apply {
text = nameToShow
setTextColor(textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, currentFontSize)
}
itemRecentsDateTime.apply {
text = call.startTS.formatDateOrTime(context, refreshItemsListener != null, false)
setTextColor(if (call.type == Calls.MISSED_TYPE) redColor else textColor)
setTextSize(TypedValue.COMPLEX_UNIT_PX, currentFontSize * 0.8f)
}
itemRecentsDuration.apply {
text = call.duration.getFormattedDuration()
setTextColor(textColor)
beVisibleIf(call.type != Calls.MISSED_TYPE && call.type != Calls.REJECTED_TYPE && call.duration > 0)
setTextSize(TypedValue.COMPLEX_UNIT_PX, currentFontSize * 0.8f)
if (!showOverflowMenu) {
itemRecentsDuration.setPadding(0, 0, durationPadding, 0)
}
}
itemRecentsSimImage.beVisibleIf(areMultipleSIMsAvailable && call.simID != -1)
itemRecentsSimId.beVisibleIf(areMultipleSIMsAvailable && call.simID != -1)
if (areMultipleSIMsAvailable && call.simID != -1) {
itemRecentsSimImage.applyColorFilter(textColor)
itemRecentsSimId.setTextColor(textColor.getContrastColor())
itemRecentsSimId.text = call.simID.toString()
}
SimpleContactsHelper(root.context).loadContactImage(call.photoUri, itemRecentsImage, call.name)
val drawable = when (call.type) {
Calls.OUTGOING_TYPE -> outgoingCallIcon
Calls.MISSED_TYPE -> incomingMissedCallIcon
else -> incomingCallIcon
}
itemRecentsType.setImageDrawable(drawable)
overflowMenuIcon.beVisibleIf(showOverflowMenu)
overflowMenuIcon.drawable.apply {
mutate()
setTint(activity.getProperTextColor())
}
overflowMenuIcon.setOnClickListener {
showPopupMenu(overflowMenuAnchor, call)
}
}
}
private fun showPopupMenu(view: View, call: RecentCall) {
finishActMode()
val theme = activity.getPopupMenuTheme()
val contextTheme = ContextThemeWrapper(activity, theme)
val contact = findContactByCall(call)
val selectedNumber = "tel:${call.phoneNumber}"
PopupMenu(contextTheme, view, Gravity.END).apply {
inflate(R.menu.menu_recent_item_options)
menu.apply {
val areMultipleSIMsAvailable = activity.areMultipleSIMsAvailable()
findItem(R.id.cab_call).isVisible = !areMultipleSIMsAvailable && !call.isUnknownNumber
findItem(R.id.cab_call_sim_1).isVisible = areMultipleSIMsAvailable && !call.isUnknownNumber
findItem(R.id.cab_call_sim_2).isVisible = areMultipleSIMsAvailable && !call.isUnknownNumber
findItem(R.id.cab_send_sms).isVisible = !call.isUnknownNumber
findItem(R.id.cab_view_details).isVisible = contact != null && !call.isUnknownNumber
findItem(R.id.cab_add_number).isVisible = !call.isUnknownNumber
findItem(R.id.cab_copy_number).isVisible = !call.isUnknownNumber
findItem(R.id.cab_show_call_details).isVisible = !call.isUnknownNumber
findItem(R.id.cab_block_number).title = activity.addLockedLabelIfNeeded(R.string.block_number)
findItem(R.id.cab_block_number).isVisible = isNougatPlus() && !call.isUnknownNumber
findItem(R.id.cab_remove_default_sim).isVisible = (activity.config.getCustomSIM(selectedNumber) ?: "") != "" && !call.isUnknownNumber
}
setOnMenuItemClickListener { item ->
val callId = call.id
when (item.itemId) {
R.id.cab_call -> {
executeItemMenuOperation(callId) {
callContact()
}
}
R.id.cab_call_sim_1 -> {
executeItemMenuOperation(callId) {
callContact(true)
}
}
R.id.cab_call_sim_2 -> {
executeItemMenuOperation(callId) {
callContact(false)
}
}
R.id.cab_send_sms -> {
executeItemMenuOperation(callId) {
sendSMS()
}
}
R.id.cab_view_details -> {
executeItemMenuOperation(callId) {
launchContactDetailsIntent(contact)
}
}
R.id.cab_add_number -> {
executeItemMenuOperation(callId) {
addNumberToContact()
}
}
R.id.cab_show_call_details -> {
executeItemMenuOperation(callId) {
showCallDetails()
}
}
R.id.cab_block_number -> {
selectedKeys.add(callId)
tryBlocking()
}
R.id.cab_remove -> {
selectedKeys.add(callId)
askConfirmRemove()
}
R.id.cab_copy_number -> {
executeItemMenuOperation(callId) {
copyNumber()
}
}
R.id.cab_remove_default_sim -> {
executeItemMenuOperation(callId) {
removeDefaultSIM()
}
}
}
true
}
show()
}
}
private fun executeItemMenuOperation(callId: Int, callback: () -> Unit) {
selectedKeys.add(callId)
callback()
selectedKeys.remove(callId)
}
}