bitchat-android/app/src/main/java/com/bitchat/android/ui/PrivateChatManager.kt
2025-07-12 17:11:19 +02:00

300 lines
10 KiB
Kotlin

package com.bitchat.android.ui
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.DeliveryStatus
import java.util.*
import android.util.Log
/**
* Handles private chat functionality including peer management and blocking
*/
class PrivateChatManager(
private val state: ChatState,
private val messageManager: MessageManager,
private val dataManager: DataManager
) {
companion object {
private const val TAG = "PrivateChatManager"
}
// Peer identification mapping
private val peerIDToPublicKeyFingerprint = mutableMapOf<String, String>()
// MARK: - Private Chat Lifecycle
fun startPrivateChat(peerID: String, meshService: Any): Boolean {
if (isPeerBlocked(peerID)) {
val peerNickname = getPeerNickname(peerID, meshService)
val systemMessage = BitchatMessage(
sender = "system",
content = "cannot start chat with $peerNickname: user is blocked.",
timestamp = Date(),
isRelay = false
)
messageManager.addMessage(systemMessage)
return false
}
state.setSelectedPrivateChatPeer(peerID)
// Clear unread
messageManager.clearPrivateUnreadMessages(peerID)
// Initialize chat if needed
messageManager.initializePrivateChat(peerID)
return true
}
fun endPrivateChat() {
state.setSelectedPrivateChatPeer(null)
}
fun sendPrivateMessage(
content: String,
peerID: String,
recipientNickname: String?,
senderNickname: String?,
myPeerID: String,
onSendMessage: (String, String, String, String) -> Unit
): Boolean {
if (isPeerBlocked(peerID)) {
val systemMessage = BitchatMessage(
sender = "system",
content = "cannot send message to $recipientNickname: user is blocked.",
timestamp = Date(),
isRelay = false
)
messageManager.addMessage(systemMessage)
return false
}
val message = BitchatMessage(
sender = senderNickname ?: myPeerID,
content = content,
timestamp = Date(),
isRelay = false,
isPrivate = true,
recipientNickname = recipientNickname,
senderPeerID = myPeerID,
deliveryStatus = DeliveryStatus.Sending
)
messageManager.addPrivateMessage(peerID, message)
onSendMessage(content, peerID, recipientNickname ?: "", message.id)
return true
}
// MARK: - Peer Management
fun registerPeerPublicKey(peerID: String, publicKeyData: ByteArray) {
val md = java.security.MessageDigest.getInstance("SHA-256")
val hash = md.digest(publicKeyData)
val fingerprint = hash.take(8).joinToString("") { "%02x".format(it) }
peerIDToPublicKeyFingerprint[peerID] = fingerprint
}
fun isPeerBlocked(peerID: String): Boolean {
val fingerprint = peerIDToPublicKeyFingerprint[peerID]
return fingerprint != null && dataManager.isUserBlocked(fingerprint)
}
fun toggleFavorite(peerID: String) {
val fingerprint = peerIDToPublicKeyFingerprint[peerID] ?: return
Log.d(TAG, "toggleFavorite called for peerID: $peerID, fingerprint: $fingerprint")
val wasFavorite = dataManager.isFavorite(fingerprint)
Log.d(TAG, "Current favorite status: $wasFavorite")
val currentFavorites = state.getFavoritePeersValue()
Log.d(TAG, "Current UI state favorites: $currentFavorites")
if (wasFavorite) {
dataManager.removeFavorite(fingerprint)
Log.d(TAG, "Removed from favorites: $fingerprint")
} else {
dataManager.addFavorite(fingerprint)
Log.d(TAG, "Added to favorites: $fingerprint")
}
// Always update state to trigger UI refresh - create new set to ensure change detection
val newFavorites = dataManager.favoritePeers.toSet()
state.setFavoritePeers(newFavorites)
Log.d(TAG, "Force updated favorite peers state. New favorites: $newFavorites")
Log.d(TAG, "All peer fingerprints: $peerIDToPublicKeyFingerprint")
}
fun isFavorite(peerID: String): Boolean {
val fingerprint = peerIDToPublicKeyFingerprint[peerID] ?: return false
val isFav = dataManager.isFavorite(fingerprint)
Log.d(TAG, "isFavorite check: peerID=$peerID, fingerprint=$fingerprint, result=$isFav")
return isFav
}
fun getPeerFingerprint(peerID: String): String? {
return peerIDToPublicKeyFingerprint[peerID]
}
fun getPeerFingerprints(): Map<String, String> {
return peerIDToPublicKeyFingerprint.toMap()
}
// MARK: - Block/Unblock Operations
fun blockPeer(peerID: String, meshService: Any): Boolean {
val fingerprint = peerIDToPublicKeyFingerprint[peerID]
if (fingerprint != null) {
dataManager.addBlockedUser(fingerprint)
val peerNickname = getPeerNickname(peerID, meshService)
val systemMessage = BitchatMessage(
sender = "system",
content = "blocked user $peerNickname",
timestamp = Date(),
isRelay = false
)
messageManager.addMessage(systemMessage)
// End private chat if currently in one with this peer
if (state.getSelectedPrivateChatPeerValue() == peerID) {
endPrivateChat()
}
return true
}
return false
}
fun unblockPeer(peerID: String, meshService: Any): Boolean {
val fingerprint = peerIDToPublicKeyFingerprint[peerID]
if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) {
dataManager.removeBlockedUser(fingerprint)
val peerNickname = getPeerNickname(peerID, meshService)
val systemMessage = BitchatMessage(
sender = "system",
content = "unblocked user $peerNickname",
timestamp = Date(),
isRelay = false
)
messageManager.addMessage(systemMessage)
return true
}
return false
}
fun blockPeerByNickname(targetName: String, meshService: Any): Boolean {
val peerID = getPeerIDForNickname(targetName, meshService)
if (peerID != null) {
return blockPeer(peerID, meshService)
} else {
val systemMessage = BitchatMessage(
sender = "system",
content = "user '$targetName' not found",
timestamp = Date(),
isRelay = false
)
messageManager.addMessage(systemMessage)
return false
}
}
fun unblockPeerByNickname(targetName: String, meshService: Any): Boolean {
val peerID = getPeerIDForNickname(targetName, meshService)
if (peerID != null) {
val fingerprint = peerIDToPublicKeyFingerprint[peerID]
if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) {
return unblockPeer(peerID, meshService)
} else {
val systemMessage = BitchatMessage(
sender = "system",
content = "user '$targetName' is not blocked",
timestamp = Date(),
isRelay = false
)
messageManager.addMessage(systemMessage)
return false
}
} else {
val systemMessage = BitchatMessage(
sender = "system",
content = "user '$targetName' not found",
timestamp = Date(),
isRelay = false
)
messageManager.addMessage(systemMessage)
return false
}
}
fun listBlockedUsers(): String {
val blockedCount = dataManager.blockedUsers.size
return if (blockedCount == 0) {
"no blocked users"
} else {
"blocked users: $blockedCount fingerprints"
}
}
// MARK: - Message Handling
fun handleIncomingPrivateMessage(message: BitchatMessage) {
message.senderPeerID?.let { senderPeerID ->
if (!isPeerBlocked(senderPeerID)) {
messageManager.addPrivateMessage(senderPeerID, message)
}
}
}
fun cleanupDisconnectedPeer(peerID: String) {
// End private chat if peer disconnected
if (state.getSelectedPrivateChatPeerValue() == peerID) {
endPrivateChat()
}
}
// MARK: - Utility Functions
private fun getPeerIDForNickname(nickname: String, meshService: Any): String? {
// This would need to access the mesh service to get peer nicknames
// For now, we'll assume the mesh service provides a way to get this mapping
return try {
val method = meshService::class.java.getDeclaredMethod("getPeerNicknames")
val peerNicknames = method.invoke(meshService) as? Map<String, String>
peerNicknames?.entries?.find { it.value == nickname }?.key
} catch (e: Exception) {
null
}
}
private fun getPeerNickname(peerID: String, meshService: Any): String {
return try {
val method = meshService::class.java.getDeclaredMethod("getPeerNicknames")
val peerNicknames = method.invoke(meshService) as? Map<String, String>
peerNicknames?.get(peerID) ?: peerID
} catch (e: Exception) {
peerID
}
}
// MARK: - Emergency Clear
fun clearAllPrivateChats() {
state.setSelectedPrivateChatPeer(null)
state.setUnreadPrivateMessages(emptySet())
peerIDToPublicKeyFingerprint.clear()
}
// MARK: - Public Getters
fun getAllPeerFingerprints(): Map<String, String> {
return peerIDToPublicKeyFingerprint.toMap()
}
}