mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 12:05:20 +00:00
improve
This commit is contained in:
@@ -444,7 +444,7 @@ private fun MainHeader(
|
||||
)
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
PeerCounter(
|
||||
connectedPeers = connectedPeers.filter { it != viewModel.meshService.myPeerID },
|
||||
connectedPeers = connectedPeers.filter { it != viewModel.myPeerID },
|
||||
joinedChannels = joinedChannels,
|
||||
hasUnreadChannels = hasUnreadChannels,
|
||||
isConnected = isConnected,
|
||||
|
||||
@@ -133,7 +133,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
||||
MessagesList(
|
||||
messages = displayMessages,
|
||||
currentUserNickname = nickname,
|
||||
meshService = viewModel.meshService,
|
||||
meshService = viewModel.meshServiceFacade,
|
||||
modifier = Modifier.weight(1f),
|
||||
forceScrollToBottom = forceScrollToBottom,
|
||||
onScrolledUpChanged = { isUp -> isScrolledUp = isUp },
|
||||
|
||||
@@ -11,7 +11,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Shield
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -42,7 +42,7 @@ fun getRSSIColor(rssi: Int): Color {
|
||||
fun formatMessageAsAnnotatedString(
|
||||
message: BitchatMessage,
|
||||
currentUserNickname: String,
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
colorScheme: ColorScheme,
|
||||
timeFormatter: SimpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||
): AnnotatedString {
|
||||
@@ -162,7 +162,7 @@ fun formatMessageAsAnnotatedString(
|
||||
fun formatMessageHeaderAnnotatedString(
|
||||
message: BitchatMessage,
|
||||
currentUserNickname: String,
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
colorScheme: ColorScheme,
|
||||
timeFormatter: SimpleDateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||
): AnnotatedString {
|
||||
|
||||
@@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import com.bitchat.android.mesh.BluetoothMeshDelegate
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.service.MeshServiceHolder
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.BitchatMessageType
|
||||
@@ -38,12 +39,16 @@ import java.security.MessageDigest
|
||||
*/
|
||||
class ChatViewModel(
|
||||
application: Application,
|
||||
initialMeshService: BluetoothMeshService
|
||||
initialMeshService: BluetoothMeshService,
|
||||
initialUnifiedMeshService: MeshService
|
||||
) : AndroidViewModel(application), BluetoothMeshDelegate {
|
||||
|
||||
// Made var to support mesh service replacement after panic clear
|
||||
var meshService: BluetoothMeshService = initialMeshService
|
||||
private set
|
||||
private var unifiedMeshService: MeshService = initialUnifiedMeshService
|
||||
private val mesh: MeshService
|
||||
get() = unifiedMeshService
|
||||
private val debugManager by lazy { try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance() } catch (e: Exception) { null } }
|
||||
|
||||
companion object {
|
||||
@@ -95,7 +100,7 @@ class ChatViewModel(
|
||||
private val noiseSessionDelegate = object : NoiseSessionDelegate {
|
||||
override fun hasEstablishedSession(peerID: String): Boolean = hasEstablishedSessionOnAnyLocalTransport(peerID)
|
||||
override fun initiateHandshake(peerID: String) = initiateNoiseHandshakeOnBestLocalTransport(peerID)
|
||||
override fun getMyPeerID(): String = meshService.myPeerID
|
||||
override fun getMyPeerID(): String = mesh.myPeerID
|
||||
}
|
||||
|
||||
val privateChatManager = PrivateChatManager(state, messageManager, dataManager, noiseSessionDelegate)
|
||||
@@ -109,7 +114,7 @@ class ChatViewModel(
|
||||
private val verificationHandler = VerificationHandler(
|
||||
context = application.applicationContext,
|
||||
scope = viewModelScope,
|
||||
getMeshService = { meshService },
|
||||
getMeshService = { mesh },
|
||||
identityManager = identityManager,
|
||||
state = state,
|
||||
notificationManager = notificationManager,
|
||||
@@ -118,7 +123,7 @@ class ChatViewModel(
|
||||
val verifiedFingerprints = verificationHandler.verifiedFingerprints
|
||||
|
||||
// Media file sending manager
|
||||
private val mediaSendingManager = MediaSendingManager(state, messageManager, channelManager) { meshService }
|
||||
private val mediaSendingManager = MediaSendingManager(state, messageManager, channelManager) { mesh }
|
||||
|
||||
// Delegate handler for mesh callbacks
|
||||
private val meshDelegateHandler = MeshDelegateHandler(
|
||||
@@ -129,8 +134,8 @@ class ChatViewModel(
|
||||
notificationManager = notificationManager,
|
||||
coroutineScope = viewModelScope,
|
||||
onHapticFeedback = { ChatViewModelUtils.triggerHapticFeedback(application.applicationContext) },
|
||||
getMyPeerID = { meshService.myPeerID },
|
||||
getMeshService = { meshService }
|
||||
getMyPeerID = { mesh.myPeerID },
|
||||
getMeshService = { mesh }
|
||||
)
|
||||
|
||||
// New Geohash architecture ViewModel (replaces God object service usage in UI path)
|
||||
@@ -184,6 +189,18 @@ class ChatViewModel(
|
||||
val geohashPeople: StateFlow<List<GeoPerson>> = state.geohashPeople
|
||||
val teleportedGeo: StateFlow<Set<String>> = state.teleportedGeo
|
||||
val geohashParticipantCounts: StateFlow<Map<String, Int>> = state.geohashParticipantCounts
|
||||
val meshServiceFacade: MeshService
|
||||
get() = mesh
|
||||
val myPeerID: String
|
||||
get() = mesh.myPeerID
|
||||
|
||||
fun getMeshPeerFingerprint(peerID: String): String? = mesh.getPeerFingerprint(peerID)
|
||||
|
||||
fun getMeshPeerInfo(peerID: String): com.bitchat.android.mesh.PeerInfo? = mesh.getPeerInfo(peerID)
|
||||
|
||||
fun initiateMeshHandshake(peerID: String) {
|
||||
mesh.initiateNoiseHandshake(peerID)
|
||||
}
|
||||
|
||||
init {
|
||||
// Note: Mesh service delegate is now set by MainActivity
|
||||
@@ -208,7 +225,7 @@ class ChatViewModel(
|
||||
// Recompute unread set using SeenMessageStore for robustness across Activity recreation
|
||||
try {
|
||||
val seen = com.bitchat.android.services.SeenMessageStore.getInstance(getApplication())
|
||||
val myNick = state.getNicknameValue() ?: meshService.myPeerID
|
||||
val myNick = state.getNicknameValue() ?: mesh.myPeerID
|
||||
val unread = mutableSetOf<String>()
|
||||
byPeer.forEach { (peer, list) ->
|
||||
if (list.any { msg -> msg.sender != myNick && !seen.hasRead(msg.id) }) unread.add(peer)
|
||||
@@ -299,7 +316,7 @@ class ChatViewModel(
|
||||
// Ensure NostrTransport knows our mesh peer ID for embedded packets
|
||||
try {
|
||||
val nostrTransport = com.bitchat.android.nostr.NostrTransport.getInstance(getApplication())
|
||||
nostrTransport.senderPeerID = meshService.myPeerID
|
||||
nostrTransport.senderPeerID = mesh.myPeerID
|
||||
} catch (_: Exception) { }
|
||||
|
||||
// Note: Mesh service is now started by MainActivity
|
||||
@@ -317,7 +334,7 @@ class ChatViewModel(
|
||||
fun setNickname(newNickname: String) {
|
||||
state.setNickname(newNickname)
|
||||
dataManager.saveNickname(newNickname)
|
||||
meshService.sendBroadcastAnnounce()
|
||||
mesh.sendBroadcastAnnounce()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,7 +380,7 @@ class ChatViewModel(
|
||||
// MARK: - Channel Management (delegated)
|
||||
|
||||
fun joinChannel(channel: String, password: String? = null): Boolean {
|
||||
return channelManager.joinChannel(channel, password, meshService.myPeerID)
|
||||
return channelManager.joinChannel(channel, password, mesh.myPeerID)
|
||||
}
|
||||
|
||||
fun switchToChannel(channel: String?) {
|
||||
@@ -372,7 +389,7 @@ class ChatViewModel(
|
||||
|
||||
fun leaveChannel(channel: String) {
|
||||
channelManager.leaveChannel(channel)
|
||||
meshService.sendMessage("left $channel", emptyList(), null)
|
||||
mesh.sendMessage("left $channel", emptyList(), null)
|
||||
}
|
||||
|
||||
// MARK: - Private Chat Management (delegated)
|
||||
@@ -383,7 +400,7 @@ class ChatViewModel(
|
||||
ensureGeohashDMSubscriptionIfNeeded(peerID)
|
||||
}
|
||||
|
||||
val success = privateChatManager.startPrivateChat(peerID, meshService)
|
||||
val success = privateChatManager.startPrivateChat(peerID, mesh)
|
||||
if (success) {
|
||||
// Notify notification manager about current private chat
|
||||
setCurrentPrivateChatPeer(peerID)
|
||||
@@ -420,7 +437,7 @@ class ChatViewModel(
|
||||
val unreadKeys = state.getUnreadPrivateMessagesValue()
|
||||
if (unreadKeys.isEmpty()) return
|
||||
|
||||
val me = state.getNicknameValue() ?: meshService.myPeerID
|
||||
val me = state.getNicknameValue() ?: mesh.myPeerID
|
||||
val chats = state.getPrivateChatsValue()
|
||||
|
||||
// Pick the latest incoming message among unread conversations
|
||||
@@ -451,8 +468,8 @@ class ChatViewModel(
|
||||
val canonical = com.bitchat.android.services.ConversationAliasResolver.resolveCanonicalPeerID(
|
||||
selectedPeerID = targetKey,
|
||||
connectedPeers = state.getConnectedPeersValue(),
|
||||
meshNoiseKeyForPeer = { pid -> meshService.getPeerInfo(pid)?.noisePublicKey },
|
||||
meshHasPeer = { pid -> meshService.getPeerInfo(pid)?.isConnected == true },
|
||||
meshNoiseKeyForPeer = { pid -> mesh.getPeerInfo(pid)?.noisePublicKey },
|
||||
meshHasPeer = { pid -> mesh.getPeerInfo(pid)?.isConnected == true },
|
||||
nostrPubHexForAlias = { alias -> com.bitchat.android.nostr.GeohashAliasRegistry.get(alias) },
|
||||
findNoiseKeyForNostr = { key -> com.bitchat.android.favorites.FavoritesPersistenceService.shared.findNoiseKey(key) }
|
||||
)
|
||||
@@ -476,25 +493,23 @@ class ChatViewModel(
|
||||
// Check for commands
|
||||
if (content.startsWith("/")) {
|
||||
val selectedLocationForCommand = state.selectedLocationChannel.value
|
||||
commandProcessor.processCommand(content, meshService, meshService.myPeerID, { messageContent, mentions, channel ->
|
||||
commandProcessor.processCommand(content, mesh, mesh.myPeerID, { messageContent, mentions, channel ->
|
||||
if (selectedLocationForCommand is com.bitchat.android.geohash.ChannelID.Location) {
|
||||
// Route command-generated public messages via Nostr in geohash channels
|
||||
geohashViewModel.sendGeohashMessage(
|
||||
messageContent,
|
||||
selectedLocationForCommand.channel,
|
||||
meshService.myPeerID,
|
||||
mesh.myPeerID,
|
||||
state.getNicknameValue()
|
||||
)
|
||||
} else {
|
||||
// Default: route via mesh + Wi‑Fi Aware
|
||||
meshService.sendMessage(messageContent, mentions, channel)
|
||||
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(messageContent, mentions, channel) } catch (_: Exception) {}
|
||||
mesh.sendMessage(messageContent, mentions, channel)
|
||||
}
|
||||
}, this)
|
||||
return
|
||||
}
|
||||
|
||||
val mentions = messageManager.parseMentions(content, meshService.getPeerNicknames().values.toSet(), state.getNicknameValue())
|
||||
val mentions = messageManager.parseMentions(content, mesh.getPeerNicknames().values.toSet(), state.getNicknameValue())
|
||||
// REMOVED: Auto-join mentioned channels feature that was incorrectly parsing hashtags from @mentions
|
||||
// This was causing messages like "test @jack#1234 test" to auto-join channel "#1234"
|
||||
|
||||
@@ -506,13 +521,13 @@ class ChatViewModel(
|
||||
selectedPeer = com.bitchat.android.services.ConversationAliasResolver.resolveCanonicalPeerID(
|
||||
selectedPeerID = selectedPeer,
|
||||
connectedPeers = state.getConnectedPeersValue(),
|
||||
meshNoiseKeyForPeer = { pid -> meshService.getPeerInfo(pid)?.noisePublicKey },
|
||||
meshHasPeer = { pid -> meshService.getPeerInfo(pid)?.isConnected == true },
|
||||
meshNoiseKeyForPeer = { pid -> mesh.getPeerInfo(pid)?.noisePublicKey },
|
||||
meshHasPeer = { pid -> mesh.getPeerInfo(pid)?.isConnected == true },
|
||||
nostrPubHexForAlias = { alias -> com.bitchat.android.nostr.GeohashAliasRegistry.get(alias) },
|
||||
findNoiseKeyForNostr = { key -> com.bitchat.android.favorites.FavoritesPersistenceService.shared.findNoiseKey(key) }
|
||||
).also { canonical ->
|
||||
if (canonical != state.getSelectedPrivateChatPeerValue()) {
|
||||
privateChatManager.startPrivateChat(canonical, meshService)
|
||||
privateChatManager.startPrivateChat(canonical, mesh)
|
||||
// If we're in the private chat sheet, update its active peer too
|
||||
if (state.getPrivateChatSheetPeerValue() != null) {
|
||||
showPrivateChatSheet(canonical)
|
||||
@@ -526,10 +541,10 @@ class ChatViewModel(
|
||||
selectedPeer,
|
||||
recipientNickname,
|
||||
state.getNicknameValue(),
|
||||
meshService.myPeerID
|
||||
mesh.myPeerID
|
||||
) { messageContent, peerID, recipientNicknameParam, messageId ->
|
||||
// Route via MessageRouter (mesh when connected+established, else Nostr)
|
||||
val router = com.bitchat.android.services.MessageRouter.getInstance(getApplication(), meshService)
|
||||
val router = com.bitchat.android.services.MessageRouter.getInstance(getApplication(), mesh)
|
||||
router.sendPrivate(messageContent, peerID, recipientNicknameParam, messageId)
|
||||
}
|
||||
} else {
|
||||
@@ -537,21 +552,21 @@ class ChatViewModel(
|
||||
val selectedLocationChannel = state.selectedLocationChannel.value
|
||||
if (selectedLocationChannel is com.bitchat.android.geohash.ChannelID.Location) {
|
||||
// Send to geohash channel via Nostr ephemeral event
|
||||
geohashViewModel.sendGeohashMessage(content, selectedLocationChannel.channel, meshService.myPeerID, state.getNicknameValue())
|
||||
geohashViewModel.sendGeohashMessage(content, selectedLocationChannel.channel, mesh.myPeerID, state.getNicknameValue())
|
||||
} else {
|
||||
// Send public/channel message via mesh
|
||||
val message = BitchatMessage(
|
||||
sender = state.getNicknameValue() ?: meshService.myPeerID,
|
||||
sender = state.getNicknameValue() ?: mesh.myPeerID,
|
||||
content = content,
|
||||
timestamp = Date(),
|
||||
isRelay = false,
|
||||
senderPeerID = meshService.myPeerID,
|
||||
senderPeerID = mesh.myPeerID,
|
||||
mentions = if (mentions.isNotEmpty()) mentions else null,
|
||||
channel = currentChannelValue
|
||||
)
|
||||
|
||||
if (currentChannelValue != null) {
|
||||
channelManager.addChannelMessage(currentChannelValue, message, meshService.myPeerID)
|
||||
channelManager.addChannelMessage(currentChannelValue, message, mesh.myPeerID)
|
||||
|
||||
// Check if encrypted channel
|
||||
if (channelManager.hasChannelKey(currentChannelValue)) {
|
||||
@@ -560,25 +575,20 @@ class ChatViewModel(
|
||||
mentions,
|
||||
currentChannelValue,
|
||||
state.getNicknameValue(),
|
||||
meshService.myPeerID,
|
||||
mesh.myPeerID,
|
||||
onEncryptedPayload = { encryptedData ->
|
||||
// Send encrypted payload announcement over both transports for reachability
|
||||
meshService.sendMessage(content, mentions, currentChannelValue)
|
||||
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
|
||||
mesh.sendMessage(content, mentions, currentChannelValue)
|
||||
},
|
||||
onFallback = {
|
||||
meshService.sendMessage(content, mentions, currentChannelValue)
|
||||
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
|
||||
mesh.sendMessage(content, mentions, currentChannelValue)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
meshService.sendMessage(content, mentions, currentChannelValue)
|
||||
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
|
||||
mesh.sendMessage(content, mentions, currentChannelValue)
|
||||
}
|
||||
} else {
|
||||
messageManager.addMessage(message)
|
||||
meshService.sendMessage(content, mentions, null)
|
||||
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, null) } catch (_: Exception) {}
|
||||
mesh.sendMessage(content, mentions, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -587,7 +597,7 @@ class ChatViewModel(
|
||||
// MARK: - Utility Functions
|
||||
|
||||
fun getPeerIDForNickname(nickname: String): String? {
|
||||
return meshService.getPeerNicknames().entries.find { it.value == nickname }?.key
|
||||
return mesh.getPeerNicknames().entries.find { it.value == nickname }?.key
|
||||
}
|
||||
|
||||
fun toggleFavorite(peerID: String) {
|
||||
@@ -597,10 +607,10 @@ class ChatViewModel(
|
||||
// Persist relationship in FavoritesPersistenceService
|
||||
try {
|
||||
var noiseKey: ByteArray? = null
|
||||
var nickname: String = meshService.getPeerNicknames()[peerID] ?: peerID
|
||||
var nickname: String = mesh.getPeerNicknames()[peerID] ?: peerID
|
||||
|
||||
// Case 1: Live mesh peer with known info
|
||||
val peerInfo = meshService.getPeerInfo(peerID)
|
||||
val peerInfo = mesh.getPeerInfo(peerID)
|
||||
if (peerInfo?.noisePublicKey != null) {
|
||||
noiseKey = peerInfo.noisePublicKey
|
||||
nickname = peerInfo.nickname
|
||||
@@ -630,22 +640,9 @@ class ChatViewModel(
|
||||
|
||||
// Send favorite notification via mesh or Nostr with our npub if available
|
||||
try {
|
||||
val myNostr = com.bitchat.android.nostr.NostrIdentityBridge.getCurrentNostrIdentity(getApplication())
|
||||
val announcementContent = if (isNowFavorite) "[FAVORITED]:${myNostr?.npub ?: ""}" else "[UNFAVORITED]:${myNostr?.npub ?: ""}"
|
||||
// Prefer mesh if session established, else try Nostr
|
||||
if (meshService.hasEstablishedSession(peerID)) {
|
||||
// Reuse existing private message path for notifications
|
||||
meshService.sendPrivateMessage(
|
||||
announcementContent,
|
||||
peerID,
|
||||
nickname,
|
||||
java.util.UUID.randomUUID().toString()
|
||||
)
|
||||
} else {
|
||||
val nostrTransport = com.bitchat.android.nostr.NostrTransport.getInstance(getApplication())
|
||||
nostrTransport.senderPeerID = meshService.myPeerID
|
||||
nostrTransport.sendFavoriteNotification(peerID, isNowFavorite)
|
||||
}
|
||||
com.bitchat.android.services.MessageRouter
|
||||
.getInstance(getApplication(), mesh)
|
||||
.sendFavoriteNotification(peerID, isNowFavorite)
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
@@ -662,21 +659,9 @@ class ChatViewModel(
|
||||
Log.i("ChatViewModel", "==============================")
|
||||
}
|
||||
|
||||
private fun getWifiAwareService(): com.bitchat.android.mesh.MeshService? {
|
||||
return try { com.bitchat.android.wifiaware.WifiAwareController.getService() } catch (_: Exception) { null }
|
||||
}
|
||||
|
||||
private fun isConnectedOnMesh(peerID: String): Boolean {
|
||||
return try {
|
||||
meshService.getPeerInfo(peerID)?.isConnected == true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isConnectedOnService(service: com.bitchat.android.mesh.MeshService?, peerID: String): Boolean {
|
||||
return try {
|
||||
service != null && service.getPeerInfo(peerID)?.isConnected == true
|
||||
mesh.getPeerInfo(peerID)?.isConnected == true
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
@@ -684,55 +669,28 @@ class ChatViewModel(
|
||||
|
||||
private fun hasEstablishedSessionOnMesh(peerID: String): Boolean {
|
||||
return try {
|
||||
meshService.getPeerInfo(peerID)?.isConnected == true &&
|
||||
meshService.hasEstablishedSession(peerID)
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasEstablishedSessionOnService(service: com.bitchat.android.mesh.MeshService?, peerID: String): Boolean {
|
||||
return try {
|
||||
service != null &&
|
||||
service.getPeerInfo(peerID)?.isConnected == true &&
|
||||
service.hasEstablishedSession(peerID)
|
||||
mesh.getPeerInfo(peerID)?.isConnected == true &&
|
||||
mesh.hasEstablishedSession(peerID)
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasEstablishedSessionOnAnyLocalTransport(peerID: String): Boolean {
|
||||
return hasEstablishedSessionOnMesh(peerID) ||
|
||||
hasEstablishedSessionOnService(getWifiAwareService(), peerID)
|
||||
return hasEstablishedSessionOnMesh(peerID)
|
||||
}
|
||||
|
||||
private fun initiateNoiseHandshakeOnBestLocalTransport(peerID: String) {
|
||||
val aware = getWifiAwareService()
|
||||
when {
|
||||
isConnectedOnMesh(peerID) -> meshService.initiateNoiseHandshake(peerID)
|
||||
isConnectedOnService(aware, peerID) -> aware?.initiateNoiseHandshake(peerID)
|
||||
else -> meshService.initiateNoiseHandshake(peerID)
|
||||
}
|
||||
mesh.initiateNoiseHandshake(peerID)
|
||||
}
|
||||
|
||||
private fun nicknameForPeer(peerID: String): String? {
|
||||
return state.peerNicknames.value[peerID]
|
||||
?: try { meshService.getPeerNicknames()[peerID] } catch (_: Exception) { null }
|
||||
?: try { getWifiAwareService()?.getPeerNicknames()?.get(peerID) } catch (_: Exception) { null }
|
||||
?: try { mesh.getPeerNicknames()[peerID] } catch (_: Exception) { null }
|
||||
}
|
||||
|
||||
private fun sessionStateForPeer(peerID: String): NoiseSession.NoiseSessionState {
|
||||
val meshState = try { meshService.getSessionState(peerID) } catch (_: Exception) { NoiseSession.NoiseSessionState.Uninitialized }
|
||||
val awareState = try { getWifiAwareService()?.getSessionState(peerID) } catch (_: Exception) { null }
|
||||
return when {
|
||||
meshState is NoiseSession.NoiseSessionState.Established -> meshState
|
||||
awareState is NoiseSession.NoiseSessionState.Established -> awareState
|
||||
meshState is NoiseSession.NoiseSessionState.Handshaking -> meshState
|
||||
awareState is NoiseSession.NoiseSessionState.Handshaking -> awareState
|
||||
meshState !is NoiseSession.NoiseSessionState.Uninitialized -> meshState
|
||||
awareState != null -> awareState
|
||||
else -> meshState
|
||||
}
|
||||
return try { mesh.getSessionState(peerID) } catch (_: Exception) { NoiseSession.NoiseSessionState.Uninitialized }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -766,7 +724,7 @@ class ChatViewModel(
|
||||
val old = prevStates[peerID]
|
||||
if (old != "established" && newState == "established") {
|
||||
com.bitchat.android.services.MessageRouter
|
||||
.getInstance(getApplication(), meshService)
|
||||
.getInstance(getApplication(), mesh)
|
||||
.onSessionEstablished(peerID)
|
||||
}
|
||||
}
|
||||
@@ -775,7 +733,7 @@ class ChatViewModel(
|
||||
state.setPeerFingerprints(fingerprints)
|
||||
fingerprints.forEach { (peerID, fingerprint) ->
|
||||
identityManager.cachePeerFingerprint(peerID, fingerprint)
|
||||
val info = try { meshService.getPeerInfo(peerID) } catch (_: Exception) { null }
|
||||
val info = try { mesh.getPeerInfo(peerID) } catch (_: Exception) { null }
|
||||
val noiseKeyHex = info?.noisePublicKey?.hexEncodedString()
|
||||
if (noiseKeyHex != null) {
|
||||
identityManager.cachePeerNoiseKey(peerID, noiseKeyHex)
|
||||
@@ -786,23 +744,14 @@ class ChatViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
// Merge nicknames from BLE and Wi‑Fi Aware to display names for all peers
|
||||
val bleNick = meshService.getPeerNicknames()
|
||||
val awareNickRaw = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerNicknamesMap() } catch (_: Exception) { null }
|
||||
val mergedNick = if (awareNickRaw != null) bleNick + awareNickRaw.filter { it.value != null }.mapValues { it.value!! }.filterKeys { it !in bleNick || bleNick[it].isNullOrBlank() } else bleNick
|
||||
state.setPeerNicknames(mergedNick)
|
||||
state.setPeerNicknames(mesh.getPeerNicknames())
|
||||
|
||||
val rssiValues = meshService.getPeerRSSI()
|
||||
val awareRssi = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerRSSI() } catch (_: Exception) { null }
|
||||
val mergedRssi = if (awareRssi != null) rssiValues + awareRssi.filterKeys { it !in rssiValues } else rssiValues
|
||||
state.setPeerRSSI(mergedRssi)
|
||||
state.setPeerRSSI(mesh.getPeerRSSI())
|
||||
|
||||
// Update directness per peer (driven by PeerManager state)
|
||||
try {
|
||||
val directMap = state.getConnectedPeersValue().associateWith { pid ->
|
||||
val ble = meshService.getPeerInfo(pid)?.isDirectConnection == true
|
||||
val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerInfo(pid)?.isDirectConnection == true } catch (_: Exception) { false }
|
||||
ble || aware
|
||||
mesh.getPeerInfo(pid)?.isDirectConnection == true
|
||||
}
|
||||
state.setPeerDirect(directMap)
|
||||
} catch (_: Exception) { }
|
||||
@@ -839,7 +788,7 @@ class ChatViewModel(
|
||||
// MARK: - Debug and Troubleshooting
|
||||
|
||||
fun getDebugStatus(): String {
|
||||
return meshService.getDebugStatus()
|
||||
return mesh.getDebugStatus()
|
||||
}
|
||||
|
||||
fun setCurrentPrivateChatPeer(peerID: String?) {
|
||||
@@ -936,7 +885,7 @@ class ChatViewModel(
|
||||
// MARK: - Mention Autocomplete
|
||||
|
||||
fun updateMentionSuggestions(input: String) {
|
||||
commandProcessor.updateMentionSuggestions(input, meshService, this)
|
||||
commandProcessor.updateMentionSuggestions(input, mesh, this)
|
||||
}
|
||||
|
||||
fun selectMentionSuggestion(nickname: String, currentText: String): String {
|
||||
@@ -953,10 +902,6 @@ class ChatViewModel(
|
||||
meshDelegateHandler.didUpdatePeerList(peers)
|
||||
}
|
||||
|
||||
fun onWifiPeersUpdated(peers: List<String>) {
|
||||
meshDelegateHandler.onWifiPeersUpdated(peers)
|
||||
}
|
||||
|
||||
override fun didReceiveChannelLeave(channel: String, fromPeer: String) {
|
||||
meshDelegateHandler.didReceiveChannelLeave(channel, fromPeer)
|
||||
}
|
||||
@@ -1040,7 +985,7 @@ class ChatViewModel(
|
||||
// Recreate mesh service with fresh identity
|
||||
recreateMeshServiceAfterPanic()
|
||||
|
||||
Log.w(TAG, "🚨 PANIC MODE COMPLETED - New identity: ${meshService.myPeerID}")
|
||||
Log.w(TAG, "🚨 PANIC MODE COMPLETED - New identity: ${mesh.myPeerID}")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1048,25 +993,27 @@ class ChatViewModel(
|
||||
* This ensures the new cryptographic keys are used for a new peer ID.
|
||||
*/
|
||||
private fun recreateMeshServiceAfterPanic() {
|
||||
val oldPeerID = meshService.myPeerID
|
||||
val oldPeerID = mesh.myPeerID
|
||||
|
||||
// Clear the holder so getOrCreate() returns a fresh instance
|
||||
MeshServiceHolder.clear()
|
||||
|
||||
// Create fresh mesh service with new identity (keys were regenerated in clearAllCryptographicData)
|
||||
val freshMeshService = MeshServiceHolder.getOrCreate(getApplication())
|
||||
val freshUnifiedMeshService = MeshServiceHolder.getUnifiedOrCreate(getApplication())
|
||||
|
||||
// Replace our reference and set up the new service
|
||||
meshService = freshMeshService
|
||||
meshService.delegate = this
|
||||
unifiedMeshService = freshUnifiedMeshService
|
||||
mesh.delegate = this
|
||||
|
||||
// Restart mesh operations with new identity
|
||||
meshService.startServices()
|
||||
meshService.sendBroadcastAnnounce()
|
||||
mesh.startServices()
|
||||
mesh.sendBroadcastAnnounce()
|
||||
|
||||
Log.d(
|
||||
TAG,
|
||||
"✅ Mesh service recreated. Old peerID: $oldPeerID, New peerID: ${meshService.myPeerID}"
|
||||
"✅ Mesh service recreated. Old peerID: $oldPeerID, New peerID: ${mesh.myPeerID}"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1076,7 +1023,7 @@ class ChatViewModel(
|
||||
private fun clearAllMeshServiceData() {
|
||||
try {
|
||||
// Request mesh service to clear all its internal data
|
||||
meshService.clearAllInternalData()
|
||||
mesh.clearAllInternalData()
|
||||
|
||||
Log.d(TAG, "✅ Cleared all mesh service data")
|
||||
} catch (e: Exception) {
|
||||
@@ -1090,7 +1037,7 @@ class ChatViewModel(
|
||||
private fun clearAllCryptographicData() {
|
||||
try {
|
||||
// Clear encryption service persistent identity (Ed25519 signing keys)
|
||||
meshService.clearAllEncryptionData()
|
||||
mesh.clearAllEncryptionData()
|
||||
|
||||
// Clear secure identity state (if used)
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import java.util.Date
|
||||
|
||||
@@ -29,7 +29,7 @@ class CommandProcessor(
|
||||
|
||||
// MARK: - Command Processing
|
||||
|
||||
fun processCommand(command: String, meshService: BluetoothMeshService, myPeerID: String, onSendMessage: (String, List<String>, String?) -> Unit, viewModel: ChatViewModel? = null): Boolean {
|
||||
fun processCommand(command: String, meshService: MeshService, myPeerID: String, onSendMessage: (String, List<String>, String?) -> Unit, viewModel: ChatViewModel? = null): Boolean {
|
||||
if (!command.startsWith("/")) return false
|
||||
|
||||
val parts = command.split(" ")
|
||||
@@ -77,7 +77,7 @@ class CommandProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMessageCommand(parts: List<String>, meshService: BluetoothMeshService, viewModel: ChatViewModel?) {
|
||||
private fun handleMessageCommand(parts: List<String>, meshService: MeshService, viewModel: ChatViewModel?) {
|
||||
if (parts.size > 1) {
|
||||
val targetName = parts[1].removePrefix("@")
|
||||
val peerID = getPeerIDForNickname(targetName, meshService)
|
||||
@@ -128,7 +128,7 @@ class CommandProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWhoCommand(meshService: BluetoothMeshService, viewModel: ChatViewModel? = null) {
|
||||
private fun handleWhoCommand(meshService: MeshService, viewModel: ChatViewModel? = null) {
|
||||
// Channel-aware who command (matches iOS behavior)
|
||||
val (peerList, contextDescription) = if (viewModel != null) {
|
||||
when (val selectedChannel = viewModel.selectedLocationChannel.value) {
|
||||
@@ -247,7 +247,7 @@ class CommandProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBlockCommand(parts: List<String>, meshService: BluetoothMeshService) {
|
||||
private fun handleBlockCommand(parts: List<String>, meshService: MeshService) {
|
||||
if (parts.size > 1) {
|
||||
val targetName = parts[1].removePrefix("@")
|
||||
privateChatManager.blockPeerByNickname(targetName, meshService)
|
||||
@@ -264,7 +264,7 @@ class CommandProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUnblockCommand(parts: List<String>, meshService: BluetoothMeshService) {
|
||||
private fun handleUnblockCommand(parts: List<String>, meshService: MeshService) {
|
||||
if (parts.size > 1) {
|
||||
val targetName = parts[1].removePrefix("@")
|
||||
privateChatManager.unblockPeerByNickname(targetName, meshService)
|
||||
@@ -283,7 +283,7 @@ class CommandProcessor(
|
||||
parts: List<String>,
|
||||
verb: String,
|
||||
object_: String,
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
myPeerID: String,
|
||||
onSendMessage: (String, List<String>, String?) -> Unit,
|
||||
viewModel: ChatViewModel?
|
||||
@@ -423,7 +423,7 @@ class CommandProcessor(
|
||||
|
||||
// MARK: - Mention Autocomplete
|
||||
|
||||
fun updateMentionSuggestions(input: String, meshService: BluetoothMeshService, viewModel: ChatViewModel? = null) {
|
||||
fun updateMentionSuggestions(input: String, meshService: MeshService, viewModel: ChatViewModel? = null) {
|
||||
// Check if input contains @ and we're at the end of a word or at the end of input
|
||||
val atIndex = input.lastIndexOf('@')
|
||||
if (atIndex == -1) {
|
||||
@@ -503,31 +503,21 @@ class CommandProcessor(
|
||||
|
||||
// MARK: - Utility Functions
|
||||
|
||||
private fun getPeerIDForNickname(nickname: String, meshService: BluetoothMeshService): String? {
|
||||
private fun getPeerIDForNickname(nickname: String, meshService: MeshService): String? {
|
||||
return meshService.getPeerNicknames().entries.find { it.value == nickname }?.key
|
||||
?: try {
|
||||
com.bitchat.android.wifiaware.WifiAwareController.getService()
|
||||
?.getPeerNicknames()
|
||||
?.entries
|
||||
?.find { it.value == nickname }
|
||||
?.key
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPeerNickname(peerID: String, meshService: BluetoothMeshService): String {
|
||||
private fun getPeerNickname(peerID: String, meshService: MeshService): String {
|
||||
return meshService.getPeerNicknames()[peerID]
|
||||
?: try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerNicknames()?.get(peerID) } catch (_: Exception) { null }
|
||||
?: peerID
|
||||
}
|
||||
|
||||
private fun getMyPeerID(meshService: BluetoothMeshService): String {
|
||||
private fun getMyPeerID(meshService: MeshService): String {
|
||||
return meshService.myPeerID
|
||||
}
|
||||
|
||||
private fun sendPrivateMessageVia(
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
content: String,
|
||||
peerID: String,
|
||||
recipientNickname: String,
|
||||
|
||||
@@ -656,7 +656,7 @@ private fun meshTitleWithCount(viewModel: ChatViewModel): String {
|
||||
}
|
||||
|
||||
private fun meshCount(viewModel: ChatViewModel): Int {
|
||||
val myID = viewModel.meshService.myPeerID
|
||||
val myID = viewModel.myPeerID
|
||||
return viewModel.connectedPeers.value?.count { peerID ->
|
||||
peerID != myID
|
||||
} ?: 0
|
||||
|
||||
@@ -77,7 +77,7 @@ fun MessageWithMatrixAnimation(
|
||||
message: com.bitchat.android.model.BitchatMessage,
|
||||
messages: List<com.bitchat.android.model.BitchatMessage> = emptyList(),
|
||||
currentUserNickname: String,
|
||||
meshService: com.bitchat.android.mesh.BluetoothMeshService,
|
||||
meshService: com.bitchat.android.mesh.MeshService,
|
||||
colorScheme: androidx.compose.material3.ColorScheme,
|
||||
timeFormatter: java.text.SimpleDateFormat,
|
||||
onNicknameClick: ((String) -> Unit)?,
|
||||
@@ -124,7 +124,7 @@ fun MessageWithMatrixAnimation(
|
||||
private fun AnimatedMessageDisplay(
|
||||
message: com.bitchat.android.model.BitchatMessage,
|
||||
currentUserNickname: String,
|
||||
meshService: com.bitchat.android.mesh.BluetoothMeshService,
|
||||
meshService: com.bitchat.android.mesh.MeshService,
|
||||
colorScheme: androidx.compose.material3.ColorScheme,
|
||||
timeFormatter: java.text.SimpleDateFormat,
|
||||
modifier: Modifier = Modifier
|
||||
@@ -241,7 +241,7 @@ private fun AnimatedMessageDisplay(
|
||||
private fun formatMessageAsAnnotatedStringWithoutTimestamp(
|
||||
message: com.bitchat.android.model.BitchatMessage,
|
||||
currentUserNickname: String,
|
||||
meshService: com.bitchat.android.mesh.BluetoothMeshService,
|
||||
meshService: com.bitchat.android.mesh.MeshService,
|
||||
colorScheme: androidx.compose.material3.ColorScheme
|
||||
): AnnotatedString {
|
||||
// Get the full formatted text first
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import android.util.Log
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatFilePacket
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.BitchatMessageType
|
||||
@@ -16,10 +16,10 @@ class MediaSendingManager(
|
||||
private val state: ChatState,
|
||||
private val messageManager: MessageManager,
|
||||
private val channelManager: ChannelManager,
|
||||
private val getMeshService: () -> BluetoothMeshService
|
||||
private val getMeshService: () -> MeshService
|
||||
) {
|
||||
// Helper to get current mesh service (may change after panic clear)
|
||||
private val meshService: BluetoothMeshService
|
||||
private val meshService: MeshService
|
||||
get() = getMeshService()
|
||||
companion object {
|
||||
private const val TAG = "MediaSendingManager"
|
||||
@@ -213,7 +213,6 @@ class MediaSendingManager(
|
||||
|
||||
Log.d(TAG, "📤 Calling meshService.sendFilePrivate to $toPeerID")
|
||||
meshService.sendFilePrivate(toPeerID, filePacket)
|
||||
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendFilePrivate(toPeerID, filePacket) } catch (_: Exception) {}
|
||||
Log.d(TAG, "✅ File send completed successfully")
|
||||
}
|
||||
|
||||
@@ -268,7 +267,6 @@ class MediaSendingManager(
|
||||
|
||||
Log.d(TAG, "📤 Calling meshService.sendFileBroadcast")
|
||||
meshService.sendFileBroadcast(filePacket)
|
||||
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendFileBroadcast(filePacket) } catch (_: Exception) {}
|
||||
Log.d(TAG, "✅ File broadcast completed successfully")
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.bitchat.android.ui
|
||||
|
||||
import com.bitchat.android.mesh.BluetoothMeshDelegate
|
||||
import com.bitchat.android.ui.NotificationTextUtils
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.DeliveryStatus
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -21,7 +21,7 @@ class MeshDelegateHandler(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val onHapticFeedback: () -> Unit,
|
||||
private val getMyPeerID: () -> String,
|
||||
private val getMeshService: () -> BluetoothMeshService
|
||||
private val getMeshService: () -> MeshService
|
||||
) : BluetoothMeshDelegate {
|
||||
|
||||
override fun didReceiveMessage(message: BitchatMessage) {
|
||||
@@ -94,30 +94,13 @@ class MeshDelegateHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private var blePeers: Set<String> = emptySet()
|
||||
private var wifiPeers: Set<String> = emptySet()
|
||||
|
||||
override fun didUpdatePeerList(peers: List<String>) {
|
||||
coroutineScope.launch {
|
||||
blePeers = peers.toSet()
|
||||
try { com.bitchat.android.services.AppStateStore.setTransportPeers("BLE", peers) } catch (_: Exception) { }
|
||||
processPeerUpdate()
|
||||
processPeerUpdate(peers.distinct())
|
||||
}
|
||||
}
|
||||
|
||||
fun onWifiPeersUpdated(peers: List<String>) {
|
||||
coroutineScope.launch {
|
||||
wifiPeers = peers.toSet()
|
||||
try { com.bitchat.android.services.AppStateStore.setTransportPeers("WIFI", peers) } catch (_: Exception) { }
|
||||
processPeerUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processPeerUpdate() {
|
||||
// Merge peers from multiple transports
|
||||
val mergedPeers = com.bitchat.android.services.AppStateStore.peers.value
|
||||
.ifEmpty { (blePeers + wifiPeers).toList() }
|
||||
|
||||
private suspend fun processPeerUpdate(mergedPeers: List<String>) {
|
||||
state.setConnectedPeers(mergedPeers)
|
||||
state.setIsConnected(mergedPeers.isNotEmpty())
|
||||
notificationManager.showActiveUserNotification(mergedPeers)
|
||||
@@ -323,14 +306,7 @@ class MeshDelegateHandler(
|
||||
mesh.sendReadReceipt(message.id, senderPeerID, nickname)
|
||||
true
|
||||
} else {
|
||||
val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService() } catch (_: Exception) { null }
|
||||
val hasAware = try { aware?.getPeerInfo(senderPeerID)?.isConnected == true && aware.hasEstablishedSession(senderPeerID) } catch (_: Exception) { false }
|
||||
if (hasAware) {
|
||||
aware?.sendReadReceipt(message.id, senderPeerID, nickname)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
false
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
|
||||
@@ -337,7 +337,7 @@ fun PeopleSection(
|
||||
// Build mapping of connected peerID -> noise key hex to unify with offline favorites
|
||||
val noiseHexByPeerID: Map<String, String> = connectedPeers.associateWith { pid ->
|
||||
try {
|
||||
viewModel.meshService.getPeerInfo(pid)?.noisePublicKey?.joinToString("") { b -> "%02x".format(b) }
|
||||
viewModel.getMeshPeerInfo(pid)?.noisePublicKey?.joinToString("") { b -> "%02x".format(b) }
|
||||
} catch (_: Exception) { null }
|
||||
}.filterValues { it != null }.mapValues { it.value!! }
|
||||
|
||||
@@ -415,7 +415,7 @@ fun PeopleSection(
|
||||
val showHash = (baseNameCounts[bName] ?: 0) > 1
|
||||
|
||||
val directMap by viewModel.peerDirect.collectAsStateWithLifecycle()
|
||||
val isDirectLive = directMap[peerID] ?: try { viewModel.meshService.getPeerInfo(peerID)?.isDirectConnection == true } catch (_: Exception) { false }
|
||||
val isDirectLive = directMap[peerID] ?: try { viewModel.getMeshPeerInfo(peerID)?.isDirectConnection == true } catch (_: Exception) { false }
|
||||
PeerItem(
|
||||
peerID = peerID,
|
||||
displayName = displayName,
|
||||
@@ -787,6 +787,8 @@ fun PrivateChatSheet(
|
||||
val peerFingerprints by viewModel.peerFingerprints.collectAsStateWithLifecycle()
|
||||
|
||||
val verifiedFingerprints by viewModel.verifiedFingerprints.collectAsStateWithLifecycle()
|
||||
val wifiAwareConnected by com.bitchat.android.wifiaware.WifiAwareController.connectedPeers.collectAsStateWithLifecycle()
|
||||
val isWifiAware = peerID in wifiAwareConnected.keys
|
||||
|
||||
// Start private chat when screen opens
|
||||
LaunchedEffect(peerID) {
|
||||
@@ -855,7 +857,7 @@ fun PrivateChatSheet(
|
||||
MessagesList(
|
||||
messages = messages,
|
||||
currentUserNickname = nickname,
|
||||
meshService = viewModel.meshService,
|
||||
meshService = viewModel.meshServiceFacade,
|
||||
modifier = Modifier.weight(1f),
|
||||
forceScrollToBottom = forceScrollToBottom,
|
||||
onScrolledUpChanged = { isUp -> isScrolledUp = isUp },
|
||||
@@ -939,10 +941,26 @@ fun PrivateChatSheet(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
when {
|
||||
isNostrPeer -> {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Public,
|
||||
contentDescription = stringResource(R.string.cd_nostr_reachable),
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = Color(0xFF9C27B0)
|
||||
)
|
||||
}
|
||||
isWifiAware -> {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Wifi,
|
||||
contentDescription = "Direct Wi-Fi Aware",
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
isDirect -> {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.SettingsInputAntenna,
|
||||
contentDescription = stringResource(R.string.cd_connected_peers),
|
||||
imageVector = Icons.Outlined.Bluetooth,
|
||||
contentDescription = "Direct Bluetooth",
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
@@ -950,19 +968,11 @@ fun PrivateChatSheet(
|
||||
isConnected -> {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Route,
|
||||
contentDescription = stringResource(R.string.cd_ready_for_handshake),
|
||||
contentDescription = "Routed",
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
isNostrPeer -> {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Public,
|
||||
contentDescription = stringResource(R.string.cd_nostr_reachable),
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = Color(0xFF9C27B0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
|
||||
@@ -31,7 +31,7 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.DeliveryStatus
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import com.bitchat.android.ui.media.VoiceNotePlayer
|
||||
@@ -58,7 +58,7 @@ import androidx.compose.ui.res.stringResource
|
||||
fun MessagesList(
|
||||
messages: List<BitchatMessage>,
|
||||
currentUserNickname: String,
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
modifier: Modifier = Modifier,
|
||||
forceScrollToBottom: Boolean = false,
|
||||
onScrolledUpChanged: ((Boolean) -> Unit)? = null,
|
||||
@@ -137,7 +137,7 @@ fun MessagesList(
|
||||
fun MessageItem(
|
||||
message: BitchatMessage,
|
||||
currentUserNickname: String,
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
messages: List<BitchatMessage> = emptyList(),
|
||||
onNicknameClick: ((String) -> Unit)? = null,
|
||||
onMessageLongPress: ((BitchatMessage) -> Unit)? = null,
|
||||
@@ -201,7 +201,7 @@ fun MessageItem(
|
||||
message: BitchatMessage,
|
||||
messages: List<BitchatMessage>,
|
||||
currentUserNickname: String,
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
colorScheme: ColorScheme,
|
||||
timeFormatter: SimpleDateFormat,
|
||||
onNicknameClick: ((String) -> Unit)?,
|
||||
|
||||
@@ -3,9 +3,9 @@ package com.bitchat.android.ui
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.DeliveryStatus
|
||||
import com.bitchat.android.mesh.PeerFingerprintManager
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import java.security.MessageDigest
|
||||
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import java.util.*
|
||||
import android.util.Log
|
||||
|
||||
@@ -42,7 +42,7 @@ class PrivateChatManager(
|
||||
|
||||
// MARK: - Private Chat Lifecycle
|
||||
|
||||
fun startPrivateChat(peerID: String, meshService: BluetoothMeshService): Boolean {
|
||||
fun startPrivateChat(peerID: String, meshService: MeshService): Boolean {
|
||||
if (isPeerBlocked(peerID)) {
|
||||
val peerNickname = getPeerNickname(peerID, meshService)
|
||||
val systemMessage = BitchatMessage(
|
||||
@@ -188,7 +188,7 @@ class PrivateChatManager(
|
||||
|
||||
// MARK: - Block/Unblock Operations
|
||||
|
||||
fun blockPeer(peerID: String, meshService: BluetoothMeshService): Boolean {
|
||||
fun blockPeer(peerID: String, meshService: MeshService): Boolean {
|
||||
val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
|
||||
if (fingerprint != null) {
|
||||
dataManager.addBlockedUser(fingerprint)
|
||||
@@ -212,7 +212,7 @@ class PrivateChatManager(
|
||||
return false
|
||||
}
|
||||
|
||||
fun unblockPeer(peerID: String, meshService: BluetoothMeshService): Boolean {
|
||||
fun unblockPeer(peerID: String, meshService: MeshService): Boolean {
|
||||
val fingerprint = fingerprintManager.getFingerprintForPeer(peerID)
|
||||
if (fingerprint != null && dataManager.isUserBlocked(fingerprint)) {
|
||||
dataManager.removeBlockedUser(fingerprint)
|
||||
@@ -230,7 +230,7 @@ class PrivateChatManager(
|
||||
return false
|
||||
}
|
||||
|
||||
fun blockPeerByNickname(targetName: String, meshService: BluetoothMeshService): Boolean {
|
||||
fun blockPeerByNickname(targetName: String, meshService: MeshService): Boolean {
|
||||
val peerID = getPeerIDForNickname(targetName, meshService)
|
||||
|
||||
if (peerID != null) {
|
||||
@@ -247,7 +247,7 @@ class PrivateChatManager(
|
||||
}
|
||||
}
|
||||
|
||||
fun unblockPeerByNickname(targetName: String, meshService: BluetoothMeshService): Boolean {
|
||||
fun unblockPeerByNickname(targetName: String, meshService: MeshService): Boolean {
|
||||
val peerID = getPeerIDForNickname(targetName, meshService)
|
||||
|
||||
if (peerID != null) {
|
||||
@@ -333,7 +333,7 @@ class PrivateChatManager(
|
||||
* Send read receipts for all unread messages from a specific peer
|
||||
* Called when the user focuses on a private chat
|
||||
*/
|
||||
fun sendReadReceiptsForPeer(peerID: String, meshService: BluetoothMeshService) {
|
||||
fun sendReadReceiptsForPeer(peerID: String, meshService: MeshService) {
|
||||
// Collect candidate messages: all incoming messages from this peer in the conversation
|
||||
val chats = try { state.getPrivateChatsValue() } catch (_: Exception) { emptyMap<String, List<BitchatMessage>>() }
|
||||
val messages = chats[peerID].orEmpty()
|
||||
@@ -343,9 +343,7 @@ class PrivateChatManager(
|
||||
}
|
||||
|
||||
val myNickname = state.getNicknameValue() ?: "unknown"
|
||||
val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService() } catch (_: Exception) { null }
|
||||
val hasMesh = try { meshService.getPeerInfo(peerID)?.isConnected == true && meshService.hasEstablishedSession(peerID) } catch (_: Exception) { false }
|
||||
val hasAware = try { aware?.getPeerInfo(peerID)?.isConnected == true && aware.hasEstablishedSession(peerID) } catch (_: Exception) { false }
|
||||
var sentCount = 0
|
||||
messages.forEach { msg ->
|
||||
// Only for incoming messages from this peer
|
||||
@@ -354,9 +352,6 @@ class PrivateChatManager(
|
||||
if (hasMesh) {
|
||||
meshService.sendReadReceipt(msg.id, peerID, myNickname)
|
||||
sentCount += 1
|
||||
} else if (hasAware) {
|
||||
aware?.sendReadReceipt(msg.id, peerID, myNickname)
|
||||
sentCount += 1
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to send read receipt for message ${msg.id}: ${e.message}")
|
||||
@@ -388,7 +383,7 @@ class PrivateChatManager(
|
||||
* Establish Noise session if needed before starting private chat
|
||||
* Uses same lexicographical logic as MessageHandler.handleNoiseIdentityAnnouncement
|
||||
*/
|
||||
private fun establishNoiseSessionIfNeeded(peerID: String, meshService: BluetoothMeshService) {
|
||||
private fun establishNoiseSessionIfNeeded(peerID: String, meshService: MeshService) {
|
||||
if (noiseSessionDelegate.hasEstablishedSession(peerID)) {
|
||||
Log.d(TAG, "Noise session already established with $peerID")
|
||||
return
|
||||
@@ -421,11 +416,11 @@ class PrivateChatManager(
|
||||
|
||||
// MARK: - Utility Functions
|
||||
|
||||
private fun getPeerIDForNickname(nickname: String, meshService: BluetoothMeshService): String? {
|
||||
private fun getPeerIDForNickname(nickname: String, meshService: MeshService): String? {
|
||||
return meshService.getPeerNicknames().entries.find { it.value == nickname }?.key
|
||||
}
|
||||
|
||||
private fun getPeerNickname(peerID: String, meshService: BluetoothMeshService): String {
|
||||
private fun getPeerNickname(peerID: String, meshService: MeshService): String {
|
||||
return meshService.getPeerNicknames()[peerID] ?: peerID
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ fun SecurityVerificationSheet(
|
||||
displayName = displayName,
|
||||
accent = accent,
|
||||
canStartHandshake = fingerprint == null && selectedPeerID.matches(peerHexRegex),
|
||||
onStartHandshake = { viewModel.meshService.initiateNoiseHandshake(selectedPeerID) },
|
||||
onStartHandshake = { viewModel.initiateMeshHandshake(selectedPeerID) },
|
||||
onVerify = { fp -> viewModel.verifyFingerprintValue(fp) },
|
||||
onUnverify = { fp -> viewModel.unverifyFingerprintValue(fp) }
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.Context
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.favorites.FavoritesPersistenceService
|
||||
import com.bitchat.android.identity.SecureIdentityStateManager
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.noise.NoiseSession
|
||||
import com.bitchat.android.nostr.GeohashAliasRegistry
|
||||
@@ -26,14 +26,14 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
class VerificationHandler(
|
||||
private val context: Context,
|
||||
private val scope: CoroutineScope,
|
||||
private val getMeshService: () -> BluetoothMeshService,
|
||||
private val getMeshService: () -> MeshService,
|
||||
private val identityManager: SecureIdentityStateManager,
|
||||
private val state: ChatState,
|
||||
private val notificationManager: NotificationManager,
|
||||
private val messageManager: MessageManager
|
||||
) {
|
||||
// Helper to get current mesh service (may change after panic clear)
|
||||
private val meshService: BluetoothMeshService
|
||||
private val meshService: MeshService
|
||||
get() = getMeshService()
|
||||
|
||||
private val _verifiedFingerprints = MutableStateFlow<Set<String>>(emptySet())
|
||||
|
||||
@@ -193,7 +193,7 @@ fun VerificationSheet(
|
||||
val fingerprints by viewModel.verifiedFingerprints.collectAsStateWithLifecycle()
|
||||
|
||||
if (peerID != null) {
|
||||
val fingerprint = viewModel.meshService.getPeerFingerprint(peerID!!)
|
||||
val fingerprint = viewModel.getMeshPeerFingerprint(peerID!!)
|
||||
if (fingerprint != null && fingerprints.contains(fingerprint)) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Button(
|
||||
|
||||
@@ -21,7 +21,7 @@ import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -30,7 +30,7 @@ import java.text.SimpleDateFormat
|
||||
fun AudioMessageItem(
|
||||
message: BitchatMessage,
|
||||
currentUserNickname: String,
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
colorScheme: ColorScheme,
|
||||
timeFormatter: SimpleDateFormat,
|
||||
onNicknameClick: ((String) -> Unit)?,
|
||||
|
||||
@@ -27,7 +27,7 @@ import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import com.bitchat.android.mesh.BluetoothMeshService
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.BitchatMessageType
|
||||
import androidx.compose.material3.ColorScheme
|
||||
@@ -39,7 +39,7 @@ fun ImageMessageItem(
|
||||
message: BitchatMessage,
|
||||
messages: List<BitchatMessage>,
|
||||
currentUserNickname: String,
|
||||
meshService: BluetoothMeshService,
|
||||
meshService: MeshService,
|
||||
colorScheme: ColorScheme,
|
||||
timeFormatter: SimpleDateFormat,
|
||||
onNicknameClick: ((String) -> Unit)?,
|
||||
|
||||
Reference in New Issue
Block a user