fix(amethyst): show caller display name in WebRTC call notification

The incoming call push notification was falling back to the raw caller
hex (or short npub) whenever the caller's User object hadn't been
created or their metadata hadn't been loaded yet — which is the common
case when the app is woken up by a push notification with a fresh,
empty in-memory cache.

Switch notifyIncomingCall to use LocalCache.getOrCreateUser, and if the
caller's metadata hasn't been loaded, briefly subscribe via
UserFinderQueryState (same pattern used by wakeUpFor) and wait up to
5 seconds for metadata to arrive before building the notification.
This lets toBestDisplayName() resolve to the user's real name in the
notification popup.

Also tighten NotificationUtils.showIncomingCallNotification to use
getOrCreateUser so that in-app calls fall back to the user's npub
display rather than a raw hex prefix when metadata is unavailable.
This commit is contained in:
Claude
2026-04-15 19:55:15 +00:00
parent 091e07324c
commit da6dae3185
2 changed files with 29 additions and 6 deletions
@@ -71,8 +71,10 @@ import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import java.math.BigDecimal
import kotlin.coroutines.cancellation.CancellationException
@@ -699,11 +701,32 @@ class EventNotificationConsumer(
if (TimeUtils.now() - event.createdAt > CallManager.MAX_EVENT_AGE_SECONDS) return
val callerUser = LocalCache.getUserIfExists(event.pubKey)
val callerName = callerUser?.toBestDisplayName() ?: event.pubKey.take(8) + "..."
val callerUser = LocalCache.getOrCreateUser(event.pubKey)
// If the caller's metadata hasn't been loaded yet (e.g. fresh process from
// a push notification), briefly subscribe to the user finder so we can
// resolve the user's display name instead of showing the raw pubkey.
if (callerUser.metadataOrNull()?.bestName() == null) {
val authorState = UserFinderQueryState(callerUser, account)
try {
Amethyst.instance.sources.userFinder
.subscribe(authorState)
withTimeoutOrNull(5_000L) {
callerUser
.metadata()
.flow
.first { it?.info?.bestName() != null }
}
} finally {
Amethyst.instance.sources.userFinder
.unsubscribe(authorState)
}
}
val callerName = callerUser.toBestDisplayName()
val callerBitmap =
callerUser?.profilePicture()?.let { pictureUrl ->
callerUser.profilePicture()?.let { pictureUrl ->
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
try {
val request =
@@ -630,12 +630,12 @@ object NotificationUtils {
callerPubKey: String,
context: Context,
) {
val callerUser = LocalCache.getUserIfExists(callerPubKey)
val callerName = callerUser?.toBestDisplayName() ?: callerPubKey.take(8) + "..."
val callerUser = LocalCache.getOrCreateUser(callerPubKey)
val callerName = callerUser.toBestDisplayName()
val uri = "nostr:${callerPubKey.hexToByteArray().toNpub()}"
val callerBitmap =
callerUser?.profilePicture()?.let { pictureUrl ->
callerUser.profilePicture()?.let { pictureUrl ->
withContext(Dispatchers.IO) {
try {
val request =