diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrGeohashService.kt b/app/src/main/java/com/bitchat/android/nostr/NostrGeohashService.kt index 687f79bcc..467f9ccf8 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrGeohashService.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrGeohashService.kt @@ -108,6 +108,18 @@ class NostrGeohashService( coroutineScope.launch { val nostrRelayManager = NostrRelayManager.getInstance(application) + // Set up network error callback to display system messages + NostrRelayManager.setNetworkErrorCallback { errorMessage -> + // Display network error as a system message in chat + val systemMessage = BitchatMessage( + sender = "system", + content = errorMessage, + timestamp = Date(), + isRelay = false + ) + messageManager.addMessage(systemMessage) + } + // Connect to relays nostrRelayManager.connect() @@ -153,6 +165,9 @@ class NostrGeohashService( // 2) Clear all subscription tracking and caches relayManager.clearAllSubscriptions() + + // Clear network error callback + NostrRelayManager.clearNetworkErrorCallback() // 3) Clear Nostr identity (npub/private) and geohash identity cache/seed try { @@ -206,6 +221,27 @@ class NostrGeohashService( } } + /** + * Clean up the service and release resources + * Should be called when the service is no longer needed + */ + fun cleanup() { + try { + // Clear the network error callback + NostrRelayManager.clearNetworkErrorCallback() + + // Cancel any running jobs + geohashSamplingJob?.cancel() + geohashSamplingJob = null + geoParticipantsTimer?.cancel() + geoParticipantsTimer = null + + Log.d(TAG, "NostrGeohashService cleaned up") + } catch (e: Exception) { + Log.e(TAG, "Error during cleanup: ${e.message}") + } + } + /** * Initialize location channel state */ diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt b/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt index 92f4afe8b..d769d3aa1 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt @@ -24,6 +24,39 @@ class NostrRelayManager private constructor() { val shared = NostrRelayManager() private const val TAG = "NostrRelayManager" + private const val NETWORK_ERROR_MESSAGE = "Network unreachable. Please ensure the device is connected and has all necessary permissions." + + // Network error callback for reporting failures to UI + @Volatile + private var networkErrorCallback: ((String) -> Unit)? = null + @Volatile + private var hasReportedNetworkError = false + + /** + * Set callback for network error reporting + * Throws IllegalStateException if a callback is already set + */ + fun setNetworkErrorCallback(callback: (String) -> Unit) { + if (networkErrorCallback != null) { + throw IllegalStateException("Network error callback already set. Only one service instance should be active.") + } + networkErrorCallback = callback + } + + /** + * Clear the network error callback (should be called when service is destroyed) + */ + fun clearNetworkErrorCallback() { + networkErrorCallback = null + hasReportedNetworkError = false + } + + /** + * Reset network error flag (e.g., when network comes back) + */ + fun resetNetworkErrorFlag() { + hasReportedNetworkError = false + } /** * Get instance for Android compatibility (context-aware calls) @@ -644,8 +677,18 @@ class NostrRelayManager private constructor() { val relay = relaysList.find { it.url == relayUrl } relay?.messagesSent = (relay?.messagesSent ?: 0) + 1 updateRelaysList() + // Reset error flag on successful send + hasReportedNetworkError = false } else { Log.e(TAG, "❌ Failed to send event to $relayUrl: WebSocket send failed") + + // Check if all relays are disconnected (likely network issue) + if (connections.isEmpty() || relaysList.none { it.isConnected }) { + if (!hasReportedNetworkError) { + hasReportedNetworkError = true + networkErrorCallback?.invoke(NETWORK_ERROR_MESSAGE) + } + } } } catch (e: Exception) { Log.e(TAG, "❌ Failed to send event to $relayUrl: ${e.message}") diff --git a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt index 0a0ea6a70..d8eeabb00 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt @@ -184,6 +184,8 @@ class ChatViewModel( override fun onCleared() { super.onCleared() + // Clean up NostrGeohashService + nostrGeohashService.cleanup() // Note: Mesh service lifecycle is now managed by MainActivity }