Skip to content
190 changes: 140 additions & 50 deletions app/src/main/kotlin/org/fossify/phone/activities/DialpadActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.Telephony.Sms.Intents.SECRET_CODE_ACTION
import android.telephony.PhoneNumberUtils
import android.telephony.TelephonyManager
import android.util.TypedValue
import android.view.KeyEvent
Expand All @@ -20,20 +21,24 @@ import org.fossify.commons.extensions.*
import org.fossify.commons.helpers.*
import org.fossify.commons.models.contacts.Contact
import org.fossify.phone.R
import org.fossify.phone.adapters.ContactsAdapter
import org.fossify.phone.adapters.DialpadAdapter
import org.fossify.phone.databinding.ActivityDialpadBinding
import org.fossify.phone.extensions.*
import org.fossify.phone.helpers.DIALPAD_TONE_LENGTH_MS
import org.fossify.phone.helpers.RecentsHelper
import org.fossify.phone.helpers.ToneGeneratorHelper
import org.fossify.phone.interfaces.CachedContacts
import org.fossify.phone.models.DialpadItem
import org.fossify.phone.models.RecentCall
import org.fossify.phone.models.SpeedDial
import java.util.Locale
import kotlin.math.roundToInt

class DialpadActivity : SimpleActivity() {
class DialpadActivity : SimpleActivity(), CachedContacts {
private val binding by viewBinding(ActivityDialpadBinding::inflate)

private var allContacts = ArrayList<Contact>()
private var allCallItems = ArrayList<DialpadItem>()
private var dialpadAdapter: DialpadAdapter? = null
private var speedDialValues = ArrayList<SpeedDial>()
private val russianCharsMap = HashMap<Char, Int>()
private var hasRussianLocale = false
Expand All @@ -42,6 +47,8 @@ class DialpadActivity : SimpleActivity() {
private val longPressTimeout = ViewConfiguration.getLongPressTimeout().toLong()
private val longPressHandler = Handler(Looper.getMainLooper())
private val pressedKeys = mutableSetOf<Char>()
override var cachedContacts = ArrayList<Contact>()
private var cachedRecentCalls = emptyList<RecentCall>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -145,10 +152,6 @@ class DialpadActivity : SimpleActivity() {
dialpadInput.disableKeyboard()
}

ContactsHelper(this).getContacts(showOnlyContactsWithNumbers = true) { allContacts ->
gotContacts(allContacts)
}

val properPrimaryColor = getProperPrimaryColor()
val callIconId = if (areMultipleSIMsAvailable()) {
val callIcon = resources.getColoredDrawableWithColor(R.drawable.ic_phone_two_vector, properPrimaryColor.getContrastColor())
Expand Down Expand Up @@ -185,6 +188,9 @@ class DialpadActivity : SimpleActivity() {
binding.dialpadClearChar.applyColorFilter(getProperTextColor())
updateNavigationBarColor(getProperBackgroundColor())
setupToolbar(binding.dialpadToolbar, NavigationIcon.Arrow)
refreshCallItems {
refreshCallItems(true)
}
}

private fun setupOptionsMenu() {
Expand Down Expand Up @@ -231,18 +237,54 @@ class DialpadActivity : SimpleActivity() {
binding.dialpadInput.setText("")
}

private fun gotContacts(newContacts: ArrayList<Contact>) {
allContacts = newContacts
private fun refreshCallItems(loadAllRecents: Boolean = false, callback: (() -> Unit)? = null) {
val newItems = ArrayList<DialpadItem>()

getContacts { contacts ->
if (contacts.isNotEmpty()) {
newItems.add(DialpadItem("Contacts", true))
newItems.addAll(contacts.map { DialpadItem(it) })
}

getRecents(contacts, loadAllRecents) { recentCalls ->
if (recentCalls.isNotEmpty()) {
newItems.add(DialpadItem("Call History", false))
Comment on lines +245 to +251
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded strings here. There's already string resources available for both of these.

newItems.addAll(recentCalls.map { DialpadItem(it) })
}
}
}

allCallItems = newItems
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since getContacts and getRecents are async, allCallItems = newItems will execute before the code finishes fetching the items. The current code will work, but it relies on an implicit behavior.

Also, the callback is never being called.

}

val privateContacts = MyContactsContentProvider.getContacts(this, privateCursor)
if (privateContacts.isNotEmpty()) {
allContacts.addAll(privateContacts)
allContacts.sort()
private fun getContacts(callback: (List<Contact>) -> Unit) {
ContactsHelper(this).getContacts(showOnlyContactsWithNumbers = true) { contacts ->
ensureBackgroundThread {
callback(contacts)
}
}
}

private fun getRecents(contacts: List<Contact>, loadAllRecents: Boolean, callback: (List<RecentCall>) -> Unit) {
val queryCount = if (loadAllRecents) Int.MAX_VALUE else RecentsHelper.QUERY_LIMIT

RecentsHelper(this).getRecentCalls(cachedRecentCalls, queryCount) { recentCalls ->
ensureBackgroundThread {
cachedRecentCalls = recentCalls
val recentCallsNonContact = filterContactsInRecentCalls(recentCalls.distinctBy { it.phoneNumber }, contacts)

val privateContacts = MyContactsContentProvider.getContacts(this, privateCursor)
if (privateContacts.isNotEmpty()) {
allCallItems.addAll(privateContacts.map { DialpadItem(it) })
}
Comment on lines +275 to +279
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what's happening here.

If the goal is to add private contacts to allCallItems, this should be handled by getContacts, not getRecents.

allCallItems should only be updated in one place. Otherwise, subtle bugs and crashes are possible.


runOnUiThread {
if (!checkDialIntent() && binding.dialpadInput.value.isEmpty()) {
dialpadValueChanged("")
runOnUiThread {
if (!checkDialIntent() && binding.dialpadInput.value.isEmpty()) {
dialpadValueChanged("")
}
}

callback(recentCallsNonContact)
}
}
}
Expand All @@ -264,47 +306,82 @@ class DialpadActivity : SimpleActivity() {
return
}

(binding.dialpadList.adapter as? ContactsAdapter)?.finishActMode()
(binding.dialpadList.adapter as? DialpadAdapter)?.finishActMode()

val filtered = allContacts.filter { contact ->
var convertedName = KeypadHelper.convertKeypadLettersToDigits(
contact.name.normalizeString()
).filterNot { it.isWhitespace() }
var filtered = allCallItems.filter { item ->
when (item.itemType) {
DialpadItem.DialpadItemType.HEADER -> true
DialpadItem.DialpadItemType.CONTACT -> {
val contact = item.contact!!

if (hasRussianLocale) {
var currConvertedName = ""
convertedName.lowercase(Locale.getDefault()).forEach { char ->
val convertedChar = russianCharsMap.getOrElse(char) { char }
currConvertedName += convertedChar
var convertedName = KeypadHelper.convertKeypadLettersToDigits(
contact.name.normalizeString()
).filterNot { it.isWhitespace() }

if (hasRussianLocale) {
var currConvertedName = ""
convertedName.lowercase(Locale.getDefault()).forEach { char ->
val convertedChar = russianCharsMap.getOrElse(char) { char }
currConvertedName += convertedChar
}
convertedName = currConvertedName
}

contact.doesContainPhoneNumber(text) || (convertedName.contains(text, true))
}

DialpadItem.DialpadItemType.RECENTCALL -> {
if (len > 0) {
val recentCall = item.recentCall!!
val fixedText = text.trim().replace("\\s+".toRegex(), " ")

recentCall.name.contains(fixedText, true) || recentCall.doesContainPhoneNumber(fixedText)
} else {
false
}
}
}
}

filtered = filtered.filter { item ->
when (item.itemType) {
DialpadItem.DialpadItemType.HEADER -> {
(item.isHeaderForContacts && filtered.any { it.isContact() }) ||
(!item.isHeaderForContacts && filtered.any { it.isRecentCall() })
}
convertedName = currConvertedName

DialpadItem.DialpadItemType.CONTACT -> true
DialpadItem.DialpadItemType.RECENTCALL -> true
}
}

if (dialpadAdapter == null) {
dialpadAdapter = DialpadAdapter(
activity = this,
recyclerView = binding.dialpadList,
highlightText = text,
itemClick = {
val dialpadItem = it as DialpadItem

startCallWithConfirmationCheck(
dialpadItem.contact?.getPrimaryNumber() ?: dialpadItem.recentCall!!.phoneNumber,
dialpadItem.contact?.getNameToDisplay() ?: dialpadItem.recentCall!!.phoneNumber
)
Handler().postDelayed({
binding.dialpadInput.setText("")
}, 1000)
}, profileIconClick = {
if ((it as DialpadItem).isContact()) {
startContactDetailsIntent(it.contact!!)
}
})

contact.doesContainPhoneNumber(text) || (convertedName.contains(text, true))
}.sortedWith(compareBy {
!it.doesContainPhoneNumber(text)
}).toMutableList() as ArrayList<Contact>

binding.letterFastscroller.setupWithContacts(binding.dialpadList, filtered)

ContactsAdapter(
activity = this,
contacts = filtered,
recyclerView = binding.dialpadList,
highlightText = text,
itemClick = {
val contact = it as Contact
startCallWithConfirmationCheck(contact.getPrimaryNumber() ?: return@ContactsAdapter, contact.getNameToDisplay())
Handler().postDelayed({
binding.dialpadInput.setText("")
}, 1000)
},
profileIconClick = {
startContactDetailsIntent(it as Contact)
}).apply {
binding.dialpadList.adapter = this
binding.dialpadList.adapter = dialpadAdapter
}

dialpadAdapter!!.updateItems(filtered, text)
binding.letterFastscroller.setupWithDialpadItems(binding.dialpadList, filtered)

binding.dialpadPlaceholder.beVisibleIf(filtered.isEmpty())
binding.dialpadList.beVisibleIf(filtered.isNotEmpty())
}
Expand Down Expand Up @@ -440,4 +517,17 @@ class DialpadActivity : SimpleActivity() {
false
}
}

private fun filterContactsInRecentCalls(recentCalls: List<RecentCall>, contacts: List<Contact>): List<RecentCall> {
val contactNumbers = contacts.flatMap { it.phoneNumbers }.map { it.value }
return recentCalls.filterNot { recentCall ->
contactNumbers.any { contactNumber ->
PhoneNumberUtils.compare(
this,
recentCall.phoneNumber,
contactNumber
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,20 @@ import org.fossify.phone.fragments.RecentsFragment
import org.fossify.phone.helpers.OPEN_DIAL_PAD_AT_LAUNCH
import org.fossify.phone.helpers.RecentsHelper
import org.fossify.phone.helpers.tabsList
import org.fossify.phone.interfaces.CachedContacts
import org.fossify.phone.models.Events
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode

class MainActivity : SimpleActivity() {
class MainActivity : SimpleActivity(), CachedContacts {
private val binding by viewBinding(ActivityMainBinding::inflate)

private var launchedDialer = false
private var storedShowTabs = 0
private var storedFontSize = 0
private var storedStartNameWithSurname = false
var cachedContacts = ArrayList<Contact>()
override var cachedContacts = ArrayList<Contact>()

override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
Expand Down Expand Up @@ -488,7 +489,7 @@ class MainActivity : SimpleActivity() {
}

fun refreshFragments() {
cacheContacts()
cacheContacts(this)
getContactsFragment()?.refreshItems()
getFavoritesFragment()?.refreshItems()
getRecentsFragment()?.refreshItems()
Expand Down Expand Up @@ -610,25 +611,6 @@ class MainActivity : SimpleActivity() {
}
}

fun cacheContacts() {
val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
ContactsHelper(this).getContacts(getAll = true, showOnlyContactsWithNumbers = true) { contacts ->
if (SMT_PRIVATE !in config.ignoredContactSources) {
val privateContacts = MyContactsContentProvider.getContacts(this, privateCursor)
if (privateContacts.isNotEmpty()) {
contacts.addAll(privateContacts)
contacts.sort()
}
}

try {
cachedContacts.clear()
cachedContacts.addAll(contacts)
} catch (ignored: Exception) {
}
}
}

@Subscribe(threadMode = ThreadMode.MAIN)
fun refreshCallLog(event: Events.RefreshCallLog) {
getRecentsFragment()?.refreshItems()
Expand Down
Loading
Loading