mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
Consolidates 137 wave commits + 31 supporting commits (CLAUDE.md bump, typealias additions, AnyObject→EngineMedia restoration) into one squashed commit. Migrates dozens of consumer-side public APIs, struct fields, protocol methods, and enum payloads from Postbox protocols/structs to TelegramEngine engine wrappers and typealiases. Drops `import Postbox` from many files. Adds new TelegramCore typealiases and one TelegramEngineUnauthorized facade. Notable changes by category: **TelegramCore typealias additions** (rule 2 — narrow utility typealiases): - EngineChatListIndex, EngineTempBoxFile, EngineItemCollectionItemIndex, EngineItemCollectionViewEntryIndex, EngineValueBoxEncryptionParameters, EngineMessageAndThreadId, EnginePeerStoryStats, EngineMessageHistoryAnchorIndex, EngineChatListTotalUnreadStateCategory, EngineChatListTotalUnreadStateStats, EnginePeerSummaryCounterTags, EngineChatListTotalUnreadState, EngineItemCacheEntryId, EngineHashFunctions, EngineCachedMediaResourceRepresentationResult, EngineMediaResourceDataFetchResult, EngineMediaResourceDataFetchError, EngineMediaResourceStatus, EngineCachedPeerData **TelegramCore engine extensions/forwarders**: - EngineMessage.engineMedia, EngineMessage.enginePeers, EngineMessage.adAttribute, EngineMessage.effectivelyIncoming - engineFileSize forwarder - TelegramEngine.Resources.clearCachedMediaResources(mediaResourceIds: Set<EngineMediaResource.Id>) - TelegramEngine.Resources.fetchStatus(id:resourceSize:) - TelegramEngineUnauthorized.UnauthorizedResources facade with storeResourceData **Public API/struct migrations to engine types**: - ChatAvailableMessageActions.banAuthor/banAuthors → EnginePeer?/[EnginePeer] - WebSessionsContextState.peers → [EnginePeer.Id: EnginePeer] - CacheUsageStats.peers → [EnginePeer.Id: EnginePeer] - PeerCommand.peer → EnginePeer - PeerInfoControllerMode.calls(messages:) → [EngineMessage] - CallControllerNodeProtocol.updatePeer → EnginePeer params - ChatHistoryListNode.messageInCurrentHistoryView (and 4 variants) → EngineMessage? - ChatHistorySearchContainerNode.messageForGallery → EngineMessage? - PeerInfoPaneNode.findLoadedMessage / ensureMessageIsVisible / transitionNodeForGallery → engine-typed - GalleryHiddenMediaTarget.getTransitionInfo / GalleryHiddenMediaManager.findTarget → engine-typed - ChatPanelInterfaceInteraction.presentReactionDeletionOptions / presentBan*MessageOptions → EnginePeer - DrawingMessageRenderer.messages → [EngineMessage] - ChatVideoGalleryItemScrubberView.setFetchStatusSignal → EngineMediaResource.FetchStatus - ChannelDiscussionGroupActionSheetItem.peer, VoiceChatPeerEntry.peer, VoiceChatFullscreenParticipantItem.peer, MediaStreamComponent.chatPeer, MediaStreamVideoComponent.callPeer, ChatMessageContactBubbleContentNode.contactPeer, ChatMessageForwardInfoNode.peer, ChatMessageCommentFooterContentNode.replyPeers, ChatReportPeerTitlePanelNode.peer, ChatMessageActionUrlAuthController.bot, PeerMediaCollectionInterfaceState.peer, ChatMessageCallBubbleContentNode.peopleAvatars, ChatLoadingNode.renderedPeer (→ EngineRenderedPeer) — all to engine types **Wave-71-shadow stored-field migrations** (Postbox Peer/Message → Engine wrapper): - LegacyCallControllerNode.peer - CallStatusBarNode.currentPeer **Dead-code / dead-field removals**: - CallController.peer, CallControllerNodeV2.account, ContactMultiselectionController PeerNameIndex fields, preparedChatListNodeViewTransition account: Account param, FetchResource.swift entirely (unused function) **Module-level Postbox import drops**: 30+ files including TelegramRootController, EditStories, GiftViewScreen, AnimatedStickerUtils, FetchPhotoLibraryImageResource, PeerInfoGiftsPaneNode, PeerInfoPaneContainerNode, PresentAddMembers, PeerInfoProfileItems, ChatControllerAdminBanUsers, PresentationData typealiases, DefaultDayPresentationTheme, ChatListViewTransition, GalleryHiddenMediaManager, RecentSessionsController, GifContext, AuthorizationSequenceController, PeerInfoHeaderEditingContentNode, PeerInfoHeaderNode, PeerAllowedReactionListController, CallControllerNodeV2, and 6 PeerInfo pane files. **AnyObject restoration**: rule 8 added (never substitute Postbox protocols with Any/AnyObject) — undid previous AnyObject substitutions in waves 141/143 back to EngineMedia. Doc maintenance: CLAUDE.md updated to reflect new typealiases and forwarders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
320 lines
11 KiB
Swift
320 lines
11 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import TelegramStringFormatting
|
|
import TelegramVoip
|
|
import TelegramAudio
|
|
import AccountContext
|
|
import TelegramCore
|
|
import MergeLists
|
|
import ItemListUI
|
|
import AppBundle
|
|
import ContextUI
|
|
import DeleteChatPeerActionSheetItem
|
|
import UndoUI
|
|
import AlertUI
|
|
import PresentationDataUtils
|
|
import DirectionalPanGesture
|
|
import AvatarNode
|
|
import TooltipUI
|
|
import LegacyUI
|
|
import LegacyComponents
|
|
import LegacyMediaPickerUI
|
|
import MapResourceToAvatarSizes
|
|
import SolidRoundedButtonNode
|
|
import AudioBlob
|
|
import DeviceAccess
|
|
import VoiceChatActionButton
|
|
|
|
let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
|
let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
|
let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
|
|
let smallButtonSize = CGSize(width: 36.0, height: 36.0)
|
|
let sideButtonSize = CGSize(width: 56.0, height: 56.0)
|
|
let topPanelHeight: CGFloat = 63.0
|
|
let bottomAreaHeight: CGFloat = 206.0
|
|
let fullscreenBottomAreaHeight: CGFloat = 80.0
|
|
let bottomGradientHeight: CGFloat = 70.0
|
|
|
|
func decorationCornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
|
if !top && !bottom {
|
|
return nil
|
|
}
|
|
return generateImage(CGSize(width: 50.0, height: 50.0), rotatedContext: { (size, context) in
|
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
|
context.setFillColor((dark ? fullscreenBackgroundColor : panelBackgroundColor).cgColor)
|
|
context.fill(bounds)
|
|
|
|
context.setBlendMode(.clear)
|
|
|
|
var corners: UIRectCorner = []
|
|
if top {
|
|
corners.insert(.topLeft)
|
|
corners.insert(.topRight)
|
|
}
|
|
if bottom {
|
|
corners.insert(.bottomLeft)
|
|
corners.insert(.bottomRight)
|
|
}
|
|
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: 11.0, height: 11.0))
|
|
context.addPath(path.cgPath)
|
|
context.fillPath()
|
|
})?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 25)
|
|
}
|
|
|
|
func decorationTopCornersImage(dark: Bool) -> UIImage? {
|
|
return generateImage(CGSize(width: 50.0, height: 110.0), rotatedContext: { (size, context) in
|
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
|
context.setFillColor((dark ? fullscreenBackgroundColor : panelBackgroundColor).cgColor)
|
|
context.fill(bounds)
|
|
|
|
context.setBlendMode(.clear)
|
|
|
|
var corners: UIRectCorner = []
|
|
corners.insert(.topLeft)
|
|
corners.insert(.topRight)
|
|
|
|
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 60.0, width: 50.0, height: 50.0), byRoundingCorners: corners, cornerRadii: CGSize(width: 11.0, height: 11.0))
|
|
context.addPath(path.cgPath)
|
|
context.fillPath()
|
|
})?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 32)
|
|
}
|
|
|
|
func decorationBottomCornersImage(dark: Bool) -> UIImage? {
|
|
return generateImage(CGSize(width: 50.0, height: 110.0), rotatedContext: { (size, context) in
|
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
|
context.setFillColor((dark ? fullscreenBackgroundColor : panelBackgroundColor).cgColor)
|
|
context.fill(bounds)
|
|
|
|
context.setBlendMode(.clear)
|
|
|
|
var corners: UIRectCorner = []
|
|
corners.insert(.bottomLeft)
|
|
corners.insert(.bottomRight)
|
|
|
|
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 50.0, height: 50.0), byRoundingCorners: corners, cornerRadii: CGSize(width: 11.0, height: 11.0))
|
|
context.addPath(path.cgPath)
|
|
context.fillPath()
|
|
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 25.0, left: 25.0, bottom: 0.0, right: 25.0), resizingMode: .stretch)
|
|
}
|
|
|
|
private func decorationBottomGradientImage(dark: Bool) -> UIImage? {
|
|
return generateImage(CGSize(width: 24.0, height: bottomGradientHeight), rotatedContext: { size, context in
|
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
|
context.clear(bounds)
|
|
|
|
let color = dark ? fullscreenBackgroundColor : panelBackgroundColor
|
|
let colorsArray = [color.withAlphaComponent(0.0).cgColor, color.cgColor] as CFArray
|
|
var locations: [CGFloat] = [1.0, 0.0]
|
|
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray, locations: &locations)!
|
|
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
|
})
|
|
}
|
|
|
|
struct VoiceChatPeerEntry: Identifiable {
|
|
enum State {
|
|
case listening
|
|
case speaking
|
|
case invited
|
|
case raisedHand
|
|
}
|
|
|
|
var peer: EnginePeer
|
|
var about: String?
|
|
var isMyPeer: Bool
|
|
var videoEndpointId: String?
|
|
var videoPaused: Bool
|
|
var presentationEndpointId: String?
|
|
var presentationPaused: Bool
|
|
var effectiveSpeakerVideoEndpointId: String?
|
|
var state: State
|
|
var muteState: GroupCallParticipantsContext.Participant.MuteState?
|
|
var canManageCall: Bool
|
|
var volume: Int32?
|
|
var raisedHand: Bool
|
|
var displayRaisedHandStatus: Bool
|
|
var active: Bool
|
|
var isLandscape: Bool
|
|
|
|
var effectiveVideoEndpointId: String? {
|
|
return self.presentationEndpointId ?? self.videoEndpointId
|
|
}
|
|
|
|
init(
|
|
peer: EnginePeer,
|
|
about: String?,
|
|
isMyPeer: Bool,
|
|
videoEndpointId: String?,
|
|
videoPaused: Bool,
|
|
presentationEndpointId: String?,
|
|
presentationPaused: Bool,
|
|
effectiveSpeakerVideoEndpointId: String?,
|
|
state: State,
|
|
muteState: GroupCallParticipantsContext.Participant.MuteState?,
|
|
canManageCall: Bool,
|
|
volume: Int32?,
|
|
raisedHand: Bool,
|
|
displayRaisedHandStatus: Bool,
|
|
active: Bool,
|
|
isLandscape: Bool
|
|
) {
|
|
self.peer = peer
|
|
self.about = about
|
|
self.isMyPeer = isMyPeer
|
|
self.videoEndpointId = videoEndpointId
|
|
self.videoPaused = videoPaused
|
|
self.presentationEndpointId = presentationEndpointId
|
|
self.presentationPaused = presentationPaused
|
|
self.effectiveSpeakerVideoEndpointId = effectiveSpeakerVideoEndpointId
|
|
self.state = state
|
|
self.muteState = muteState
|
|
self.canManageCall = canManageCall
|
|
self.volume = volume
|
|
self.raisedHand = raisedHand
|
|
self.displayRaisedHandStatus = displayRaisedHandStatus
|
|
self.active = active
|
|
self.isLandscape = isLandscape
|
|
}
|
|
|
|
var stableId: EnginePeer.Id {
|
|
return self.peer.id
|
|
}
|
|
|
|
static func ==(lhs: VoiceChatPeerEntry, rhs: VoiceChatPeerEntry) -> Bool {
|
|
if lhs.peer != rhs.peer {
|
|
return false
|
|
}
|
|
if lhs.about != rhs.about {
|
|
return false
|
|
}
|
|
if lhs.isMyPeer != rhs.isMyPeer {
|
|
return false
|
|
}
|
|
if lhs.videoEndpointId != rhs.videoEndpointId {
|
|
return false
|
|
}
|
|
if lhs.videoPaused != rhs.videoPaused {
|
|
return false
|
|
}
|
|
if lhs.presentationEndpointId != rhs.presentationEndpointId {
|
|
return false
|
|
}
|
|
if lhs.presentationPaused != rhs.presentationPaused {
|
|
return false
|
|
}
|
|
if lhs.effectiveSpeakerVideoEndpointId != rhs.effectiveSpeakerVideoEndpointId {
|
|
return false
|
|
}
|
|
if lhs.state != rhs.state {
|
|
return false
|
|
}
|
|
if lhs.muteState != rhs.muteState {
|
|
return false
|
|
}
|
|
if lhs.canManageCall != rhs.canManageCall {
|
|
return false
|
|
}
|
|
if lhs.volume != rhs.volume {
|
|
return false
|
|
}
|
|
if lhs.raisedHand != rhs.raisedHand {
|
|
return false
|
|
}
|
|
if lhs.displayRaisedHandStatus != rhs.displayRaisedHandStatus {
|
|
return false
|
|
}
|
|
if lhs.active != rhs.active {
|
|
return false
|
|
}
|
|
if lhs.isLandscape != rhs.isLandscape {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
public protocol VoiceChatController: ViewController {
|
|
var call: VideoChatCall { get }
|
|
var currentOverlayController: VoiceChatOverlayController? { get }
|
|
var parentNavigationController: NavigationController? { get set }
|
|
var onViewDidAppear: (() -> Void)? { get set }
|
|
var onViewDidDisappear: (() -> Void)? { get set }
|
|
|
|
func updateCall(call: VideoChatCall)
|
|
|
|
func dismiss(closing: Bool, manual: Bool)
|
|
}
|
|
|
|
private final class VoiceChatContextExtractedContentSource: ContextExtractedContentSource {
|
|
var keepInPlace: Bool
|
|
let ignoreContentTouches: Bool = false
|
|
let blurBackground: Bool
|
|
let maskView: UIView?
|
|
|
|
private var animateTransitionIn: () -> Void
|
|
private var animateTransitionOut: () -> Void
|
|
|
|
private let sourceNode: ContextExtractedContentContainingNode
|
|
|
|
var centerVertically: Bool
|
|
var shouldBeDismissed: Signal<Bool, NoError>
|
|
|
|
init(sourceNode: ContextExtractedContentContainingNode, maskView: UIView?, keepInPlace: Bool, blurBackground: Bool, centerVertically: Bool, shouldBeDismissed: Signal<Bool, NoError>, animateTransitionIn: @escaping () -> Void, animateTransitionOut: @escaping () -> Void) {
|
|
self.sourceNode = sourceNode
|
|
self.maskView = maskView
|
|
self.keepInPlace = keepInPlace
|
|
self.blurBackground = blurBackground
|
|
self.centerVertically = centerVertically
|
|
self.shouldBeDismissed = shouldBeDismissed
|
|
self.animateTransitionIn = animateTransitionIn
|
|
self.animateTransitionOut = animateTransitionOut
|
|
}
|
|
|
|
func takeView() -> ContextControllerTakeViewInfo? {
|
|
self.animateTransitionIn()
|
|
return ContextControllerTakeViewInfo(containingItem: .node(self.sourceNode), contentAreaInScreenSpace: UIScreen.main.bounds, maskView: self.maskView)
|
|
}
|
|
|
|
func putBack() -> ContextControllerPutBackViewInfo? {
|
|
self.animateTransitionOut()
|
|
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds, maskView: self.maskView)
|
|
}
|
|
}
|
|
|
|
final class VoiceChatContextReferenceContentSource: ContextReferenceContentSource {
|
|
private let controller: ViewController
|
|
private let sourceView: UIView
|
|
|
|
init(controller: ViewController, sourceView: UIView) {
|
|
self.controller = controller
|
|
self.sourceView = sourceView
|
|
}
|
|
|
|
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
|
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
|
}
|
|
}
|
|
|
|
public func shouldUseV2VideoChatImpl(context: AccountContext) -> Bool {
|
|
var useV2 = true
|
|
if context.sharedContext.immediateExperimentalUISettings.disableCallV2 {
|
|
useV2 = false
|
|
}
|
|
if let data = context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_videochatui_v2"] {
|
|
useV2 = false
|
|
}
|
|
return useV2
|
|
}
|
|
|
|
public func makeVoiceChatControllerInitialData(sharedContext: SharedAccountContext, accountContext: AccountContext, call: VideoChatCall) -> Signal<Any, NoError> {
|
|
return VideoChatScreenV2Impl.initialData(call: call) |> map { $0 as Any }
|
|
}
|
|
|
|
public func makeVoiceChatController(sharedContext: SharedAccountContext, accountContext: AccountContext, call: VideoChatCall, initialData: Any, sourceCallController: CallController?) -> VoiceChatController {
|
|
return VideoChatScreenV2Impl(initialData: initialData as! VideoChatScreenV2Impl.InitialData, call: call, sourceCallController: sourceCallController)
|
|
}
|