initial commit: Proximity iOS app proposal, full Swift code scaffold, reference backend, testing docs, generated Xcode project
This commit is contained in:
26
Proximity/Models/ChatMessage.swift
Normal file
26
Proximity/Models/ChatMessage.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
import Foundation
|
||||
|
||||
/// A single end-to-end encrypted message within a revealed connection.
|
||||
struct ChatMessage: Identifiable, Codable, Hashable {
|
||||
let id: UUID
|
||||
let connectionID: UUID
|
||||
let senderID: String
|
||||
let ciphertext: Data
|
||||
let sentAt: Date
|
||||
var deliveredAt: Date?
|
||||
var readAt: Date?
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
connectionID: UUID,
|
||||
senderID: String,
|
||||
ciphertext: Data,
|
||||
sentAt: Date = Date()
|
||||
) {
|
||||
self.id = id
|
||||
self.connectionID = connectionID
|
||||
self.senderID = senderID
|
||||
self.ciphertext = ciphertext
|
||||
self.sentAt = sentAt
|
||||
}
|
||||
}
|
||||
35
Proximity/Models/Connection.swift
Normal file
35
Proximity/Models/Connection.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
import Foundation
|
||||
|
||||
/// A fully revealed, mutually-consented connection between two present users.
|
||||
///
|
||||
/// Connections can **only** be created from a mutual `Encounter`. There is no
|
||||
/// path to create one online — you must have been physically near each other.
|
||||
struct Connection: Identifiable, Codable, Hashable {
|
||||
let id: UUID
|
||||
let encounterID: UUID
|
||||
let remoteUserID: String
|
||||
let displayName: String
|
||||
let createdAt: Date
|
||||
let lastMessageAt: Date?
|
||||
|
||||
/// The E2E session key used for this conversation.
|
||||
let sessionKeyID: String
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
encounterID: UUID,
|
||||
remoteUserID: String,
|
||||
displayName: String,
|
||||
createdAt: Date = Date(),
|
||||
lastMessageAt: Date? = nil,
|
||||
sessionKeyID: String
|
||||
) {
|
||||
self.id = id
|
||||
self.encounterID = encounterID
|
||||
self.remoteUserID = remoteUserID
|
||||
self.displayName = displayName
|
||||
self.createdAt = createdAt
|
||||
self.lastMessageAt = lastMessageAt
|
||||
self.sessionKeyID = sessionKeyID
|
||||
}
|
||||
}
|
||||
49
Proximity/Models/DistanceTier.swift
Normal file
49
Proximity/Models/DistanceTier.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
import Foundation
|
||||
|
||||
/// The physical range band a user is open to connecting within.
|
||||
///
|
||||
/// Each tier maps to a different sensing technology. The tiers are ordered so
|
||||
/// higher tiers *include* lower ones (being open "in the area" also means
|
||||
/// you're open "nearby" and "right here").
|
||||
enum DistanceTier: Int, CaseIterable, Identifiable, Codable {
|
||||
/// 0–9 m. Ultra-wideband (Nearby Interaction). "This exact person."
|
||||
case rightHere = 1
|
||||
|
||||
/// 10–100 m. Bluetooth Low Energy / iBeacon. "This room, this block."
|
||||
case nearby = 2
|
||||
|
||||
/// 100 m–50 mi. Server-relayed, opt-in coarse location. "This neighborhood."
|
||||
case inTheArea = 3
|
||||
|
||||
var id: Int { rawValue }
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .rightHere: return "Right Here"
|
||||
case .nearby: return "Nearby"
|
||||
case .inTheArea: return "In the Area"
|
||||
}
|
||||
}
|
||||
|
||||
var subtitle: String {
|
||||
switch self {
|
||||
case .rightHere: return "0–9 m · UWB"
|
||||
case .nearby: return "10–100 m · BLE"
|
||||
case .inTheArea: return "100 m–50 mi · Network"
|
||||
}
|
||||
}
|
||||
|
||||
/// The primary engine used for this tier.
|
||||
var engine: ProximityEngine {
|
||||
switch self {
|
||||
case .rightHere: return .uwb
|
||||
case .nearby: return .ble
|
||||
case .inTheArea: return .beacon
|
||||
}
|
||||
}
|
||||
|
||||
/// Higher tiers include lower ones.
|
||||
func includes(_ other: DistanceTier) -> Bool {
|
||||
self.rawValue >= other.rawValue
|
||||
}
|
||||
}
|
||||
55
Proximity/Models/Encounter.swift
Normal file
55
Proximity/Models/Encounter.swift
Normal file
@@ -0,0 +1,55 @@
|
||||
import Foundation
|
||||
|
||||
/// A real-world encounter between two present users.
|
||||
///
|
||||
/// An encounter is deliberately **anonymous** until both parties mutually
|
||||
/// consent to reveal. Until then it is a "silhouette" — a proof that two
|
||||
/// humans were physically near each other, with no identity attached.
|
||||
struct Encounter: Identifiable, Codable, Hashable {
|
||||
let id: UUID
|
||||
let remoteAnonToken: String
|
||||
let timestamp: Date
|
||||
let tier: DistanceTier
|
||||
let engine: ProximityEngine
|
||||
|
||||
/// Coarse geohash (optional). Never precise location.
|
||||
var geohash: String?
|
||||
|
||||
/// The relationship state of this encounter.
|
||||
var status: EncounterStatus
|
||||
|
||||
/// A human-readable hint, e.g. "Cafe on 5th Ave" if the user tagged it.
|
||||
var placeHint: String?
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
remoteAnonToken: String,
|
||||
timestamp: Date = Date(),
|
||||
tier: DistanceTier,
|
||||
engine: ProximityEngine,
|
||||
geohash: String? = nil,
|
||||
status: EncounterStatus = .silhouette,
|
||||
placeHint: String? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.remoteAnonToken = remoteAnonToken
|
||||
self.timestamp = timestamp
|
||||
self.tier = tier
|
||||
self.engine = engine
|
||||
self.geohash = geohash
|
||||
self.status = status
|
||||
self.placeHint = placeHint
|
||||
}
|
||||
}
|
||||
|
||||
/// Relationship lifecycle of an encounter.
|
||||
enum EncounterStatus: String, Codable, Hashable {
|
||||
/// Seen but not yet acknowledged. A silhouette.
|
||||
case silhouette
|
||||
/// This user has sent a "wave" (interest) but the other hasn't replied.
|
||||
case waved
|
||||
/// Both parties waved — identities revealed, chat unlocked.
|
||||
case mutual
|
||||
/// One party declined or blocked. Permanently removed from the graph.
|
||||
case declined
|
||||
}
|
||||
18
Proximity/Models/ProximityEngine.swift
Normal file
18
Proximity/Models/ProximityEngine.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
import Foundation
|
||||
|
||||
/// The underlying sensing technology powering a connection tier.
|
||||
enum ProximityEngine: String, CaseIterable, Codable {
|
||||
case uwb // Nearby Interaction (U1/U2 chip)
|
||||
case ble // Core Bluetooth
|
||||
case beacon // Core Location (iBeacon region monitoring)
|
||||
case nfc // Core NFC (tap-to-connect)
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .uwb: return "Ultra-Wideband"
|
||||
case .ble: return "Bluetooth"
|
||||
case .beacon: return "Beacon"
|
||||
case .nfc: return "NFC"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Proximity/Models/Reveal.swift
Normal file
21
Proximity/Models/Reveal.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
/// The moment two present users mutually acknowledge each other.
|
||||
///
|
||||
/// This is the emotional climax of the product — the instant two strangers
|
||||
/// who were physically near each other both chose to be seen, and their
|
||||
/// silhouettes resolve into people. Everything else in the app exists to
|
||||
/// create this moment.
|
||||
struct Reveal: Identifiable {
|
||||
let id: UUID
|
||||
/// The encounter that produced this reveal.
|
||||
let encounter: Encounter
|
||||
/// The connection created by mutual consent.
|
||||
let connection: Connection
|
||||
|
||||
init(encounter: Encounter, connection: Connection) {
|
||||
self.id = connection.id
|
||||
self.encounter = encounter
|
||||
self.connection = connection
|
||||
}
|
||||
}
|
||||
33
Proximity/Models/UserProfile.swift
Normal file
33
Proximity/Models/UserProfile.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
|
||||
/// The user's own local identity and preferences.
|
||||
///
|
||||
/// Deliberately minimal: Proximity collects the *least* data needed to connect
|
||||
/// humans. There is no profile photo, no bio, no social graph import.
|
||||
struct UserProfile: Codable {
|
||||
var id: UUID
|
||||
var displayName: String
|
||||
var publicKey: Data
|
||||
|
||||
/// Master "open to connection" state.
|
||||
var isOpenToConnect: Bool
|
||||
|
||||
/// Preferred radius tier.
|
||||
var radiusTier: DistanceTier
|
||||
|
||||
/// When true, the user is present but invisible to others.
|
||||
var isIncognito: Bool
|
||||
|
||||
/// When true, the user is not discoverable at all.
|
||||
var isBlocked: Bool
|
||||
|
||||
static let empty = UserProfile(
|
||||
id: UUID(),
|
||||
displayName: "",
|
||||
publicKey: Data(),
|
||||
isOpenToConnect: false,
|
||||
radiusTier: .rightHere,
|
||||
isIncognito: false,
|
||||
isBlocked: false
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user