mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:45:19 +00:00
When a private message has no reachable transport, the router now seals it to the recipient's Noise static key (new one-way Noise X pattern) and hands the envelope to up to three connected mutual favorites. Couriers store the opaque ciphertext under strict quotas (20 total, 5 per depositor, 16 KiB, 24 h) and hand it over when the recipient's announce matches a rotating HMAC recipient tag; the recipient opens it and the message flows through the normal private-message pipeline, so dedup and delivery acks just work. - CourierEnvelope TLV + courierEnvelope (0x04) message type in BitFoundation - Noise X one-way pattern reusing the existing handshake machinery, domain-separated by a courier prologue; sender identity authenticated via the ss DH (no forward secrecy - documented tradeoff) - CourierStore with eviction, file persistence, and panic-wipe integration - Rotating recipient tags (HMAC over epoch day) so carried envelopes don't correlate for observers who don't already know the recipient's key - New "carried" delivery status with figure.walk glyph; header indicator while carrying mail for others - Three-node end-to-end test ferrying packets through real BLEService instances, plus codec/crypto/store/router suites (986 tests green) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
153 lines
4.9 KiB
Swift
153 lines
4.9 KiB
Swift
//
|
|
// DeliveryStatusView.swift
|
|
// bitchat
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import SwiftUI
|
|
import BitFoundation
|
|
|
|
struct DeliveryStatusView: View {
|
|
@ThemedPalette private var palette
|
|
let status: DeliveryStatus
|
|
|
|
// MARK: - Computed Properties
|
|
|
|
private var textColor: Color { palette.primary }
|
|
|
|
private var secondaryTextColor: Color { palette.secondary }
|
|
|
|
private enum Strings {
|
|
static func delivered(to nickname: String) -> String {
|
|
String(
|
|
format: String(localized: "content.delivery.delivered_to", comment: "Tooltip for delivered private messages"),
|
|
locale: .current,
|
|
nickname
|
|
)
|
|
}
|
|
|
|
static func read(by nickname: String) -> String {
|
|
String(
|
|
format: String(localized: "content.delivery.read_by", comment: "Tooltip for read private messages"),
|
|
locale: .current,
|
|
nickname
|
|
)
|
|
}
|
|
|
|
static func failed(_ reason: String) -> String {
|
|
String(
|
|
format: String(localized: "content.delivery.failed", comment: "Tooltip for failed message delivery"),
|
|
locale: .current,
|
|
reason
|
|
)
|
|
}
|
|
|
|
static var carried: String {
|
|
String(localized: "content.delivery.carried", defaultValue: "Carried by a friend who may meet them", comment: "Tooltip for messages handed to a courier for physical delivery")
|
|
}
|
|
|
|
static func deliveredToMembers(_ reached: Int, _ total: Int) -> String {
|
|
String(
|
|
format: String(localized: "content.delivery.delivered_members", comment: "Tooltip for partially delivered messages"),
|
|
locale: .current,
|
|
reached,
|
|
total
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
switch status {
|
|
case .sending:
|
|
Image(systemName: "circle")
|
|
.font(.bitchatSystem(size: 10))
|
|
.foregroundColor(secondaryTextColor.opacity(0.6))
|
|
|
|
case .sent:
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10))
|
|
.foregroundColor(secondaryTextColor.opacity(0.6))
|
|
|
|
case .carried:
|
|
Image(systemName: "figure.walk")
|
|
.font(.bitchatSystem(size: 10))
|
|
.foregroundColor(secondaryTextColor.opacity(0.8))
|
|
.help(Strings.carried)
|
|
|
|
case .delivered(let nickname, _):
|
|
HStack(spacing: -2) {
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10))
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10))
|
|
}
|
|
.foregroundColor(textColor.opacity(0.8))
|
|
.help(Strings.delivered(to: nickname))
|
|
|
|
case .read(let nickname, _):
|
|
HStack(spacing: -2) {
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10, weight: .bold))
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10, weight: .bold))
|
|
}
|
|
.foregroundColor(palette.accentBlue)
|
|
.help(Strings.read(by: nickname))
|
|
|
|
case .failed(let reason):
|
|
Image(systemName: "exclamationmark.triangle")
|
|
.font(.bitchatSystem(size: 10))
|
|
.foregroundColor(Color.red.opacity(0.8))
|
|
.help(Strings.failed(reason))
|
|
|
|
case .partiallyDelivered(let reached, let total):
|
|
HStack(spacing: 1) {
|
|
Image(systemName: "checkmark")
|
|
.font(.bitchatSystem(size: 10))
|
|
Text(verbatim: "\(reached)/\(total)")
|
|
.bitchatFont(size: 10)
|
|
}
|
|
.foregroundColor(secondaryTextColor.opacity(0.6))
|
|
.help(Strings.deliveredToMembers(reached, total))
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
let statuses: [DeliveryStatus] = [
|
|
.sending,
|
|
.sent,
|
|
.carried,
|
|
.delivered(to: "John Doe", at: Date()),
|
|
.read(by: "Jane Doe", at: Date()),
|
|
.failed(reason: "Offline"),
|
|
.partiallyDelivered(reached: 2, total: 5)
|
|
]
|
|
|
|
List {
|
|
ForEach(statuses, id: \.self) { status in
|
|
HStack {
|
|
Text(status.displayText)
|
|
Spacer()
|
|
DeliveryStatusView(status: status)
|
|
}
|
|
}
|
|
}
|
|
.environment(\.colorScheme, .light)
|
|
|
|
List {
|
|
ForEach(statuses, id: \.self) { status in
|
|
HStack {
|
|
Text(status.displayText)
|
|
Spacer()
|
|
DeliveryStatusView(status: status)
|
|
}
|
|
}
|
|
}
|
|
.environment(\.colorScheme, .dark)
|
|
}
|