Skip to content

Add dynamic shortcuts for starred contacts#249

Closed
Grafcube wants to merge 5 commits into
FossifyOrg:mainfrom
Grafcube:dynamic-shortcuts-starred
Closed

Add dynamic shortcuts for starred contacts#249
Grafcube wants to merge 5 commits into
FossifyOrg:mainfrom
Grafcube:dynamic-shortcuts-starred

Conversation

@Grafcube
Copy link
Copy Markdown

What is it?

  • Bugfix
  • Feature
  • Codebase improvement

Description of the changes in your PR

  • It uses the custom sorting if set.
  • It uses ACTION_DIAL instead of ACTION_CALL if calling permission is denied.

Fixes the following issue(s)

Acknowledgement

@naveensingh naveensingh added the testers needed We need testers for this issue or pull request label Sep 28, 2024
@Aga-C
Copy link
Copy Markdown
Contributor

Aga-C commented Sep 28, 2024

I think that visible shortcuts should always be the first ones according to current sorting of Favorites tab. For example, if I sort descending by name, I want to see in shortcuts three first items by that sorting, not by the default one; if I use custom sorting, I want to have specific contacts visible at the top, so also in shortcuts.

@Grafcube
Copy link
Copy Markdown
Author

Done! I recall sorting the list at first but I must have accidentally removed it while refactoring.

@Aga-C
Copy link
Copy Markdown
Contributor

Aga-C commented Sep 28, 2024

I've downloaded your latest changes and sorting is still not applied. See screenshots:

obraz

obraz

Also, there are two other bugs:

  • Changing sorting doesn't invoke refreshing shortcuts.
  • After fresh installation of the app, there are no shortcuts at all. I have to add/remove any contact from favorites first.

@Grafcube
Copy link
Copy Markdown
Author

Grafcube commented Sep 28, 2024

The changes aren't applied immediately. It's called onResume so it'll apply if you close the app and reopen it (you don't have to fully close it, you can open it through recents as well).

I have to add/remove any contact from favorites first.

I didn't need to add/remove anything when I was testing it so I'm not sure why that's happening.

@Aga-C
Copy link
Copy Markdown
Contributor

Aga-C commented Sep 28, 2024

I did it, and sorting is still not applied.

@Grafcube
Copy link
Copy Markdown
Author

Grafcube commented Sep 28, 2024

I just tested it again with a fresh install and it seems to work fine. It only shows up after the second or third time I open the app after a fresh install, but after that it reflects sorting changes relatively quickly.

I'm not sure where the issue is happening.

Edit: Just to clarify, I'm talking about the sorting in the Favourites tab.

@Grafcube
Copy link
Copy Markdown
Author

Grafcube commented Oct 1, 2024

@Aga-C can you test out the latest commit? It should work now.

@Aga-C
Copy link
Copy Markdown
Contributor

Aga-C commented Oct 1, 2024

But you still haven't solved a problem, which is in your code:

                if (order.isEmpty()) {
                    starred.sorted()
                } else {
                    val orderList = Converters().jsonToStringList(order).withIndex().associate { it.value to it.index }
                    starred.sortedBy { orderList[it.contactId.toString()] }
                }

You always assume, that if order is set, that's the only way to sort, while you should check config.isCustomOrderSelected. That's why I haven't seen changes - I had order set, but I was changing sorting to different from custom.

@Grafcube
Copy link
Copy Markdown
Author

Grafcube commented Oct 1, 2024

I see. Sorry about that, it seems like I fundamentally misunderstood the issue. It should be fixed now.

@Aga-C
Copy link
Copy Markdown
Contributor

Aga-C commented Oct 1, 2024

It still doesn't show the first three items from the current sorting. Check the recording below:

qemu-system-x86_64_xeknsQ9wCX.mp4

Also, one of these contacts (Test Hmm) is created as a private contact. It should still display it.

@Grafcube
Copy link
Copy Markdown
Author

Grafcube commented Oct 1, 2024

At this point, I genuinely have no idea where the problem is. I'm just not encountering that issue so I have no way to debug it.

