fix: keep contact nickname in private chat title when peer goes offline

This commit is contained in:
callebtc 2026-07-27 21:58:17 +02:00
parent c76c233aae
commit 83b2c51301
3 changed files with 81 additions and 2 deletions

View File

@ -26,6 +26,10 @@ object ContactDirectory {
@Volatile
private var meshProvider: (() -> MeshService?)? = null
@Volatile
internal var identityManagerProvider: (Context) -> SecureIdentityStateManager =
{ SecureIdentityStateManager(it) }
fun initialize(context: Context, meshProvider: () -> MeshService?) {
appContext = context.applicationContext
this.meshProvider = meshProvider
@ -79,7 +83,8 @@ object ContactDirectory {
noisePublicKey = noiseKey ?: liveMeshPeerID?.let { meshProvider?.invoke()?.getPeerInfo(it)?.noisePublicKey },
nostrPubkey = favorite?.peerNostrPublicKey,
displayName = favorite?.peerNickname?.takeIf { it.isNotBlank() && !it.equals("Unknown", ignoreCase = true) }
?: liveMeshPeerID?.let { meshProvider?.invoke()?.getPeerInfo(it)?.nickname },
?: liveMeshPeerID?.let { meshProvider?.invoke()?.getPeerInfo(it)?.nickname }
?: contactFingerprint?.let { cachedFingerprintNickname(it) },
isMutualFavorite = favorite?.isMutual == true
)
}
@ -132,7 +137,7 @@ object ContactDirectory {
private fun cachedNoiseKey(peerID: String): ByteArray? {
val context = appContext ?: return null
return try {
SecureIdentityStateManager(context)
identityManagerProvider(context)
.getCachedNoiseKey(peerID)
?.let { ContactIdentityResolver.bytesFromHex(it) }
} catch (_: Exception) {
@ -140,6 +145,17 @@ object ContactDirectory {
}
}
private fun cachedFingerprintNickname(fingerprint: String): String? {
val context = appContext ?: return null
return try {
identityManagerProvider(context)
.getCachedFingerprintNickname(fingerprint)
?.takeIf { it.isNotBlank() && !it.equals("Unknown", ignoreCase = true) }
} catch (_: Exception) {
null
}
}
private fun favoriteForMeshPeerID(peerID: String): FavoriteRelationship? =
try {
FavoritesPersistenceService.shared.getFavoriteStatus(peerID)

View File

@ -8,6 +8,7 @@ import com.bitchat.android.mesh.MeshService
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.noise.NoiseSession
import com.bitchat.android.nostr.GeohashAliasRegistry
import com.bitchat.android.services.ContactIdentityResolver
import com.bitchat.android.services.VerificationService
import com.bitchat.android.util.dataFromHexString
import com.bitchat.android.util.hexEncodedString
@ -185,6 +186,9 @@ class VerificationHandler(
val hexRegex = Regex("^[0-9a-fA-F]+$")
return try {
when {
ContactIdentityResolver.isContactConversationId(peerID) -> {
ContactIdentityResolver.fingerprintFromContactConversationId(peerID)
}
peerID.length == 64 && peerID.matches(hexRegex) -> {
identityManager.getCachedNoiseFingerprint(peerID)?.let { return it }
fingerprintFromNoiseHex(peerID)?.also { identityManager.cacheNoiseFingerprint(peerID, it) }

View File

@ -0,0 +1,59 @@
package com.bitchat.android.services
import android.content.Context
import android.os.Build
import com.bitchat.android.identity.SecureIdentityStateManager
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import java.util.UUID
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P], manifest = Config.NONE)
class ContactDirectoryTest {
private lateinit var identityManager: SecureIdentityStateManager
@Before
fun setup() {
val context = RuntimeEnvironment.getApplication()
val prefs = context.getSharedPreferences(
"contact-directory-test-${UUID.randomUUID()}",
Context.MODE_PRIVATE
)
identityManager = SecureIdentityStateManager(prefs, testOnly = true)
ContactDirectory.initialize(context) { null }
ContactDirectory.identityManagerProvider = { identityManager }
}
@After
fun tearDown() {
ContactDirectory.identityManagerProvider = { SecureIdentityStateManager(it) }
}
@Test
fun `offline contact resolves display name from cached fingerprint nickname`() {
val fingerprint = "ab".repeat(32)
identityManager.cacheFingerprintNickname(fingerprint, "Alice")
val resolution = ContactDirectory.resolve("contact_$fingerprint")
assertEquals("Alice", resolution.displayName)
assertNull(resolution.meshPeerID)
}
@Test
fun `offline contact without cached nickname has no display name`() {
val fingerprint = "cd".repeat(32)
val resolution = ContactDirectory.resolve("contact_$fingerprint")
assertNull(resolution.displayName)
}
}