diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ProfileScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ProfileScreen.kt index d635d80..c5dec81 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ProfileScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ProfileScreen.kt @@ -85,6 +85,8 @@ fun ProfileScreen(pubkey: String, screening: Boolean = false) { var addressVerified by remember { mutableStateOf(false) } var lastActivity by remember { mutableStateOf(null) } + var isContact by remember { mutableStateOf(false) } + var mutualContacts by remember { mutableStateOf>(emptySet()) } LaunchedEffect(screening) { if (screening) { @@ -95,6 +97,10 @@ fun ProfileScreen(pubkey: String, screening: Boolean = false) { } // Get the last activity viewModel.verifyActivity(pubkey)?.let { lastActivity = it } + // Check contact + viewModel.verifyContact(pubkey).let { isContact = it } + // Get mutual contacts + viewModel.mutualContacts(pubkey).let { mutualContacts = it } } } } diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt index 66e70e6..3e51b67 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt @@ -296,7 +296,11 @@ class Nostr { if (event.kind().asStd()?.equals(KindStandard.CONTACT_LIST) == true) { if (isSignedByUser(event = event)) { - onContactListUpdate(event.tags().publicKeys()) + val pubkeys = event.tags().publicKeys(); + // Get mutual contacts + getMutualContacts(pubkeys) + // Emit contact list update + onContactListUpdate(pubkeys) } } @@ -313,6 +317,7 @@ class Nostr { // Logic to notify UI after processing // Cancel previous tracker if it exists eoseTrackerJob?.cancel() + // Start a new tracker eoseTrackerJob = launch { delay(10000.milliseconds) // Wait for 10 seconds @@ -360,6 +365,27 @@ class Nostr { } } + private suspend fun getMutualContacts(pubkeys: List) { + try { + val kind = Kind.fromStd(KindStandard.CONTACT_LIST); + val filter = Filter().kind(kind).authors(pubkeys).limit(200u) + val opts = SubscribeAutoCloseOptions().exitPolicy(ReqExitPolicy.ExitOnEose) + + val target = mutableMapOf>() + NostrManager.BOOTSTRAP_RELAYS.forEach { relay -> + target[RelayUrl.parse(relay)] = listOf(filter) + } + + client?.subscribe( + target = ReqTarget.manual(target), + id = "mutual-contacts", + closeOn = opts, + ) + } catch (e: Exception) { + throw IllegalStateException("Failed to fetch mutual contacts: ${e.message}", e) + } + } + private suspend fun getCachedRumor(giftId: EventId): UnsignedEvent? { try { val filter = Filter().identifier(giftId.toHex()) @@ -977,4 +1003,39 @@ class Nostr { throw IllegalStateException("Failed to get latest activity: ${e.message}", e) } } + + suspend fun verifyContact(pubkey: PublicKey): Boolean { + try { + val kind = Kind.fromStd(KindStandard.CONTACT_LIST) + val filter = Filter().kind(kind).author(pubkey).limit(1u) + + val events = client?.database()?.query(filter) + val pubkeys = events?.first()?.tags()?.publicKeys() ?: listOf() + + return pubkeys.contains(pubkey) + } catch (e: Exception) { + throw IllegalStateException("Failed to get mutual contacts: ${e.message}", e) + } + } + + suspend fun mutualContacts(pubkey: PublicKey): Set { + try { + val currentUser = + signer.getPublicKeyAsync() ?: throw IllegalStateException("User not signed in") + + val kind = Kind.fromStd(KindStandard.CONTACT_LIST) + val filter = Filter().kind(kind).pubkey(pubkey).limit(1u) + + val events = client?.database()?.query(filter) + val contacts = mutableSetOf() + + events?.toVec()?.filter { it.author() != currentUser }?.forEach { event -> + contacts.add(event.author()) + } + + return contacts.toSet() + } catch (e: Exception) { + throw IllegalStateException("Failed to get mutual contacts: ${e.message}", e) + } + } } diff --git a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt index 5544d9c..3c32fe8 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt @@ -839,6 +839,24 @@ class NostrViewModel( null } } + + suspend fun verifyContact(pubkey: PublicKey): Boolean { + return try { + nostr.verifyContact(pubkey) + } catch (e: Exception) { + showError("Error: ${e.message}") + false + } + } + + suspend fun mutualContacts(pubkey: PublicKey): Set { + return try { + nostr.mutualContacts(pubkey) + } catch (e: Exception) { + showError("Error: ${e.message}") + setOf() + } + } } fun PublicKey.short(): String {