@naveensingh naveensingh self-assigned this May 26, 2025
@naveensingh naveensingh removed the testers needed We need testers for this issue or pull request label Jun 4, 2025
Comment on lines +300 to +310
private fun pushStarredShortcuts() {
if (VERSION.SDK_INT >= VERSION_CODES.R) {
val starred = cachedContacts.filter { it.starred == 1 && it.phoneNumbers.isNotEmpty() }
this.config.favoritesContactsOrder.let { order ->
if (!config.isCustomOrderSelected) {
starred.sorted()
} else {
val orderList = Converters().jsonToStringList(order).withIndex().associate { it.value to it.index }
starred.sortedBy { orderList[it.contactId.toString()] }
}
}.take(3).forEachIndexed { index, contact ->
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.

That's a very convoluted way to access config.favoritesContactsOrder.

It's better to extract this into a separate function:

Suggested change
private fun pushStarredShortcuts() {
if (VERSION.SDK_INT >= VERSION_CODES.R) {
val starred = cachedContacts.filter { it.starred == 1 && it.phoneNumbers.isNotEmpty() }
this.config.favoritesContactsOrder.let { order ->
if (!config.isCustomOrderSelected) {
starred.sorted()
} else {
val orderList = Converters().jsonToStringList(order).withIndex().associate { it.value to it.index }
starred.sortedBy { orderList[it.contactId.toString()] }
}
}.take(3).forEachIndexed { index, contact ->
private fun getTopStarredContacts(): List<Contact> {
val starred = cachedContacts.filter { it.starred == 1 && it.phoneNumbers.isNotEmpty() }
return if (!config.isCustomOrderSelected) {
starred.sorted()
} else {
val orderList = Converters().jsonToStringList(config.favoritesContactsOrder)
.withIndex()
.associate { it.value to it.index }
starred.sortedBy { orderList[it.contactId.toString()] ?: Int.MAX_VALUE }
}.take(3)
}
private fun pushStarredShortcuts() {
if (isRPlus()) {
getTopStarredContacts().forEachIndexed { index, contact ->

Or even better, convert that sortByCustomOrder function from FavoritesFragment to an extension and reuse it here.

Comment on lines +12 to +13
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
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.

Suggested change
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES

Comment on lines +311 to +323
var number = if (contact.phoneNumbers.size == 1) {
contact.phoneNumbers[0].normalizedNumber
} else {
contact.phoneNumbers.firstOrNull { it.isPrimary }?.normalizedNumber
}
if (number == null) {
val radioItems = contact.phoneNumbers.mapIndexed { index, item ->
RadioItem(index, item.normalizedNumber, item)
}
RadioGroupDialog(this, ArrayList(radioItems)) {
number = (it as PhoneNumber).normalizedNumber
}
}
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.

Radio dialog here is bad UX, and it doesn't even work. Let's fall back to the first number when there is no primary number. Multiple numbers will be handled when #102 is done.

Suggested change
var number = if (contact.phoneNumbers.size == 1) {
contact.phoneNumbers[0].normalizedNumber
} else {
contact.phoneNumbers.firstOrNull { it.isPrimary }?.normalizedNumber
}
if (number == null) {
val radioItems = contact.phoneNumbers.mapIndexed { index, item ->
RadioItem(index, item.normalizedNumber, item)
}
RadioGroupDialog(this, ArrayList(radioItems)) {
number = (it as PhoneNumber).normalizedNumber
}
}
val phoneNumbers = contact.phoneNumbers
var number = if (phoneNumbers.size == 1) {
phoneNumbers[0].normalizedNumber
} else {
phoneNumbers.firstOrNull { it.isPrimary }?.normalizedNumber
?: phoneNumbers.firstOrNull()?.normalizedNumber ?: return@forEachIndexed
}

Comment on lines +324 to +336
this.handlePermission(PERMISSION_CALL_PHONE) { hasPermission ->
val action = if (hasPermission) Intent.ACTION_CALL else Intent.ACTION_DIAL
val intent = Intent(action).apply {
data = Uri.fromParts("tel", number, null)
}
SimpleContactsHelper(this).getShortcutImage(contact.photoUri, contact.getNameToDisplay()) { image ->
this.runOnUiThread {
val shortcut = ShortcutInfo.Builder(this, "sd$index")
.setShortLabel(contact.getNameToDisplay())
.setIcon(Icon.createWithAdaptiveBitmap(image))
.setIntent(intent)
.build()
this.shortcutManager.pushDynamicShortcut(shortcut)
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.

Suggested change
this.handlePermission(PERMISSION_CALL_PHONE) { hasPermission ->
val action = if (hasPermission) Intent.ACTION_CALL else Intent.ACTION_DIAL
val intent = Intent(action).apply {
data = Uri.fromParts("tel", number, null)
}
SimpleContactsHelper(this).getShortcutImage(contact.photoUri, contact.getNameToDisplay()) { image ->
this.runOnUiThread {
val shortcut = ShortcutInfo.Builder(this, "sd$index")
.setShortLabel(contact.getNameToDisplay())
.setIcon(Icon.createWithAdaptiveBitmap(image))
.setIntent(intent)
.build()
this.shortcutManager.pushDynamicShortcut(shortcut)
handlePermission(PERMISSION_CALL_PHONE) { hasPermission ->
val action = if (hasPermission) Intent.ACTION_CALL else Intent.ACTION_DIAL
val intent = Intent(action).apply {
data = Uri.fromParts("tel", number, null)
}
SimpleContactsHelper(this).getShortcutImage(contact.photoUri, contact.getNameToDisplay()) { image ->
runOnUiThread {
val shortcut = ShortcutInfo.Builder(this, "sd$index")
.setShortLabel(contact.getNameToDisplay())
.setIcon(Icon.createWithAdaptiveBitmap(image))
.setIntent(intent)
.build()
shortcutManager.pushDynamicShortcut(shortcut)

@naveensingh
Copy link
Copy Markdown
Member

naveensingh commented Jun 5, 2025

I can reproduce the sorting issue as well. Sometimes, I get shortcuts that are not even on my favorite list. Restarting the app doesn't help. It's probably because you are relying on cachedContacts, which is not even initialized when pushStarredShortcuts() is called on resume.

Also, it should directly initiate a call instead of asking for a phone app.

@naveensingh naveensingh added the waiting for author If the author does not respond, the issue will be closed. Otherwise, the label will be removed. label Jun 5, 2025
@naveensingh naveensingh removed their assignment Jun 9, 2025
@fossifybot
Copy link
Copy Markdown
Contributor

fossifybot Bot commented Jun 19, 2025

This pull request was automatically closed due to inactivity and/or lack of response from the author. If you have further updates or additional information, please comment or reopen the PR to continue.

@fossifybot fossifybot Bot removed the waiting for author If the author does not respond, the issue will be closed. Otherwise, the label will be removed. label Jun 19, 2025
@fossifybot fossifybot Bot closed this Jun 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add launcher shortcuts to call contacts

3 participants