package com.bitchat.android.ui import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import com.bitchat.android.model.BitchatMessage /** * Centralized state definitions and data classes for the chat system */ // Command suggestion data class data class CommandSuggestion( val command: String, val aliases: List = emptyList(), val syntax: String? = null, val description: String ) /** * Contains all the observable state for the chat system */ class ChatState { // Core messages and peer state private val _messages = MutableLiveData>(emptyList()) val messages: LiveData> = _messages private val _connectedPeers = MutableLiveData>(emptyList()) val connectedPeers: LiveData> = _connectedPeers private val _nickname = MutableLiveData() val nickname: LiveData = _nickname private val _isConnected = MutableLiveData(false) val isConnected: LiveData = _isConnected // Private chats private val _privateChats = MutableLiveData>>(emptyMap()) val privateChats: LiveData>> = _privateChats private val _selectedPrivateChatPeer = MutableLiveData(null) val selectedPrivateChatPeer: LiveData = _selectedPrivateChatPeer private val _unreadPrivateMessages = MutableLiveData>(emptySet()) val unreadPrivateMessages: LiveData> = _unreadPrivateMessages // Channels private val _joinedChannels = MutableLiveData>(emptySet()) val joinedChannels: LiveData> = _joinedChannels private val _currentChannel = MutableLiveData(null) val currentChannel: LiveData = _currentChannel private val _channelMessages = MutableLiveData>>(emptyMap()) val channelMessages: LiveData>> = _channelMessages private val _unreadChannelMessages = MutableLiveData>(emptyMap()) val unreadChannelMessages: LiveData> = _unreadChannelMessages private val _passwordProtectedChannels = MutableLiveData>(emptySet()) val passwordProtectedChannels: LiveData> = _passwordProtectedChannels private val _showPasswordPrompt = MutableLiveData(false) val showPasswordPrompt: LiveData = _showPasswordPrompt private val _passwordPromptChannel = MutableLiveData(null) val passwordPromptChannel: LiveData = _passwordPromptChannel // Sidebar state private val _showSidebar = MutableLiveData(false) val showSidebar: LiveData = _showSidebar // Command autocomplete private val _showCommandSuggestions = MutableLiveData(false) val showCommandSuggestions: LiveData = _showCommandSuggestions private val _commandSuggestions = MutableLiveData>(emptyList()) val commandSuggestions: LiveData> = _commandSuggestions // Mention autocomplete private val _showMentionSuggestions = MutableLiveData(false) val showMentionSuggestions: LiveData = _showMentionSuggestions private val _mentionSuggestions = MutableLiveData>(emptyList()) val mentionSuggestions: LiveData> = _mentionSuggestions // Favorites private val _favoritePeers = MutableLiveData>(emptySet()) val favoritePeers: LiveData> = _favoritePeers // Noise session states for peers (for reactive UI updates) private val _peerSessionStates = MutableLiveData>(emptyMap()) val peerSessionStates: LiveData> = _peerSessionStates // Peer fingerprint state for reactive favorites (for reactive UI updates) private val _peerFingerprints = MutableLiveData>(emptyMap()) val peerFingerprints: LiveData> = _peerFingerprints private val _peerNicknames = MutableLiveData>(emptyMap()) val peerNicknames: LiveData> = _peerNicknames private val _peerRSSI = MutableLiveData>(emptyMap()) val peerRSSI: LiveData> = _peerRSSI // Direct connection status per peer (for live UI updates) private val _peerDirect = MutableLiveData>(emptyMap()) val peerDirect: LiveData> = _peerDirect // peerIDToPublicKeyFingerprint REMOVED - fingerprints now handled centrally in PeerManager // Navigation state private val _showAppInfo = MutableLiveData(false) val showAppInfo: LiveData = _showAppInfo // Location channels state (for Nostr geohash features) private val _selectedLocationChannel = MutableLiveData(com.bitchat.android.geohash.ChannelID.Mesh) val selectedLocationChannel: LiveData = _selectedLocationChannel private val _isTeleported = MutableLiveData(false) val isTeleported: LiveData = _isTeleported // Geohash people state (iOS-compatible) private val _geohashPeople = MutableLiveData>(emptyList()) val geohashPeople: LiveData> = _geohashPeople // For background thread updates by repositories/handlers in their own scopes val geohashPeopleMutable: MutableLiveData> get() = _geohashPeople private val _teleportedGeo = MutableLiveData>(emptySet()) val teleportedGeo: LiveData> = _teleportedGeo // Geohash participant counts reactive state (for real-time location channel counts) private val _geohashParticipantCounts = MutableLiveData>(emptyMap()) val geohashParticipantCounts: LiveData> = _geohashParticipantCounts // Unread state computed properties val hasUnreadChannels: MediatorLiveData = MediatorLiveData() val hasUnreadPrivateMessages: MediatorLiveData = MediatorLiveData() init { // Initialize unread state mediators hasUnreadChannels.addSource(_unreadChannelMessages) { unreadMap -> hasUnreadChannels.value = unreadMap.values.any { it > 0 } } hasUnreadPrivateMessages.addSource(_unreadPrivateMessages) { unreadSet -> hasUnreadPrivateMessages.value = unreadSet.isNotEmpty() } } // Getters for internal state access fun getMessagesValue() = _messages.value ?: emptyList() fun getConnectedPeersValue() = _connectedPeers.value ?: emptyList() fun getNicknameValue() = _nickname.value fun getPrivateChatsValue() = _privateChats.value ?: emptyMap() fun getSelectedPrivateChatPeerValue() = _selectedPrivateChatPeer.value fun getUnreadPrivateMessagesValue() = _unreadPrivateMessages.value ?: emptySet() fun getJoinedChannelsValue() = _joinedChannels.value ?: emptySet() // Thread-safe posting helpers for background updates fun postGeohashPeople(people: List) { _geohashPeople.postValue(people) } fun postGeohashParticipantCounts(counts: Map) { _geohashParticipantCounts.postValue(counts) } fun getCurrentChannelValue() = _currentChannel.value fun getChannelMessagesValue() = _channelMessages.value ?: emptyMap() fun getUnreadChannelMessagesValue() = _unreadChannelMessages.value ?: emptyMap() fun getPasswordProtectedChannelsValue() = _passwordProtectedChannels.value ?: emptySet() fun getShowPasswordPromptValue() = _showPasswordPrompt.value ?: false fun getPasswordPromptChannelValue() = _passwordPromptChannel.value fun getShowSidebarValue() = _showSidebar.value ?: false fun getShowCommandSuggestionsValue() = _showCommandSuggestions.value ?: false fun getCommandSuggestionsValue() = _commandSuggestions.value ?: emptyList() fun getShowMentionSuggestionsValue() = _showMentionSuggestions.value ?: false fun getMentionSuggestionsValue() = _mentionSuggestions.value ?: emptyList() fun getFavoritePeersValue() = _favoritePeers.value ?: emptySet() fun getPeerSessionStatesValue() = _peerSessionStates.value ?: emptyMap() fun getPeerFingerprintsValue() = _peerFingerprints.value ?: emptyMap() fun getShowAppInfoValue() = _showAppInfo.value ?: false fun getGeohashPeopleValue() = _geohashPeople.value ?: emptyList() fun getTeleportedGeoValue() = _teleportedGeo.value ?: emptySet() fun getGeohashParticipantCountsValue() = _geohashParticipantCounts.value ?: emptyMap() // Setters for state updates fun setMessages(messages: List) { _messages.value = messages } fun setConnectedPeers(peers: List) { _connectedPeers.value = peers } fun postTeleportedGeo(teleported: Set) { _teleportedGeo.postValue(teleported) } fun setNickname(nickname: String) { _nickname.value = nickname } fun setIsConnected(connected: Boolean) { _isConnected.value = connected } fun setPrivateChats(chats: Map>) { _privateChats.value = chats } fun setSelectedPrivateChatPeer(peerID: String?) { _selectedPrivateChatPeer.value = peerID } fun setUnreadPrivateMessages(unread: Set) { _unreadPrivateMessages.value = unread } fun setJoinedChannels(channels: Set) { _joinedChannels.value = channels } fun setCurrentChannel(channel: String?) { _currentChannel.value = channel } fun setChannelMessages(messages: Map>) { _channelMessages.value = messages } fun setUnreadChannelMessages(unread: Map) { _unreadChannelMessages.value = unread } fun setPasswordProtectedChannels(channels: Set) { _passwordProtectedChannels.value = channels } fun setShowPasswordPrompt(show: Boolean) { _showPasswordPrompt.value = show } fun setPasswordPromptChannel(channel: String?) { _passwordPromptChannel.value = channel } fun setShowSidebar(show: Boolean) { _showSidebar.value = show } fun setShowCommandSuggestions(show: Boolean) { _showCommandSuggestions.value = show } fun setCommandSuggestions(suggestions: List) { _commandSuggestions.value = suggestions } fun setShowMentionSuggestions(show: Boolean) { _showMentionSuggestions.value = show } fun setMentionSuggestions(suggestions: List) { _mentionSuggestions.value = suggestions } fun setFavoritePeers(favorites: Set) { val currentValue = _favoritePeers.value ?: emptySet() Log.d("ChatState", "setFavoritePeers called with ${favorites.size} favorites: $favorites") Log.d("ChatState", "Current value: $currentValue") Log.d("ChatState", "Values equal: ${currentValue == favorites}") Log.d("ChatState", "Setting on thread: ${Thread.currentThread().name}") // Always set the value - even if equal, this ensures observers are triggered _favoritePeers.value = favorites Log.d("ChatState", "LiveData value after set: ${_favoritePeers.value}") Log.d("ChatState", "LiveData has active observers: ${_favoritePeers.hasActiveObservers()}") } fun setPeerSessionStates(states: Map) { _peerSessionStates.value = states } fun setPeerFingerprints(fingerprints: Map) { _peerFingerprints.value = fingerprints } fun setPeerNicknames(nicknames: Map) { _peerNicknames.value = nicknames } fun setPeerRSSI(rssi: Map) { _peerRSSI.value = rssi } fun setPeerDirect(direct: Map) { _peerDirect.value = direct } fun setShowAppInfo(show: Boolean) { _showAppInfo.value = show } fun setSelectedLocationChannel(channel: com.bitchat.android.geohash.ChannelID?) { _selectedLocationChannel.value = channel } fun setIsTeleported(teleported: Boolean) { _isTeleported.value = teleported } fun setGeohashPeople(people: List) { _geohashPeople.value = people } fun setTeleportedGeo(teleported: Set) { _teleportedGeo.value = teleported } fun setGeohashParticipantCounts(counts: Map) { _geohashParticipantCounts.value = counts } }