Files
Luma/Sources/Shared/Models/CallSnapshot.swift
T
2026-07-30 20:07:51 +07:00

115 lines
3.0 KiB
Swift

import Foundation
enum CallMedia: String, CaseIterable, Hashable, Sendable {
case audio
case video
}
enum CallDirection: String, Equatable, Sendable {
case incoming
case outgoing
}
enum CallPhase: Equatable, Sendable {
case ringing
case connecting
case connected
}
/// Stable value stored with a local call-history message.
enum CallHistoryOutcome: String, Codable, Hashable, Sendable {
case completed
case declined
case missed
case cancelled
case unanswered
case failed
case answeredElsewhere
}
struct CallHistoryMetadata: Codable, Hashable, Sendable {
let isVideo: Bool
let outcome: CallHistoryOutcome
}
/// Internal reason used to turn every call-ending path into one history result.
enum CallTerminationCause: Equatable, Sendable {
case localEnded
case localRejected
case remoteEnded
case remoteRejected
case remoteCancelled
case timedOut
case failed
case connectionDetached
case answeredElsewhere
}
struct CallHistoryEntry: Equatable, Sendable {
let id: String
let peerJID: String
let direction: CallDirection
let isVideo: Bool
let startedAt: Date
let endedAt: Date
let duration: TimeInterval?
let outcome: CallHistoryOutcome
}
/// Pure classification kept outside WebRTC so every termination scenario can
/// be tested without constructing a live peer connection.
enum CallHistoryPolicy {
static func outcome(
direction: CallDirection,
phase: CallPhase,
connectedAt: Date?,
cause: CallTerminationCause
) -> CallHistoryOutcome {
// Once media connected, a later hang-up or transport loss still belongs
// to the successful-call history and keeps its measured duration.
if connectedAt != nil || phase == .connected {
return .completed
}
switch cause {
case .localRejected, .remoteRejected:
return .declined
case .localEnded:
return .cancelled
case .remoteCancelled, .remoteEnded:
guard phase == .ringing else { return .failed }
return direction == .incoming ? .missed : .declined
case .timedOut:
guard phase == .ringing else { return .failed }
return direction == .incoming ? .missed : .unanswered
case .answeredElsewhere:
return .answeredElsewhere
case .failed, .connectionDetached:
return .failed
}
}
}
/// Value-type projection of the live WebRTC/Jingle session used by SwiftUI.
struct CallSnapshot: Identifiable, Equatable, Sendable {
let id: UUID
let peerJID: String
let direction: CallDirection
let media: Set<CallMedia>
let phase: CallPhase
let connectedAt: Date?
let isMuted: Bool
let isCameraEnabled: Bool
let isSpeakerEnabled: Bool
let hasLocalVideo: Bool
let hasRemoteVideo: Bool
var isVideoCall: Bool {
media.contains(.video)
}
var isIncomingRinging: Bool {
direction == .incoming && phase == .ringing
}
}