mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 01:45:20 +00:00
PeerID 23/n: ChatViewModel + its dependences (#801)
This commit is contained in:
@@ -221,7 +221,7 @@ final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
||||
// Get peer ID from userInfo
|
||||
if let peerID = userInfo["peerID"] as? String {
|
||||
DispatchQueue.main.async {
|
||||
self.chatViewModel?.startPrivateChat(with: peerID)
|
||||
self.chatViewModel?.startPrivateChat(with: PeerID(str: peerID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,9 +148,9 @@ final class CommandProcessor {
|
||||
|
||||
if chatViewModel?.selectedPrivateChatPeer != nil {
|
||||
// In private chat
|
||||
if let peerNickname = meshService?.peerNickname(peerID: PeerID(str: targetPeerID)) {
|
||||
if let peerNickname = meshService?.peerNickname(peerID: targetPeerID) {
|
||||
let personalMessage = "* \(emoji) \(myNickname) \(action) you\(suffix) *"
|
||||
meshService?.sendPrivateMessage(personalMessage, to: PeerID(str: targetPeerID),
|
||||
meshService?.sendPrivateMessage(personalMessage, to: targetPeerID,
|
||||
recipientNickname: peerNickname,
|
||||
messageID: UUID().uuidString)
|
||||
// Also add a local system message so the sender sees a natural-language confirmation
|
||||
@@ -214,7 +214,7 @@ final class CommandProcessor {
|
||||
let nickname = targetName.hasPrefix("@") ? String(targetName.dropFirst()) : targetName
|
||||
|
||||
if let peerID = chatViewModel?.getPeerIDForNickname(nickname),
|
||||
let fingerprint = meshService?.getFingerprint(for: PeerID(str: peerID)) {
|
||||
let fingerprint = meshService?.getFingerprint(for: peerID) {
|
||||
if identityManager.isBlocked(fingerprint: fingerprint) {
|
||||
return .success(message: "\(nickname) is already blocked")
|
||||
}
|
||||
@@ -258,7 +258,7 @@ final class CommandProcessor {
|
||||
let nickname = targetName.hasPrefix("@") ? String(targetName.dropFirst()) : targetName
|
||||
|
||||
if let peerID = chatViewModel?.getPeerIDForNickname(nickname),
|
||||
let fingerprint = meshService?.getFingerprint(for: PeerID(str: peerID)) {
|
||||
let fingerprint = meshService?.getFingerprint(for: peerID) {
|
||||
if !identityManager.isBlocked(fingerprint: fingerprint) {
|
||||
return .success(message: "\(nickname) is not blocked")
|
||||
}
|
||||
@@ -285,7 +285,7 @@ final class CommandProcessor {
|
||||
let nickname = targetName.hasPrefix("@") ? String(targetName.dropFirst()) : targetName
|
||||
|
||||
guard let peerID = chatViewModel?.getPeerIDForNickname(nickname),
|
||||
let noisePublicKey = Data(hexString: peerID) else {
|
||||
let noisePublicKey = Data(hexString: peerID.id) else {
|
||||
return .error(message: "can't find peer: \(nickname)")
|
||||
}
|
||||
|
||||
|
||||
@@ -235,10 +235,10 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate {
|
||||
}
|
||||
|
||||
/// Get peer ID for nickname
|
||||
func getPeerID(for nickname: String) -> String? {
|
||||
func getPeerID(for nickname: String) -> PeerID? {
|
||||
for peer in peers {
|
||||
if peer.displayName == nickname || peer.nickname == nickname {
|
||||
return peer.peerID.id
|
||||
return peer.peerID
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -347,7 +347,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate {
|
||||
// MARK: - Compatibility Methods (for easy migration)
|
||||
|
||||
var allPeers: [BitchatPeer] { peers }
|
||||
var connectedPeers: [PeerID] { Array(connectedPeerIDs) }
|
||||
var connectedPeers: Set<PeerID> { connectedPeerIDs }
|
||||
var favoritePeers: Set<String> {
|
||||
Set(favorites.compactMap { getFingerprint(for: $0.peerID) })
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -207,7 +207,7 @@ struct ContentView: View {
|
||||
set: { _ in viewModel.showingFingerprintFor = nil }
|
||||
)) {
|
||||
if let peerID = viewModel.showingFingerprintFor {
|
||||
FingerprintView(viewModel: viewModel, peerID: peerID)
|
||||
FingerprintView(viewModel: viewModel, peerID: peerID.id)
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
@@ -262,11 +262,11 @@ struct ContentView: View {
|
||||
Button("content.actions.direct_message") {
|
||||
if let peerID = selectedMessageSenderID {
|
||||
if peerID.hasPrefix("nostr:") {
|
||||
if let full = viewModel.fullNostrHex(forSenderPeerID: peerID) {
|
||||
if let full = viewModel.fullNostrHex(forSenderPeerID: PeerID(str: peerID)) {
|
||||
viewModel.startGeohashDM(withPubkeyHex: full)
|
||||
}
|
||||
} else {
|
||||
viewModel.startPrivateChat(with: peerID)
|
||||
viewModel.startPrivateChat(with: PeerID(str: peerID))
|
||||
}
|
||||
withAnimation(.easeInOut(duration: TransportConfig.uiAnimationMediumSeconds)) {
|
||||
showSidebar = true
|
||||
@@ -289,7 +289,7 @@ struct ContentView: View {
|
||||
Button("content.actions.block", role: .destructive) {
|
||||
// Prefer direct geohash block when we have a Nostr sender ID
|
||||
if let peerID = selectedMessageSenderID, peerID.hasPrefix("nostr:"),
|
||||
let full = viewModel.fullNostrHex(forSenderPeerID: peerID),
|
||||
let full = viewModel.fullNostrHex(forSenderPeerID: PeerID(str: peerID)),
|
||||
let sender = selectedMessageSender {
|
||||
viewModel.blockGeohashUser(pubkeyHexLowercased: full, displayName: sender)
|
||||
} else if let sender = selectedMessageSender {
|
||||
@@ -322,8 +322,8 @@ struct ContentView: View {
|
||||
|
||||
private func messagesView(privatePeer: String?, isAtBottom: Binding<Bool>) -> some View {
|
||||
let messages: [BitchatMessage] = {
|
||||
if let privatePeer = privatePeer {
|
||||
return viewModel.getPrivateChatMessages(for: privatePeer)
|
||||
if let privatePeer {
|
||||
return viewModel.getPrivateChatMessages(for: PeerID(str: privatePeer))
|
||||
}
|
||||
return viewModel.messages
|
||||
}()
|
||||
@@ -517,7 +517,7 @@ struct ContentView: View {
|
||||
}
|
||||
.onAppear {
|
||||
// Also check when view appears
|
||||
if let peerID = privatePeer {
|
||||
if let peerID = PeerID(str: privatePeer) {
|
||||
// Try multiple times to ensure read receipts are sent
|
||||
viewModel.markPrivateMessagesAsRead(from: peerID)
|
||||
|
||||
@@ -767,20 +767,20 @@ struct ContentView: View {
|
||||
private func handleOpenURL(_ url: URL) {
|
||||
guard url.scheme == "bitchat", url.host == "user" else { return }
|
||||
let id = url.path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
let peerID = id.removingPercentEncoding ?? id
|
||||
selectedMessageSenderID = peerID
|
||||
let peerID = PeerID(str: id.removingPercentEncoding ?? id)
|
||||
selectedMessageSenderID = peerID.id
|
||||
|
||||
if peerID.hasPrefix("nostr") {
|
||||
if peerID.isGeoDM || peerID.isGeoChat {
|
||||
selectedMessageSender = viewModel.geohashDisplayName(for: peerID)
|
||||
} else {
|
||||
if let name = viewModel.meshService.peerNickname(peerID: PeerID(str: peerID)) {
|
||||
if let name = viewModel.meshService.peerNickname(peerID: peerID) {
|
||||
selectedMessageSender = name
|
||||
} else {
|
||||
selectedMessageSender = viewModel.messages.last(where: { $0.senderPeerID == peerID && $0.sender != "system" })?.sender
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.isSelfSender(peerID: selectedMessageSenderID, displayName: selectedMessageSender) {
|
||||
if viewModel.isSelfSender(peerID: peerID, displayName: selectedMessageSender) {
|
||||
selectedMessageSender = nil
|
||||
selectedMessageSenderID = nil
|
||||
} else {
|
||||
@@ -793,7 +793,7 @@ struct ContentView: View {
|
||||
isAtBottom: Binding<Bool>) {
|
||||
let targetID: String? = {
|
||||
if let peer = privatePeer,
|
||||
let last = viewModel.getPrivateChatMessages(for: peer).suffix(300).last?.id {
|
||||
let last = viewModel.getPrivateChatMessages(for: PeerID(str: peer)).suffix(300).last?.id {
|
||||
return "dm:\(peer)|\(last)"
|
||||
}
|
||||
let contextKey: String = {
|
||||
@@ -931,14 +931,14 @@ struct ContentView: View {
|
||||
textColor: textColor,
|
||||
secondaryTextColor: secondaryTextColor,
|
||||
onTapPeer: { peerID in
|
||||
viewModel.startPrivateChat(with: peerID)
|
||||
viewModel.startPrivateChat(with: PeerID(str: peerID))
|
||||
showSidebar = true
|
||||
},
|
||||
onToggleFavorite: { peerID in
|
||||
viewModel.toggleFavorite(peerID: peerID)
|
||||
viewModel.toggleFavorite(peerID: PeerID(str: peerID))
|
||||
},
|
||||
onShowFingerprint: { peerID in
|
||||
viewModel.showFingerprint(for: peerID)
|
||||
viewModel.showFingerprint(for: PeerID(str: peerID))
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -977,18 +977,20 @@ struct ContentView: View {
|
||||
|
||||
HStack(spacing: 8) {
|
||||
privateHeaderInfo(context: headerContext, privatePeerID: privatePeerID)
|
||||
let peerID = PeerID(str: headerContext.headerPeerID)
|
||||
let isFavorite = viewModel.isFavorite(peerID: peerID)
|
||||
|
||||
if !privatePeerID.hasPrefix("nostr_") {
|
||||
Button(action: {
|
||||
viewModel.toggleFavorite(peerID: headerContext.headerPeerID)
|
||||
viewModel.toggleFavorite(peerID: peerID)
|
||||
}) {
|
||||
Image(systemName: viewModel.isFavorite(peerID: headerContext.headerPeerID) ? "star.fill" : "star")
|
||||
Image(systemName: isFavorite ? "star.fill" : "star")
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(viewModel.isFavorite(peerID: headerContext.headerPeerID) ? Color.yellow : textColor)
|
||||
.foregroundColor(isFavorite ? Color.yellow : textColor)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(
|
||||
viewModel.isFavorite(peerID: headerContext.headerPeerID)
|
||||
isFavorite
|
||||
? String(localized: "content.accessibility.remove_favorite", comment: "Accessibility label to remove a favorite")
|
||||
: String(localized: "content.accessibility.add_favorite", comment: "Accessibility label to add a favorite")
|
||||
)
|
||||
@@ -1043,7 +1045,7 @@ struct ContentView: View {
|
||||
|
||||
private func privateHeaderInfo(context: PrivateHeaderContext, privatePeerID: String) -> some View {
|
||||
Button(action: {
|
||||
viewModel.showFingerprint(for: context.headerPeerID)
|
||||
viewModel.showFingerprint(for: PeerID(str: context.headerPeerID))
|
||||
}) {
|
||||
HStack(spacing: 6) {
|
||||
if let connectionState = context.peer?.connectionState {
|
||||
@@ -1076,7 +1078,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(context.headerPeerID) {
|
||||
} else if viewModel.meshService.isPeerConnected(PeerID(str: context.headerPeerID)) || viewModel.connectedPeers.contains(PeerID(str: context.headerPeerID)) {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
.font(.bitchatSystem(size: 14))
|
||||
.foregroundColor(textColor)
|
||||
@@ -1090,11 +1092,11 @@ struct ContentView: View {
|
||||
if !privatePeerID.hasPrefix("nostr_") {
|
||||
let statusPeerID: String = {
|
||||
if privatePeerID.count == 64, let short = viewModel.getShortIDForNoiseKey(privatePeerID) {
|
||||
return short
|
||||
return short.id
|
||||
}
|
||||
return context.headerPeerID
|
||||
}()
|
||||
let encryptionStatus = viewModel.getEncryptionStatus(for: statusPeerID)
|
||||
let encryptionStatus = viewModel.getEncryptionStatus(for: PeerID(str: statusPeerID))
|
||||
if let icon = encryptionStatus.icon {
|
||||
Image(systemName: icon)
|
||||
.font(.bitchatSystem(size: 14))
|
||||
@@ -1129,16 +1131,16 @@ struct ContentView: View {
|
||||
private func makePrivateHeaderContext(for privatePeerID: String) -> PrivateHeaderContext {
|
||||
let headerPeerID: String = {
|
||||
if privatePeerID.count == 64, let short = viewModel.getShortIDForNoiseKey(privatePeerID) {
|
||||
return short
|
||||
return short.id
|
||||
}
|
||||
return privatePeerID
|
||||
}()
|
||||
|
||||
let peer = viewModel.getPeer(byID: headerPeerID)
|
||||
let peer = viewModel.getPeer(byID: PeerID(str: headerPeerID))
|
||||
|
||||
let displayName: String = {
|
||||
if privatePeerID.hasPrefix("nostr_"), case .location(let ch) = locationManager.selectedChannel {
|
||||
let disp = viewModel.geohashDisplayName(for: privatePeerID)
|
||||
let disp = viewModel.geohashDisplayName(for: PeerID(str: privatePeerID))
|
||||
return "#\(ch.geohash)/@\(disp)"
|
||||
}
|
||||
if let name = peer?.displayName { return name }
|
||||
|
||||
@@ -66,12 +66,12 @@ struct FingerprintView: View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
// Prefer short mesh ID for session/encryption status
|
||||
let statusPeerID: String = {
|
||||
if peerID.count == 64, let short = viewModel.getShortIDForNoiseKey(peerID) { return short }
|
||||
if peerID.count == 64, let short = viewModel.getShortIDForNoiseKey(peerID) { return short.id }
|
||||
return peerID
|
||||
}()
|
||||
// Resolve a friendly name
|
||||
let peerNickname: String = {
|
||||
if let p = viewModel.getPeer(byID: statusPeerID) { return p.displayName }
|
||||
if let p = viewModel.getPeer(byID: PeerID(str: statusPeerID)) { return p.displayName }
|
||||
if let name = viewModel.meshService.peerNickname(peerID: PeerID(str: statusPeerID)) { return name }
|
||||
if peerID.count == 64, let data = Data(hexString: peerID) {
|
||||
if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: data), !fav.peerNickname.isEmpty { return fav.peerNickname }
|
||||
@@ -84,7 +84,7 @@ struct FingerprintView: View {
|
||||
return Strings.unknownPeer()
|
||||
}()
|
||||
// Accurate encryption state based on short ID session
|
||||
let encryptionStatus = viewModel.getEncryptionStatus(for: statusPeerID)
|
||||
let encryptionStatus = viewModel.getEncryptionStatus(for: PeerID(str: statusPeerID))
|
||||
|
||||
HStack {
|
||||
if let icon = encryptionStatus.icon {
|
||||
@@ -115,7 +115,7 @@ struct FingerprintView: View {
|
||||
.font(.bitchatSystem(size: 12, weight: .bold, design: .monospaced))
|
||||
.foregroundColor(textColor.opacity(0.7))
|
||||
|
||||
if let fingerprint = viewModel.getFingerprint(for: statusPeerID) {
|
||||
if let fingerprint = viewModel.getFingerprint(for: PeerID(str: statusPeerID)) {
|
||||
Text(formatFingerprint(fingerprint))
|
||||
.font(.bitchatSystem(size: 14, design: .monospaced))
|
||||
.foregroundColor(textColor)
|
||||
@@ -176,6 +176,7 @@ struct FingerprintView: View {
|
||||
// Verification status
|
||||
if encryptionStatus == .noiseSecured || encryptionStatus == .noiseVerified {
|
||||
let isVerified = encryptionStatus == .noiseVerified
|
||||
let peerID = PeerID(str: peerID)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
Text(isVerified ? Strings.verifiedBadge : Strings.notVerifiedBadge)
|
||||
|
||||
@@ -21,8 +21,8 @@ struct MeshPeerList: View {
|
||||
let myPeerID = viewModel.meshService.myPeerID
|
||||
let mapped: [(peer: BitchatPeer, isMe: Bool, hasUnread: Bool, enc: EncryptionStatus)] = viewModel.allPeers.map { peer in
|
||||
let isMe = peer.peerID == myPeerID
|
||||
let hasUnread = viewModel.hasUnreadMessages(for: peer.peerID.id)
|
||||
let enc = viewModel.getEncryptionStatus(for: peer.peerID.id)
|
||||
let hasUnread = viewModel.hasUnreadMessages(for: peer.peerID)
|
||||
let enc = viewModel.getEncryptionStatus(for: peer.peerID)
|
||||
return (peer, isMe, hasUnread, enc)
|
||||
}
|
||||
// Stable visual order without mutating state here
|
||||
@@ -47,7 +47,7 @@ struct MeshPeerList: View {
|
||||
let peer = item.peer
|
||||
let isMe = item.isMe
|
||||
HStack(spacing: 4) {
|
||||
let assigned = viewModel.colorForMeshPeer(id: peer.peerID.id, isDark: colorScheme == .dark)
|
||||
let assigned = viewModel.colorForMeshPeer(id: peer.peerID, isDark: colorScheme == .dark)
|
||||
let baseColor = isMe ? Color.orange : assigned
|
||||
if isMe {
|
||||
Image(systemName: "person.fill")
|
||||
@@ -89,7 +89,7 @@ struct MeshPeerList: View {
|
||||
}
|
||||
}
|
||||
|
||||
if !isMe, viewModel.isPeerBlocked(peer.peerID.id) {
|
||||
if !isMe, viewModel.isPeerBlocked(peer.peerID) {
|
||||
Image(systemName: "nosign")
|
||||
.font(.bitchatSystem(size: 10))
|
||||
.foregroundColor(.red)
|
||||
@@ -105,7 +105,7 @@ struct MeshPeerList: View {
|
||||
}
|
||||
} else {
|
||||
// Offline: prefer showing verified badge from persisted fingerprints
|
||||
if let fp = viewModel.getFingerprint(for: peer.peerID.id),
|
||||
if let fp = viewModel.getFingerprint(for: peer.peerID),
|
||||
viewModel.verifiedFingerprints.contains(fp) {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.font(.bitchatSystem(size: 10))
|
||||
|
||||
@@ -373,7 +373,7 @@ struct VerificationSheetView: View {
|
||||
}
|
||||
|
||||
// Optional: Remove verification for selected peer (if verified)
|
||||
if let pid = viewModel.selectedPrivateChatPeer?.id,
|
||||
if let pid = viewModel.selectedPrivateChatPeer,
|
||||
let fp = viewModel.getFingerprint(for: pid),
|
||||
viewModel.verifiedFingerprints.contains(fp) {
|
||||
Button(action: { viewModel.unverifyFingerprint(for: pid) }) {
|
||||
|
||||
Reference in New Issue
Block a user