From 13b19fb8eba7e9332f360a5a8b5f34d9d3836ef5 Mon Sep 17 00:00:00 2001 From: Islam <2553451+qalandarov@users.noreply.github.com> Date: Sun, 19 Oct 2025 13:42:50 +0100 Subject: [PATCH] PeerID 27/n: `ContentView` and `MeshPeerList` (#835) --- bitchat/Views/ContentView.swift | 81 ++++++++++++++++---------------- bitchat/Views/MeshPeerList.swift | 12 ++--- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 05b70da4..7fc7d606 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -80,7 +80,7 @@ struct ContentView: View { // Timer-based refresh removed; use LocationChannelManager live updates instead // Window sizes for rendering (infinite scroll up) @State private var windowCountPublic: Int = 300 - @State private var windowCountPrivate: [String: Int] = [:] + @State private var windowCountPrivate: [PeerID: Int] = [:] // MARK: - Computed Properties @@ -124,7 +124,7 @@ struct ContentView: View { private struct PrivateHeaderContext { - let headerPeerID: String + let headerPeerID: PeerID let peer: BitchatPeer? let displayName: String let isNostrAvailable: Bool @@ -328,9 +328,9 @@ struct ContentView: View { // MARK: - Message List View - private func messagesView(privatePeer: String?, isAtBottom: Binding) -> some View { + private func messagesView(privatePeer: PeerID?, isAtBottom: Binding) -> some View { let messages: [BitchatMessage] = { - if let peerID = PeerID(str: privatePeer) { + if let peerID = privatePeer { return viewModel.getPrivateChatMessages(for: peerID) } return viewModel.messages @@ -470,7 +470,7 @@ struct ContentView: View { } .onChange(of: viewModel.privateChats) { _ in if let peerID = privatePeer, - let messages = viewModel.privateChats[PeerID(str: peerID)], + let messages = viewModel.privateChats[peerID], !messages.isEmpty { // If the newest private message is from me, always scroll let lastMsg = messages.last! @@ -525,7 +525,7 @@ struct ContentView: View { } .onAppear { // Also check when view appears - if let peerID = PeerID(str: privatePeer) { + if let peerID = privatePeer { // Try multiple times to ensure read receipts are sent viewModel.markPrivateMessagesAsRead(from: peerID) @@ -826,10 +826,10 @@ struct ContentView: View { } private func scrollToBottom(on proxy: ScrollViewProxy, - privatePeer: String?, + privatePeer: PeerID?, isAtBottom: Binding) { let targetID: String? = { - if let peer = PeerID(str: privatePeer), + if let peer = privatePeer, let last = viewModel.getPrivateChatMessages(for: peer).suffix(300).last?.id { return "dm:\(peer)|\(last)" } @@ -855,7 +855,7 @@ struct ContentView: View { DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { let secondTarget: String? = { - if let peer = PeerID(str: privatePeer), + if let peer = privatePeer, let last = viewModel.getPrivateChatMessages(for: peer).suffix(300).last?.id { return "dm:\(peer)|\(last)" } @@ -992,14 +992,14 @@ struct ContentView: View { textColor: textColor, secondaryTextColor: secondaryTextColor, onTapPeer: { peerID in - viewModel.startPrivateChat(with: PeerID(str: peerID)) + viewModel.startPrivateChat(with: peerID) showSidebar = true }, onToggleFavorite: { peerID in - viewModel.toggleFavorite(peerID: PeerID(str: peerID)) + viewModel.toggleFavorite(peerID: peerID) }, onShowFingerprint: { peerID in - viewModel.showFingerprint(for: PeerID(str: peerID)) + viewModel.showFingerprint(for: peerID) } ) } @@ -1014,7 +1014,7 @@ struct ContentView: View { private var privateChatSheetView: some View { VStack(spacing: 0) { - if let privatePeerID = viewModel.selectedPrivateChatPeer?.id { + if let privatePeerID = viewModel.selectedPrivateChatPeer { let headerContext = makePrivateHeaderContext(for: privatePeerID) HStack(spacing: 12) { @@ -1038,12 +1038,11 @@ struct ContentView: View { HStack(spacing: 8) { privateHeaderInfo(context: headerContext, privatePeerID: privatePeerID) - let peerID = PeerID(str: headerContext.headerPeerID) - let isFavorite = viewModel.isFavorite(peerID: peerID) + let isFavorite = viewModel.isFavorite(peerID: headerContext.headerPeerID) - if !privatePeerID.hasPrefix("nostr_") { + if !privatePeerID.isGeoDM { Button(action: { - viewModel.toggleFavorite(peerID: peerID) + viewModel.toggleFavorite(peerID: headerContext.headerPeerID) }) { Image(systemName: isFavorite ? "star.fill" : "star") .font(.bitchatSystem(size: 14)) @@ -1082,7 +1081,7 @@ struct ContentView: View { .background(backgroundColor) } - messagesView(privatePeer: viewModel.selectedPrivateChatPeer?.id, isAtBottom: $isAtBottomPrivate) + messagesView(privatePeer: viewModel.selectedPrivateChatPeer, isAtBottom: $isAtBottomPrivate) .background(backgroundColor) .frame(maxWidth: .infinity, maxHeight: .infinity) Divider() @@ -1104,9 +1103,9 @@ struct ContentView: View { ) } - private func privateHeaderInfo(context: PrivateHeaderContext, privatePeerID: String) -> some View { + private func privateHeaderInfo(context: PrivateHeaderContext, privatePeerID: PeerID) -> some View { Button(action: { - viewModel.showFingerprint(for: PeerID(str: context.headerPeerID)) + viewModel.showFingerprint(for: context.headerPeerID) }) { HStack(spacing: 6) { if let connectionState = context.peer?.connectionState { @@ -1129,7 +1128,7 @@ struct ContentView: View { case .offline: EmptyView() } - } else if viewModel.meshService.isPeerReachable(PeerID(str: context.headerPeerID)) { + } else if viewModel.meshService.isPeerReachable(context.headerPeerID) { Image(systemName: "point.3.filled.connected.trianglepath.dotted") .font(.bitchatSystem(size: 14)) .foregroundColor(textColor) @@ -1139,7 +1138,7 @@ struct ContentView: View { .font(.bitchatSystem(size: 14)) .foregroundColor(.purple) .accessibilityLabel(String(localized: "content.accessibility.available_nostr", comment: "Accessibility label for Nostr-available peer indicator")) - } else if viewModel.meshService.isPeerConnected(PeerID(str: context.headerPeerID)) || viewModel.connectedPeers.contains(PeerID(str: context.headerPeerID)) { + } else if viewModel.meshService.isPeerConnected(context.headerPeerID) || viewModel.connectedPeers.contains(context.headerPeerID) { Image(systemName: "dot.radiowaves.left.and.right") .font(.bitchatSystem(size: 14)) .foregroundColor(textColor) @@ -1150,14 +1149,14 @@ struct ContentView: View { .font(.bitchatSystem(size: 16, weight: .medium, design: .monospaced)) .foregroundColor(textColor) - if !privatePeerID.hasPrefix("nostr_") { - let statusPeerID: String = { - if privatePeerID.count == 64, let short = viewModel.getShortIDForNoiseKey(privatePeerID) { - return short.id + if !privatePeerID.isGeoDM { + let statusPeerID: PeerID = { + if privatePeerID.id.count == 64, let short = viewModel.getShortIDForNoiseKey(privatePeerID.id) { + return short } return context.headerPeerID }() - let encryptionStatus = viewModel.getEncryptionStatus(for: PeerID(str: statusPeerID)) + let encryptionStatus = viewModel.getEncryptionStatus(for: statusPeerID) if let icon = encryptionStatus.icon { Image(systemName: icon) .font(.bitchatSystem(size: 14)) @@ -1189,33 +1188,33 @@ struct ContentView: View { .frame(height: headerHeight) } - private func makePrivateHeaderContext(for privatePeerID: String) -> PrivateHeaderContext { - let headerPeerID: String = { - if privatePeerID.count == 64, let short = viewModel.getShortIDForNoiseKey(privatePeerID) { - return short.id + private func makePrivateHeaderContext(for privatePeerID: PeerID) -> PrivateHeaderContext { + let headerPeerID: PeerID = { + if privatePeerID.id.count == 64, let short = viewModel.getShortIDForNoiseKey(privatePeerID.id) { + return short } return privatePeerID }() - let peer = viewModel.getPeer(byID: PeerID(str: headerPeerID)) + let peer = viewModel.getPeer(byID: headerPeerID) let displayName: String = { - if privatePeerID.hasPrefix("nostr_"), case .location(let ch) = locationManager.selectedChannel { - let disp = viewModel.geohashDisplayName(for: PeerID(str: privatePeerID)) + if privatePeerID.isGeoDM, case .location(let ch) = locationManager.selectedChannel { + let disp = viewModel.geohashDisplayName(for: privatePeerID) return "#\(ch.geohash)/@\(disp)" } if let name = peer?.displayName { return name } - if let name = viewModel.meshService.peerNickname(peerID: PeerID(str: headerPeerID)) { return name } - if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: Data(hexString: headerPeerID) ?? Data()), + if let name = viewModel.meshService.peerNickname(peerID: headerPeerID) { return name } + if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: Data(hexString: headerPeerID.id) ?? Data()), !fav.peerNickname.isEmpty { return fav.peerNickname } - if headerPeerID.count == 16 { - let candidates = viewModel.identityManager.getCryptoIdentitiesByPeerIDPrefix(PeerID(str: headerPeerID)) + if headerPeerID.id.count == 16 { + let candidates = viewModel.identityManager.getCryptoIdentitiesByPeerIDPrefix(headerPeerID) if let id = candidates.first, let social = viewModel.identityManager.getSocialIdentity(for: id.fingerprint) { if let pet = social.localPetname, !pet.isEmpty { return pet } if !social.claimedNickname.isEmpty { return social.claimedNickname } } - } else if headerPeerID.count == 64, let keyData = Data(hexString: headerPeerID) { + } else if let keyData = headerPeerID.noiseKey { let fp = keyData.sha256Fingerprint() if let social = viewModel.identityManager.getSocialIdentity(for: fp) { if let pet = social.localPetname, !pet.isEmpty { return pet } @@ -1227,7 +1226,7 @@ struct ContentView: View { let isNostrAvailable: Bool = { guard let connectionState = peer?.connectionState else { - if let noiseKey = Data(hexString: headerPeerID), + if let noiseKey = Data(hexString: headerPeerID.id), let favoriteStatus = FavoritesPersistenceService.shared.getFavoriteStatus(for: noiseKey), favoriteStatus.isMutual { return true @@ -1746,7 +1745,7 @@ private extension ContentView { private func expandWindow(ifNeededFor message: BitchatMessage, allMessages: [BitchatMessage], - privatePeer: String?, + privatePeer: PeerID?, proxy: ScrollViewProxy) { let step = TransportConfig.uiWindowStepCount let contextKey: String = { diff --git a/bitchat/Views/MeshPeerList.swift b/bitchat/Views/MeshPeerList.swift index 25d0bcb3..188e4ebd 100644 --- a/bitchat/Views/MeshPeerList.swift +++ b/bitchat/Views/MeshPeerList.swift @@ -4,9 +4,9 @@ struct MeshPeerList: View { @ObservedObject var viewModel: ChatViewModel let textColor: Color let secondaryTextColor: Color - let onTapPeer: (String) -> Void - let onToggleFavorite: (String) -> Void - let onShowFingerprint: (String) -> Void + let onTapPeer: (PeerID) -> Void + let onToggleFavorite: (PeerID) -> Void + let onShowFingerprint: (PeerID) -> Void @Environment(\.colorScheme) var colorScheme @State private var orderedIDs: [String] = [] @@ -130,7 +130,7 @@ struct MeshPeerList: View { } if !isMe { - Button(action: { onToggleFavorite(peer.peerID.id) }) { + Button(action: { onToggleFavorite(peer.peerID) }) { Image(systemName: (peer.favoriteStatus?.isFavorite ?? false) ? "star.fill" : "star") .font(.bitchatSystem(size: 12)) .foregroundColor((peer.favoriteStatus?.isFavorite ?? false) ? .yellow : secondaryTextColor) @@ -142,8 +142,8 @@ struct MeshPeerList: View { .padding(.vertical, 4) .padding(.top, idx == 0 ? 10 : 0) .contentShape(Rectangle()) - .onTapGesture { if !isMe { onTapPeer(peer.peerID.id) } } - .onTapGesture(count: 2) { if !isMe { onShowFingerprint(peer.peerID.id) } } + .onTapGesture { if !isMe { onTapPeer(peer.peerID) } } + .onTapGesture(count: 2) { if !isMe { onShowFingerprint(peer.peerID) } } } } // Seed and update order outside result builder