mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 22:05:21 +00:00
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:
@@ -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
|
||||||
|
status = .noiseSecured
|
||||||
}
|
}
|
||||||
// We have a session but no fingerprint yet - still secured
|
} else if hasSession {
|
||||||
return .noiseSecured
|
// Check if handshaking
|
||||||
|
status = .noiseHandshaking
|
||||||
|
} else {
|
||||||
|
// Fall back to stored status
|
||||||
|
status = storedStatus ?? .none
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if handshaking
|
// Cache the result
|
||||||
if hasSession {
|
encryptionStatusCache[peerID] = status
|
||||||
return .noiseHandshaking
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to stored status
|
|
||||||
let finalStatus = storedStatus ?? .none
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
Reference in New Issue
Block a user