Add 1pt more padding around link preview box

- Increased padding from 9pt to 10pt for better visual spacing
- Improves readability and touch target size
This commit is contained in:
jack
2025-07-24 13:04:43 +02:00
parent 5a0c8f0a26
commit 5d3512dfbc
+54 -16
View File
@@ -36,6 +36,10 @@ class ChatViewModel: ObservableObject {
private let userDefaults = UserDefaults.standard private let userDefaults = UserDefaults.standard
private let nicknameKey = "bitchat.nickname" private let nicknameKey = "bitchat.nickname"
// Caches for expensive computations
private var rssiColorCache: [String: Color] = [:] // key: "\(rssi)_\(isDark)"
private var encryptionStatusCache: [String: EncryptionStatus] = [:] // key: peerID
@Published var favoritePeers: Set<String> = [] // Now stores public key fingerprints instead of peer IDs @Published var favoritePeers: Set<String> = [] // Now stores public key fingerprints instead of peer IDs
private var peerIDToPublicKeyFingerprint: [String: String] = [:] // Maps ephemeral peer IDs to persistent fingerprints private var peerIDToPublicKeyFingerprint: [String: String] = [:] // Maps ephemeral peer IDs to persistent fingerprints
private var blockedUsers: Set<String> = [] // Stores public key fingerprints of blocked users private var blockedUsers: Set<String> = [] // Stores public key fingerprints of blocked users
@@ -765,25 +769,37 @@ class ChatViewModel: ObservableObject {
func getRSSIColor(rssi: Int, colorScheme: ColorScheme) -> Color { func getRSSIColor(rssi: Int, colorScheme: ColorScheme) -> Color {
let isDark = colorScheme == .dark let isDark = colorScheme == .dark
let cacheKey = "\(rssi)_\(isDark)"
// Check cache first
if let cachedColor = rssiColorCache[cacheKey] {
return cachedColor
}
// RSSI typically ranges from -30 (excellent) to -90 (poor) // RSSI typically ranges from -30 (excellent) to -90 (poor)
// We'll map this to colors from green (strong) to red (weak) // We'll map this to colors from green (strong) to red (weak)
let color: Color
if rssi >= -50 { if rssi >= -50 {
// Excellent signal: bright green // Excellent signal: bright green
return isDark ? Color(red: 0.0, green: 1.0, blue: 0.0) : Color(red: 0.0, green: 0.7, blue: 0.0) color = isDark ? Color(red: 0.0, green: 1.0, blue: 0.0) : Color(red: 0.0, green: 0.7, blue: 0.0)
} else if rssi >= -60 { } else if rssi >= -60 {
// Good signal: green-yellow // Good signal: green-yellow
return isDark ? Color(red: 0.5, green: 1.0, blue: 0.0) : Color(red: 0.3, green: 0.7, blue: 0.0) color = isDark ? Color(red: 0.5, green: 1.0, blue: 0.0) : Color(red: 0.3, green: 0.7, blue: 0.0)
} else if rssi >= -70 { } else if rssi >= -70 {
// Fair signal: yellow // Fair signal: yellow
return isDark ? Color(red: 1.0, green: 1.0, blue: 0.0) : Color(red: 0.7, green: 0.7, blue: 0.0) color = isDark ? Color(red: 1.0, green: 1.0, blue: 0.0) : Color(red: 0.7, green: 0.7, blue: 0.0)
} else if rssi >= -80 { } else if rssi >= -80 {
// Weak signal: orange // Weak signal: orange
return isDark ? Color(red: 1.0, green: 0.6, blue: 0.0) : Color(red: 0.8, green: 0.4, blue: 0.0) color = isDark ? Color(red: 1.0, green: 0.6, blue: 0.0) : Color(red: 0.8, green: 0.4, blue: 0.0)
} else { } else {
// Poor signal: red // Poor signal: red
return isDark ? Color(red: 1.0, green: 0.2, blue: 0.2) : Color(red: 0.8, green: 0.0, blue: 0.0) color = isDark ? Color(red: 1.0, green: 0.2, blue: 0.2) : Color(red: 0.8, green: 0.0, blue: 0.0)
} }
// Cache the result
rssiColorCache[cacheKey] = color
return color
} }
func updateAutocomplete(for text: String, cursorPosition: Int) { func updateAutocomplete(for text: String, cursorPosition: Int) {
@@ -1203,6 +1219,11 @@ class ChatViewModel: ObservableObject {
} }
func getEncryptionStatus(for peerID: String) -> EncryptionStatus { func getEncryptionStatus(for peerID: String) -> EncryptionStatus {
// Check cache first
if let cachedStatus = encryptionStatusCache[peerID] {
return cachedStatus
}
// This must be a pure function - no state mutations allowed // This must be a pure function - no state mutations allowed
// to avoid SwiftUI update loops // to avoid SwiftUI update loops
@@ -1210,34 +1231,51 @@ class ChatViewModel: ObservableObject {
let hasSession = meshService.getNoiseService().hasSession(with: peerID) let hasSession = meshService.getNoiseService().hasSession(with: peerID)
let storedStatus = peerEncryptionStatus[peerID] let storedStatus = peerEncryptionStatus[peerID]
let status: EncryptionStatus
// First check if we have an established session // First check if we have an established session
if hasEstablished { if hasEstablished {
// We have encryption, now check if it's verified // We have encryption, now check if it's verified
if let fingerprint = getFingerprint(for: peerID) { if let fingerprint = getFingerprint(for: peerID) {
if verifiedFingerprints.contains(fingerprint) { if verifiedFingerprints.contains(fingerprint) {
return .noiseVerified status = .noiseVerified
} else { } else {
return .noiseSecured status = .noiseSecured
}
} }
} else {
// We have a session but no fingerprint yet - still secured // We have a session but no fingerprint yet - still secured
return .noiseSecured status = .noiseSecured
} }
} else if hasSession {
// Check if handshaking // Check if handshaking
if hasSession { status = .noiseHandshaking
return .noiseHandshaking } else {
// Fall back to stored status
status = storedStatus ?? .none
} }
// Fall back to stored status // Cache the result
let finalStatus = storedStatus ?? .none encryptionStatusCache[peerID] = status
// Only log occasionally to avoid spam // Only log occasionally to avoid spam
if Int.random(in: 0..<100) == 0 { if Int.random(in: 0..<100) == 0 {
SecureLogger.log("getEncryptionStatus for \(peerID): hasEstablished=\(hasEstablished), hasSession=\(hasSession), stored=\(String(describing: storedStatus)), final=\(finalStatus)", category: SecureLogger.security, level: .debug) SecureLogger.log("getEncryptionStatus for \(peerID): hasEstablished=\(hasEstablished), hasSession=\(hasSession), stored=\(String(describing: storedStatus)), final=\(status)", category: SecureLogger.security, level: .debug)
} }
return finalStatus return status
}
// Clear caches when data changes
private func invalidateEncryptionCache(for peerID: String? = nil) {
if let peerID = peerID {
encryptionStatusCache.removeValue(forKey: peerID)
} else {
encryptionStatusCache.removeAll()
}
}
private func invalidateRSSIColorCache() {
rssiColorCache.removeAll()
} }
// Update encryption status in appropriate places, not during view updates // Update encryption status in appropriate places, not during view updates