mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 17:05:19 +00:00
Fix duplicate messages after channel switch\n\n- Dedup on per-geohash append (skip if ID exists)\n- Dedup and sort timeline by timestamp when switching into a geohash\n- Keep content/layout unchanged; add lightweight debug logs for empty rows
This commit is contained in:
@@ -1412,6 +1412,11 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
switch channel {
|
switch channel {
|
||||||
case .mesh:
|
case .mesh:
|
||||||
messages = meshTimeline
|
messages = meshTimeline
|
||||||
|
// Debug: log if any empty messages are present
|
||||||
|
let emptyMesh = messages.filter { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }.count
|
||||||
|
if emptyMesh > 0 {
|
||||||
|
SecureLogger.log("RenderGuard: mesh timeline contains \(emptyMesh) empty messages", category: SecureLogger.session, level: .debug)
|
||||||
|
}
|
||||||
stopGeoParticipantsTimer()
|
stopGeoParticipantsTimer()
|
||||||
geohashPeople = []
|
geohashPeople = []
|
||||||
teleportedGeo.removeAll()
|
teleportedGeo.removeAll()
|
||||||
@@ -1419,13 +1424,26 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
// Sanitize existing timeline (filter any prior empty-content entries)
|
// Sanitize existing timeline (filter any prior empty-content entries)
|
||||||
var arr = geoTimelines[ch.geohash] ?? []
|
var arr = geoTimelines[ch.geohash] ?? []
|
||||||
arr.removeAll { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
|
arr.removeAll { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
|
||||||
// Ensure chronological order when returning to a geohash
|
// Deduplicate by ID while preserving order (from oldest to newest)
|
||||||
if arr.count > 1 {
|
if arr.count > 1 {
|
||||||
arr.sort { $0.timestamp < $1.timestamp }
|
var seen = Set<String>()
|
||||||
|
var dedup: [BitchatMessage] = []
|
||||||
|
for m in arr.sorted(by: { $0.timestamp < $1.timestamp }) {
|
||||||
|
if !seen.contains(m.id) {
|
||||||
|
dedup.append(m)
|
||||||
|
seen.insert(m.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arr = dedup
|
||||||
}
|
}
|
||||||
// Persist the cleaned/sorted timeline for this geohash
|
// Persist the cleaned/sorted timeline for this geohash
|
||||||
geoTimelines[ch.geohash] = arr
|
geoTimelines[ch.geohash] = arr
|
||||||
messages = arr
|
messages = arr
|
||||||
|
// Debug: log if any empty messages are present post-sanitize
|
||||||
|
let emptyGeo = messages.filter { $0.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }.count
|
||||||
|
if emptyGeo > 0 {
|
||||||
|
SecureLogger.log("RenderGuard: geohash \(ch.geohash) timeline has \(emptyGeo) empty messages after sanitize", category: SecureLogger.session, level: .debug)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Unsubscribe previous
|
// Unsubscribe previous
|
||||||
if let sub = geoSubscriptionID {
|
if let sub = geoSubscriptionID {
|
||||||
@@ -5797,9 +5815,12 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
if isGeo && finalMessage.sender != "system" {
|
if isGeo && finalMessage.sender != "system" {
|
||||||
if let gh = currentGeohash {
|
if let gh = currentGeohash {
|
||||||
var arr = geoTimelines[gh] ?? []
|
var arr = geoTimelines[gh] ?? []
|
||||||
arr.append(finalMessage)
|
// Dedup by message ID before appending to per-geohash timeline
|
||||||
if arr.count > geoTimelineCap { arr = Array(arr.suffix(geoTimelineCap)) }
|
if !arr.contains(where: { $0.id == finalMessage.id }) {
|
||||||
geoTimelines[gh] = arr
|
arr.append(finalMessage)
|
||||||
|
if arr.count > geoTimelineCap { arr = Array(arr.suffix(geoTimelineCap)) }
|
||||||
|
geoTimelines[gh] = arr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -281,8 +281,10 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
let items = windowedMessages.map { (uiID: "\(contextKey)|\($0.id)", message: $0) }
|
let items = windowedMessages.map { (uiID: "\(contextKey)|\($0.id)", message: $0) }
|
||||||
|
// Filter out empty/whitespace-only messages to avoid blank rows
|
||||||
|
let filteredItems = items.filter { !$0.message.content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }
|
||||||
|
|
||||||
ForEach(items, id: \.uiID) { item in
|
ForEach(filteredItems, id: \.uiID) { item in
|
||||||
let message = item.message
|
let message = item.message
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
// Check if current user is mentioned
|
// Check if current user is mentioned
|
||||||
|
|||||||
Reference in New Issue
Block a user