mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 18:05:19 +00:00
Refactor/robustness (#446)
* Refactor BitChat for improved robustness and performance Major refactoring to simplify architecture and fix critical issues: Architecture Improvements: - Replace complex BluetoothMeshService with simplified SimplifiedBluetoothService - Consolidate message routing and peer management into unified services - Remove redundant caching layers and optimize performance Bug Fixes: - Fix critical BLE peer mapping corruption in mesh networks - Fix encrypted message routing failures in multi-peer scenarios - Fix app freezes and Main Thread Checker warnings - Fix BLE message delivery in dual-role connections - Fix favorite toggle UI not updating instantly - Fix Nostr offline messaging with 24-hour message filtering Features: - Add command processor for chat commands - Add autocomplete service for mentions and commands - Improve private chat management with dedicated service - Add unified peer service for consistent state management Performance: - Optimize BLE reconnection speed - Reduce excessive logging throughout codebase - Improve message deduplication efficiency - Optimize UI updates and state management * Improvements refactor robust (#441) * remove unused code * remove more * TLV for announcement * restore * restore * restore? * messages tlv too (#442) * Fix Nostr notification and read receipt issues, add TLV encoding, cleanup unused code ## Notification & Read Receipt Fixes - Fixed toolbar notification icon appearing incorrectly on app restart for already-read messages - Fixed read receipts being incorrectly deleted on startup when privateChats was empty - Fixed messages not being marked as read when opening chat for first time - Fixed senderPeerID not being updated during message consolidation - Added startup phase logic to block old messages (>30s) while allowing recent ones - Fixed unread status checking across all storage locations (ephemeral, stable Noise keys, temporary Nostr IDs) ## TLV Encoding Implementation - Implemented Type-Length-Value encoding for private message payloads - Added PrivateMessagePacket struct with TLV encode/decode methods - Enhanced message structure for better extensibility and robustness ## Code Cleanup - Removed unused PeerStateManager class and related dependencies - Removed dead protocol types (DeliveryAck, ProtocolAck/Nack, NoiseIdentityAnnouncement) - Cleaned up BitchatDelegate by removing unused methods - Removed excessive debug logging throughout ChatViewModel - Added test-only ProtocolNack helper for integration tests ## Technical Details - Messages stored under three ID types: ephemeral peer IDs, stable Noise key hexes, temporary Nostr IDs - Fixed cleanupOldReadReceipts() to skip when privateChats is empty or during startup - Updated message consolidation to properly update senderPeerID - Restored NIP-17 timestamp randomization (±15 minutes) for privacy --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// AutocompleteService.swift
|
||||
// bitchat
|
||||
//
|
||||
// Handles autocomplete suggestions for mentions and commands
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Manages autocomplete functionality for chat
|
||||
class AutocompleteService {
|
||||
private let mentionRegex = try? NSRegularExpression(pattern: "@([\\p{L}0-9_]*)$", options: [])
|
||||
private let commandRegex = try? NSRegularExpression(pattern: "^/([a-z]*)$", options: [])
|
||||
|
||||
private let commands = [
|
||||
"/msg", "/who", "/clear", "/help",
|
||||
"/hug", "/slap", "/fav", "/unfav",
|
||||
"/block", "/unblock"
|
||||
]
|
||||
|
||||
/// Get autocomplete suggestions for current text
|
||||
func getSuggestions(for text: String, peers: [String], cursorPosition: Int) -> (suggestions: [String], range: NSRange?) {
|
||||
let textToPosition = String(text.prefix(cursorPosition))
|
||||
|
||||
// Check for mention autocomplete
|
||||
if let (mentionSuggestions, mentionRange) = getMentionSuggestions(textToPosition, peers: peers) {
|
||||
return (mentionSuggestions, mentionRange)
|
||||
}
|
||||
|
||||
// Don't handle command autocomplete here - ContentView handles it with better UI
|
||||
// if let (commandSuggestions, commandRange) = getCommandSuggestions(textToPosition) {
|
||||
// return (commandSuggestions, commandRange)
|
||||
// }
|
||||
|
||||
return ([], nil)
|
||||
}
|
||||
|
||||
/// Apply selected suggestion to text
|
||||
func applySuggestion(_ suggestion: String, to text: String, range: NSRange) -> String {
|
||||
guard let textRange = Range(range, in: text) else { return text }
|
||||
|
||||
var replacement = suggestion
|
||||
|
||||
// Add space after command if it takes arguments
|
||||
if suggestion.hasPrefix("/") && needsArgument(command: suggestion) {
|
||||
replacement += " "
|
||||
}
|
||||
|
||||
return text.replacingCharacters(in: textRange, with: replacement)
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func getMentionSuggestions(_ text: String, peers: [String]) -> ([String], NSRange)? {
|
||||
guard let regex = mentionRegex else { return nil }
|
||||
|
||||
let nsText = text as NSString
|
||||
let matches = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsText.length))
|
||||
|
||||
guard let match = matches.last else { return nil }
|
||||
|
||||
let fullRange = match.range(at: 0)
|
||||
let captureRange = match.range(at: 1)
|
||||
let prefix = nsText.substring(with: captureRange).lowercased()
|
||||
|
||||
let suggestions = peers
|
||||
.filter { $0.lowercased().hasPrefix(prefix) }
|
||||
.sorted()
|
||||
.prefix(5)
|
||||
.map { "@\($0)" }
|
||||
|
||||
return suggestions.isEmpty ? nil : (Array(suggestions), fullRange)
|
||||
}
|
||||
|
||||
private func getCommandSuggestions(_ text: String) -> ([String], NSRange)? {
|
||||
guard let regex = commandRegex else { return nil }
|
||||
|
||||
let nsText = text as NSString
|
||||
let matches = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsText.length))
|
||||
|
||||
guard let match = matches.last else { return nil }
|
||||
|
||||
let fullRange = match.range(at: 0)
|
||||
let captureRange = match.range(at: 1)
|
||||
let prefix = nsText.substring(with: captureRange).lowercased()
|
||||
|
||||
let suggestions = commands
|
||||
.filter { $0.hasPrefix("/\(prefix)") }
|
||||
.sorted()
|
||||
.prefix(5)
|
||||
|
||||
return suggestions.isEmpty ? nil : (Array(suggestions), fullRange)
|
||||
}
|
||||
|
||||
private func needsArgument(command: String) -> Bool {
|
||||
switch command {
|
||||
case "/who", "/clear", "/help":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user