mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
d33dc058c2
The call package now says why a call ended. Only our own hang-up is an end call action; a call the other side declined or left, that nobody answered, or that failed is reported to CallKit with that reason, so the call log no longer shows it as one we ended, and no end action comes back to hang up a call that is already over. Also adopts the package's other host changes: the voice call icon beside "Calling…", and the call's large avatar rendered at the 140 pt it is drawn at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
156 lines
4.7 KiB
Swift
156 lines
4.7 KiB
Swift
//
|
|
// Copyright 2026 Element Creations Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import Compound
|
|
import ElementCall
|
|
import SwiftUI
|
|
|
|
// Colours, icons and avatars for the call, read from this app's Compound.
|
|
//
|
|
// Why the package takes a theme instead of linking Compound itself: `Color.compound` is a single
|
|
// shared instance that Element Pro re-brands at runtime, and a second copy inside the package would
|
|
// never see the override. Every member is computed so each read goes to the live instance.
|
|
|
|
struct NativeCallTheme: ElementCallThemeProtocol {
|
|
var bgCanvasDefault: Color {
|
|
.compound.bgCanvasDefault
|
|
}
|
|
|
|
var bgCanvasDefaultLevel: Color {
|
|
.compound.bgCanvasDefaultLevel1
|
|
}
|
|
|
|
var bgSubtleSecondary: Color {
|
|
.compound.bgSubtleSecondary
|
|
}
|
|
|
|
var bgAccentRest: Color {
|
|
.compound.bgAccentRest
|
|
}
|
|
|
|
var bgActionPrimaryRest: Color {
|
|
.compound.bgActionPrimaryRest
|
|
}
|
|
|
|
var bgCriticalPrimary: Color {
|
|
.compound.bgCriticalPrimary
|
|
}
|
|
|
|
var iconPrimary: Color {
|
|
.compound.iconPrimary
|
|
}
|
|
|
|
var iconQuaternary: Color {
|
|
.compound.iconQuaternary
|
|
}
|
|
|
|
var iconAccentPrimary: Color {
|
|
.compound.iconAccentPrimary
|
|
}
|
|
|
|
var iconCriticalPrimary: Color {
|
|
.compound.iconCriticalPrimary
|
|
}
|
|
|
|
var iconOnSolidPrimary: Color {
|
|
.compound.iconOnSolidPrimary
|
|
}
|
|
|
|
var textPrimary: Color {
|
|
.compound.textPrimary
|
|
}
|
|
|
|
var textSecondary: Color {
|
|
.compound.textSecondary
|
|
}
|
|
|
|
var textCriticalPrimary: Color {
|
|
.compound.textCriticalPrimary
|
|
}
|
|
|
|
var borderSuccessSubtle: Color {
|
|
.compound.borderSuccessSubtle
|
|
}
|
|
|
|
var borderInteractiveSecondary: Color {
|
|
.compound.borderInteractiveSecondary
|
|
}
|
|
|
|
var bodySM: Font {
|
|
.compound.bodySM
|
|
}
|
|
|
|
var bodySMSemibold: Font {
|
|
.compound.bodySMSemibold
|
|
}
|
|
|
|
var bodyLGSemibold: Font {
|
|
.compound.bodyLGSemibold
|
|
}
|
|
}
|
|
|
|
struct NativeCallIcons: ElementCallIconRenderingProtocol {
|
|
func icon(_ icon: ElementCallIcon, size: ElementCallIconSize, relativeTo textStyle: ElementCallTextStyle?) -> AnyView {
|
|
// The package asks for a text style rather than a point size so CompoundIcon can scale it.
|
|
AnyView(CompoundIcon(Self.keyPath(for: icon), size: Self.size(for: size), relativeTo: Self.font(for: textStyle)))
|
|
}
|
|
|
|
private static func keyPath(for icon: ElementCallIcon) -> KeyPath<CompoundIcons, Image> {
|
|
switch icon {
|
|
case .endCall: \.endCall
|
|
case .micOn: \.micOnSolid
|
|
case .micOff: \.micOffSolid
|
|
case .videoCall: \.videoCallSolid
|
|
case .videoCallOff: \.videoCallOffSolid
|
|
case .voiceCall: \.voiceCallSolid
|
|
case .switchCamera: \.switchCameraSolid
|
|
case .shareScreen: \.shareScreenSolid
|
|
case .volumeOn: \.volumeOnSolid
|
|
case .volumeOff: \.volumeOffSolid
|
|
case .headphones: \.headphonesSolid
|
|
case .bluetooth: \.bluetoothOutput
|
|
case .raisedHand: \.raisedHandSolid
|
|
case .userProfile: \.userProfileSolid
|
|
case .overflow: \.overflowHorizontal
|
|
case .collapse: \.collapse
|
|
}
|
|
}
|
|
|
|
private static func size(for size: ElementCallIconSize) -> CompoundIcon.Size {
|
|
switch size {
|
|
case .xSmall: .xSmall
|
|
case .small: .small
|
|
case .medium: .medium
|
|
}
|
|
}
|
|
|
|
// Not redundant despite the target's default isolation: a static member of a type conforming to
|
|
// a nonisolated, Sendable port is nonisolated, and `Font.compound` is main actor-isolated.
|
|
@MainActor
|
|
private static func font(for textStyle: ElementCallTextStyle?) -> Font {
|
|
switch textStyle {
|
|
case .bodySM: .compound.bodySM
|
|
case .bodySMSemibold: .compound.bodySMSemibold
|
|
case .bodyLGSemibold: .compound.bodyLGSemibold
|
|
case nil: .compound.bodyMD
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Keeps avatars looking like the rest of the app, and keeps media loading out of the package.
|
|
struct NativeCallAvatars: ElementCallAvatarRenderingProtocol {
|
|
let mediaProvider: MediaProviderProtocol?
|
|
|
|
func avatar(userID: String, displayName: String?, avatarURL: URL?, size: ElementCallAvatarSize) -> AnyView {
|
|
AnyView(LoadableAvatarImage(url: avatarURL,
|
|
name: displayName,
|
|
contentID: userID,
|
|
avatarSize: size == .thumbnail ? .user(on: .roomDetails) : .user(on: .call),
|
|
mediaProvider: mediaProvider))
|
|
}
|
|
}
|